Skip to content

Commit

Permalink
Preflight upgrade validations for pod disruption budgets
Browse files Browse the repository at this point in the history
  • Loading branch information
mitalipaygude committed Apr 25, 2023
1 parent d4e63d7 commit 32ee30e
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 25 deletions.
6 changes: 4 additions & 2 deletions cmd/eksctl-anywhere/cmd/createcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ func (cc *createClusterOptions) createCluster(cmd *cobra.Command, _ []string) er
WithGitOpsFlux(clusterSpec.Cluster, clusterSpec.FluxConfig, cliConfig).
WithWriter().
WithEksdInstaller().
WithPackageInstaller(clusterSpec, cc.installPackages, cc.managementKubeconfig)
WithPackageInstaller(clusterSpec, cc.installPackages, cc.managementKubeconfig).
WithUnAuthKubeClient().
WithValidatorClients()

if cc.timeoutOptions.noTimeouts {
factory.WithNoTimeouts()
Expand All @@ -142,7 +144,7 @@ func (cc *createClusterOptions) createCluster(cmd *cobra.Command, _ []string) er
)

validationOpts := &validations.Opts{
Kubectl: deps.Kubectl,
Kubectl: deps.UnAuthKubectlClient,
Spec: clusterSpec,
WorkloadCluster: &types.Cluster{
Name: clusterSpec.Cluster.Name,
Expand Down
14 changes: 12 additions & 2 deletions cmd/eksctl-anywhere/cmd/upgradecluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type upgradeClusterOptions struct {
forceClean bool
hardwareCSVPath string
tinkerbellBootstrapIP string
skipValidations []string
}

var uc = &upgradeClusterOptions{}
Expand All @@ -48,6 +49,7 @@ func init() {
applyTinkerbellHardwareFlag(upgradeClusterCmd.Flags(), &uc.hardwareCSVPath)
upgradeClusterCmd.Flags().StringVarP(&uc.wConfig, "w-config", "w", "", "Kubeconfig file to use when upgrading a workload cluster")
upgradeClusterCmd.Flags().BoolVar(&uc.forceClean, "force-cleanup", false, "Force deletion of previously created bootstrap cluster")
upgradeClusterCmd.Flags().StringArrayVar(&uc.skipValidations, "skip-validations", []string{}, "Bypass validations by name")

if err := upgradeClusterCmd.MarkFlagRequired("filename"); err != nil {
log.Fatalf("Error marking flag as required: %v", err)
Expand Down Expand Up @@ -107,7 +109,8 @@ func (uc *upgradeClusterOptions) upgradeCluster(cmd *cobra.Command) error {
WithCAPIManager().
WithEksdUpgrader().
WithEksdInstaller().
WithKubectl()
WithKubectl().
WithValidatorClients()

if uc.timeoutOptions.noTimeouts {
factory.WithNoTimeouts()
Expand Down Expand Up @@ -144,13 +147,20 @@ func (uc *upgradeClusterOptions) upgradeCluster(cmd *cobra.Command) error {
}

validationOpts := &validations.Opts{
Kubectl: deps.Kubectl,
Kubectl: deps.UnAuthKubectlClient,
Spec: clusterSpec,
WorkloadCluster: workloadCluster,
ManagementCluster: managementCluster,
Provider: deps.Provider,
CliConfig: cliConfig,
}

if len(uc.skipValidations) != 0 {
validationOpts.SkippedValidations, err = upgradevalidations.ValidateSkippableUpgradeValidation(uc.skipValidations)
if err != nil {
return err
}
}
upgradeValidations := upgradevalidations.New(validationOpts)

err = upgradeCluster.Run(ctx, clusterSpec, managementCluster, workloadCluster, upgradeValidations, uc.forceClean)
Expand Down
4 changes: 3 additions & 1 deletion cmd/eksctl-anywhere/cmd/validatecreatecluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func (valOpt *validateOptions) validateCreateCluster(cmd *cobra.Command, _ []str
WithKubectl().
WithProvider(valOpt.fileName, clusterSpec.Cluster, false, valOpt.hardwareCSVPath, true, valOpt.tinkerbellBootstrapIP).
WithGitOpsFlux(clusterSpec.Cluster, clusterSpec.FluxConfig, cliConfig).
WithUnAuthKubeClient().
WithValidatorClients().
Build(ctx)
if err != nil {
cleanupDirectory(tmpPath)
Expand All @@ -83,7 +85,7 @@ func (valOpt *validateOptions) validateCreateCluster(cmd *cobra.Command, _ []str
defer close(ctx, deps)

validationOpts := &validations.Opts{
Kubectl: deps.Kubectl,
Kubectl: deps.UnAuthKubectlClient,
Spec: clusterSpec,
WorkloadCluster: &types.Cluster{
Name: clusterSpec.Cluster.Name,
Expand Down
23 changes: 23 additions & 0 deletions pkg/dependencies/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ type Dependencies struct {
NutanixValidator *nutanix.Validator
SnowValidator *snow.Validator
IPValidator *validator.IPValidator
UnAuthKubectlClient KubeClients
}

// KubeClients defines super struct that exposes all behavior.
type KubeClients struct {
*executables.Kubectl
*kubernetes.UnAuthClient
}

func (d *Dependencies) Close(ctx context.Context) error {
Expand Down Expand Up @@ -1075,6 +1082,22 @@ func (f *Factory) WithKubeProxyCLIUpgrader() *Factory {
return f
}

// WithValidatorClients builds KubeClients.
func (f *Factory) WithValidatorClients() *Factory {
f.WithKubectl().WithUnAuthKubeClient()

f.buildSteps = append(f.buildSteps, func(ctx context.Context) error {
f.dependencies.UnAuthKubectlClient = KubeClients{
Kubectl: f.dependencies.Kubectl,
UnAuthClient: f.dependencies.UnAuthKubeClient,
}

return nil
})

return f
}

// WithLogger setups a logger to be injected in constructors. It uses the logger
// package level logger.
func (f *Factory) WithLogger() *Factory {
Expand Down
13 changes: 11 additions & 2 deletions pkg/validations/createvalidations/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ import (

"github.com/aws/eks-anywhere/internal/test"
"github.com/aws/eks-anywhere/pkg/api/v1alpha1"
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
"github.com/aws/eks-anywhere/pkg/constants"
"github.com/aws/eks-anywhere/pkg/executables"
"github.com/aws/eks-anywhere/pkg/validations"
"github.com/aws/eks-anywhere/pkg/validations/createvalidations"
)

const testclustername string = "testcluster"

type UnAuthKubectlClient struct {
*executables.Kubectl
*kubernetes.UnAuthClient
}

func TestValidateClusterPresent(t *testing.T) {
tests := []struct {
name string
Expand All @@ -44,12 +51,13 @@ func TestValidateClusterPresent(t *testing.T) {
}

k, ctx, cluster, e := validations.NewKubectl(t)
uk := kubernetes.NewUnAuthClient(k)
cluster.Name = testclustername
for _, tc := range tests {
t.Run(tc.name, func(tt *testing.T) {
fileContent := test.ReadFile(t, tc.getClusterResponse)
e.EXPECT().Execute(ctx, []string{"get", capiClustersResourceType, "-o", "json", "--kubeconfig", cluster.KubeconfigFile, "--namespace", constants.EksaSystemNamespace}).Return(*bytes.NewBufferString(fileContent), nil)
err := createvalidations.ValidateClusterNameIsUnique(ctx, k, cluster, testclustername)
err := createvalidations.ValidateClusterNameIsUnique(ctx, UnAuthKubectlClient{k, uk}, cluster, testclustername)
if !reflect.DeepEqual(err, tc.wantErr) {
t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr)
}
Expand Down Expand Up @@ -93,12 +101,13 @@ func TestValidateManagementClusterCRDs(t *testing.T) {
}

k, ctx, cluster, e := validations.NewKubectl(t)
uk := kubernetes.NewUnAuthClient(k)
cluster.Name = testclustername
for _, tc := range tests {
t.Run(tc.name, func(tt *testing.T) {
e.EXPECT().Execute(ctx, []string{"get", "customresourcedefinition", capiClustersResourceType, "--kubeconfig", cluster.KubeconfigFile}).Return(bytes.Buffer{}, tc.errGetClusterCRD).Times(tc.errGetClusterCRDCount)
e.EXPECT().Execute(ctx, []string{"get", "customresourcedefinition", eksaClusterResourceType, "--kubeconfig", cluster.KubeconfigFile}).Return(bytes.Buffer{}, tc.errGetEKSAClusterCRD).Times(tc.errGetEKSAClusterCRDCount)
err := createvalidations.ValidateManagementCluster(ctx, k, cluster)
err := createvalidations.ValidateManagementCluster(ctx, UnAuthKubectlClient{k, uk}, cluster)
if tc.wantErr {
assert.Error(tt, err, "expected ValidateManagementCluster to return an error", "test", tc.name)
} else {
Expand Down
7 changes: 5 additions & 2 deletions pkg/validations/createvalidations/identityproviders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/aws/eks-anywhere/internal/test"
"github.com/aws/eks-anywhere/pkg/api/v1alpha1"
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
"github.com/aws/eks-anywhere/pkg/cluster"
"github.com/aws/eks-anywhere/pkg/validations"
"github.com/aws/eks-anywhere/pkg/validations/createvalidations"
Expand Down Expand Up @@ -62,6 +63,7 @@ func TestValidateIdendityProviderForWorkloadClusters(t *testing.T) {
s.OIDCConfig = defaultOIDC
})
k, ctx, cluster, e := validations.NewKubectl(t)
uk := kubernetes.NewUnAuthClient(k)
cluster.Name = testclustername
for _, tc := range tests {
t.Run(tc.name, func(tt *testing.T) {
Expand All @@ -73,7 +75,7 @@ func TestValidateIdendityProviderForWorkloadClusters(t *testing.T) {
"--field-selector=metadata.name=oidc-config-test",
}).Return(*bytes.NewBufferString(fileContent), nil)

err := createvalidations.ValidateIdentityProviderNameIsUnique(ctx, k, cluster, clusterSpec)
err := createvalidations.ValidateIdentityProviderNameIsUnique(ctx, UnAuthKubectlClient{k, uk}, cluster, clusterSpec)
if !reflect.DeepEqual(err, tc.wantErr) {
t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr)
}
Expand Down Expand Up @@ -123,6 +125,7 @@ func TestValidateIdentityProviderForSelfManagedCluster(t *testing.T) {
s.Cluster.SetSelfManaged()
})
k, ctx, cluster, e := validations.NewKubectl(t)
uk := kubernetes.NewUnAuthClient(k)
cluster.Name = testclustername
for _, tc := range tests {
t.Run(tc.name, func(tt *testing.T) {
Expand All @@ -133,7 +136,7 @@ func TestValidateIdentityProviderForSelfManagedCluster(t *testing.T) {
"--field-selector=metadata.name=oidc-config-test",
}).Times(0)

err := createvalidations.ValidateIdentityProviderNameIsUnique(ctx, k, cluster, clusterSpec)
err := createvalidations.ValidateIdentityProviderNameIsUnique(ctx, UnAuthKubectlClient{k, uk}, cluster, clusterSpec)
if !reflect.DeepEqual(err, tc.wantErr) {
t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/validations/createvalidations/preflightvalidations.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (v *CreateValidations) PreflightValidations(ctx context.Context) []validati
return &validations.ValidationResult{
Name: "validate certificate for registry mirror",
Remediation: fmt.Sprintf("provide a valid certificate for you registry endpoint using %s env var", anywherev1.RegistryMirrorCAKey),
Err: validations.ValidateCertForRegistryMirror(v.Opts.Spec, v.Opts.TlsValidator),
Err: validations.ValidateCertForRegistryMirror(v.Opts.Spec, v.Opts.TLSValidator),
}
},
func() *validations.ValidationResult {
Expand Down
2 changes: 2 additions & 0 deletions pkg/validations/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import (
"k8s.io/apimachinery/pkg/runtime"

"github.com/aws/eks-anywhere/pkg/api/v1alpha1"
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
"github.com/aws/eks-anywhere/pkg/executables"
mockexecutables "github.com/aws/eks-anywhere/pkg/executables/mocks"
"github.com/aws/eks-anywhere/pkg/types"
releasev1alpha1 "github.com/aws/eks-anywhere/release/api/v1alpha1"
)

type KubectlClient interface {
List(ctx context.Context, kubeconfig string, list kubernetes.ObjectList) error
ValidateControlPlaneNodes(ctx context.Context, cluster *types.Cluster, clusterName string) error
ValidateWorkerNodes(ctx context.Context, clusterName string, kubeconfig string) error
ValidateNodes(ctx context.Context, kubeconfig string) error
Expand Down
15 changes: 15 additions & 0 deletions pkg/validations/mocks/kubectl.go

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

5 changes: 4 additions & 1 deletion pkg/validations/upgradevalidations/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/aws/eks-anywhere/internal/test"
"github.com/aws/eks-anywhere/pkg/api/v1alpha1"
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
"github.com/aws/eks-anywhere/pkg/constants"
"github.com/aws/eks-anywhere/pkg/validations"
"github.com/aws/eks-anywhere/pkg/validations/upgradevalidations"
Expand Down Expand Up @@ -43,12 +44,14 @@ func TestValidateClusterPresent(t *testing.T) {
}

k, ctx, cluster, e := validations.NewKubectl(t)
uk := kubernetes.NewUnAuthClient(k)

cluster.Name = testclustername
for _, tc := range tests {
t.Run(tc.name, func(tt *testing.T) {
fileContent := test.ReadFile(t, tc.getClusterResponse)
e.EXPECT().Execute(ctx, []string{"get", capiClustersResourceType, "-o", "json", "--kubeconfig", cluster.KubeconfigFile, "--namespace", constants.EksaSystemNamespace}).Return(*bytes.NewBufferString(fileContent), nil)
err := upgradevalidations.ValidateClusterObjectExists(ctx, k, cluster)
err := upgradevalidations.ValidateClusterObjectExists(ctx, UnAuthKubectlClient{k, uk}, cluster)
if !reflect.DeepEqual(err, tc.wantErr) {
t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr)
}
Expand Down
28 changes: 28 additions & 0 deletions pkg/validations/upgradevalidations/poddisruptionbudgets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package upgradevalidations

import (
"context"
"fmt"

"github.com/pkg/errors"
policy "k8s.io/api/policy/v1"

"github.com/aws/eks-anywhere/pkg/cluster"
"github.com/aws/eks-anywhere/pkg/providers"
"github.com/aws/eks-anywhere/pkg/types"
"github.com/aws/eks-anywhere/pkg/validations"
)

// ValidatePodDisruptionBudgets returns an error if any pdbs are detected on a cluster.
func ValidatePodDisruptionBudgets(ctx context.Context, k validations.KubectlClient, cluster *types.Cluster, spec *cluster.Spec, provider providers.Provider) error {
podDisruptionBudgets := &policy.PodDisruptionBudgetList{}
if err := k.List(ctx, cluster.KubeconfigFile, podDisruptionBudgets); err != nil {
return errors.Wrap(err, "listing cluster pod disruption budgets for upgrade")
}

if len(podDisruptionBudgets.Items) != 0 {
return fmt.Errorf("one or more pod disruption budgets were detected on the cluster. Use the --skip-validations=%s flag if you wish to skip the validations for pod disruption budgets and proceed with the upgrade operation", PDB)
}

return nil
}
14 changes: 13 additions & 1 deletion pkg/validations/upgradevalidations/preflightvalidations.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (u *UpgradeValidations) PreflightValidations(ctx context.Context) []validat
return &validations.ValidationResult{
Name: "validate certificate for registry mirror",
Remediation: fmt.Sprintf("provide a valid certificate for you registry endpoint using %s env var", anywherev1.RegistryMirrorCAKey),
Err: validations.ValidateCertForRegistryMirror(u.Opts.Spec, u.Opts.TlsValidator),
Err: validations.ValidateCertForRegistryMirror(u.Opts.Spec, u.Opts.TLSValidator),
}
},
func() *validations.ValidationResult {
Expand Down Expand Up @@ -104,5 +104,17 @@ func (u *UpgradeValidations) PreflightValidations(ctx context.Context) []validat
}
})
}

if !u.Opts.SkippedValidations[PDB] {
upgradeValidations = append(
upgradeValidations,
func() *validations.ValidationResult {
return &validations.ValidationResult{
Name: "validate pod disruption budgets",
Remediation: "",
Err: ValidatePodDisruptionBudgets(ctx, k, targetCluster, u.Opts.Spec, u.Opts.Provider),
}
})
}
return upgradeValidations
}
Loading

0 comments on commit 32ee30e

Please sign in to comment.