diff --git a/controllers/factory.go b/controllers/factory.go index 7ff9c4764c2d..5215a72732da 100644 --- a/controllers/factory.go +++ b/controllers/factory.go @@ -20,6 +20,7 @@ import ( "github.com/aws/eks-anywhere/pkg/dependencies" "github.com/aws/eks-anywhere/pkg/executables" "github.com/aws/eks-anywhere/pkg/executables/cmk" + "github.com/aws/eks-anywhere/pkg/networking/cilium" ciliumreconciler "github.com/aws/eks-anywhere/pkg/networking/cilium/reconciler" cnireconciler "github.com/aws/eks-anywhere/pkg/networking/reconciler" "github.com/aws/eks-anywhere/pkg/providers/cloudstack" @@ -56,6 +57,8 @@ type Factory struct { deps *dependencies.Dependencies packageControllerClient *curatedpackages.PackageControllerClient cloudStackValidatorRegistry cloudstack.ValidatorRegistry + ciliumTemplater ciliumreconciler.Templater + helmFactory *HelmFactory } type Reconcilers struct { @@ -465,15 +468,45 @@ func (f *Factory) withCloudStackValidatorRegistry() *Factory { return f } +func (f *Factory) withHelmFactory() *Factory { + f.dependencyFactory.WithHelmFactory() + + f.buildSteps = append(f.buildSteps, func(ctx context.Context) error { + if f.helmFactory != nil { + return nil + } + + f.helmFactory = NewHelmFactory(f.manager.GetClient(), f.deps.HelmFactory) + return nil + }) + + return f +} + +func (f *Factory) withCiliumTemplater() *Factory { + f.withHelmFactory() + + f.buildSteps = append(f.buildSteps, func(ctx context.Context) error { + if f.ciliumTemplater != nil { + return nil + } + f.ciliumTemplater = cilium.NewTemplater(f.helmFactory) + + return nil + }) + + return f +} + func (f *Factory) withCNIReconciler() *Factory { - f.dependencyFactory.WithCiliumTemplater() + f.withCiliumTemplater() f.buildSteps = append(f.buildSteps, func(ctx context.Context) error { if f.cniReconciler != nil { return nil } - f.cniReconciler = cnireconciler.New(ciliumreconciler.New(f.deps.CiliumTemplater)) + f.cniReconciler = cnireconciler.New(ciliumreconciler.New(f.ciliumTemplater)) return nil }) diff --git a/controllers/helm.go b/controllers/helm.go new file mode 100644 index 000000000000..329d35fc4d2a --- /dev/null +++ b/controllers/helm.go @@ -0,0 +1,44 @@ +package controllers + +import ( + "context" + + anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" + "github.com/aws/eks-anywhere/pkg/constants" + "github.com/aws/eks-anywhere/pkg/dependencies" + "github.com/aws/eks-anywhere/pkg/executables" + "github.com/aws/eks-anywhere/pkg/registrymirror" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// HelmFactory is responsible for creating and owning instances of Helm client, +// configured using information from the current state of the cluster using a k8s client. +type HelmFactory struct { + client client.Client + dependencyHelmFactory dependencies.HelmFactory +} + +// NewHelmFactory returns a new HelmFactory. +func NewHelmFactory(client client.Client, dependencyHelmFactory *dependencies.HelmFactory) *HelmFactory { + return &HelmFactory{ + client: client, + } +} + +// GetClientForCluster returns a new Helm client configured using information from the provided cluster. +func (f *HelmFactory) GetClientForCluster(ctx context.Context, clusterName string) (*executables.Helm, error) { + cluster := &anywherev1.Cluster{} + namespacedNamed := types.NamespacedName{ + Name: clusterName, + Namespace: constants.EksaSystemNamespace, + } + + if err := f.client.Get(ctx, namespacedNamed, cluster); err != nil { + return nil, err + } + + r := registrymirror.FromCluster(cluster) + return f.dependencyHelmFactory.GetClient(executables.WithRegistryMirror(r)), nil + +} diff --git a/controllers/helm_test.go b/controllers/helm_test.go new file mode 100644 index 000000000000..3e1568b14c02 --- /dev/null +++ b/controllers/helm_test.go @@ -0,0 +1,31 @@ +package controllers_test + +import ( + "reflect" + "testing" + + "github.com/aws/eks-anywhere/controllers" + "github.com/aws/eks-anywhere/pkg/dependencies" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestNewHelmFactory(t *testing.T) { + type args struct { + client client.Client + dependencyHelmFactory *dependencies.HelmFactory + } + tests := []struct { + name string + args args + want *controllers.HelmFactory + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := controllers.NewHelmFactory(tt.args.client, tt.args.dependencyHelmFactory); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewHelmFactory() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/dependencies/factory.go b/pkg/dependencies/factory.go index 16eca5d0e96f..3e1afcf5f6c8 100644 --- a/pkg/dependencies/factory.go +++ b/pkg/dependencies/factory.go @@ -107,6 +107,7 @@ type Dependencies struct { SnowValidator *snow.Validator IPValidator *validator.IPValidator UnAuthKubectlClient KubeClients + HelmFactory *HelmFactory CreateClusterDefaulter cli.CreateClusterDefaulter UpgradeClusterDefaulter cli.UpgradeClusterDefaulter } @@ -770,6 +771,24 @@ func (f *Factory) WithHelm(opts ...executables.HelmOpt) *Factory { return f } +func (f *Factory) WithHelmFactory() *Factory { + f.WithExecutableBuilder() + + f.buildSteps = append(f.buildSteps, func(ctx context.Context) error { + if f.dependencies.HelmFactory != nil { + return nil + } + + f.dependencies.HelmFactory = NewHelmFactory(f.executablesConfig.builder). + WithRegistryMirror(f.registryMirror). + WithProxyConfigurations(f.proxyConfiguration). + WithInsecure() + return nil + }) + + return f +} + // WithNetworking builds a Networking. func (f *Factory) WithNetworking(clusterConfig *v1alpha1.Cluster) *Factory { var networkingBuilder func() clustermanager.Networking @@ -845,13 +864,13 @@ func (f *Factory) WithCNIInstaller(spec *cluster.Spec, provider providers.Provid } func (f *Factory) WithCiliumTemplater() *Factory { - f.WithHelm(executables.WithInsecure()) + f.WithHelmFactory() f.buildSteps = append(f.buildSteps, func(ctx context.Context) error { if f.dependencies.CiliumTemplater != nil { return nil } - f.dependencies.CiliumTemplater = cilium.NewTemplater(f.dependencies.Helm) + f.dependencies.CiliumTemplater = cilium.NewTemplater(f.dependencies.HelmFactory) return nil }) diff --git a/pkg/dependencies/helm.go b/pkg/dependencies/helm.go new file mode 100644 index 000000000000..c4bbd9bf002d --- /dev/null +++ b/pkg/dependencies/helm.go @@ -0,0 +1,80 @@ +package dependencies + +import ( + "context" + "sync" + + "github.com/aws/eks-anywhere/pkg/executables" + "github.com/aws/eks-anywhere/pkg/registrymirror" +) + +type ExecutableBuilder interface { + BuildHelmExecutable(...executables.HelmOpt) *executables.Helm +} + +// HelmFactory is responsible for creating and owning instances of Helm client. +type HelmFactory struct { + mu sync.Mutex + builder ExecutableBuilder + helm *executables.Helm + registryMirror *registrymirror.RegistryMirror + proxyConfiguration map[string]string + insecure bool +} + +// WithRegistryMirror configures the factory to use registry mirror wherever applicable. +func (f *HelmFactory) WithRegistryMirror(registryMirror *registrymirror.RegistryMirror) *HelmFactory { + f.registryMirror = registryMirror + + return f +} + +// WithProxyConfigurations configures the factory to use proxy configurations wherever applicable. +func (f *HelmFactory) WithProxyConfigurations(proxyConfiguration map[string]string) *HelmFactory { + f.proxyConfiguration = proxyConfiguration + + return f +} + +// WithInsecure configures the factory to configure helm to use to allow connections to TLS registry without certs or with self-signed certs +func (f *HelmFactory) WithInsecure() *HelmFactory { + f.insecure = true + + return f +} + +func NewHelmFactory(builder ExecutableBuilder) *HelmFactory { + return &HelmFactory{ + builder: builder, + mu: sync.Mutex{}, + } + +} + +// GetClient returns a new Helm executeble client. +func (f *HelmFactory) GetClient(opts ...executables.HelmOpt) *executables.Helm { + f.mu.Lock() + defer f.mu.Unlock() + + if f.registryMirror != nil { + opts = append(opts, executables.WithRegistryMirror(f.registryMirror)) + } + + if f.proxyConfiguration != nil { + opts = append(opts, executables.WithEnv(f.proxyConfiguration)) + } + + if f.insecure { + opts = append(opts, executables.WithInsecure()) + } + + f.helm = f.builder.BuildHelmExecutable(opts...) + return f.helm +} + +// GetClientForCluster returns a new helm client. +// There is no cluster information that needs to be passed, but this method was needed to satisfy +// an interface together with the controller helm factory. +func (f *HelmFactory) GetClientForCluster(_ context.Context, _ string) (*executables.Helm, error) { + return f.GetClient(), nil +} diff --git a/pkg/dependencies/helm_test.go b/pkg/dependencies/helm_test.go new file mode 100644 index 000000000000..cb9aa43b3650 --- /dev/null +++ b/pkg/dependencies/helm_test.go @@ -0,0 +1,41 @@ +package dependencies_test + +import ( + "context" + "testing" + + "github.com/aws/eks-anywhere/pkg/dependencies" + . "github.com/onsi/gomega" +) + +func TestHelmFactory_GetClient(t *testing.T) { + tests := map[string]struct { + wantErr error + }{ + "Success": { + wantErr: nil, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + ctx := context.Background() + g := NewWithT(t) + deps, err := dependencies.NewFactory(). + WithLocalExecutables(). + WithHelmFactory(). + Build(context.Background()) + + g.Expect(err).To(BeNil()) + + helm, err := deps.HelmFactory.GetClientForCluster(ctx, "") + + if tt.wantErr != nil { + g.Expect(err).To(Equal(tt.wantErr)) + } else { + g.Expect(err).To(BeNil()) + g.Expect(deps.HelmFactory).NotTo(BeNil()) + g.Expect(helm).NotTo(BeNil()) + } + }) + } +} diff --git a/pkg/networking/cilium/templater.go b/pkg/networking/cilium/templater.go index fc873784d2a6..0829f3e57233 100644 --- a/pkg/networking/cilium/templater.go +++ b/pkg/networking/cilium/templater.go @@ -11,6 +11,7 @@ import ( anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/cluster" "github.com/aws/eks-anywhere/pkg/config" + "github.com/aws/eks-anywhere/pkg/executables" "github.com/aws/eks-anywhere/pkg/retrier" "github.com/aws/eks-anywhere/pkg/semver" "github.com/aws/eks-anywhere/pkg/templater" @@ -29,13 +30,17 @@ type Helm interface { RegistryLogin(ctx context.Context, registry, username, password string) error } +type HelmFactory interface { + GetClientForCluster(ctx context.Context, clusterName string) (*executables.Helm, error) +} + type Templater struct { - helm Helm + helmFactory HelmFactory } -func NewTemplater(helm Helm) *Templater { +func NewTemplater(helmFactory HelmFactory) *Templater { return &Templater{ - helm: helm, + helmFactory: helmFactory, } } @@ -61,8 +66,17 @@ func (t *Templater) GenerateUpgradePreflightManifest(ctx context.Context, spec * if err != nil { return nil, err } + helm, err := t.helmFactory.GetClientForCluster(ctx, spec.Cluster.ManagedBy()) + if err != nil { + return nil, fmt.Errorf("failed to get helm client for cluster %s: %v", spec.Cluster.ManagedBy(), err) + } + if spec.Cluster.Spec.RegistryMirrorConfiguration != nil && spec.Cluster.Spec.RegistryMirrorConfiguration.Authenticate { + if err := t.registryLogin(ctx, helm, spec); err != nil { + return nil, err + } + } - manifest, err := t.helm.Template(ctx, uri, version, namespace, v, kubeVersion) + manifest, err := helm.Template(ctx, uri, version, namespace, v, kubeVersion) if err != nil { return nil, fmt.Errorf("failed generating cilium upgrade preflight manifest: %v", err) } @@ -112,6 +126,18 @@ func WithPolicyAllowedNamespaces(namespaces []string) ManifestOpt { } } +func (t *Templater) registryLogin(ctx context.Context, helm Helm, spec *cluster.Spec) error { + username, password, err := config.ReadCredentials() + if err != nil { + return err + } + endpoint := net.JoinHostPort(spec.Cluster.Spec.RegistryMirrorConfiguration.Endpoint, spec.Cluster.Spec.RegistryMirrorConfiguration.Port) + if err := helm.RegistryLogin(ctx, endpoint, username, password); err != nil { + return err + } + return nil +} + func (t *Templater) GenerateManifest(ctx context.Context, spec *cluster.Spec, opts ...ManifestOpt) ([]byte, error) { versionsBundle := spec.RootVersionsBundle() kubeVersion, err := getKubeVersionString(spec, versionsBundle) @@ -131,21 +157,18 @@ func (t *Templater) GenerateManifest(ctx context.Context, spec *cluster.Spec, op uri, version := getChartURIAndVersion(versionsBundle) var manifest []byte - if spec.Cluster.Spec.RegistryMirrorConfiguration != nil { - if spec.Cluster.Spec.RegistryMirrorConfiguration.Authenticate { - username, password, err := config.ReadCredentials() - if err != nil { - return nil, err - } - endpoint := net.JoinHostPort(spec.Cluster.Spec.RegistryMirrorConfiguration.Endpoint, spec.Cluster.Spec.RegistryMirrorConfiguration.Port) - if err := t.helm.RegistryLogin(ctx, endpoint, username, password); err != nil { - return nil, err - } + helm, err := t.helmFactory.GetClientForCluster(ctx, spec.Cluster.ManagedBy()) + if err != nil { + return nil, fmt.Errorf("failed to get helm client for cluster %s: %v", spec.Cluster.ManagedBy(), err) + } + if spec.Cluster.Spec.RegistryMirrorConfiguration != nil && spec.Cluster.Spec.RegistryMirrorConfiguration.Authenticate { + if err := t.registryLogin(ctx, helm, spec); err != nil { + return nil, err } } err = c.retrier.Retry(func() error { - manifest, err = t.helm.Template(ctx, uri, version, namespace, c.values, c.kubeVersion) + manifest, err = helm.Template(ctx, uri, version, namespace, c.values, c.kubeVersion) return err }) if err != nil {