diff --git a/cmd/eksctl-anywhere/cmd/installpackagecontroller.go b/cmd/eksctl-anywhere/cmd/installpackagecontroller.go index b7f97fc514694..769503a3b671d 100644 --- a/cmd/eksctl-anywhere/cmd/installpackagecontroller.go +++ b/cmd/eksctl-anywhere/cmd/installpackagecontroller.go @@ -72,7 +72,7 @@ func installPackageController(ctx context.Context) error { } curatedpackages.PrintLicense() - err = ctrlClient.EnableCuratedPackages(ctx) + err = ctrlClient.Enable(ctx) if err != nil { return err } diff --git a/controllers/cluster_controller.go b/controllers/cluster_controller.go index f001c7fe8b7f0..1838d7b3268df 100644 --- a/controllers/cluster_controller.go +++ b/controllers/cluster_controller.go @@ -48,7 +48,7 @@ type ClusterReconciler struct { providerReconcilerRegistry ProviderClusterReconcilerRegistry awsIamAuth AWSIamConfigReconciler clusterValidator ClusterValidator - packageControllerClient PackageControllerClient + packagesClient PackagesClient } type ProviderClusterReconcilerRegistry interface { @@ -68,13 +68,13 @@ type ClusterValidator interface { } // NewClusterReconciler constructs a new ClusterReconciler. -func NewClusterReconciler(client client.Client, registry ProviderClusterReconcilerRegistry, awsIamAuth AWSIamConfigReconciler, clusterValidator ClusterValidator, packageControllerClient PackageControllerClient) *ClusterReconciler { +func NewClusterReconciler(client client.Client, registry ProviderClusterReconcilerRegistry, awsIamAuth AWSIamConfigReconciler, clusterValidator ClusterValidator, pkgs PackagesClient) *ClusterReconciler { return &ClusterReconciler{ client: client, providerReconcilerRegistry: registry, awsIamAuth: awsIamAuth, clusterValidator: clusterValidator, - packageControllerClient: packageControllerClient, + packagesClient: pkgs, } } @@ -270,7 +270,7 @@ func (r *ClusterReconciler) postClusterProviderReconcile(ctx context.Context, lo } } - if !cluster.IsSelfManaged() { + if cluster.IsPackagesEnabled() { if err := r.postReconcilePackagesForWorkloadCluster(ctx, log, cluster); err != nil { return controller.Result{}, err } @@ -279,88 +279,124 @@ func (r *ClusterReconciler) postClusterProviderReconcile(ctx context.Context, lo return controller.Result{}, nil } -type PackageControllerClient interface { - EnableCuratedPackagesFullLifecycle(context.Context, logr.Logger, string, string, *v1alpha1.Image, *registrymirror.RegistryMirror, ...curatedpackages.PackageControllerClientOpt) error +// PackagesClient handles curated packages operations from within the cluster +// controller. +type PackagesClient interface { + EnableFullLifecycle(context.Context, logr.Logger, string, string, *v1alpha1.Image, *registrymirror.RegistryMirror, ...curatedpackages.PackageControllerClientOpt) error ReconcileDelete(context.Context, logr.Logger, client.Client, *anywherev1.Cluster) error } -var _ PackageControllerClient = (*curatedpackages.PackageControllerClient)(nil) - func (r *ClusterReconciler) postReconcilePackagesForWorkloadCluster(ctx context.Context, log logr.Logger, cluster *anywherev1.Cluster) (err error) { + image, err := r.getBundleFromCluster(ctx, cluster) + if err != nil { + return err + } + + kubeConfig, err := r.writeKubeConfigSecret(ctx, cluster) + if err != nil { + return err + } + defer func() { + if err := kubeConfig.Remove(); err != nil { + log.Error(err, "removing kubeconfig file", "filename", kubeConfig.Name()) + } + }() + + registry := registrymirror.FromCluster(cluster) + + if err := r.packagesClient.EnableFullLifecycle(ctx, log, cluster.Name, kubeConfig.Name(), image, registry); err != nil { + return fmt.Errorf("packages client error: %w", err) + } + + log.V(6).Info("Installed curated packages on workload cluster", "cluster", cluster.Name) + + return nil +} + +// getBundleFromCluster based on the cluster's k8s version. +func (r *ClusterReconciler) getBundleFromCluster(ctx context.Context, cluster *anywherev1.Cluster) (*v1alpha1.Image, error) { bundles := &v1alpha1.Bundles{} nn := types.NamespacedName{ Name: cluster.Spec.BundlesRef.Name, Namespace: cluster.Spec.BundlesRef.Namespace, } if err := r.client.Get(ctx, nn, bundles); err != nil { - return err + return nil, fmt.Errorf("retrieving bundle: %w", err) } verBundle, err := r.findMatchingBundle(bundles, string(cluster.Spec.KubernetesVersion)) if err != nil { - return err + return nil, err } - image, ok := verBundle.Charts()["eks-anywhere-packages"] - if !ok { - return fmt.Errorf("no chart image") + return &verBundle.PackageController.HelmChart, nil +} + +func (r *ClusterReconciler) findMatchingBundle(bundles *v1alpha1.Bundles, kubeVersion string) (*v1alpha1.VersionsBundle, error) { + var verBundle *v1alpha1.VersionsBundle + for _, b := range bundles.Spec.VersionsBundles { + if b.KubeVersion == string(kubeVersion) { + verBundle = &b + break + } + } + if verBundle == nil { + return nil, fmt.Errorf("no bundle for kube version %q", kubeVersion) } + return verBundle, nil +} + +// writeKubeConfigSecret so that the helm executable can pick it up to install +// curated packages helm charts. +func (r *ClusterReconciler) writeKubeConfigSecret(ctx context.Context, cluster *anywherev1.Cluster) (kubeconfigFile, error) { + kubeConfigSecret := &corev1.Secret{} kubeConfigNN := types.NamespacedName{ Namespace: constants.EksaSystemNamespace, Name: cluster.Name + "-kubeconfig", } - kubeConfigSecret := &corev1.Secret{} if err := r.client.Get(ctx, kubeConfigNN, kubeConfigSecret); err != nil { - return fmt.Errorf("getting kubeconfig secret: %w", err) + return nil, fmt.Errorf("getting kubeconfig secret: %w", err) } secretBytes, err := yaml.Marshal(kubeConfigSecret) if err != nil { - return fmt.Errorf("marshaling secret %w", err) + return nil, fmt.Errorf("marshaling secret %w", err) } f, err := os.CreateTemp("", "kubeconfig-*.yaml") if err != nil { - return fmt.Errorf("opening kubeconfig file %w", err) + return nil, fmt.Errorf("opening kubeconfig file %w", err) } defer f.Close() - // TODO unlink - if _, err := f.Write(secretBytes); err != nil { - return fmt.Errorf("writing kubeconfig file %w", err) + return nil, fmt.Errorf("writing kubeconfig file %w", err) } - f.Close() - rm := registrymirror.FromCluster(cluster) - var options []curatedpackages.PackageControllerClientOpt - err = r.packageControllerClient.EnableCuratedPackagesFullLifecycle(ctx, log, - cluster.Name, - f.Name(), - image, - rm, - options..., - ) - if err != nil { - return fmt.Errorf("package controller client error: %w", err) - } + return &removeableFile{f}, nil +} - log.V(6).Info("Installed curated packages on workload cluster", "cluster", cluster.Name) +// kubeconfigFile adds utility operations to delete a filename after its no +// longer needed. +type kubeconfigFile interface { + // Name is the file's name on disk. + Name() string + // Remove is a best-effort deletion of the file on disk. + Remove() error +} - return nil +// removeableFile wraps os.File to implement kubeconfigFile. +type removeableFile struct { + *os.File } -func (r *ClusterReconciler) findMatchingBundle(bundles *v1alpha1.Bundles, kubeVersion string) (*v1alpha1.VersionsBundle, error) { - var verBundle *v1alpha1.VersionsBundle - for _, b := range bundles.Spec.VersionsBundles { - if b.KubeVersion == string(kubeVersion) { - verBundle = &b - break +// Remove the file from disk. +func (f *removeableFile) Remove() error { + f.Close() + if err := os.Remove(f.Name()); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("removing removeableFile: %w", err) } } - if verBundle == nil { - return nil, fmt.Errorf("no bundle for kube version %q", kubeVersion) - } - - return verBundle, nil + return nil } func (r *ClusterReconciler) reconcileDelete(ctx context.Context, log logr.Logger, cluster *anywherev1.Cluster) (ctrl.Result, error) { @@ -409,13 +445,8 @@ func (r *ClusterReconciler) reconcileDelete(ctx context.Context, log logr.Logger } if !cluster.IsSelfManaged() { - // TODO all those tests that pass a nil package controller client will bork here, as the packageControllerClient will be nil. - if r.packageControllerClient == nil { - return ctrl.Result{}, nil - // return ctrl.Result{}, fmt.Errorf("controller has a nil package controller client, cannot delete package bundle controller") - } - if err := r.packageControllerClient.ReconcileDelete(ctx, log, r.client, cluster); err != nil { - return ctrl.Result{}, fmt.Errorf("deleting packages controller for cluster %q %w", cluster.Name, err) + if err := r.packagesClient.ReconcileDelete(ctx, log, r.client, cluster); err != nil { + return ctrl.Result{}, fmt.Errorf("deleting packages for cluster %q: %w", cluster.Name, err) } } diff --git a/controllers/cluster_controller_test.go b/controllers/cluster_controller_test.go index eb8504fe9f400..538427faa09f0 100644 --- a/controllers/cluster_controller_test.go +++ b/controllers/cluster_controller_test.go @@ -3,10 +3,12 @@ package controllers_test import ( "context" "fmt" + "strings" "testing" "time" "github.com/go-logr/logr" + "github.com/go-logr/logr/testr" "github.com/golang/mock/gomock" . "github.com/onsi/gomega" apiv1 "k8s.io/api/core/v1" @@ -24,6 +26,7 @@ import ( "github.com/aws/eks-anywhere/controllers/mocks" "github.com/aws/eks-anywhere/internal/test/envtest" anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" + "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/controller/clusters" "github.com/aws/eks-anywhere/pkg/govmomi" "github.com/aws/eks-anywhere/pkg/providers/vsphere" @@ -68,7 +71,12 @@ func newVsphereClusterReconcilerTest(t *testing.T, objs ...runtime.Object) *vsph Add(anywherev1.VSphereDatacenterKind, reconciler). Build() - r := controllers.NewClusterReconciler(cl, ®istry, iam, clusterValidator, nil) + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockPkgs.EXPECT(). + ReconcileDelete(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil).AnyTimes() + + r := controllers.NewClusterReconciler(cl, ®istry, iam, clusterValidator, mockPkgs) return &vsphereClusterReconcilerTest{ govcClient: govcClient, @@ -98,12 +106,12 @@ func TestClusterReconcilerReconcileSelfManagedCluster(t *testing.T) { clusterValidator := mocks.NewMockClusterValidator(controller) registry := newRegistryMock(providerReconciler) c := fake.NewClientBuilder().WithRuntimeObjects(selfManagedCluster).Build() - + mockPkgs := mocks.NewMockPackagesClient(controller) providerReconciler.EXPECT().ReconcileWorkerNodes(ctx, gomock.AssignableToTypeOf(logr.Logger{}), sameName(selfManagedCluster)) - r := controllers.NewClusterReconciler(c, registry, iam, clusterValidator, nil) + r := controllers.NewClusterReconciler(c, registry, iam, clusterValidator, mockPkgs) result, err := r.Reconcile(ctx, clusterRequest(selfManagedCluster)) - g.Expect(err).NotTo(HaveOccurred()) + g.Expect(err).ToNot(HaveOccurred()) g.Expect(result).To(Equal(ctrl.Result{})) } @@ -335,6 +343,281 @@ func TestClusterReconcilerDeleteNoCAPIClusterSuccess(t *testing.T) { } } +func TestClusterReconcilerSkipDontInstallPackagesOnSelfManaged(t *testing.T) { + ctx := context.Background() + cluster := &anywherev1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "my-namespace", + }, + Spec: anywherev1.ClusterSpec{ + KubernetesVersion: "v1.25", + BundlesRef: &anywherev1.BundlesRef{ + Name: "my-bundles-ref", + Namespace: "my-namespace", + }, + ManagementCluster: anywherev1.ManagementCluster{ + Name: "", + }, + }, + } + objs := []runtime.Object{cluster} + cb := fake.NewClientBuilder() + mockClient := cb.WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + + ctrl := gomock.NewController(t) + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockPkgs.EXPECT().ReconcileDelete(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + r := controllers.NewClusterReconciler(mockClient, nullRegistry, nil, nil, mockPkgs) + _, err := r.Reconcile(ctx, clusterRequest(cluster)) + if err != nil { + t.Fatalf("expected err to be nil, got %s", err) + } +} + +func TestClusterReconcilerDontDeletePackagesOnSelfManaged(t *testing.T) { + ctx := context.Background() + deleteTime := metav1.NewTime(time.Now().Add(-1 * time.Second)) + cluster := &anywherev1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "my-namespace", + DeletionTimestamp: &deleteTime, + }, + Spec: anywherev1.ClusterSpec{ + KubernetesVersion: "v1.25", + BundlesRef: &anywherev1.BundlesRef{ + Name: "my-bundles-ref", + Namespace: "my-namespace", + }, + ManagementCluster: anywherev1.ManagementCluster{ + Name: "", + }, + }, + } + objs := []runtime.Object{cluster} + cb := fake.NewClientBuilder() + mockClient := cb.WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + + ctrl := gomock.NewController(t) + // At the moment, Reconcile won't get this far, but if the time comes when + // deleting self-managed clusters via full cluster lifecycle happens, we + // need to be aware and adapt appropriately. + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockPkgs.EXPECT().ReconcileDelete(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + r := controllers.NewClusterReconciler(mockClient, nullRegistry, nil, nil, mockPkgs) + _, err := r.Reconcile(ctx, clusterRequest(cluster)) + if err == nil || !strings.Contains(err.Error(), "deleting self-managed clusters is not supported") { + t.Fatalf("unexpected error %s", err) + } +} + +func TestClusterReconcilerPackagesDeletion(s *testing.T) { + newTestCluster := func() *anywherev1.Cluster { + deleteTime := metav1.NewTime(time.Now().Add(-1 * time.Second)) + return &anywherev1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-workload-cluster", + Namespace: "my-namespace", + DeletionTimestamp: &deleteTime, + }, + Spec: anywherev1.ClusterSpec{ + KubernetesVersion: "v1.25", + BundlesRef: &anywherev1.BundlesRef{ + Name: "my-bundles-ref", + Namespace: "my-namespace", + }, + ManagementCluster: anywherev1.ManagementCluster{ + Name: "my-management-cluster", + }, + }, + } + } + + s.Run("errors when packages client errors", func(t *testing.T) { + ctx := context.Background() + log := testr.New(t) + logCtx := ctrl.LoggerInto(ctx, log) + cluster := newTestCluster() + cluster.Spec.BundlesRef.Name = "non-existent" + ctrl := gomock.NewController(t) + objs := []runtime.Object{cluster} + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockPkgs.EXPECT().ReconcileDelete(logCtx, log, gomock.Any(), gomock.Any()).Return(fmt.Errorf("test error")) + mockIAM := mocks.NewMockAWSIamConfigReconciler(ctrl) + mockValid := mocks.NewMockClusterValidator(ctrl) + + r := controllers.NewClusterReconciler(fakeClient, nullRegistry, mockIAM, mockValid, mockPkgs) + _, err := r.Reconcile(logCtx, clusterRequest(cluster)) + if err == nil || !strings.Contains(err.Error(), "test error") { + t.Errorf("expected packages client deletion error, got %s", err) + } + }) +} + +func TestClusterReconcilerPackagesInstall(s *testing.T) { + newTestCluster := func() *anywherev1.Cluster { + return &anywherev1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-workload-cluster", + Namespace: "my-namespace", + }, + Spec: anywherev1.ClusterSpec{ + KubernetesVersion: "v1.25", + BundlesRef: &anywherev1.BundlesRef{ + Name: "my-bundles-ref", + Namespace: "my-namespace", + }, + ManagementCluster: anywherev1.ManagementCluster{ + Name: "my-management-cluster", + }, + }, + } + } + + s.Run("errors when bundles aren't found", func(t *testing.T) { + ctx := context.Background() + log := testr.New(t) + logCtx := ctrl.LoggerInto(ctx, log) + cluster := newTestCluster() + ctrl := gomock.NewController(t) + objs := []runtime.Object{cluster} + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockIAM := mocks.NewMockAWSIamConfigReconciler(ctrl) + mockValid := mocks.NewMockClusterValidator(ctrl) + mockValid.EXPECT().ValidateManagementClusterName(logCtx, log, gomock.Any()).Return(nil) + + r := controllers.NewClusterReconciler(fakeClient, nullRegistry, mockIAM, mockValid, mockPkgs) + _, err := r.Reconcile(logCtx, clusterRequest(cluster)) + if err == nil || !apierrors.IsNotFound(err) { + t.Errorf("expected not found err getting cluster resource, got %s", err) + } + }) + + s.Run("errors when a matching k8s bundle version isn't found", func(t *testing.T) { + ctx := context.Background() + log := testr.New(t) + logCtx := ctrl.LoggerInto(ctx, log) + cluster := newTestCluster() + cluster.Spec.KubernetesVersion = "non-existent" + ctrl := gomock.NewController(t) + bundles := createBundle(cluster) + bundles.ObjectMeta.Name = cluster.Spec.BundlesRef.Name + bundles.ObjectMeta.Namespace = cluster.Spec.BundlesRef.Namespace + objs := []runtime.Object{cluster, bundles} + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockIAM := mocks.NewMockAWSIamConfigReconciler(ctrl) + mockValid := mocks.NewMockClusterValidator(ctrl) + mockValid.EXPECT().ValidateManagementClusterName(logCtx, log, gomock.Any()).Return(nil) + + r := controllers.NewClusterReconciler(fakeClient, nullRegistry, mockIAM, mockValid, mockPkgs) + _, err := r.Reconcile(logCtx, clusterRequest(cluster)) + if err == nil || !strings.Contains(err.Error(), "no bundle for kube version") { + t.Errorf("expected no bundle for kube version error, got %s", err) + } + }) + + s.Run("errors when the kube config secret isn't found", func(t *testing.T) { + ctx := context.Background() + log := testr.New(t) + logCtx := ctrl.LoggerInto(ctx, log) + cluster := newTestCluster() + ctrl := gomock.NewController(t) + bundles := createBundle(cluster) + bundles.Spec.VersionsBundles[0].KubeVersion = string(cluster.Spec.KubernetesVersion) + bundles.ObjectMeta.Name = cluster.Spec.BundlesRef.Name + bundles.ObjectMeta.Namespace = cluster.Spec.BundlesRef.Namespace + objs := []runtime.Object{cluster, bundles} + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockIAM := mocks.NewMockAWSIamConfigReconciler(ctrl) + mockValid := mocks.NewMockClusterValidator(ctrl) + mockValid.EXPECT().ValidateManagementClusterName(logCtx, log, gomock.Any()).Return(nil) + + r := controllers.NewClusterReconciler(fakeClient, nullRegistry, mockIAM, mockValid, mockPkgs) + _, err := r.Reconcile(logCtx, clusterRequest(cluster)) + if err == nil || !apierrors.IsNotFound(err) { + t.Errorf("expected no kubeconfig secret error, got %s", err) + } + }) + + s.Run("errors when enable fails", func(t *testing.T) { + ctx := context.Background() + log := testr.New(t) + logCtx := ctrl.LoggerInto(ctx, log) + cluster := newTestCluster() + ctrl := gomock.NewController(t) + bundles := createBundle(cluster) + bundles.Spec.VersionsBundles[0].KubeVersion = string(cluster.Spec.KubernetesVersion) + bundles.ObjectMeta.Name = cluster.Spec.BundlesRef.Name + bundles.ObjectMeta.Namespace = cluster.Spec.BundlesRef.Namespace + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EksaSystemNamespace, + Name: cluster.Name + "-kubeconfig", + }, + } + objs := []runtime.Object{cluster, bundles, secret} + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + mockIAM := mocks.NewMockAWSIamConfigReconciler(ctrl) + mockValid := mocks.NewMockClusterValidator(ctrl) + mockValid.EXPECT().ValidateManagementClusterName(logCtx, log, gomock.Any()).Return(nil) + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockPkgs.EXPECT().EnableFullLifecycle(logCtx, log, cluster.Name, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf("test error")) + + r := controllers.NewClusterReconciler(fakeClient, nullRegistry, mockIAM, mockValid, mockPkgs) + _, err := r.Reconcile(logCtx, clusterRequest(cluster)) + if err == nil || !strings.Contains(err.Error(), "packages client error: test error") { + t.Errorf("expected packages client error, got %s", err) + } + }) + + s.Run("skips installation when disabled via cluster spec", func(t *testing.T) { + ctx := context.Background() + log := testr.New(t) + logCtx := ctrl.LoggerInto(ctx, log) + cluster := newTestCluster() + cluster.Spec.Packages = &anywherev1.PackageConfiguration{Disable: true} + ctrl := gomock.NewController(t) + bundles := createBundle(cluster) + bundles.Spec.VersionsBundles[0].KubeVersion = string(cluster.Spec.KubernetesVersion) + bundles.ObjectMeta.Name = cluster.Spec.BundlesRef.Name + bundles.ObjectMeta.Namespace = cluster.Spec.BundlesRef.Namespace + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EksaSystemNamespace, + Name: cluster.Name + "-kubeconfig", + }, + } + objs := []runtime.Object{cluster, bundles, secret} + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + nullRegistry := newRegistryForDummyProviderReconciler() + mockIAM := mocks.NewMockAWSIamConfigReconciler(ctrl) + mockValid := mocks.NewMockClusterValidator(ctrl) + mockValid.EXPECT().ValidateManagementClusterName(logCtx, log, gomock.Any()).Return(nil) + mockPkgs := mocks.NewMockPackagesClient(ctrl) + mockPkgs.EXPECT(). + EnableFullLifecycle(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Times(0) + + r := controllers.NewClusterReconciler(fakeClient, nullRegistry, mockIAM, mockValid, mockPkgs) + _, err := r.Reconcile(logCtx, clusterRequest(cluster)) + if err != nil { + t.Errorf("expected nil error, got %s", err) + } + }) +} + func createWNMachineConfig() *anywherev1.VSphereMachineConfig { return &anywherev1.VSphereMachineConfig{ TypeMeta: metav1.TypeMeta{ diff --git a/controllers/cluster_controller_test_test.go b/controllers/cluster_controller_test_test.go index 8ab0389b819cd..84b7f5e37f72c 100644 --- a/controllers/cluster_controller_test_test.go +++ b/controllers/cluster_controller_test_test.go @@ -121,8 +121,8 @@ func TestClusterReconcilerEnsureOwnerReferences(t *testing.T) { validator := newMockClusterValidator(t) validator.EXPECT().ValidateManagementClusterName(ctx, gomock.AssignableToTypeOf(logr.Logger{}), gomock.AssignableToTypeOf(cluster)).Return(nil) - pcc := newMockPackageControllerClient(t) - pcc.EXPECT().EnableCuratedPackagesFullLifecycle(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + pcc := newMockPackagesClient(t) + pcc.EXPECT().EnableFullLifecycle(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) r := controllers.NewClusterReconciler(cl, newRegistryForDummyProviderReconciler(), iam, validator, pcc) _, err := r.Reconcile(ctx, clusterRequest(cluster)) @@ -286,8 +286,8 @@ func TestClusterReconcilerSetBundlesRef(t *testing.T) { g.Expect(cl.Get(ctx, client.ObjectKey{Namespace: cluster.Namespace, Name: managementCluster.Name}, mgmtCluster)).To(Succeed()) g.Expect(cl.Get(ctx, client.ObjectKey{Namespace: cluster.Spec.BundlesRef.Namespace, Name: cluster.Spec.BundlesRef.Name}, bundles)).To(Succeed()) g.Expect(cl.Get(ctx, client.ObjectKey{Namespace: constants.EksaSystemNamespace, Name: cluster.Name + "-kubeconfig"}, secret)).To(Succeed()) - pcc := newMockPackageControllerClient(t) - pcc.EXPECT().EnableCuratedPackagesFullLifecycle(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + pcc := newMockPackagesClient(t) + pcc.EXPECT().EnableFullLifecycle(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) validator := newMockClusterValidator(t) validator.EXPECT().ValidateManagementClusterName(ctx, gomock.AssignableToTypeOf(logr.Logger{}), gomock.AssignableToTypeOf(cluster)).Return(nil) @@ -388,7 +388,7 @@ func newMockClusterValidator(t *testing.T) *mocks.MockClusterValidator { return mocks.NewMockClusterValidator(ctrl) } -func newMockPackageControllerClient(t *testing.T) *mocks.MockPackageControllerClient { +func newMockPackagesClient(t *testing.T) *mocks.MockPackagesClient { ctrl := gomock.NewController(t) - return mocks.NewMockPackageControllerClient(ctrl) + return mocks.NewMockPackagesClient(ctrl) } diff --git a/controllers/mocks/cluster_controller.go b/controllers/mocks/cluster_controller.go index 72357030b1e7a..a929ffcfb16dc 100644 --- a/controllers/mocks/cluster_controller.go +++ b/controllers/mocks/cluster_controller.go @@ -160,50 +160,50 @@ func (mr *MockClusterValidatorMockRecorder) ValidateManagementClusterName(ctx, l return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateManagementClusterName", reflect.TypeOf((*MockClusterValidator)(nil).ValidateManagementClusterName), ctx, log, cluster) } -// MockPackageControllerClient is a mock of PackageControllerClient interface. -type MockPackageControllerClient struct { +// MockPackagesClient is a mock of PackagesClient interface. +type MockPackagesClient struct { ctrl *gomock.Controller - recorder *MockPackageControllerClientMockRecorder + recorder *MockPackagesClientMockRecorder } -// MockPackageControllerClientMockRecorder is the mock recorder for MockPackageControllerClient. -type MockPackageControllerClientMockRecorder struct { - mock *MockPackageControllerClient +// MockPackagesClientMockRecorder is the mock recorder for MockPackagesClient. +type MockPackagesClientMockRecorder struct { + mock *MockPackagesClient } -// NewMockPackageControllerClient creates a new mock instance. -func NewMockPackageControllerClient(ctrl *gomock.Controller) *MockPackageControllerClient { - mock := &MockPackageControllerClient{ctrl: ctrl} - mock.recorder = &MockPackageControllerClientMockRecorder{mock} +// NewMockPackagesClient creates a new mock instance. +func NewMockPackagesClient(ctrl *gomock.Controller) *MockPackagesClient { + mock := &MockPackagesClient{ctrl: ctrl} + mock.recorder = &MockPackagesClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPackageControllerClient) EXPECT() *MockPackageControllerClientMockRecorder { +func (m *MockPackagesClient) EXPECT() *MockPackagesClientMockRecorder { return m.recorder } -// EnableCuratedPackagesFullLifecycle mocks base method. -func (m *MockPackageControllerClient) EnableCuratedPackagesFullLifecycle(arg0 context.Context, arg1 logr.Logger, arg2, arg3 string, arg4 *v1alpha10.Image, arg5 *registrymirror.RegistryMirror, arg6 ...curatedpackages.PackageControllerClientOpt) error { +// EnableFullLifecycle mocks base method. +func (m *MockPackagesClient) EnableFullLifecycle(arg0 context.Context, arg1 logr.Logger, arg2, arg3 string, arg4 *v1alpha10.Image, arg5 *registrymirror.RegistryMirror, arg6 ...curatedpackages.PackageControllerClientOpt) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1, arg2, arg3, arg4, arg5} for _, a := range arg6 { varargs = append(varargs, a) } - ret := m.ctrl.Call(m, "EnableCuratedPackagesFullLifecycle", varargs...) + ret := m.ctrl.Call(m, "EnableFullLifecycle", varargs...) ret0, _ := ret[0].(error) return ret0 } -// EnableCuratedPackagesFullLifecycle indicates an expected call of EnableCuratedPackagesFullLifecycle. -func (mr *MockPackageControllerClientMockRecorder) EnableCuratedPackagesFullLifecycle(arg0, arg1, arg2, arg3, arg4, arg5 interface{}, arg6 ...interface{}) *gomock.Call { +// EnableFullLifecycle indicates an expected call of EnableFullLifecycle. +func (mr *MockPackagesClientMockRecorder) EnableFullLifecycle(arg0, arg1, arg2, arg3, arg4, arg5 interface{}, arg6 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1, arg2, arg3, arg4, arg5}, arg6...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableCuratedPackagesFullLifecycle", reflect.TypeOf((*MockPackageControllerClient)(nil).EnableCuratedPackagesFullLifecycle), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableFullLifecycle", reflect.TypeOf((*MockPackagesClient)(nil).EnableFullLifecycle), varargs...) } // ReconcileDelete mocks base method. -func (m *MockPackageControllerClient) ReconcileDelete(arg0 context.Context, arg1 logr.Logger, arg2 client.Client, arg3 *v1alpha1.Cluster) error { +func (m *MockPackagesClient) ReconcileDelete(arg0 context.Context, arg1 logr.Logger, arg2 client.Client, arg3 *v1alpha1.Cluster) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReconcileDelete", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) @@ -211,7 +211,58 @@ func (m *MockPackageControllerClient) ReconcileDelete(arg0 context.Context, arg1 } // ReconcileDelete indicates an expected call of ReconcileDelete. -func (mr *MockPackageControllerClientMockRecorder) ReconcileDelete(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockPackagesClientMockRecorder) ReconcileDelete(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReconcileDelete", reflect.TypeOf((*MockPackageControllerClient)(nil).ReconcileDelete), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReconcileDelete", reflect.TypeOf((*MockPackagesClient)(nil).ReconcileDelete), arg0, arg1, arg2, arg3) +} + +// MockkubeconfigFile is a mock of kubeconfigFile interface. +type MockkubeconfigFile struct { + ctrl *gomock.Controller + recorder *MockkubeconfigFileMockRecorder +} + +// MockkubeconfigFileMockRecorder is the mock recorder for MockkubeconfigFile. +type MockkubeconfigFileMockRecorder struct { + mock *MockkubeconfigFile +} + +// NewMockkubeconfigFile creates a new mock instance. +func NewMockkubeconfigFile(ctrl *gomock.Controller) *MockkubeconfigFile { + mock := &MockkubeconfigFile{ctrl: ctrl} + mock.recorder = &MockkubeconfigFileMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockkubeconfigFile) EXPECT() *MockkubeconfigFileMockRecorder { + return m.recorder +} + +// Name mocks base method. +func (m *MockkubeconfigFile) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockkubeconfigFileMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockkubeconfigFile)(nil).Name)) +} + +// Remove mocks base method. +func (m *MockkubeconfigFile) Remove() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Remove") + ret0, _ := ret[0].(error) + return ret0 +} + +// Remove indicates an expected call of Remove. +func (mr *MockkubeconfigFileMockRecorder) Remove() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockkubeconfigFile)(nil).Remove)) } diff --git a/pkg/api/v1alpha1/cluster_types.go b/pkg/api/v1alpha1/cluster_types.go index 2cd1983c7b158..7442c6009db4f 100644 --- a/pkg/api/v1alpha1/cluster_types.go +++ b/pkg/api/v1alpha1/cluster_types.go @@ -70,6 +70,12 @@ func (c *Cluster) HasAWSIamConfig() bool { return false } +// IsPackagesEnabled checks if the configuration supports curated packages +// installation. +func (c *Cluster) IsPackagesEnabled() bool { + return !c.IsSelfManaged() && (c.Spec.Packages == nil || !c.Spec.Packages.Disable) +} + func (n *Cluster) Equal(o *Cluster) bool { if n == o { return true diff --git a/pkg/curatedpackages/mocks/packageinstaller.go b/pkg/curatedpackages/mocks/packageinstaller.go index a36ef0ec3c241..50609050885ba 100644 --- a/pkg/curatedpackages/mocks/packageinstaller.go +++ b/pkg/curatedpackages/mocks/packageinstaller.go @@ -34,18 +34,18 @@ func (m *MockPackageController) EXPECT() *MockPackageControllerMockRecorder { return m.recorder } -// EnableCuratedPackages mocks base method. -func (m *MockPackageController) EnableCuratedPackages(ctx context.Context) error { +// Enable mocks base method. +func (m *MockPackageController) Enable(ctx context.Context) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableCuratedPackages", ctx) + ret := m.ctrl.Call(m, "Enable", ctx) ret0, _ := ret[0].(error) return ret0 } -// EnableCuratedPackages indicates an expected call of EnableCuratedPackages. -func (mr *MockPackageControllerMockRecorder) EnableCuratedPackages(ctx interface{}) *gomock.Call { +// Enable indicates an expected call of Enable. +func (mr *MockPackageControllerMockRecorder) Enable(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableCuratedPackages", reflect.TypeOf((*MockPackageController)(nil).EnableCuratedPackages), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enable", reflect.TypeOf((*MockPackageController)(nil).Enable), ctx) } // IsInstalled mocks base method. diff --git a/pkg/curatedpackages/packagecontrollerclient.go b/pkg/curatedpackages/packagecontrollerclient.go index 1b2b28b5fdf60..6e770d887f9aa 100644 --- a/pkg/curatedpackages/packagecontrollerclient.go +++ b/pkg/curatedpackages/packagecontrollerclient.go @@ -38,9 +38,10 @@ const ( type PackageControllerClientOpt func(client *PackageControllerClient) type PackageControllerClient struct { - kubeConfig string - chart *releasev1.Image - chartInstaller ChartInstaller + kubeConfig string + chart *releasev1.Image + chartInstaller ChartInstaller + // deleter handles helm chart install deletion. deleter ChartInstallationDeleter clusterName string clusterSpec *v1alpha1.ClusterSpec @@ -73,11 +74,11 @@ type ChartInstallationDeleter interface { Delete(ctx context.Context, kubeconfigFilePath, installName, namespace string) error } -// NewPackageControllerClientFullLifecycle instantiates a new instance of -// PackageControllerClient for the Full Cluster Lifecycle controller. +// NewPackageControllerClientFullLifecycle creates a PackageControllerClient +// for the Full Cluster Lifecycle controller. // // It differs because the CLI use case has far more information available at -// instantiation, while the FCL use case has some information at +// instantiation, while the FCL use case has less information at // instantiation, and the rest when cluster creation is triggered. func NewPackageControllerClientFullLifecycle(logger logr.Logger, chartInstaller ChartInstaller, deleter ChartInstallationDeleter, kubectl KubectlRunner) *PackageControllerClient { return &PackageControllerClient{ @@ -85,22 +86,23 @@ func NewPackageControllerClientFullLifecycle(logger logr.Logger, chartInstaller deleter: deleter, kubectl: kubectl, skipWaitForPackageBundle: true, + eksaRegion: eksaDefaultRegion, } } -// EnableCuratedPackagesFullLifecycle receives additional run-time arguments. +// EnableFullLifecycle wraps Enable to handle run-time arguments. // // This method fills in the gaps between the original CLI use case, where all // information is known at PackageControllerClient initialization, and the // Full Cluster Lifecycle use case, where there's limited information at // initialization. -func (pc *PackageControllerClient) EnableCuratedPackagesFullLifecycle(ctx context.Context, log logr.Logger, clusterName, kubeConfig string, chart *releasev1.Image, registryMirror *registrymirror.RegistryMirror, options ...PackageControllerClientOpt) (err error) { - log.V(1).Info("enabling curated package full lifecycle", "clusterName", clusterName) +func (pc *PackageControllerClient) EnableFullLifecycle(ctx context.Context, log logr.Logger, clusterName, kubeConfig string, chart *releasev1.Image, registryMirror *registrymirror.RegistryMirror, options ...PackageControllerClientOpt) (err error) { + log.V(6).Info("enabling curated package full lifecycle", "clusterName", clusterName) defer func(err *error) { if err != nil && *err != nil { - log.V(1).Info("error enabling curated packages full lifecycle", "error", *err, "clusterName", clusterName) + log.V(6).Error(*err, "enabling curated packages full lifecycle", "clusterName", clusterName) } else { - log.V(1).Info("success enabling curated packages full lifecycle") + log.V(6).Info("success enabling curated packages full lifecycle") } }(&err) pc.skipWaitForPackageBundle = true @@ -117,7 +119,7 @@ func (pc *PackageControllerClient) EnableCuratedPackagesFullLifecycle(ctx contex o(pc) } - return pc.EnableCuratedPackages(ctx) + return pc.Enable(ctx) } // NewPackageControllerClient instantiates a new instance of PackageControllerClient. @@ -139,7 +141,8 @@ func NewPackageControllerClient(chartInstaller ChartInstaller, deleter ChartInst return pcc } -// EnableCuratedPackages enables curated packages in a cluster +// Enable curated packages in a cluster +// // In case the cluster is management cluster, it performs the following actions: // - Installation of Package Controller through helm chart installation // - Creation of secret credentials @@ -148,7 +151,7 @@ func NewPackageControllerClient(chartInstaller ChartInstaller, deleter ChartInst // // In case the cluster is a workload cluster, it performs the following actions: // - Creation of package bundle controller (PBC) custom resource in management cluster -func (pc *PackageControllerClient) EnableCuratedPackages(ctx context.Context) error { +func (pc *PackageControllerClient) Enable(ctx context.Context) error { ociURI := fmt.Sprintf("%s%s", "oci://", pc.registryMirror.ReplaceRegistry(pc.chart.Image())) clusterName := fmt.Sprintf("clusterName=%s", pc.clusterName) sourceRegistry, defaultRegistry, defaultImageRegistry := pc.GetCuratedPackagesRegistries() @@ -431,7 +434,7 @@ func (pc *PackageControllerClient) ReconcileDelete(ctx context.Context, logger l } name := "eks-anywhere-packages-" + pc.clusterName - if err := pc.deleter.Delete(ctx, pc.kubeConfig, name, ""); err != nil { + if err := pc.deleter.Delete(ctx, pc.kubeConfig, name, constants.EksaPackagesName); err != nil { return err } diff --git a/pkg/curatedpackages/packagecontrollerclient_test.go b/pkg/curatedpackages/packagecontrollerclient_test.go index 198843e2d7c63..c3d9472ef239e 100644 --- a/pkg/curatedpackages/packagecontrollerclient_test.go +++ b/pkg/curatedpackages/packagecontrollerclient_test.go @@ -11,8 +11,12 @@ import ( "testing" "time" + "github.com/go-logr/logr/testr" "github.com/golang/mock/gomock" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" packagesv1 "github.com/aws/eks-anywhere-packages/api/v1alpha1" "github.com/aws/eks-anywhere/internal/test" @@ -261,7 +265,7 @@ func newPackageControllerTests(t *testing.T) []*packageControllerTest { } } -func TestEnableCuratedPackagesSuccess(t *testing.T) { +func TestEnableSuccess(t *testing.T) { for _, tt := range newPackageControllerTests(t) { clusterName := fmt.Sprintf("clusterName=%s", "billy") valueFilePath := filepath.Join("billy", filewriter.DefaultTmpFolder, valueFileName) @@ -278,7 +282,7 @@ func TestEnableCuratedPackagesSuccess(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). @@ -288,14 +292,14 @@ func TestEnableCuratedPackagesSuccess(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err != nil { t.Errorf("Install Controller Should succeed when installation passes") } } } -func TestEnableCuratedPackagesSucceedInWorkloadCluster(t *testing.T) { +func TestEnableSucceedInWorkloadCluster(t *testing.T) { for _, tt := range newPackageControllerTests(t) { tt.command = curatedpackages.NewPackageControllerClient( tt.chartInstaller, tt.deleter, tt.kubectl, tt.clusterName, tt.kubeConfig, tt.chart, @@ -322,7 +326,7 @@ func TestEnableCuratedPackagesSucceedInWorkloadCluster(t *testing.T) { values = append(values, "cronjob.suspend=true") } values = append(values, "workloadOnly=true") - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name+"-billy", ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name+"-billy", ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). @@ -332,7 +336,7 @@ func TestEnableCuratedPackagesSucceedInWorkloadCluster(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) tt.Expect(err).To(BeNil()) } } @@ -355,7 +359,7 @@ func getPBCFail(t *testing.T) func(context.Context, string, string, string, stri } } -func TestEnableCuratedPackagesWithProxy(t *testing.T) { +func TestEnableWithProxy(t *testing.T) { for _, tt := range newPackageControllerTests(t) { tt.command = curatedpackages.NewPackageControllerClient( tt.chartInstaller, tt.deleter, tt.kubectl, "billy", tt.kubeConfig, tt.chart, @@ -392,7 +396,7 @@ func TestEnableCuratedPackagesWithProxy(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). @@ -402,14 +406,14 @@ func TestEnableCuratedPackagesWithProxy(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err != nil { t.Errorf("Install Controller Should succeed when installation passes") } } } -func TestEnableCuratedPackagesWithEmptyProxy(t *testing.T) { +func TestEnableWithEmptyProxy(t *testing.T) { for _, tt := range newPackageControllerTests(t) { tt.command = curatedpackages.NewPackageControllerClient( tt.chartInstaller, tt.deleter, tt.kubectl, "billy", tt.kubeConfig, tt.chart, @@ -443,7 +447,7 @@ func TestEnableCuratedPackagesWithEmptyProxy(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). @@ -453,14 +457,14 @@ func TestEnableCuratedPackagesWithEmptyProxy(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err != nil { t.Errorf("Install Controller Should succeed when installation passes") } } } -func TestEnableCuratedPackagesFail(t *testing.T) { +func TestEnableFail(t *testing.T) { for _, tt := range newPackageControllerTests(t) { clusterName := fmt.Sprintf("clusterName=%s", "billy") valueFilePath := filepath.Join("billy", filewriter.DefaultTmpFolder, valueFileName) @@ -477,20 +481,20 @@ func TestEnableCuratedPackagesFail(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(errors.New("login failed")) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(errors.New("login failed")) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err == nil { t.Errorf("Install Controller Should fail when installation fails") } } } -func TestEnableCuratedPackagesFailNoActiveBundle(t *testing.T) { +func TestEnableFailNoActiveBundle(t *testing.T) { for _, tt := range newPackageControllerTests(t) { clusterName := fmt.Sprintf("clusterName=%s", "billy") valueFilePath := filepath.Join("billy", filewriter.DefaultTmpFolder, valueFileName) @@ -507,20 +511,20 @@ func TestEnableCuratedPackagesFailNoActiveBundle(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCFail(t)). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err == nil { t.Errorf("expected error, got nil") } } } -func TestEnableCuratedPackagesSuccessWhenCronJobFails(t *testing.T) { +func TestEnableSuccessWhenCronJobFails(t *testing.T) { for _, tt := range newPackageControllerTests(t) { clusterName := fmt.Sprintf("clusterName=%s", "billy") valueFilePath := filepath.Join("billy", filewriter.DefaultTmpFolder, valueFileName) @@ -537,7 +541,7 @@ func TestEnableCuratedPackagesSuccessWhenCronJobFails(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). @@ -547,7 +551,7 @@ func TestEnableCuratedPackagesSuccessWhenCronJobFails(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err != nil { t.Errorf("Install Controller Should succeed when cron job fails") } @@ -578,7 +582,7 @@ func TestIsInstalledFalse(t *testing.T) { } } -func TestEnableCuratedPackagesActiveBundleCustomTimeout(t *testing.T) { +func TestEnableActiveBundleCustomTimeout(t *testing.T) { for _, tt := range newPackageControllerTests(t) { tt.command = curatedpackages.NewPackageControllerClient( tt.chartInstaller, tt.deleter, tt.kubectl, "billy", tt.kubeConfig, tt.chart, @@ -610,7 +614,7 @@ func TestEnableCuratedPackagesActiveBundleCustomTimeout(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). @@ -620,14 +624,14 @@ func TestEnableCuratedPackagesActiveBundleCustomTimeout(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err != nil { t.Errorf("Install Controller Should succeed when installation passes") } } } -func TestEnableCuratedPackagesActiveBundleWaitLoops(t *testing.T) { +func TestEnableActiveBundleWaitLoops(t *testing.T) { for _, tt := range newPackageControllerTests(t) { clusterName := fmt.Sprintf("clusterName=%s", "billy") valueFilePath := filepath.Join("billy", filewriter.DefaultTmpFolder, valueFileName) @@ -644,7 +648,7 @@ func TestEnableCuratedPackagesActiveBundleWaitLoops(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCLoops(t, 3)). @@ -654,7 +658,7 @@ func TestEnableCuratedPackagesActiveBundleWaitLoops(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) if err != nil { t.Errorf("expected no error, got %v", err) } @@ -677,7 +681,7 @@ func getPBCLoops(t *testing.T, loops int) func(context.Context, string, string, } } -func TestEnableCuratedPackagesActiveBundleTimesOut(t *testing.T) { +func TestEnableActiveBundleTimesOut(t *testing.T) { for _, tt := range newPackageControllerTests(t) { tt.command = curatedpackages.NewPackageControllerClient( tt.chartInstaller, tt.deleter, tt.kubectl, "billy", tt.kubeConfig, tt.chart, @@ -709,13 +713,13 @@ func TestEnableCuratedPackagesActiveBundleTimesOut(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCDelay(t, time.Second)). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) expectedErr := fmt.Errorf("timed out finding an active package bundle / eksa-packages-billy namespace for the current cluster: %v", context.DeadlineExceeded) if err.Error() != expectedErr.Error() { t.Errorf("expected %v, got %v", expectedErr, err) @@ -723,7 +727,7 @@ func TestEnableCuratedPackagesActiveBundleTimesOut(t *testing.T) { } } -func TestEnableCuratedPackagesActiveBundleNamespaceTimesOut(t *testing.T) { +func TestEnableActiveBundleNamespaceTimesOut(t *testing.T) { for _, tt := range newPackageControllerTests(t) { tt.command = curatedpackages.NewPackageControllerClient( tt.chartInstaller, tt.deleter, tt.kubectl, "billy", tt.kubeConfig, tt.chart, @@ -755,7 +759,7 @@ func TestEnableCuratedPackagesActiveBundleNamespaceTimesOut(t *testing.T) { if (tt.eksaAccessID == "" || tt.eksaAccessKey == "") && tt.registryMirror == nil { values = append(values, "cronjob.suspend=true") } - tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, "eksa-packages", valueFilePath, values).Return(nil) + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, values).Return(nil) tt.kubectl.EXPECT(). GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(getPBCSuccess(t)). @@ -765,7 +769,7 @@ func TestEnableCuratedPackagesActiveBundleNamespaceTimesOut(t *testing.T) { DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return false, nil }). AnyTimes() - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) expectedErr := fmt.Errorf("timed out finding an active package bundle / eksa-packages-billy namespace for the current cluster: %v", context.DeadlineExceeded) if err.Error() != expectedErr.Error() { t.Errorf("expected %v, got %v", expectedErr, err) @@ -825,7 +829,7 @@ func TestCreateHelmOverrideValuesYamlFailWithNoWriter(t *testing.T) { t.Setenv("REGISTRY_PASSWORD", "password") } - err := tt.command.EnableCuratedPackages(tt.ctx) + err := tt.command.Enable(tt.ctx) expectedErr := fmt.Errorf("valuesFileWriter is nil") if err.Error() != expectedErr.Error() { t.Errorf("expected %v, got %v", expectedErr, err) @@ -926,7 +930,7 @@ func TestGetCuratedPackagesRegistriesDefaultRegion(t *testing.T) { } g := NewWithT(t) cluster := cluster.Spec{Config: &cluster.Config{Cluster: &v1alpha1.Cluster{Spec: clusterSpec}}} - sut := curatedpackages.NewPackageControllerClient(nil, nil, "billy", "", chart, nil, curatedpackages.WithClusterSpec(&cluster)) + sut := curatedpackages.NewPackageControllerClient(nil, nil, nil, "billy", "", chart, nil, curatedpackages.WithClusterSpec(&cluster)) _, _, img := sut.GetCuratedPackagesRegistries() g.Expect(img).To(Equal("783794618700.dkr.ecr.us-west-2.amazonaws.com")) } @@ -943,7 +947,7 @@ func TestGetCuratedPackagesRegistriesCustomRegion(t *testing.T) { } g := NewWithT(t) cluster := cluster.Spec{Config: &cluster.Config{Cluster: &v1alpha1.Cluster{Spec: clusterSpec}}} - sut := curatedpackages.NewPackageControllerClient(nil, nil, "billy", "", chart, nil, curatedpackages.WithClusterSpec(&cluster), curatedpackages.WithEksaRegion("test")) + sut := curatedpackages.NewPackageControllerClient(nil, nil, nil, "billy", "", chart, nil, curatedpackages.WithClusterSpec(&cluster), curatedpackages.WithEksaRegion("test")) _, _, img := sut.GetCuratedPackagesRegistries() g.Expect(img).To(Equal("783794618700.dkr.ecr.test.amazonaws.com")) } @@ -964,3 +968,187 @@ func TestGetPackageControllerConfigurationError(t *testing.T) { g.Expect(err).NotTo(BeNil()) g.Expect(err.Error()).To(Equal("invalid environment in specification ")) } + +func TestReconcileDeleteGoldenPath(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + log := testr.New(t) + + cluster := &v1alpha1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "billy"}} + kubeconfig := "test.kubeconfig" + nsName := constants.EksaPackagesName + "-" + cluster.Name + ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nsName}} + client := fake.NewClientBuilder().WithRuntimeObjects(ns).Build() + ctrl := gomock.NewController(t) + deleter := mocks.NewMockChartInstallationDeleter(ctrl) + deleter.EXPECT().Delete(ctx, kubeconfig, "eks-anywhere-packages-"+cluster.Name, constants.EksaPackagesName) + + sut := curatedpackages.NewPackageControllerClient(nil, deleter, nil, "billy", kubeconfig, nil, nil) + + err := sut.ReconcileDelete(ctx, log, client, cluster) + g.Expect(err).To(BeNil()) +} + +func TestReconcileDeleteNamespaceErrorsOut(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + log := testr.New(t) + + cluster := &v1alpha1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "billy"}} + kubeconfig := "test.kubeconfig" + // There's no namespace defined in the fake client; deleting will fail + // with not found. + client := fake.NewClientBuilder().Build() + ctrl := gomock.NewController(t) + deleter := mocks.NewMockChartInstallationDeleter(ctrl) + + sut := curatedpackages.NewPackageControllerClient(nil, deleter, nil, "billy", kubeconfig, nil, nil) + + err := sut.ReconcileDelete(ctx, log, client, cluster) + g.Expect(err).Should(HaveOccurred()) + g.Expect(err.Error()).Should(ContainSubstring("\"eksa-packages-billy\" not found")) +} + +func TestReconcileDeleteHelmErrorsOut(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + log := testr.New(t) + + cluster := &v1alpha1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "billy"}} + kubeconfig := "test.kubeconfig" + nsName := constants.EksaPackagesName + "-" + cluster.Name + ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nsName}} + client := fake.NewClientBuilder().WithRuntimeObjects(ns).Build() + ctrl := gomock.NewController(t) + deleter := mocks.NewMockChartInstallationDeleter(ctrl) + // TODO this namespace should no longer be empty, following PR 5081 + testErr := fmt.Errorf("test error") + deleter.EXPECT(). + Delete(ctx, kubeconfig, "eks-anywhere-packages-"+cluster.Name, constants.EksaPackagesName). + Return(testErr) + + sut := curatedpackages.NewPackageControllerClient(nil, deleter, nil, "billy", kubeconfig, nil, nil) + + err := sut.ReconcileDelete(ctx, log, client, cluster) + g.Expect(err).Should(HaveOccurred()) + g.Expect(err.Error()).Should(Equal("test error")) +} + +func TestEnableFullLifecyclePath(t *testing.T) { + log := testr.New(t) + ctrl := gomock.NewController(t) + k := mocks.NewMockKubectlRunner(ctrl) + ci := mocks.NewMockChartInstaller(ctrl) + del := mocks.NewMockChartInstallationDeleter(ctrl) + kubeConfig := "kubeconfig.kubeconfig" + chart := &artifactsv1.Image{ + Name: "test_controller", + URI: "test_registry/eks-anywhere/eks-anywhere-packages:v1", + } + clusterName := "billy" + writer, _ := filewriter.NewWriter(clusterName) + + tt := packageControllerTest{ + WithT: NewWithT(t), + ctx: context.Background(), + kubectl: k, + chartInstaller: ci, + command: curatedpackages.NewPackageControllerClientFullLifecycle(log, ci, del, k), + clusterName: clusterName, + kubeConfig: kubeConfig, + chart: chart, + registryMirror: nil, + writer: writer, + wantValueFile: "testdata/values_empty.yaml", + } + + valueFilePath := filepath.Join("billy", filewriter.DefaultTmpFolder, valueFileName) + ociURI := fmt.Sprintf("%s%s", "oci://", tt.registryMirror.ReplaceRegistry(tt.chart.Image())) + // GetCuratedPackagesRegistries can't be used here, as when initialized + // via full cluster lifecycle the package controller client hasn't yet + // determined its chart. + values := []string{ + "clusterName=" + clusterName, + "workloadOnly=true", + "sourceRegistry=public.ecr.aws/eks-anywhere", + "defaultRegistry=public.ecr.aws/eks-anywhere", + "defaultImageRegistry=783794618700.dkr.ecr.us-west-2.amazonaws.com", + "cronjob.suspend=true", + } + + tt.chartInstaller.EXPECT().InstallChart(tt.ctx, tt.chart.Name+"-"+clusterName, ociURI, tt.chart.Tag(), tt.kubeConfig, constants.EksaPackagesName, valueFilePath, gomock.InAnyOrder(values)).Return(nil) + tt.kubectl.EXPECT(). + GetObject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(getPBCSuccess(t)). + AnyTimes() + tt.kubectl.EXPECT(). + HasResource(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_, _, _, _, _ interface{}) (bool, error) { return true, nil }). + AnyTimes() + chartImage := &artifactsv1.Image{ + Name: "test_controller", + URI: "test_registry/eks-anywhere/eks-anywhere-packages:v1", + } + + err := tt.command.EnableFullLifecycle(tt.ctx, log, clusterName, kubeConfig, chartImage, tt.registryMirror, curatedpackages.WithEksaRegion("us-west-2")) + if err != nil { + t.Errorf("Install Controller Should succeed when installation passes") + } +} + +func TestGetCuratedPackagesRegistries(s *testing.T) { + s.Run("substitutes a region if set", func(t *testing.T) { + ctrl := gomock.NewController(t) + k := mocks.NewMockKubectlRunner(ctrl) + ci := mocks.NewMockChartInstaller(ctrl) + del := mocks.NewMockChartInstallationDeleter(ctrl) + kubeConfig := "kubeconfig.kubeconfig" + chart := &artifactsv1.Image{ + Name: "test_controller", + URI: "test_registry/eks-anywhere/eks-anywhere-packages:v1", + } + // eksaRegion := "test-region" + clusterName := "billy" + writer, _ := filewriter.NewWriter(clusterName) + client := curatedpackages.NewPackageControllerClient( + ci, del, k, clusterName, kubeConfig, chart, nil, + curatedpackages.WithManagementClusterName(clusterName), + curatedpackages.WithValuesFileWriter(writer), + curatedpackages.WithEksaRegion("testing"), + ) + + expected := "783794618700.dkr.ecr.testing.amazonaws.com" + _, _, got := client.GetCuratedPackagesRegistries() + + if got != expected { + t.Errorf("expected %q, got %q", expected, got) + } + }) + + s.Run("won't substitute a blank region", func(t *testing.T) { + ctrl := gomock.NewController(t) + k := mocks.NewMockKubectlRunner(ctrl) + ci := mocks.NewMockChartInstaller(ctrl) + del := mocks.NewMockChartInstallationDeleter(ctrl) + kubeConfig := "kubeconfig.kubeconfig" + chart := &artifactsv1.Image{ + Name: "test_controller", + URI: "test_registry/eks-anywhere/eks-anywhere-packages:v1", + } + // eksaRegion := "test-region" + clusterName := "billy" + writer, _ := filewriter.NewWriter(clusterName) + client := curatedpackages.NewPackageControllerClient( + ci, del, k, clusterName, kubeConfig, chart, nil, + curatedpackages.WithManagementClusterName(clusterName), + curatedpackages.WithValuesFileWriter(writer), + ) + + expected := "783794618700.dkr.ecr.us-west-2.amazonaws.com" + _, _, got := client.GetCuratedPackagesRegistries() + + if got != expected { + t.Errorf("expected %q, got %q", expected, got) + } + }) +} diff --git a/pkg/curatedpackages/packageinstaller.go b/pkg/curatedpackages/packageinstaller.go index 669eaee661d51..affcffbf9e62c 100644 --- a/pkg/curatedpackages/packageinstaller.go +++ b/pkg/curatedpackages/packageinstaller.go @@ -9,7 +9,8 @@ import ( ) type PackageController interface { - EnableCuratedPackages(ctx context.Context) error + // Enable curated packages support. + Enable(ctx context.Context) error IsInstalled(ctx context.Context) bool } @@ -68,7 +69,7 @@ func (pi *Installer) installPackagesController(ctx context.Context) error { logger.Info(" Package controller disabled") return nil } - err := pi.packageController.EnableCuratedPackages(ctx) + err := pi.packageController.Enable(ctx) if err != nil { return err } diff --git a/pkg/curatedpackages/packageinstaller_test.go b/pkg/curatedpackages/packageinstaller_test.go index 8db6140759c7b..c2a70180e372d 100644 --- a/pkg/curatedpackages/packageinstaller_test.go +++ b/pkg/curatedpackages/packageinstaller_test.go @@ -72,7 +72,7 @@ func TestPackageInstallerSuccess(t *testing.T) { tt := newPackageInstallerTest(t) tt.packageClient.EXPECT().CreatePackages(tt.ctx, tt.packagePath, tt.kubeConfigPath).Return(nil) - tt.packageControllerClient.EXPECT().EnableCuratedPackages(tt.ctx).Return(nil) + tt.packageControllerClient.EXPECT().Enable(tt.ctx).Return(nil) tt.command.InstallCuratedPackages(tt.ctx) } @@ -80,7 +80,7 @@ func TestPackageInstallerSuccess(t *testing.T) { func TestPackageInstallerFailWhenControllerFails(t *testing.T) { tt := newPackageInstallerTest(t) - tt.packageControllerClient.EXPECT().EnableCuratedPackages(tt.ctx).Return(errors.New("controller installation failed")) + tt.packageControllerClient.EXPECT().Enable(tt.ctx).Return(errors.New("controller installation failed")) tt.command.InstallCuratedPackages(tt.ctx) } @@ -89,7 +89,7 @@ func TestPackageInstallerFailWhenPackageFails(t *testing.T) { tt := newPackageInstallerTest(t) tt.packageClient.EXPECT().CreatePackages(tt.ctx, tt.packagePath, tt.kubeConfigPath).Return(errors.New("path doesn't exist")) - tt.packageControllerClient.EXPECT().EnableCuratedPackages(tt.ctx).Return(nil) + tt.packageControllerClient.EXPECT().Enable(tt.ctx).Return(nil) tt.command.InstallCuratedPackages(tt.ctx) } diff --git a/pkg/executables/helm.go b/pkg/executables/helm.go index a422c5cb6a4c1..83348ce3ab133 100644 --- a/pkg/executables/helm.go +++ b/pkg/executables/helm.go @@ -171,8 +171,11 @@ func (h *Helm) Delete(ctx context.Context, kubeconfigFilePath, installName, name params := []string{ "delete", installName, "--kubeconfig", kubeconfigFilePath, - "--namespace", namespace, } + if namespace != "" { + params = append(params, "--namespace", namespace) + } + params = h.addInsecureFlagIfProvided(params) if _, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).Run(); err != nil { return fmt.Errorf("deleting helm installation %w", err) diff --git a/pkg/executables/helm_test.go b/pkg/executables/helm_test.go index 8276fc9beeb28..eeb3451a1f762 100644 --- a/pkg/executables/helm_test.go +++ b/pkg/executables/helm_test.go @@ -4,11 +4,13 @@ import ( "bytes" "context" "errors" + "fmt" "testing" "github.com/golang/mock/gomock" . "github.com/onsi/gomega" + "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/executables" "github.com/aws/eks-anywhere/pkg/executables/mocks" "github.com/aws/eks-anywhere/pkg/registrymirror" @@ -140,6 +142,19 @@ func TestHelmSaveChartSuccessWithInsecure(t *testing.T) { tt.Expect(tt.h.SaveChart(tt.ctx, url, version, destinationFolder)).To(Succeed()) } +func TestHelmSkipCRDs(t *testing.T) { + tt := newHelmTest(t, executables.WithSkipCRDs()) + url := "url" + version := "1.1" + kubeconfig := "kubeconfig" + chart := "chart" + expectCommand( + tt.e, tt.ctx, "upgrade", "--install", chart, url, "--version", version, "--skip-crds", "--kubeconfig", kubeconfig, "--create-namespace", "--namespace", constants.EksaPackagesName, + ).withEnvVars(tt.envVars).to().Return(bytes.Buffer{}, nil) + + tt.Expect(tt.h.InstallChart(tt.ctx, chart, url, version, kubeconfig, constants.EksaPackagesName, "", nil)).To(Succeed()) +} + func TestHelmInstallChartSuccess(t *testing.T) { tt := newHelmTest(t) chart := "chart" @@ -277,3 +292,40 @@ func TestHelmListCharts(t *testing.T) { tt.Expect(result).To(Equal(expected)) }) } + +func TestHelmDelete(s *testing.T) { + kubeconfig := "/root/.kube/config" + + s.Run("Success", func(t *testing.T) { + tt := newHelmTest(s) + installName := "test-install" + expectCommand(tt.e, tt.ctx, "delete", installName, "--kubeconfig", kubeconfig).withEnvVars(tt.envVars).to().Return(bytes.Buffer{}, nil) + err := tt.h.Delete(tt.ctx, kubeconfig, installName, "") + tt.Expect(err).NotTo(HaveOccurred()) + }) + + s.Run("passes the namespace, if present", func(t *testing.T) { + tt := newHelmTest(s) + testNamespace := "testing" + installName := "test-install" + expectCommand(tt.e, tt.ctx, "delete", installName, "--kubeconfig", kubeconfig, "--namespace", testNamespace).withEnvVars(tt.envVars).to().Return(bytes.Buffer{}, nil) + err := tt.h.Delete(tt.ctx, kubeconfig, installName, testNamespace) + tt.Expect(err).NotTo(HaveOccurred()) + }) + + s.Run("passes the insecure skip flag", func(t *testing.T) { + tt := newHelmTest(t, executables.WithInsecure()) + installName := "test-install" + expectCommand(tt.e, tt.ctx, "delete", installName, "--kubeconfig", kubeconfig, "--insecure-skip-tls-verify").withEnvVars(tt.envVars).to().Return(bytes.Buffer{}, nil) + err := tt.h.Delete(tt.ctx, kubeconfig, installName, "") + tt.Expect(err).NotTo(HaveOccurred()) + }) + + s.Run("returns errors from the helm executable", func(t *testing.T) { + tt := newHelmTest(s) + installName := "test-install" + expectCommand(tt.e, tt.ctx, "delete", installName, "--kubeconfig", kubeconfig).withEnvVars(tt.envVars).to().Return(bytes.Buffer{}, fmt.Errorf("test error")) + err := tt.h.Delete(tt.ctx, kubeconfig, installName, "") + tt.Expect(err).To(HaveOccurred()) + }) +}