From c1750bc0f4d7b53972dbe651370c6ec0462f5d31 Mon Sep 17 00:00:00 2001 From: tytv2 Date: Thu, 2 Jul 2026 10:06:40 +0700 Subject: [PATCH] feat(vks): add --dry-run to remaining mutating commands Every mutating VKS command now supports --dry-run. Added it to config-auto-healing, config-auto-upgrade, update-nodegroup-metadata, upgrade-nodegroup-version, and generate-kubeconfig via a shared cli.PrintDryRun(verb, target, body) helper that previews the request payload (sorted keys) and the standard footer. The dry-run short-circuits before createClient, so it needs no credentials/network. Co-Authored-By: Claude Opus 4.8 --- .../enhancement-vks-p8bowfym.json | 5 +++++ go/cmd/vks/auto_upgrade.go | 17 ++++++++++----- go/cmd/vks/config_auto_healing.go | 8 +++++++ go/cmd/vks/generate_kubeconfig.go | 8 +++++++ go/cmd/vks/update_nodegroup_metadata.go | 8 +++++++ go/cmd/vks/upgrade_nodegroup_version.go | 8 +++++++ go/internal/cli/confirm.go | 21 +++++++++++++++++++ 7 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 .changes/next-release/enhancement-vks-p8bowfym.json diff --git a/.changes/next-release/enhancement-vks-p8bowfym.json b/.changes/next-release/enhancement-vks-p8bowfym.json new file mode 100644 index 0000000..81cbfe4 --- /dev/null +++ b/.changes/next-release/enhancement-vks-p8bowfym.json @@ -0,0 +1,5 @@ +{ + "type": "enhancement", + "category": "vks", + "description": "Add --dry-run to the remaining mutating VKS commands (config-auto-healing, config-auto-upgrade, update-nodegroup-metadata, upgrade-nodegroup-version, generate-kubeconfig); it previews the request payload and exits without calling the API (works offline)" +} diff --git a/go/cmd/vks/auto_upgrade.go b/go/cmd/vks/auto_upgrade.go index 3fca285..cdc301c 100644 --- a/go/cmd/vks/auto_upgrade.go +++ b/go/cmd/vks/auto_upgrade.go @@ -29,6 +29,7 @@ func init() { f.String("cluster-id", "", "Cluster ID (required)") f.String("weekdays", "", "Days of the week, e.g. Mon,Wed,Fri (required)") f.String("time", "", "Time of day in 24h format HH:mm, e.g. 03:00 (required)") + f.Bool("dry-run", false, "Preview the auto-upgrade config without executing") setAutoUpgradeConfigCmd.MarkFlagRequired("cluster-id") setAutoUpgradeConfigCmd.MarkFlagRequired("weekdays") setAutoUpgradeConfigCmd.MarkFlagRequired("time") @@ -45,21 +46,27 @@ func runSetAutoUpgradeConfig(cmd *cobra.Command, args []string) error { clusterID, _ := cmd.Flags().GetString("cluster-id") weekdays, _ := cmd.Flags().GetString("weekdays") timeVal, _ := cmd.Flags().GetString("time") + dryRun, _ := cmd.Flags().GetBool("dry-run") if err := validator.ValidateID(clusterID, "cluster-id"); err != nil { return err } - apiClient, err := createClient(cmd) - if err != nil { - return err - } - body := map[string]interface{}{ "weekdays": weekdays, "time": timeVal, } + if dryRun { + cli.PrintDryRun("configure", fmt.Sprintf("auto-upgrade for cluster %s", clusterID), body) + return nil + } + + apiClient, err := createClient(cmd) + if err != nil { + return err + } + result, err := apiClient.Put( fmt.Sprintf("/v1/clusters/%s/auto-upgrade-config", clusterID), body, ) diff --git a/go/cmd/vks/config_auto_healing.go b/go/cmd/vks/config_auto_healing.go index 2803f39..51734ef 100644 --- a/go/cmd/vks/config_auto_healing.go +++ b/go/cmd/vks/config_auto_healing.go @@ -5,6 +5,7 @@ import ( "os" "github.com/spf13/cobra" + "github.com/vngcloud/greennode-cli/internal/cli" "github.com/vngcloud/greennode-cli/internal/validator" ) @@ -21,6 +22,7 @@ func init() { f.String("max-unhealthy", "", "Max unhealthy nodes, e.g. \"30%\"") f.String("unhealthy-range", "", "Unhealthy node count range as \"[min-max]\", e.g. \"[2-5]\"") f.Int("timeout-unhealthy", 0, "Unhealthy timeout in seconds") + f.Bool("dry-run", false, "Preview the auto-healing config without executing") configAutoHealingCmd.MarkFlagRequired("cluster-id") configAutoHealingCmd.MarkFlagRequired("enable-auto-healing") @@ -46,6 +48,7 @@ func runConfigAutoHealing(cmd *cobra.Command, args []string) error { maxUnhealthy, _ := cmd.Flags().GetString("max-unhealthy") unhealthyRange, _ := cmd.Flags().GetString("unhealthy-range") timeoutUnhealthy, _ := cmd.Flags().GetInt("timeout-unhealthy") + dryRun, _ := cmd.Flags().GetBool("dry-run") if err := validator.ValidateID(clusterID, "cluster-id"); err != nil { return err @@ -58,6 +61,11 @@ func runConfigAutoHealing(cmd *cobra.Command, args []string) error { } body := buildAutoHealingBody(enable, maxUnhealthy, unhealthyRange, timeoutUnhealthy, changed) + if dryRun { + cli.PrintDryRun("configure", fmt.Sprintf("auto-healing for cluster %s", clusterID), body) + return nil + } + apiClient, err := createClient(cmd) if err != nil { return err diff --git a/go/cmd/vks/generate_kubeconfig.go b/go/cmd/vks/generate_kubeconfig.go index 25ea18e..9eb5cbf 100644 --- a/go/cmd/vks/generate_kubeconfig.go +++ b/go/cmd/vks/generate_kubeconfig.go @@ -5,6 +5,7 @@ import ( "os" "github.com/spf13/cobra" + "github.com/vngcloud/greennode-cli/internal/cli" "github.com/vngcloud/greennode-cli/internal/validator" ) @@ -20,6 +21,7 @@ func init() { f := generateKubeconfigCmd.Flags() f.String("cluster-id", "", "Cluster ID (required)") f.Int("expiration-days", 30, "Number of days until the kubeconfig expires") + f.Bool("dry-run", false, "Preview without requesting generation") generateKubeconfigCmd.MarkFlagRequired("cluster-id") } @@ -27,6 +29,7 @@ func init() { func runGenerateKubeconfig(cmd *cobra.Command, args []string) error { clusterID, _ := cmd.Flags().GetString("cluster-id") expirationDays, _ := cmd.Flags().GetInt("expiration-days") + dryRun, _ := cmd.Flags().GetBool("dry-run") if err := validator.ValidateID(clusterID, "cluster-id"); err != nil { return err @@ -34,6 +37,11 @@ func runGenerateKubeconfig(cmd *cobra.Command, args []string) error { body := map[string]interface{}{"expirationDays": expirationDays} + if dryRun { + cli.PrintDryRun("generate", fmt.Sprintf("kubeconfig for cluster %s", clusterID), body) + return nil + } + apiClient, err := createClient(cmd) if err != nil { return err diff --git a/go/cmd/vks/update_nodegroup_metadata.go b/go/cmd/vks/update_nodegroup_metadata.go index 9e561e3..c382448 100644 --- a/go/cmd/vks/update_nodegroup_metadata.go +++ b/go/cmd/vks/update_nodegroup_metadata.go @@ -5,6 +5,7 @@ import ( "os" "github.com/spf13/cobra" + "github.com/vngcloud/greennode-cli/internal/cli" "github.com/vngcloud/greennode-cli/internal/validator" ) @@ -21,6 +22,7 @@ func init() { f.String("labels", "", "Node labels as key=value pairs (comma-separated)") f.String("tags", "", "Tags as key=value pairs (comma-separated)") f.String("taints", "", "Node taints as key=value:effect (comma-separated)") + f.Bool("dry-run", false, "Preview the metadata update without executing") updateNodegroupMetadataCmd.MarkFlagRequired("cluster-id") updateNodegroupMetadataCmd.MarkFlagRequired("nodegroup-id") @@ -46,6 +48,7 @@ func runUpdateNodegroupMetadata(cmd *cobra.Command, args []string) error { labelsStr, _ := cmd.Flags().GetString("labels") tagsStr, _ := cmd.Flags().GetString("tags") taintsStr, _ := cmd.Flags().GetString("taints") + dryRun, _ := cmd.Flags().GetBool("dry-run") if err := validator.ValidateID(clusterID, "cluster-id"); err != nil { return err @@ -64,6 +67,11 @@ func runUpdateNodegroupMetadata(cmd *cobra.Command, args []string) error { } body := buildMetadataBody(labelsStr, tagsStr, taintsStr, changed) + if dryRun { + cli.PrintDryRun("update", fmt.Sprintf("metadata for node group %s", nodegroupID), body) + return nil + } + apiClient, err := createClient(cmd) if err != nil { return err diff --git a/go/cmd/vks/upgrade_nodegroup_version.go b/go/cmd/vks/upgrade_nodegroup_version.go index 7868d95..328e4c0 100644 --- a/go/cmd/vks/upgrade_nodegroup_version.go +++ b/go/cmd/vks/upgrade_nodegroup_version.go @@ -5,6 +5,7 @@ import ( "os" "github.com/spf13/cobra" + "github.com/vngcloud/greennode-cli/internal/cli" "github.com/vngcloud/greennode-cli/internal/validator" ) @@ -19,6 +20,7 @@ func init() { f.String("cluster-id", "", "Cluster ID (required)") f.String("nodegroup-id", "", "Node group ID (required)") f.String("k8s-version", "", "Target Kubernetes version (required)") + f.Bool("dry-run", false, "Preview the upgrade without executing") upgradeNodegroupVersionCmd.MarkFlagRequired("cluster-id") upgradeNodegroupVersionCmd.MarkFlagRequired("nodegroup-id") @@ -33,6 +35,7 @@ func runUpgradeNodegroupVersion(cmd *cobra.Command, args []string) error { clusterID, _ := cmd.Flags().GetString("cluster-id") nodegroupID, _ := cmd.Flags().GetString("nodegroup-id") k8sVersion, _ := cmd.Flags().GetString("k8s-version") + dryRun, _ := cmd.Flags().GetBool("dry-run") if err := validator.ValidateID(clusterID, "cluster-id"); err != nil { return err @@ -43,6 +46,11 @@ func runUpgradeNodegroupVersion(cmd *cobra.Command, args []string) error { body := buildUpgradeNodegroupBody(k8sVersion) + if dryRun { + cli.PrintDryRun("upgrade", fmt.Sprintf("node group %s", nodegroupID), body) + return nil + } + apiClient, err := createClient(cmd) if err != nil { return err diff --git a/go/internal/cli/confirm.go b/go/internal/cli/confirm.go index 8820fb6..b6aeec2 100644 --- a/go/internal/cli/confirm.go +++ b/go/internal/cli/confirm.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "sort" "strings" ) @@ -14,6 +15,26 @@ func DryRunNotice(verb string) { fmt.Printf("\nRun without --dry-run to %s.\n", verb) } +// PrintDryRun prints a consistent --dry-run preview for a mutating request: a +// header, the target being changed, the request body (keys sorted for stable +// output), and the standard footer. verb is the action (e.g. "update", +// "upgrade", "configure"). +func PrintDryRun(verb, target string, body map[string]interface{}) { + fmt.Println("=== DRY RUN ===") + if target != "" { + fmt.Printf("Would %s %s:\n", verb, target) + } + keys := make([]string, 0, len(body)) + for k := range body { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Printf(" %s: %v\n", k, body[k]) + } + DryRunNotice(verb) +} + // Confirm asks the user to confirm a destructive action and reports whether to // proceed. It returns true immediately when force is true. The prompt is shown // as " [y/N]: "; only "y" or "yes" (case-insensitive) proceeds.