From 453668c868a16b0bf2818868eb1f6eb597412ddb Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Fri, 3 Mar 2023 15:07:16 -0600 Subject: [PATCH] validate persistent volumes for aks clusters --- cmd/src/validate_kube.go | 14 ++++++ internal/validate/kube/aks.go | 91 ++++++++++++++++++++++++++++++++++ internal/validate/kube/kube.go | 29 +++++++++++ 3 files changed, 134 insertions(+) create mode 100644 internal/validate/kube/aks.go diff --git a/cmd/src/validate_kube.go b/cmd/src/validate_kube.go index fa42327372..46f9374f42 100644 --- a/cmd/src/validate_kube.go +++ b/cmd/src/validate_kube.go @@ -33,6 +33,15 @@ Examples: Suppress output (useful for CI/CD pipelines) $ src validate kube --quiet + + Validate EKS cluster: + $ src validate kube --eks + + Validate GKE cluster: + $ src validate kube --gke + + Validate AKS cluster: + $ src validate kube --aks ` flagSet := flag.NewFlagSet("kube", flag.ExitOnError) @@ -48,6 +57,7 @@ Examples: quiet = flagSet.Bool("quiet", false, "(optional) suppress output and return exit status only") eks = flagSet.Bool("eks", false, "(optional) validate EKS cluster") gke = flagSet.Bool("gke", false, "(optional) validate GKE cluster") + aks = flagSet.Bool("aks", false, "(optional) validate AKS cluster") ) if home := homedir.HomeDir(); home != "" { @@ -96,6 +106,10 @@ Examples: options = append(options, kube.Gke()) } + if *aks { + options = append(options, kube.Aks()) + } + return kube.Validate(context.Background(), clientSet, config, options...) } diff --git a/internal/validate/kube/aks.go b/internal/validate/kube/aks.go new file mode 100644 index 0000000000..dec8b4f921 --- /dev/null +++ b/internal/validate/kube/aks.go @@ -0,0 +1,91 @@ +package kube + +import ( + "context" + + "github.com/sourcegraph/src-cli/internal/validate" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Aks() Option { + return func(config *Config) { + config.aks = true + } +} + +func AksCsiDrivers(ctx context.Context, config *Config) ([]validate.Result, error) { + var results []validate.Result + + storageClassResults, err := validateStorageClass(ctx, config) + if err != nil { + results = append(results, validate.Result{ + Status: validate.Failure, + Message: "AKS: could not validate if persistent volumes are enabled", + }) + + return results, err + } + + results = append(results, storageClassResults...) + + return results, nil +} + +func validateStorageClass(ctx context.Context, config *Config) ([]validate.Result, error) { + var results []validate.Result + + storageClient := config.clientSet.StorageV1() + storageClasses, err := storageClient.StorageClasses().List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + + for _, item := range storageClasses.Items { + if item.Name == "sourcegraph" { + if item.Provisioner != "disk.csi.azure.com" { + results = append(results, validate.Result{ + Status: validate.Failure, + Message: "provisioner does not enable persistent volumes", + }) + } else { + results = append(results, validate.Result{ + Status: validate.Success, + Message: "persistent volumes enabled", + }) + } + + if string(*item.ReclaimPolicy) != "Retain" { + results = append(results, validate.Result{ + Status: validate.Failure, + Message: "storageclass has a reclaim policy other than 'Retain'", + }) + } else { + results = append(results, validate.Result{ + Status: validate.Success, + Message: "storageclass has correct reclaim policy (Retain)", + }) + } + + if string(*item.VolumeBindingMode) != "WaitForFirstConsumer" { + results = append(results, validate.Result{ + Status: validate.Failure, + Message: "storageclass has a binding mode other than 'WaitForFirstConsumer'", + }) + } else { + results = append(results, validate.Result{ + Status: validate.Success, + Message: "storageclass has correct volumeBindingMode (WaitForFirstConsumer)", + }) + } + } + } + + if len(results) == 0 { + results = append(results, validate.Result{ + Status: validate.Warning, + Message: "you have not yet deployed a sourcegraph instance to this cluster, or you've named your storageclass something other than 'sourcegraph'", + }) + } + + return results, nil +} diff --git a/internal/validate/kube/kube.go b/internal/validate/kube/kube.go index f3db17c038..6a1dc71010 100644 --- a/internal/validate/kube/kube.go +++ b/internal/validate/kube/kube.go @@ -46,6 +46,7 @@ type Config struct { restConfig *rest.Config eks bool gke bool + aks bool eksClient *eks.Client ec2Client *ec2.Client iamClient *iam.Client @@ -81,6 +82,7 @@ func Validate(ctx context.Context, clientSet *kubernetes.Clientset, restConfig * restConfig: restConfig, eks: false, gke: false, + aks: false, } for _, opt := range opts { @@ -133,6 +135,21 @@ func Validate(ctx context.Context, clientSet *kubernetes.Clientset, restConfig * }) } + if cfg.aks { + if err := CurrentContextSetTo("aks"); err != nil { + return errors.Newf("%s %s", validate.FailureEmoji, err) + } + + Aks() + + validations = append(validations, validation{ + Validate: AksCsiDrivers, + WaitMsg: "AKS: validating persistent volumes", + SuccessMsg: "AKS: persistent volumes validated", + ErrMsg: "AKS: validating persistent volumes failed", + }) + } + var totalFailCount int for _, v := range validations { @@ -457,12 +474,24 @@ func CurrentContextSetTo(clusterService string) error { got := strings.Split(currentContext, ":") want := []string{"arn", "aws", clusterService} + if got[0] != "arn" { + return errors.New("no eks cluster configured") + } + if len(got) >= 3 { got = got[:3] if !reflect.DeepEqual(got, want) { return errors.New("no eks cluster configured") } } + } else if clusterService == "aks" { + colons := strings.Split(currentContext, ":") // aws string + underscores := strings.Split(currentContext, "_") // gke string + + // if current context has 'arn' or 'gke' in string, return error + if colons[0] == "arn" || underscores[0] == "gke" { + return errors.New("no aks cluster configured") + } } return nil