Skip to content

Commit

Permalink
Resolve files with duplicate content, add tests
Browse files Browse the repository at this point in the history
Signed-off-by: Danil-Grigorev <[email protected]>
  • Loading branch information
Danil-Grigorev committed Jan 24, 2025
1 parent e79481c commit efb530f
Show file tree
Hide file tree
Showing 23 changed files with 894 additions and 97 deletions.
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions api/v1alpha2/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,19 +211,16 @@ type ContainerSpec struct {

// FetchConfiguration determines the way to fetch the components and metadata for the provider.
type FetchConfiguration struct {
// OCI configurations to be used for fetching the provider’s components and metadata from an OCI artifact.
OCIConfiguration `json:",inline"`

// URL to be used for fetching the provider’s components and metadata from a remote Github repository.
// For example, https://github.com/{owner}/{repository}/releases
// You must set `providerSpec.Version` field for operator to pick up
// desired version of the release from GitHub.
// +optional
URL string `json:"url,omitempty"`

// OCI to be used for fetching the provider’s components and metadata from an OCI artifact.
// You must set `providerSpec.Version` field for operator to pick up desired version of the release from GitHub.
// If the providerSpec.Version is missing, latest provider version from clusterctl defaults is used.
// +optional
OCI string `json:"oci,omitempty"`

// Selector to be used for fetching provider’s components and metadata from
// ConfigMaps stored inside the cluster. Each ConfigMap is expected to contain
// components and metadata for a specific version only.
Expand All @@ -233,6 +230,14 @@ type FetchConfiguration struct {
Selector *metav1.LabelSelector `json:"selector,omitempty"`
}

type OCIConfiguration struct {
// OCI to be used for fetching the provider’s components and metadata from an OCI artifact.
// You must set `providerSpec.Version` field for operator to pick up desired version of the release from GitHub.
// If the providerSpec.Version is missing, latest provider version from clusterctl defaults is used.
// +optional
OCI string `json:"oci,omitempty"`
}

// ProviderStatus defines the observed state of the Provider.
type ProviderStatus struct {
// Contract will contain the core provider contract that the provider is
Expand Down
16 changes: 16 additions & 0 deletions api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion cmd/plugin/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ func TestInitProviders(t *testing.T) {
opts: &initOptions{
coreProvider: "cluster-api:capi-system:v1.8.0",
infrastructureProviders: []string{
"cluster-api:capi-system:v1.8.0",
"aws:capa-operator-system",
"docker:capd-operator-system",
},
Expand Down
104 changes: 77 additions & 27 deletions cmd/plugin/cmd/preload.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import (
"fmt"
"os"
"strings"
"time"

"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/wait"
"oras.land/oras-go/v2/registry/remote/auth"
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
providercontroller "sigs.k8s.io/cluster-api-operator/internal/controller"
Expand Down Expand Up @@ -207,50 +207,99 @@ func runPreLoad() error {
configMaps = append(configMaps, configMap)
}

errors := []error{}

if !loadOpts.existing {
for _, cm := range configMaps {
out, err := yaml.Marshal(cm)
if err != nil {
return fmt.Errorf("cannot serialize provider config map: %w", err)
}
if loadOpts.existing {
client, err := CreateKubeClient(loadOpts.kubeconfig, "")
if err != nil {
return fmt.Errorf("cannot create a client: %w", err)
}

fmt.Printf("---\n%s", string(out))
existing, err := preloadExisting(ctx, client)
if err != nil {
return err
}

return nil
configMaps = append(configMaps, existing...)
}

client, err := CreateKubeClient(loadOpts.kubeconfig, "")
if err != nil {
return fmt.Errorf("cannot create a client: %w", err)
for _, cm := range configMaps {
out, err := yaml.Marshal(cm)
if err != nil {
return fmt.Errorf("cannot serialize provider config map: %w", err)
}

fmt.Printf("---\n%s", string(out))
}

return nil
}

// preloadExisting uses existing cluster kubeconfig to list providers and create configmaps with components for each provider.
func preloadExisting(ctx context.Context, cl client.Client) ([]*corev1.ConfigMap, error) {
errors := []error{}
configMaps := []*corev1.ConfigMap{}

for _, list := range operatorv1.ProviderLists {
maps, err := fetchProviders(ctx, client, list.(genericProviderList))
list, ok := list.(genericProviderList)
if !ok {
log.V(5).Info("Expected to get GenericProviderList")
continue
}

list, ok = list.DeepCopyObject().(genericProviderList)
if !ok {
log.V(5).Info("Expected to get GenericProviderList")
continue
}

maps, err := fetchProviders(ctx, cl, list)
configMaps = append(configMaps, maps...)
errors = append(errors, err)
}

for _, cm := range configMaps {
out, err := yaml.Marshal(cm)
if err != nil {
return fmt.Errorf("cannot serialize provider config map: %w", err)
return configMaps, kerrors.NewAggregate(errors)
}

// retryWithExponentialBackoff repeats an operation until it passes or the exponential backoff times out.
func retryWithExponentialBackoff(ctx context.Context, opts wait.Backoff, operation func(ctx context.Context) error) error {
i := 0
if err := wait.ExponentialBackoffWithContext(ctx, opts, func(ctx context.Context) (bool, error) {
i++
if err := operation(ctx); err != nil {
if i < opts.Steps {
log.V(5).Info("Retrying with backoff", "cause", err.Error())
return false, nil
}

return false, err
}

fmt.Printf("---\n%s", string(out))
return true, nil
}); err != nil {
return fmt.Errorf("action failed after %d attempts: %w", i, err)
}

return kerrors.NewAggregate(errors)
return nil
}

// newReadBackoff creates a new API Machinery backoff parameter set suitable for use with CLI cluster operations.
func newReadBackoff() wait.Backoff {
// Return a exponential backoff configuration which returns durations for a total time of ~15s.
// Example: 0, .25s, .6s, 1.2, 2.1s, 3.4s, 5.5s, 8s, 12s
// Jitter is added as a random fraction of the duration multiplied by the jitter factor.
return wait.Backoff{
Duration: 250 * time.Millisecond,
Factor: 1.5,
Steps: 9,
Jitter: 0.1,
}
}

func fetchProviders(ctx context.Context, cl client.Client, providerList genericProviderList) ([]*corev1.ConfigMap, error) {
configMaps := []*corev1.ConfigMap{}

if err := cl.List(ctx, providerList, client.InNamespace("")); meta.IsNoMatchError(err) || apierrors.IsNotFound(err) {
return configMaps, nil
} else if err != nil {
if err := retryWithExponentialBackoff(ctx, newReadBackoff(), func(ctx context.Context) error {
return cl.List(ctx, providerList, client.InNamespace(""))
}); err != nil {
log.Error(err, fmt.Sprintf("Unable to list providers, %#v", err))

return configMaps, err
Expand Down Expand Up @@ -285,9 +334,10 @@ func templateConfigMap(ctx context.Context, providerType clusterctlv1.ProviderTy

spec := provider.GetSpec()
spec.FetchConfig = &operatorv1.FetchConfiguration{
OCI: url,
OCIConfiguration: operatorv1.OCIConfiguration{
OCI: url,
},
}

provider.SetSpec(spec)

if spec.Version != "" {
Expand Down
Loading

0 comments on commit efb530f

Please sign in to comment.