From 4eb5eaa534adb81a3446ab5467354d87a4e46078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=C3=AFc=20Huard?= Date: Tue, 5 May 2026 18:44:33 +0200 Subject: [PATCH 1/8] [CASCL-1323] Refactor cluster autoscaling plugin into install + apply + shared subpackages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare ground for the upcoming `kubectl datadog autoscaling cluster update` subcommand by extracting the convergence logic that install will share with update into a new `apply` package, and by elevating `guess` and `k8s` to siblings of the cobra commands now that several callers depend on them. * `cluster/install/{guess,k8s}/` → `cluster/{guess,k8s}/`. install, uninstall and the new apply package all import them; keeping them under install/ would create awkward `…/install/guess` import paths in the unrelated callers. * `cluster/install/steps.go` (and its test) → `cluster/apply/run.go`, reshaped into a package-level `Run(ctx, streams, configFlags, clientset, RunOptions) error` plus exported helpers (`KarpenterStackName`, `DDKarpenterStackName`, `InstallModeTagKey`, `DetectedInstallMode`). `RunOptions` replaces what used to be eight package-level globals mutated by cobra flag binding. Display helpers now take `genericclioptions.IOStreams` instead of `*cobra.Command`. * `cluster/install/{create_karpenter_resources,inference_method}.go` (with their tests) → `cluster/apply/`. Both flags are bound by install today and by update tomorrow, so their pflag.Value methods belong where both callers can reach them. * `cluster/install/install_mode.go` is split: the bare type plus `DetectedInstallMode` go to `cluster/apply/install_mode.go`; the pflag.Value wrapper stays in `cluster/install/install_mode.go` as a package-private type around `apply.InstallMode`. Only install binds `--install-mode`, so the wrapper has a single consumer. * `cluster/install/install.go` becomes a thin cobra wrapper: flags bind to fields on the local `options` struct (no more globals), `RunE` builds an `apply.RunOptions{ActionLabel: "Installing"}` and delegates to `apply.Run`. * `cluster/uninstall/uninstall.go` migrates inline stack-name concatenations to `apply.KarpenterStackName` and `apply.DDKarpenterStackName`, eliminating the duplication. * `cluster/common/clients/clients.go` gains `ResolveClusterName`, collapsing the explicit-flag-or-kubeconfig fallback that install, update and uninstall would otherwise repeat. Behaviour preserved end to end; tests cover the moved code. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../assets/dd-karpenter-fargate.yaml | 0 .../assets/dd-karpenter.yaml | 0 .../{install => apply}/assets/karpenter.yaml | 0 .../create_karpenter_resources.go | 2 +- .../create_karpenter_resources_test.go | 2 +- .../{install => apply}/inference_method.go | 2 +- .../inference_method_test.go | 2 +- .../autoscaling/cluster/apply/install_mode.go | 33 +++ .../{install/steps.go => apply/run.go} | 200 ++++++++++-------- .../steps_test.go => apply/run_test.go} | 12 +- .../cluster/common/clients/clients.go | 20 +- .../cluster/{install => }/guess/aws-auth.go | 0 .../{install => }/guess/clusterauthmode.go | 0 .../{install => }/guess/clustername.go | 0 .../{install => }/guess/clustername_test.go | 0 .../{install => }/guess/eksautomode.go | 0 .../{install => }/guess/eksautomode_test.go | 0 .../guess/ekspodidentityagent.go | 0 .../cluster/{install => }/guess/karpenter.go | 0 .../{install => }/guess/karpenter_test.go | 0 .../guess/nodegroupproperties.go | 0 .../{install => }/guess/nodepoolsset.go | 0 .../{install => }/guess/nodepoolsset_test.go | 0 .../{install => }/guess/nodesproperties.go | 0 .../guess/nodesproperties_test.go | 0 .../{install => }/guess/oidcprovider.go | 0 .../{install => }/guess/oidcprovider_test.go | 0 .../{install => }/guess/privatesubnets.go | 0 .../guess/privatesubnets_test.go | 0 .../guess/testdata/kubeconfig-eks.yaml | 0 .../autoscaling/cluster/install/install.go | 88 +++++--- .../cluster/install/install_mode.go | 41 ++-- .../cluster/install/install_mode_test.go | 18 +- .../cluster/install/install_test.go | 96 ++++----- .../cluster/{install => }/k8s/ec2nodeclass.go | 2 +- .../{install => }/k8s/ec2nodeclass_test.go | 0 .../cluster/{install => }/k8s/nodepool.go | 2 +- .../cluster/uninstall/uninstall.go | 23 +- 38 files changed, 316 insertions(+), 227 deletions(-) rename cmd/kubectl-datadog/autoscaling/cluster/{install => apply}/assets/dd-karpenter-fargate.yaml (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => apply}/assets/dd-karpenter.yaml (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => apply}/assets/karpenter.yaml (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => apply}/create_karpenter_resources.go (98%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => apply}/create_karpenter_resources_test.go (99%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => apply}/inference_method.go (98%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => apply}/inference_method_test.go (99%) create mode 100644 cmd/kubectl-datadog/autoscaling/cluster/apply/install_mode.go rename cmd/kubectl-datadog/autoscaling/cluster/{install/steps.go => apply/run.go} (67%) rename cmd/kubectl-datadog/autoscaling/cluster/{install/steps_test.go => apply/run_test.go} (92%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/aws-auth.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/clusterauthmode.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/clustername.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/clustername_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/eksautomode.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/eksautomode_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/ekspodidentityagent.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/karpenter.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/karpenter_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/nodegroupproperties.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/nodepoolsset.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/nodepoolsset_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/nodesproperties.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/nodesproperties_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/oidcprovider.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/oidcprovider_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/privatesubnets.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/privatesubnets_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/guess/testdata/kubeconfig-eks.yaml (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/k8s/ec2nodeclass.go (99%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/k8s/ec2nodeclass_test.go (100%) rename cmd/kubectl-datadog/autoscaling/cluster/{install => }/k8s/nodepool.go (98%) diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/assets/dd-karpenter-fargate.yaml b/cmd/kubectl-datadog/autoscaling/cluster/apply/assets/dd-karpenter-fargate.yaml similarity index 100% rename from cmd/kubectl-datadog/autoscaling/cluster/install/assets/dd-karpenter-fargate.yaml rename to cmd/kubectl-datadog/autoscaling/cluster/apply/assets/dd-karpenter-fargate.yaml diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/assets/dd-karpenter.yaml b/cmd/kubectl-datadog/autoscaling/cluster/apply/assets/dd-karpenter.yaml similarity index 100% rename from cmd/kubectl-datadog/autoscaling/cluster/install/assets/dd-karpenter.yaml rename to cmd/kubectl-datadog/autoscaling/cluster/apply/assets/dd-karpenter.yaml diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/assets/karpenter.yaml b/cmd/kubectl-datadog/autoscaling/cluster/apply/assets/karpenter.yaml similarity index 100% rename from cmd/kubectl-datadog/autoscaling/cluster/install/assets/karpenter.yaml rename to cmd/kubectl-datadog/autoscaling/cluster/apply/assets/karpenter.yaml diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/create_karpenter_resources.go b/cmd/kubectl-datadog/autoscaling/cluster/apply/create_karpenter_resources.go similarity index 98% rename from cmd/kubectl-datadog/autoscaling/cluster/install/create_karpenter_resources.go rename to cmd/kubectl-datadog/autoscaling/cluster/apply/create_karpenter_resources.go index abb8505ce7..2438362830 100644 --- a/cmd/kubectl-datadog/autoscaling/cluster/install/create_karpenter_resources.go +++ b/cmd/kubectl-datadog/autoscaling/cluster/apply/create_karpenter_resources.go @@ -1,4 +1,4 @@ -package install +package apply import "fmt" diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/create_karpenter_resources_test.go b/cmd/kubectl-datadog/autoscaling/cluster/apply/create_karpenter_resources_test.go similarity index 99% rename from cmd/kubectl-datadog/autoscaling/cluster/install/create_karpenter_resources_test.go rename to cmd/kubectl-datadog/autoscaling/cluster/apply/create_karpenter_resources_test.go index 0a95cdbf96..54cb50439b 100644 --- a/cmd/kubectl-datadog/autoscaling/cluster/install/create_karpenter_resources_test.go +++ b/cmd/kubectl-datadog/autoscaling/cluster/apply/create_karpenter_resources_test.go @@ -1,4 +1,4 @@ -package install +package apply import ( "testing" diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/inference_method.go b/cmd/kubectl-datadog/autoscaling/cluster/apply/inference_method.go similarity index 98% rename from cmd/kubectl-datadog/autoscaling/cluster/install/inference_method.go rename to cmd/kubectl-datadog/autoscaling/cluster/apply/inference_method.go index cff35f9412..0a26feee0b 100644 --- a/cmd/kubectl-datadog/autoscaling/cluster/install/inference_method.go +++ b/cmd/kubectl-datadog/autoscaling/cluster/apply/inference_method.go @@ -1,4 +1,4 @@ -package install +package apply import "fmt" diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/inference_method_test.go b/cmd/kubectl-datadog/autoscaling/cluster/apply/inference_method_test.go similarity index 99% rename from cmd/kubectl-datadog/autoscaling/cluster/install/inference_method_test.go rename to cmd/kubectl-datadog/autoscaling/cluster/apply/inference_method_test.go index 46b20bd0fa..e958b2ba49 100644 --- a/cmd/kubectl-datadog/autoscaling/cluster/install/inference_method_test.go +++ b/cmd/kubectl-datadog/autoscaling/cluster/apply/inference_method_test.go @@ -1,4 +1,4 @@ -package install +package apply import ( "testing" diff --git a/cmd/kubectl-datadog/autoscaling/cluster/apply/install_mode.go b/cmd/kubectl-datadog/autoscaling/cluster/apply/install_mode.go new file mode 100644 index 0000000000..b4af87618a --- /dev/null +++ b/cmd/kubectl-datadog/autoscaling/cluster/apply/install_mode.go @@ -0,0 +1,33 @@ +package apply + +import ( + "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/aws" +) + +// InstallMode defines how to run the Karpenter controller. +type InstallMode string + +const ( + // InstallModeFargate runs the Karpenter controller on dedicated Fargate nodes. + InstallModeFargate InstallMode = "fargate" + // InstallModeExistingNodes runs the Karpenter controller on existing cluster nodes. + InstallModeExistingNodes InstallMode = "existing-nodes" +) + +// InstallModeTagKey is the CloudFormation stack tag tracking the deployment's +// install-mode. Stacks created before this tag was introduced have no tag and +// are treated as install-mode=existing-nodes. +const InstallModeTagKey = "install-mode" + +// DetectedInstallMode reads the install-mode tag from a CFN stack. Stacks +// created before this tag was introduced have no tag and default to +// existing-nodes for backward compatibility. +func DetectedInstallMode(stack *aws.Stack) InstallMode { + if stack == nil { + return "" + } + if tag, ok := stack.TagMap()[InstallModeTagKey]; ok && tag != "" { + return InstallMode(tag) + } + return InstallModeExistingNodes +} diff --git a/cmd/kubectl-datadog/autoscaling/cluster/install/steps.go b/cmd/kubectl-datadog/autoscaling/cluster/apply/run.go similarity index 67% rename from cmd/kubectl-datadog/autoscaling/cluster/install/steps.go rename to cmd/kubectl-datadog/autoscaling/cluster/apply/run.go index 4dfe380465..3b0e7ea8e8 100644 --- a/cmd/kubectl-datadog/autoscaling/cluster/install/steps.go +++ b/cmd/kubectl-datadog/autoscaling/cluster/apply/run.go @@ -1,24 +1,28 @@ -package install +// Package apply contains the convergence logic shared between the `install` +// and `update` cobra commands. Both subcommands ultimately call Run with a +// RunOptions describing the desired Karpenter deployment; Run creates or +// updates the CloudFormation stacks, the Helm release, the aws-auth role, the +// optional EC2NodeClass/NodePool resources and the cluster-info ConfigMap. +package apply import ( "context" - "errors" "fmt" + "io" "log" "net/url" - "os/signal" "slices" "strconv" "strings" - "syscall" awssdk "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/eks" "github.com/davecgh/go-spew/spew" "github.com/fatih/color" "github.com/pkg/browser" - "github.com/spf13/cobra" "helm.sh/helm/v3/pkg/registry" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -27,8 +31,8 @@ import ( "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/clusterinfo" "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/display" "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/helm" - "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/guess" - "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/k8s" + "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/guess" + "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/k8s" "github.com/DataDog/datadog-operator/pkg/version" _ "embed" @@ -49,117 +53,131 @@ var ( DdKarpenterFargateCfn string ) -// installModeTagKey is the CloudFormation stack tag tracking the deployment's -// install-mode. Stacks created before this tag was introduced are treated as -// install-mode=existing-nodes. -const installModeTagKey = "install-mode" - -func (o *options) run(cmd *cobra.Command) error { - ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer stop() - - log.SetOutput(cmd.OutOrStderr()) - ctrl.SetLogger(zap.New(zap.UseDevMode(false), zap.WriteTo(cmd.ErrOrStderr()))) - - if clusterName == "" { - if name, err := clients.GetClusterNameFromKubeconfig(o.ConfigFlags); err != nil { - return err - } else if name != "" { - clusterName = name - } else { - return errors.New("cluster name must be specified either via --cluster-name or in the current kubeconfig context") - } - } +// RunOptions captures every parameter that callers (the install and update +// cobra commands) feed into Run. +type RunOptions struct { + ClusterName string + KarpenterNamespace string + KarpenterVersion string + InstallMode InstallMode + FargateSubnets []string + CreateKarpenterResources CreateKarpenterResources + InferenceMethod InferenceMethod + Debug bool + // ActionLabel prefixes the opening "