From 7744f4f06acaefe1ac44bcceb6b123c278288f40 Mon Sep 17 00:00:00 2001 From: Tanvir Tatla Date: Fri, 22 Dec 2023 13:48:03 -0800 Subject: [PATCH] validate eksarelease exists --- pkg/cluster/builder.go | 5 ++++- pkg/executables/kubectl.go | 18 ++++++++++++++++++ pkg/features/features.go | 9 +++++++++ pkg/features/features_test.go | 8 ++++++++ pkg/validations/cluster.go | 18 ++++++++++++++++-- .../createvalidations/preflightvalidations.go | 7 +++++++ .../preflightvalidations_test.go | 3 +++ pkg/validations/kubectl.go | 1 + pkg/validations/mocks/kubectl.go | 15 +++++++++++++++ .../upgradevalidations/preflightvalidations.go | 11 +++++++++-- .../preflightvalidations_test.go | 1 + 11 files changed, 91 insertions(+), 5 deletions(-) diff --git a/pkg/cluster/builder.go b/pkg/cluster/builder.go index 9f35550499b09..4afebadede4e9 100644 --- a/pkg/cluster/builder.go +++ b/pkg/cluster/builder.go @@ -7,6 +7,7 @@ import ( "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/constants" + "github.com/aws/eks-anywhere/pkg/features" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/manifests" "github.com/aws/eks-anywhere/pkg/manifests/bundles" @@ -125,7 +126,9 @@ func (b FileSpecBuilder) Build(clusterConfigURL string) (*Spec, error) { } releaseVersion := v1alpha1.EksaVersion(release.Version) - config.Cluster.Spec.EksaVersion = &releaseVersion + if config.Cluster.IsSelfManaged() || (config.Cluster.IsManaged() && !features.OverrideEksaVersion().IsActive()) { + config.Cluster.Spec.EksaVersion = &releaseVersion + } eksaRelease := buildEKSARelease(release, bundlesManifest) return NewSpec(config, bundlesManifest, eksdReleases, eksaRelease) diff --git a/pkg/executables/kubectl.go b/pkg/executables/kubectl.go index 9e3a612c741cc..7a6419e91fa83 100644 --- a/pkg/executables/kubectl.go +++ b/pkg/executables/kubectl.go @@ -92,6 +92,7 @@ var ( eksaPackageBundlesType = fmt.Sprintf("packagebundles.%s", packagesv1.GroupVersion.Group) kubectlConnectionRefusedRegex = regexp.MustCompile("The connection to the server .* was refused") kubectlConnectionTimeoutRegex = regexp.MustCompile("Unable to connect to the server.*timeout.*") + eksaReleaseType = fmt.Sprintf("eksareleases.%s", releasev1alpha1.GroupVersion.Group) ) type Kubectl struct { @@ -2535,3 +2536,20 @@ func (k *Kubectl) DeleteCRD(ctx context.Context, crd, kubeconfig string) error { return nil } + +// GetEKSARelease returns an eksareleases CR on the cluster. +func (k *Kubectl) GetEKSARelease(ctx context.Context, releaseName string, kubeconfigFile string) (*releasev1alpha1.EKSARelease, error) { + params := []string{"get", eksaReleaseType, releaseName, "-o", "json", "--kubeconfig", kubeconfigFile, "--namespace", constants.EksaSystemNamespace} + stdOut, err := k.Execute(ctx, params...) + if err != nil { + return nil, fmt.Errorf("getting eksarelease %v", err) + } + + response := &releasev1alpha1.EKSARelease{} + err = json.Unmarshal(stdOut.Bytes(), response) + if err != nil { + return nil, fmt.Errorf("parsing get eksarelease response: %v", err) + } + + return response, nil +} diff --git a/pkg/features/features.go b/pkg/features/features.go index 5c02c4823bd50..6c2351fdee2c6 100644 --- a/pkg/features/features.go +++ b/pkg/features/features.go @@ -7,6 +7,7 @@ const ( UseNewWorkflowsEnvVar = "USE_NEW_WORKFLOWS" UseControllerForCli = "USE_CONTROLLER_FOR_CLI" K8s129SupportEnvVar = "K8S_1_29_SUPPORT" + OverrideEksaVersionEnvVar = "OVERRIDE_EKSA_VERSION" ) func FeedGates(featureGates []string) { @@ -63,3 +64,11 @@ func K8s129Support() Feature { IsActive: globalFeatures.isActiveForEnvVar(K8s129SupportEnvVar), } } + +// OverrideEksaVersion is the feature flag to allow users to use their own value for eksaVersion for workload clusters. +func OverrideEksaVersion() Feature { + return Feature{ + Name: "Allow users to provide their own eksaVersion value for workload clusters", + IsActive: globalFeatures.isActiveForEnvVar(OverrideEksaVersionEnvVar), + } +} diff --git a/pkg/features/features_test.go b/pkg/features/features_test.go index cd3cf4f3f80d3..81e044ef4817a 100644 --- a/pkg/features/features_test.go +++ b/pkg/features/features_test.go @@ -93,3 +93,11 @@ func TestWithK8s129FeatureFlag(t *testing.T) { g.Expect(os.Setenv(K8s129SupportEnvVar, "true")).To(Succeed()) g.Expect(IsActive(K8s129Support())).To(BeTrue()) } + +func TestOverrideEksaVersionTrue(t *testing.T) { + g := NewWithT(t) + setupContext(t) + + t.Setenv(OverrideEksaVersionEnvVar, "true") + g.Expect(OverrideEksaVersion().IsActive()).To(BeTrue()) +} diff --git a/pkg/validations/cluster.go b/pkg/validations/cluster.go index 593843c08495b..4af940c192656 100644 --- a/pkg/validations/cluster.go +++ b/pkg/validations/cluster.go @@ -112,8 +112,10 @@ func ValidateEksaVersion(ctx context.Context, cliVersion string, workload *clust return fmt.Errorf("parsing eksa cli version: %v", err) } - if !parsedVersion.SamePatch(parsedCLIVersion) { - return fmt.Errorf("cluster's eksaVersion does not match EKS-A CLI's version") + if features.OverrideEksaVersion().IsActive() && workload.Cluster.IsManaged() && parsedVersion.GreaterThan(parsedCLIVersion) { + return fmt.Errorf("workload cluster's eksaVersion cannot be greater than EKS-Anywhere CLI's version") + } else if !parsedVersion.SamePatch(parsedCLIVersion) { + return fmt.Errorf("cluster's eksaVersion does not match EKS-Anywhere CLI's version") } return nil @@ -215,3 +217,15 @@ func ValidateK8s129Support(clusterSpec *cluster.Spec) error { } return nil } + +// ValidateEksaReleaseExistOnManagement checks if there is a corresponding eksareleases CR for workload's eksaVersion on the mgmt cluster. +func ValidateEksaReleaseExistOnManagement(ctx context.Context, k KubectlClient, mgmtCluster *types.Cluster, workload *cluster.Spec) error { + v := workload.Cluster.Spec.EksaVersion + if v == nil { + return fmt.Errorf("cluster has nil EksaVersion") + } + if release, err := k.GetEKSARelease(ctx, string(*workload.Cluster.Spec.EksaVersion), mgmtCluster.KubeconfigFile); err != nil || release == nil { + return fmt.Errorf("management cluster does have components for eksaVersion installed") + } + return nil +} diff --git a/pkg/validations/createvalidations/preflightvalidations.go b/pkg/validations/createvalidations/preflightvalidations.go index 7ea7b28feb81b..9b651f906c613 100644 --- a/pkg/validations/createvalidations/preflightvalidations.go +++ b/pkg/validations/createvalidations/preflightvalidations.go @@ -105,6 +105,13 @@ func (v *CreateValidations) PreflightValidations(ctx context.Context) []validati Err: validations.ValidateManagementClusterEksaVersion(ctx, k, v.Opts.ManagementCluster, v.Opts.Spec), } }, + func() *validations.ValidationResult { + return &validations.ValidationResult{ + Name: "validate eksa release components exist on management cluster", + Remediation: fmt.Sprintf("check eksaVersion field for workload cluster %s matches release previously used to upgrade maangement cluster %s", v.Opts.WorkloadCluster.Name, v.Opts.ManagementCluster.Name), + Err: validations.ValidateEksaReleaseExistOnManagement(ctx, k, v.Opts.ManagementCluster, v.Opts.Spec), + } + }, ) } diff --git a/pkg/validations/createvalidations/preflightvalidations_test.go b/pkg/validations/createvalidations/preflightvalidations_test.go index 4996d8861bc1c..34a338d59293a 100644 --- a/pkg/validations/createvalidations/preflightvalidations_test.go +++ b/pkg/validations/createvalidations/preflightvalidations_test.go @@ -82,10 +82,13 @@ func TestPreFlightValidationsWorkloadCluster(t *testing.T) { }, } + testRelease := test.EKSARelease() + tt.k.EXPECT().GetClusters(tt.ctx, tt.c.Opts.WorkloadCluster).Return(nil, nil) tt.k.EXPECT().ValidateClustersCRD(tt.ctx, tt.c.Opts.WorkloadCluster).Return(nil) tt.k.EXPECT().ValidateEKSAClustersCRD(tt.ctx, tt.c.Opts.WorkloadCluster).Return(nil) tt.k.EXPECT().GetEksaCluster(tt.ctx, tt.c.Opts.ManagementCluster, mgmtClusterName).Return(mgmt, nil).MaxTimes(3) + tt.k.EXPECT().GetEKSARelease(tt.ctx, string(version), tt.c.Opts.ManagementCluster.KubeconfigFile).Return(testRelease, nil) tt.Expect(validations.ProcessValidationResults(tt.c.PreflightValidations(tt.ctx))).To(Succeed()) } diff --git a/pkg/validations/kubectl.go b/pkg/validations/kubectl.go index 5d352565c2cc4..a93159feb2be6 100644 --- a/pkg/validations/kubectl.go +++ b/pkg/validations/kubectl.go @@ -29,6 +29,7 @@ type KubectlClient interface { GetEksaGitOpsConfig(ctx context.Context, gitOpsConfigName string, kubeconfigFile string, namespace string) (*v1alpha1.GitOpsConfig, error) GetEksaFluxConfig(ctx context.Context, fluxConfigName string, kubeconfigFile string, namespace string) (*v1alpha1.FluxConfig, error) GetEksaOIDCConfig(ctx context.Context, oidcConfigName string, kubeconfigFile string, namespace string) (*v1alpha1.OIDCConfig, error) + GetEKSARelease(ctx context.Context, releaseName string, kubeconfigFile string) (*releasev1alpha1.EKSARelease, error) GetEksaVSphereDatacenterConfig(ctx context.Context, vsphereDatacenterConfigName string, kubeconfigFile string, namespace string) (*v1alpha1.VSphereDatacenterConfig, error) GetEksaTinkerbellDatacenterConfig(ctx context.Context, tinkerbellDatacenterConfigName string, kubeconfigFile string, namespace string) (*v1alpha1.TinkerbellDatacenterConfig, error) GetEksaTinkerbellMachineConfig(ctx context.Context, tinkerbellMachineConfigName string, kubeconfigFile string, namespace string) (*v1alpha1.TinkerbellMachineConfig, error) diff --git a/pkg/validations/mocks/kubectl.go b/pkg/validations/mocks/kubectl.go index 1c60831b219e8..7754effa3dd73 100644 --- a/pkg/validations/mocks/kubectl.go +++ b/pkg/validations/mocks/kubectl.go @@ -70,6 +70,21 @@ func (mr *MockKubectlClientMockRecorder) GetClusters(ctx, cluster interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusters", reflect.TypeOf((*MockKubectlClient)(nil).GetClusters), ctx, cluster) } +// GetEKSARelease mocks base method. +func (m *MockKubectlClient) GetEKSARelease(ctx context.Context, releaseName, kubeconfigFile string) (*v1alpha10.EKSARelease, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEKSARelease", ctx, releaseName, kubeconfigFile) + ret0, _ := ret[0].(*v1alpha10.EKSARelease) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEKSARelease indicates an expected call of GetEKSARelease. +func (mr *MockKubectlClientMockRecorder) GetEKSARelease(ctx, releaseName, kubeconfigFile interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEKSARelease", reflect.TypeOf((*MockKubectlClient)(nil).GetEKSARelease), ctx, releaseName, kubeconfigFile) +} + // GetEksaAWSIamConfig mocks base method. func (m *MockKubectlClient) GetEksaAWSIamConfig(ctx context.Context, awsIamConfigName, kubeconfigFile, namespace string) (*v1alpha1.AWSIamConfig, error) { m.ctrl.T.Helper() diff --git a/pkg/validations/upgradevalidations/preflightvalidations.go b/pkg/validations/upgradevalidations/preflightvalidations.go index d361d4457b66c..44c9ad22074a7 100644 --- a/pkg/validations/upgradevalidations/preflightvalidations.go +++ b/pkg/validations/upgradevalidations/preflightvalidations.go @@ -109,8 +109,8 @@ func (u *UpgradeValidations) PreflightValidations(ctx context.Context) []validat }, func() *validations.ValidationResult { return &validations.ValidationResult{ - Name: "validate cluster's eksaVersion matches EKS-A Version", - Remediation: "ensure EksaVersion matches the EKS-A release or omit the value from the cluster config", + Name: "validate cluster's eksaVersion matches EKS-Anywhere Version", + Remediation: "ensure eksaVersion matches the EKS-Anywhere release or omit the value from the cluster config", Err: validations.ValidateEksaVersion(ctx, u.Opts.CliVersion, u.Opts.Spec), } }, @@ -134,6 +134,13 @@ func (u *UpgradeValidations) PreflightValidations(ctx context.Context) []validat Err: validations.ValidateManagementClusterEksaVersion(ctx, k, u.Opts.ManagementCluster, u.Opts.Spec), } }, + func() *validations.ValidationResult { + return &validations.ValidationResult{ + Name: "validate eksa release components exist on management cluster", + Remediation: fmt.Sprintf("check eksaVersion field for workload cluster %s matches release previously used to upgrade maangement cluster %s", u.Opts.WorkloadCluster.Name, u.Opts.ManagementCluster.Name), + Err: validations.ValidateEksaReleaseExistOnManagement(ctx, k, u.Opts.ManagementCluster, u.Opts.Spec), + } + }, ) } diff --git a/pkg/validations/upgradevalidations/preflightvalidations_test.go b/pkg/validations/upgradevalidations/preflightvalidations_test.go index d8300f183872a..a526bbe80f1cd 100644 --- a/pkg/validations/upgradevalidations/preflightvalidations_test.go +++ b/pkg/validations/upgradevalidations/preflightvalidations_test.go @@ -1148,6 +1148,7 @@ func TestPreflightValidationsVsphere(t *testing.T) { k.EXPECT().GetEksaCluster(ctx, workloadCluster, clusterSpec.Cluster.Name).Return(existingClusterSpec.Cluster, nil).MaxTimes(4) if opts.Spec.Cluster.IsManaged() { k.EXPECT().GetEksaCluster(ctx, workloadCluster, workloadCluster.Name).Return(existingClusterSpec.Cluster, nil).MaxTimes(4) + k.EXPECT().GetEKSARelease(ctx, string(ver), workloadCluster.KubeconfigFile).Return(test.EKSARelease(), nil) } k.EXPECT().GetEksaGitOpsConfig(ctx, clusterSpec.Cluster.Spec.GitOpsRef.Name, gomock.Any(), gomock.Any()).Return(existingClusterSpec.GitOpsConfig, nil).MaxTimes(1) k.EXPECT().GetEksaOIDCConfig(ctx, clusterSpec.Cluster.Spec.IdentityProviderRefs[1].Name, gomock.Any(), gomock.Any()).Return(existingClusterSpec.OIDCConfig, nil).MaxTimes(1)