From 8f6d9e78432451d86e4fe16332a647272d45ecdf Mon Sep 17 00:00:00 2001 From: tytv2 Date: Mon, 29 Jun 2026 21:50:33 +0700 Subject: [PATCH 1/3] feat(vks): revamp create/update flags, drop get-nodegroup-events - remove get-nodegroup-events command (+ docs/nav) - network-type: CALICO -> TIGERA (help, completion, cidr validation) - --os: add rocky (ubuntu, linux, rocky) - replace boolean toggle flags with explicit enabled|disabled string flags: create-cluster: --private-cluster, --private-nodes, --load-balancer-plugin, --block-store-csi-plugin; create-nodegroup: --private-nodes; update-cluster: --load-balancer-plugin/--block-store-csi-plugin (unset=unchanged) via a shared parseToggle helper (rejects invalid values) - completion + docs updated; changelog (api-change) BREAKING: --enable-private-cluster/--enable-private-nodes/--no-*-plugin and update-cluster's --enabled-*/--no-* flags are replaced; CALICO no longer accepted. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../next-release/api-change-vks-ufyxl12t.json | 5 ++ README.md | 1 - docs/commands/vks/create-cluster.md | 44 +++++------- docs/commands/vks/create-nodegroup.md | 8 +-- docs/commands/vks/get-nodegroup-events.md | 59 ---------------- docs/commands/vks/index.md | 1 - docs/commands/vks/update-cluster.md | 22 ++---- docs/usage/dry-run.md | 2 +- go/cmd/vks/completion.go | 26 ++++--- go/cmd/vks/create_cluster.go | 46 +++++++++---- go/cmd/vks/create_nodegroup.go | 11 ++- go/cmd/vks/get_nodegroup_events.go | 67 ------------------- go/cmd/vks/helpers.go | 14 ++++ go/cmd/vks/update_cluster.go | 34 +++++----- go/cmd/vks/vks.go | 1 - mkdocs.yml | 1 - 16 files changed, 121 insertions(+), 221 deletions(-) create mode 100644 .changes/next-release/api-change-vks-ufyxl12t.json delete mode 100644 docs/commands/vks/get-nodegroup-events.md delete mode 100644 go/cmd/vks/get_nodegroup_events.go diff --git a/.changes/next-release/api-change-vks-ufyxl12t.json b/.changes/next-release/api-change-vks-ufyxl12t.json new file mode 100644 index 0000000..ce1ff88 --- /dev/null +++ b/.changes/next-release/api-change-vks-ufyxl12t.json @@ -0,0 +1,5 @@ +{ + "type": "api-change", + "category": "vks", + "description": "Remove get-nodegroup-events; network-type CALICO->TIGERA; --os adds rocky; replace boolean toggle flags with --private-cluster/--private-nodes/--load-balancer-plugin/--block-store-csi-plugin " +} diff --git a/README.md b/README.md index c51a8af..b5e98d7 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,6 @@ grn --version **Events** - `get-cluster-events` — Get the list of events for a cluster -- `get-nodegroup-events` — Get the list of events for a node group **Kubeconfig** diff --git a/docs/commands/vks/create-cluster.md b/docs/commands/vks/create-cluster.md index f62b9b2..0bb22b6 100644 --- a/docs/commands/vks/create-cluster.md +++ b/docs/commands/vks/create-cluster.md @@ -6,7 +6,7 @@ Create a new VKS cluster with an initial default node group. The command provisi Cluster names must be 5–20 characters, lowercase alphanumeric and hyphens, starting and ending with an alphanumeric character. Node group names follow the same pattern with a length of 5–15 characters. -When `--network-type` is `CALICO` or `CILIUM_OVERLAY`, the `--cidr` option is required. By default, both the load balancer plugin and the block store CSI plugin are enabled; use the `--no-*` flags to disable them. +When `--network-type` is `TIGERA` or `CILIUM_OVERLAY`, the `--cidr` option is required. By default, both the load balancer plugin and the block store CSI plugin are enabled; use `--load-balancer-plugin disabled` or `--block-store-csi-plugin disabled` to turn them off. Use `--dry-run` to validate all parameters without sending a create request. @@ -26,15 +26,13 @@ grn vks create-cluster --ssh-key-id [--cidr ] [--description ] - [--enable-private-cluster] + [--private-cluster ] [--release-channel ] - [--enabled-load-balancer-plugin] - [--no-load-balancer-plugin] - [--enabled-block-store-csi-plugin] - [--no-block-store-csi-plugin] + [--load-balancer-plugin ] + [--block-store-csi-plugin ] [--disk-size ] [--num-nodes ] - [--enable-private-nodes] + [--private-nodes ] [--security-groups ] [--labels ] [--taints ] @@ -52,7 +50,7 @@ grn vks create-cluster : Kubernetes version for the cluster (e.g. `v1.29.1`). `--network-type` (required) -: Network type for the cluster. Accepted values: `CALICO`, `CILIUM_OVERLAY`, `CILIUM_NATIVE_ROUTING`. +: Network type for the cluster. Accepted values: `TIGERA`, `CILIUM_OVERLAY`, `CILIUM_NATIVE_ROUTING`. `--vpc-id` (required) : VPC ID where the cluster will be provisioned. @@ -61,28 +59,22 @@ grn vks create-cluster : Subnet ID for the cluster control plane and the default node group. `--cidr` (optional) -: Pod CIDR block. Required when `--network-type` is `CALICO` or `CILIUM_OVERLAY` (e.g. `10.96.0.0/12`). +: Pod CIDR block. Required when `--network-type` is `TIGERA` or `CILIUM_OVERLAY` (e.g. `10.96.0.0/12`). `--description` (optional) : Human-readable description for the cluster. -`--enable-private-cluster` (optional) -: Enable private cluster mode (control plane not accessible from the public internet). +`--private-cluster` (optional, default `disabled`) +: Control plane accessibility. `enabled` makes the control plane inaccessible from the public internet. Accepted values: `enabled`, `disabled`. `--release-channel` (optional) : Release channel for automatic upgrades. Accepted values: `RAPID`, `STABLE`. Default: `STABLE`. -`--enabled-load-balancer-plugin` (optional) -: Explicitly enable the load balancer plugin (enabled by default). +`--load-balancer-plugin` (optional, default `enabled`) +: Load balancer plugin state. Accepted values: `enabled`, `disabled`. -`--no-load-balancer-plugin` (optional) -: Disable the load balancer plugin. - -`--enabled-block-store-csi-plugin` (optional) -: Explicitly enable the block store CSI plugin (enabled by default). - -`--no-block-store-csi-plugin` (optional) -: Disable the block store CSI plugin. +`--block-store-csi-plugin` (optional, default `enabled`) +: Block store CSI plugin state. Accepted values: `enabled`, `disabled`. **Node group settings** @@ -93,7 +85,7 @@ grn vks create-cluster : Flavor (instance type) ID for the nodes. `--os` (optional, default `ubuntu`) -: Node group OS image. Supported values: `ubuntu`, `linux`. +: Node group OS image. Supported values: `ubuntu`, `linux`, `rocky`. `--disk-type` (required) : Disk type ID for the node boot volumes. @@ -107,8 +99,8 @@ grn vks create-cluster `--num-nodes` (optional) : Number of nodes to create in the default node group. Accepted range: 0–10. Default: `1`. -`--enable-private-nodes` (optional) -: Enable private nodes (nodes will not have public IP addresses). +`--private-nodes` (optional, default `disabled`) +: Private nodes state. `enabled` means nodes will not have public IP addresses. Accepted values: `enabled`, `disabled`. `--security-groups` (optional) : Comma-separated list of security group IDs to attach to the nodes (e.g. `sg-aaa111,sg-bbb222`). @@ -140,13 +132,13 @@ grn vks create-cluster \ --ssh-key-id key-abc12345-0000-0000-0000-000000000001 ``` -Create a cluster with CALICO network type (CIDR required): +Create a cluster with TIGERA network type (CIDR required): ```bash grn vks create-cluster \ --name prod-cluster \ --k8s-version v1.29.1 \ - --network-type CALICO \ + --network-type TIGERA \ --cidr 10.96.0.0/12 \ --vpc-id net-abc12345-0000-0000-0000-000000000001 \ --subnet-id sub-abc12345-0000-0000-0000-000000000001 \ diff --git a/docs/commands/vks/create-nodegroup.md b/docs/commands/vks/create-nodegroup.md index 00fbbfe..ae6d4e1 100644 --- a/docs/commands/vks/create-nodegroup.md +++ b/docs/commands/vks/create-nodegroup.md @@ -16,7 +16,7 @@ grn vks create-nodegroup --flavor-id --disk-type --ssh-key-id - [--enable-private-nodes] + [--private-nodes ] [--num-nodes ] [--disk-size ] [--security-groups ] @@ -36,7 +36,7 @@ grn vks create-nodegroup : Node group name. Must be 5–15 characters, lowercase alphanumeric and hyphens, starting and ending with an alphanumeric character. `--os` (optional, default `ubuntu`) -: Node group OS image. Supported values: `ubuntu`, `linux`. +: Node group OS image. Supported values: `ubuntu`, `linux`, `rocky`. `--flavor-id` (required) : Flavor (instance type) ID for the nodes. @@ -47,8 +47,8 @@ grn vks create-nodegroup `--ssh-key-id` (required) : SSH key pair ID to inject into each node. -`--enable-private-nodes` (optional) -: Enable private nodes (nodes will not have public IP addresses). +`--private-nodes` (optional, default `disabled`) +: Private nodes state. `enabled` means nodes will not have public IP addresses. Accepted values: `enabled`, `disabled`. `--num-nodes` (optional) : Number of nodes to create. Accepted range: 0–10. Default: `1`. diff --git a/docs/commands/vks/get-nodegroup-events.md b/docs/commands/vks/get-nodegroup-events.md deleted file mode 100644 index 547ffd0..0000000 --- a/docs/commands/vks/get-nodegroup-events.md +++ /dev/null @@ -1,59 +0,0 @@ -# get-nodegroup-events - -## Description - -Get the list of events for a node group. Events can be filtered by action and type, and the results are paginated. - -## Synopsis - -``` -grn vks get-nodegroup-events - --cluster-id - --nodegroup-id - [--action ] - [--type ] - [--page ] - [--page-size ] -``` - -## Options - -`--cluster-id` (required) -: The ID of the cluster. - -`--nodegroup-id` (required) -: The ID of the node group. - -`--action` (optional) -: Filter events by action. - -`--type` (optional) -: Filter events by event type. - -`--page` (optional) -: Page number to retrieve. Pagination is 0-based (page 0 is the first page). Default: `0`. - -`--page-size` (optional) -: Number of events per page. Default: `50`. - -## Examples - -Get the first page of node group events: - -```bash -grn vks get-nodegroup-events \ - --cluster-id cls-abc12345-6789-def0-1234-abcdef012345 \ - --nodegroup-id ng-abc12345-6789-def0-1234-abcdef012345 -``` - -Filter events by action and type with a custom page size: - -```bash -grn vks get-nodegroup-events \ - --cluster-id cls-abc12345-6789-def0-1234-abcdef012345 \ - --nodegroup-id ng-abc12345-6789-def0-1234-abcdef012345 \ - --action SCALE \ - --type INFO \ - --page 0 \ - --page-size 20 -``` diff --git a/docs/commands/vks/index.md b/docs/commands/vks/index.md index 0ba9af4..a160522 100644 --- a/docs/commands/vks/index.md +++ b/docs/commands/vks/index.md @@ -55,7 +55,6 @@ grn vks [options] | Command | Description | |---------|-------------| | [get-cluster-events](get-cluster-events.md) | Get the list of events for a cluster | -| [get-nodegroup-events](get-nodegroup-events.md) | Get the list of events for a node group | ### Kubeconfig diff --git a/docs/commands/vks/update-cluster.md b/docs/commands/vks/update-cluster.md index caafe8c..7a4f014 100644 --- a/docs/commands/vks/update-cluster.md +++ b/docs/commands/vks/update-cluster.md @@ -13,10 +13,8 @@ grn vks update-cluster --cluster-id --k8s-version --whitelist-node-cidrs - [--enabled-load-balancer-plugin] - [--no-load-balancer-plugin] - [--enabled-block-store-csi-plugin] - [--no-block-store-csi-plugin] + [--load-balancer-plugin ] + [--block-store-csi-plugin ] [--dry-run] ``` @@ -31,17 +29,11 @@ grn vks update-cluster `--whitelist-node-cidrs` (required) : Comma-separated list of CIDRs allowed to communicate with cluster nodes. At least one value is required (e.g. `10.0.0.0/8,192.168.0.0/16`). -`--enabled-load-balancer-plugin` (optional) -: Enable the load balancer plugin. +`--load-balancer-plugin` (optional) +: Set the load balancer plugin state. When omitted, the current state is left unchanged. Accepted values: `enabled`, `disabled`. -`--no-load-balancer-plugin` (optional) -: Disable the load balancer plugin. - -`--enabled-block-store-csi-plugin` (optional) -: Enable the block store CSI plugin. - -`--no-block-store-csi-plugin` (optional) -: Disable the block store CSI plugin. +`--block-store-csi-plugin` (optional) +: Set the block store CSI plugin state. When omitted, the current state is left unchanged. Accepted values: `enabled`, `disabled`. `--dry-run` (optional) : Print the update payload without sending the request. @@ -64,7 +56,7 @@ grn vks update-cluster \ --cluster-id cls-abc12345-6789-def0-1234-abcdef012345 \ --k8s-version v1.29.1 \ --whitelist-node-cidrs 10.0.0.0/8 \ - --no-load-balancer-plugin + --load-balancer-plugin disabled ``` Preview what would be sent (dry run): diff --git a/docs/usage/dry-run.md b/docs/usage/dry-run.md index 3796707..ec2c5d9 100644 --- a/docs/usage/dry-run.md +++ b/docs/usage/dry-run.md @@ -25,7 +25,7 @@ Validates parameters offline: - Cluster/nodegroup name format - Disk size range (20-5000 GiB) - Number of nodes range (0-10) -- CIDR requirement for CALICO/CILIUM_OVERLAY networks +- CIDR requirement for TIGERA/CILIUM_OVERLAY networks ### Delete dry-run diff --git a/go/cmd/vks/completion.go b/go/cmd/vks/completion.go index 797fa88..888cd05 100644 --- a/go/cmd/vks/completion.go +++ b/go/cmd/vks/completion.go @@ -50,17 +50,21 @@ func fetchK8sVersions(_ context.Context, cmd *cobra.Command) ([]string, error) { func flagCompleters() map[string]cli.CompFunc { return map[string]cli.CompFunc{ - "cluster-id": cli.FlagFromAPI(fetchClusterIDs), - "nodegroup-id": cli.FlagFromAPI(fetchNodegroupIDs), - "k8s-version": cli.FlagFromAPI(fetchK8sVersions), - "os": cli.FlagValues("ubuntu", "linux"), - "network-type": cli.FlagValues("CALICO", "CILIUM_OVERLAY", "CILIUM_NATIVE_ROUTING"), - "release-channel": cli.FlagValues("RAPID", "STABLE"), - "vpc-id": cli.ResourceCompletion("vserver:network"), - "subnet-id": cli.ResourceCompletion("vserver:subnet"), - "ssh-key-id": cli.ResourceCompletion("vserver:sshkey"), - "security-groups": cli.ResourceCompletion("vserver:secgroup"), - "disk-type": cli.ResourceCompletion("vserver:volumetype"), + "cluster-id": cli.FlagFromAPI(fetchClusterIDs), + "nodegroup-id": cli.FlagFromAPI(fetchNodegroupIDs), + "k8s-version": cli.FlagFromAPI(fetchK8sVersions), + "os": cli.FlagValues("ubuntu", "linux", "rocky"), + "network-type": cli.FlagValues("TIGERA", "CILIUM_OVERLAY", "CILIUM_NATIVE_ROUTING"), + "release-channel": cli.FlagValues("RAPID", "STABLE"), + "private-cluster": cli.FlagValues("enabled", "disabled"), + "private-nodes": cli.FlagValues("enabled", "disabled"), + "load-balancer-plugin": cli.FlagValues("enabled", "disabled"), + "block-store-csi-plugin": cli.FlagValues("enabled", "disabled"), + "vpc-id": cli.ResourceCompletion("vserver:network"), + "subnet-id": cli.ResourceCompletion("vserver:subnet"), + "ssh-key-id": cli.ResourceCompletion("vserver:sshkey"), + "security-groups": cli.ResourceCompletion("vserver:secgroup"), + "disk-type": cli.ResourceCompletion("vserver:volumetype"), } } diff --git a/go/cmd/vks/create_cluster.go b/go/cmd/vks/create_cluster.go index 993023f..51f1476 100644 --- a/go/cmd/vks/create_cluster.go +++ b/go/cmd/vks/create_cluster.go @@ -20,13 +20,13 @@ func init() { // Cluster settings (required) f.String("name", "", "Cluster name (required)") f.String("k8s-version", "", "Kubernetes version (required)") - f.String("network-type", "", "Network type: CALICO, CILIUM_OVERLAY, CILIUM_NATIVE_ROUTING (required)") + f.String("network-type", "", "Network type: TIGERA, CILIUM_OVERLAY, CILIUM_NATIVE_ROUTING (required)") f.String("vpc-id", "", "VPC ID (required)") f.String("subnet-id", "", "Subnet ID (required)") // Node group settings (required) f.String("node-group-name", "", "Default node group name (required)") f.String("flavor-id", "", "Flavor ID for node group (required)") - f.String("os", "ubuntu", "Node group OS image (ubuntu, linux)") + f.String("os", "ubuntu", "Node group OS image (ubuntu, linux, rocky)") f.String("disk-type", "", "Disk type ID (required)") f.String("ssh-key-id", "", "SSH key ID for node group (required)") @@ -35,17 +35,17 @@ func init() { } // Cluster settings (optional) - f.String("cidr", "", "CIDR block (required for CALICO and CILIUM_OVERLAY)") + f.String("cidr", "", "CIDR block (required for TIGERA and CILIUM_OVERLAY)") f.String("description", "", "Cluster description") - f.Bool("enable-private-cluster", false, "Enable private cluster") + f.String("private-cluster", "disabled", "Private cluster (enabled, disabled)") f.String("release-channel", "STABLE", "Release channel (RAPID, STABLE)") - f.Bool("no-load-balancer-plugin", false, "Disable load balancer plugin") - f.Bool("no-block-store-csi-plugin", false, "Disable block store CSI plugin") + f.String("load-balancer-plugin", "enabled", "Load balancer plugin (enabled, disabled)") + f.String("block-store-csi-plugin", "enabled", "Block store CSI plugin (enabled, disabled)") // Node group settings (optional) f.Int("disk-size", 100, "Disk size in GiB (20-5000)") f.Int("num-nodes", 1, "Number of nodes (0-10)") - f.Bool("enable-private-nodes", false, "Enable private nodes") + f.String("private-nodes", "disabled", "Private nodes (enabled, disabled)") f.String("security-groups", "", "Security group IDs (comma-separated)") f.String("labels", "", "Node labels as key=value pairs (comma-separated)") f.String("taints", "", "Node taints as key=value:effect (comma-separated)") @@ -60,10 +60,7 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { subnetID, _ := cmd.Flags().GetString("subnet-id") cidr, _ := cmd.Flags().GetString("cidr") description, _ := cmd.Flags().GetString("description") - enablePrivateCluster, _ := cmd.Flags().GetBool("enable-private-cluster") releaseChannel, _ := cmd.Flags().GetString("release-channel") - noLBPlugin, _ := cmd.Flags().GetBool("no-load-balancer-plugin") - noCSIPlugin, _ := cmd.Flags().GetBool("no-block-store-csi-plugin") ngName, _ := cmd.Flags().GetString("node-group-name") flavorID, _ := cmd.Flags().GetString("flavor-id") @@ -72,12 +69,33 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { sshKeyID, _ := cmd.Flags().GetString("ssh-key-id") diskSize, _ := cmd.Flags().GetInt("disk-size") numNodes, _ := cmd.Flags().GetInt("num-nodes") - enablePrivateNodes, _ := cmd.Flags().GetBool("enable-private-nodes") securityGroups, _ := cmd.Flags().GetString("security-groups") labels, _ := cmd.Flags().GetString("labels") taints, _ := cmd.Flags().GetString("taints") dryRun, _ := cmd.Flags().GetBool("dry-run") + // Parse enabled/disabled toggle flags. + privateClusterVal, _ := cmd.Flags().GetString("private-cluster") + privateNodesVal, _ := cmd.Flags().GetString("private-nodes") + lbPluginVal, _ := cmd.Flags().GetString("load-balancer-plugin") + csiPluginVal, _ := cmd.Flags().GetString("block-store-csi-plugin") + enablePrivateCluster, err := parseToggle("private-cluster", privateClusterVal) + if err != nil { + return err + } + enablePrivateNodes, err := parseToggle("private-nodes", privateNodesVal) + if err != nil { + return err + } + enabledLBPlugin, err := parseToggle("load-balancer-plugin", lbPluginVal) + if err != nil { + return err + } + enabledCSIPlugin, err := parseToggle("block-store-csi-plugin", csiPluginVal) + if err != nil { + return err + } + // Build node group nodeGroup := map[string]interface{}{ "name": ngName, @@ -116,8 +134,8 @@ func runCreateCluster(cmd *cobra.Command, args []string) error { "subnetId": subnetID, "enablePrivateCluster": enablePrivateCluster, "releaseChannel": releaseChannel, - "enabledBlockStoreCsiPlugin": !noCSIPlugin, - "enabledLoadBalancerPlugin": !noLBPlugin, + "enabledBlockStoreCsiPlugin": enabledCSIPlugin, + "enabledLoadBalancerPlugin": enabledLBPlugin, "enabledServiceEndpoint": false, "azStrategy": "SINGLE", "nodeGroups": []interface{}{nodeGroup}, @@ -159,7 +177,7 @@ func validateCreateCluster(name, ngName, networkType, cidr string, diskSize, num "Cluster name '%s' is invalid. Must be 5-20 chars, lowercase alphanumeric and hyphens, start/end with alphanumeric.", name)) } - if (networkType == "CALICO" || networkType == "CILIUM_OVERLAY") && cidr == "" { + if (networkType == "TIGERA" || networkType == "CILIUM_OVERLAY") && cidr == "" { errors = append(errors, fmt.Sprintf("--cidr is required when network-type is %s", networkType)) } diff --git a/go/cmd/vks/create_nodegroup.go b/go/cmd/vks/create_nodegroup.go index 5267c82..8e74efe 100644 --- a/go/cmd/vks/create_nodegroup.go +++ b/go/cmd/vks/create_nodegroup.go @@ -19,11 +19,11 @@ func init() { f := createNodegroupCmd.Flags() f.String("cluster-id", "", "Cluster ID (required)") f.String("name", "", "Node group name (required)") - f.String("os", "ubuntu", "Node group OS image (ubuntu, linux)") + f.String("os", "ubuntu", "Node group OS image (ubuntu, linux, rocky)") f.String("flavor-id", "", "Flavor ID (required)") f.String("disk-type", "", "Disk type ID (required)") f.String("ssh-key-id", "", "SSH key ID (required)") - f.Bool("enable-private-nodes", false, "Enable private nodes") + f.String("private-nodes", "disabled", "Private nodes (enabled, disabled)") f.Int("num-nodes", 1, "Number of nodes (0-10)") f.Int("disk-size", 100, "Disk size in GiB (20-5000)") f.String("security-groups", "", "Security group IDs (comma-separated)") @@ -45,7 +45,7 @@ func runCreateNodegroup(cmd *cobra.Command, args []string) error { flavorID, _ := cmd.Flags().GetString("flavor-id") diskType, _ := cmd.Flags().GetString("disk-type") sshKeyID, _ := cmd.Flags().GetString("ssh-key-id") - enablePrivateNodes, _ := cmd.Flags().GetBool("enable-private-nodes") + privateNodesVal, _ := cmd.Flags().GetString("private-nodes") numNodes, _ := cmd.Flags().GetInt("num-nodes") diskSize, _ := cmd.Flags().GetInt("disk-size") securityGroups, _ := cmd.Flags().GetString("security-groups") @@ -59,6 +59,11 @@ func runCreateNodegroup(cmd *cobra.Command, args []string) error { return err } + enablePrivateNodes, err := parseToggle("private-nodes", privateNodesVal) + if err != nil { + return err + } + body := map[string]interface{}{ "name": name, "numNodes": numNodes, diff --git a/go/cmd/vks/get_nodegroup_events.go b/go/cmd/vks/get_nodegroup_events.go deleted file mode 100644 index 8bff6c5..0000000 --- a/go/cmd/vks/get_nodegroup_events.go +++ /dev/null @@ -1,67 +0,0 @@ -package vks - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vngcloud/greennode-cli/internal/validator" -) - -var getNodegroupEventsCmd = &cobra.Command{ - Use: "get-nodegroup-events", - Short: "Get the list of events for a node group", - RunE: runGetNodegroupEvents, -} - -func init() { - f := getNodegroupEventsCmd.Flags() - f.String("cluster-id", "", "Cluster ID (required)") - f.String("nodegroup-id", "", "Node group ID (required)") - f.String("action", "", "Filter by action") - f.String("type", "", "Filter by event type") - f.Int("page", 0, "Page number (0-based)") - f.Int("page-size", 50, "Page size") - - getNodegroupEventsCmd.MarkFlagRequired("cluster-id") - getNodegroupEventsCmd.MarkFlagRequired("nodegroup-id") -} - -func runGetNodegroupEvents(cmd *cobra.Command, args []string) error { - clusterID, _ := cmd.Flags().GetString("cluster-id") - nodegroupID, _ := cmd.Flags().GetString("nodegroup-id") - action, _ := cmd.Flags().GetString("action") - eventType, _ := cmd.Flags().GetString("type") - page, _ := cmd.Flags().GetInt("page") - pageSize, _ := cmd.Flags().GetInt("page-size") - - if err := validator.ValidateID(clusterID, "cluster-id"); err != nil { - return err - } - if err := validator.ValidateID(nodegroupID, "nodegroup-id"); err != nil { - return err - } - - changed := map[string]bool{ - "action": cmd.Flags().Changed("action"), - "type": cmd.Flags().Changed("type"), - "page": cmd.Flags().Changed("page"), - "page-size": cmd.Flags().Changed("page-size"), - } - params := buildEventsQuery(action, eventType, page, pageSize, changed) - - apiClient, err := createClient(cmd) - if err != nil { - return err - } - - result, err := apiClient.Get( - fmt.Sprintf("/v1/clusters/%s/node-groups/%s/events", clusterID, nodegroupID), params, - ) - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - - return outputResult(cmd, result) -} diff --git a/go/cmd/vks/helpers.go b/go/cmd/vks/helpers.go index 1bb755d..2522e14 100644 --- a/go/cmd/vks/helpers.go +++ b/go/cmd/vks/helpers.go @@ -1,6 +1,7 @@ package vks import ( + "fmt" "strings" "github.com/spf13/cobra" @@ -8,6 +9,19 @@ import ( "github.com/vngcloud/greennode-cli/internal/client" ) +// parseToggle converts an "enabled"/"disabled" flag value to a bool, erroring on +// any other value. Used by --private-cluster/--private-nodes/--*-plugin flags. +func parseToggle(name, value string) (bool, error) { + switch value { + case "enabled": + return true, nil + case "disabled": + return false, nil + default: + return false, fmt.Errorf("--%s must be 'enabled' or 'disabled', got %q", name, value) + } +} + // createClient builds a GreenodeClient for the VKS service from command flags. func createClient(cmd *cobra.Command) (*client.GreenodeClient, error) { return cli.NewClient(cmd, "vks") diff --git a/go/cmd/vks/update_cluster.go b/go/cmd/vks/update_cluster.go index 1e6ed2d..616c999 100644 --- a/go/cmd/vks/update_cluster.go +++ b/go/cmd/vks/update_cluster.go @@ -19,10 +19,8 @@ func init() { f.String("cluster-id", "", "Cluster ID (required)") f.String("k8s-version", "", "Kubernetes version (required)") f.String("whitelist-node-cidrs", "", "Whitelist CIDRs, comma-separated (required)") - f.Bool("enabled-load-balancer-plugin", false, "Enable load balancer plugin") - f.Bool("no-load-balancer-plugin", false, "Disable load balancer plugin") - f.Bool("enabled-block-store-csi-plugin", false, "Enable block store CSI plugin") - f.Bool("no-block-store-csi-plugin", false, "Disable block store CSI plugin") + f.String("load-balancer-plugin", "", "Load balancer plugin (enabled, disabled); unset = unchanged") + f.String("block-store-csi-plugin", "", "Block store CSI plugin (enabled, disabled); unset = unchanged") f.Bool("dry-run", false, "Validate parameters without updating") updateClusterCmd.MarkFlagRequired("cluster-id") @@ -34,10 +32,6 @@ func runUpdateCluster(cmd *cobra.Command, args []string) error { clusterID, _ := cmd.Flags().GetString("cluster-id") k8sVersion, _ := cmd.Flags().GetString("k8s-version") whitelistCIDRs, _ := cmd.Flags().GetString("whitelist-node-cidrs") - enabledLB, _ := cmd.Flags().GetBool("enabled-load-balancer-plugin") - noLB, _ := cmd.Flags().GetBool("no-load-balancer-plugin") - enabledCSI, _ := cmd.Flags().GetBool("enabled-block-store-csi-plugin") - noCSI, _ := cmd.Flags().GetBool("no-block-store-csi-plugin") dryRun, _ := cmd.Flags().GetBool("dry-run") if err := validator.ValidateID(clusterID, "cluster-id"); err != nil { @@ -49,16 +43,22 @@ func runUpdateCluster(cmd *cobra.Command, args []string) error { "whitelistNodeCIDRs": parseCommaSeparated(whitelistCIDRs), } - if enabledLB { - body["enabledLoadBalancerPlugin"] = true - } else if noLB { - body["enabledLoadBalancerPlugin"] = false + // Plugin toggles are only sent when explicitly provided (unset = unchanged). + if cmd.Flags().Changed("load-balancer-plugin") { + v, _ := cmd.Flags().GetString("load-balancer-plugin") + enabled, err := parseToggle("load-balancer-plugin", v) + if err != nil { + return err + } + body["enabledLoadBalancerPlugin"] = enabled } - - if enabledCSI { - body["enabledBlockStoreCsiPlugin"] = true - } else if noCSI { - body["enabledBlockStoreCsiPlugin"] = false + if cmd.Flags().Changed("block-store-csi-plugin") { + v, _ := cmd.Flags().GetString("block-store-csi-plugin") + enabled, err := parseToggle("block-store-csi-plugin", v) + if err != nil { + return err + } + body["enabledBlockStoreCsiPlugin"] = enabled } if dryRun { diff --git a/go/cmd/vks/vks.go b/go/cmd/vks/vks.go index 250818d..7b1f4c7 100644 --- a/go/cmd/vks/vks.go +++ b/go/cmd/vks/vks.go @@ -49,7 +49,6 @@ func init() { VksCmd.AddCommand(listClusterVersionsCmd) VksCmd.AddCommand(upgradeNodegroupVersionCmd) VksCmd.AddCommand(getClusterEventsCmd) - VksCmd.AddCommand(getNodegroupEventsCmd) // Kubeconfig commands VksCmd.AddCommand(generateKubeconfigCmd) diff --git a/mkdocs.yml b/mkdocs.yml index db25f92..62bd281 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,7 +65,6 @@ nav: - config-auto-healing: commands/vks/config-auto-healing.md - Events: - get-cluster-events: commands/vks/get-cluster-events.md - - get-nodegroup-events: commands/vks/get-nodegroup-events.md - Kubeconfig: - generate-kubeconfig: commands/vks/generate-kubeconfig.md - update-kubeconfig: commands/vks/update-kubeconfig.md From e10fb6f0db7aa0c102eef087265e2513830a502a Mon Sep 17 00:00:00 2001 From: tytv2 Date: Mon, 29 Jun 2026 21:54:28 +0700 Subject: [PATCH 2/3] fix(configure): don't panic when configuring a non-existent profile config.LoadConfig returns (nil, err) when the profile is absent from the credentials file; runConfigure ignored the error and dereferenced nil at configure.go:47. Guard for err/nil and start from empty defaults so `grn configure --profile ` creates the profile instead of crashing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changes/next-release/bugfix-configure-0wjocz7d.json | 5 +++++ go/cmd/configure/configure.go | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changes/next-release/bugfix-configure-0wjocz7d.json diff --git a/.changes/next-release/bugfix-configure-0wjocz7d.json b/.changes/next-release/bugfix-configure-0wjocz7d.json new file mode 100644 index 0000000..ecf81ce --- /dev/null +++ b/.changes/next-release/bugfix-configure-0wjocz7d.json @@ -0,0 +1,5 @@ +{ + "type": "bugfix", + "category": "configure", + "description": "Fix panic (nil pointer) when running 'grn configure --profile ' for a profile that does not exist yet; start from empty defaults so the profile can be created" +} diff --git a/go/cmd/configure/configure.go b/go/cmd/configure/configure.go index a8afb74..68f5783 100644 --- a/go/cmd/configure/configure.go +++ b/go/cmd/configure/configure.go @@ -39,8 +39,13 @@ func runConfigure(cmd *cobra.Command, args []string) { profile = "default" } - // Load existing config for defaults - cfg, _ := config.LoadConfig(profile) + // Load existing config for defaults. A new/unknown profile (or a parse error) + // yields no config — start from empty defaults so `configure` can create it + // instead of crashing on a nil dereference. + cfg, err := config.LoadConfig(profile) + if err != nil || cfg == nil { + cfg = &config.Config{} + } reader := bufio.NewReader(os.Stdin) From 26139aea785b85871d7601cb92ed2bd74becb17c Mon Sep 17 00:00:00 2001 From: tytv2 Date: Mon, 29 Jun 2026 22:01:08 +0700 Subject: [PATCH 3/3] fix(cli): error on unknown subcommands instead of exit 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nested group commands (grn vks, grn vserver and its sub-groups) used Run: cmd.Help() with cobra's default arg handling, which (unlike the root) does not reject unknown subcommands — so `grn vks foobar` printed help and exited 0. Add Args: cobra.NoArgs to every group command so an unknown subcommand yields an "unknown command" error and a non-zero exit, while bare invocation still shows help. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changes/next-release/bugfix-vks-uv426h3g.json | 5 +++++ go/cmd/vks/vks.go | 2 ++ go/cmd/vserver/flavor/flavor.go | 1 + go/cmd/vserver/image/image.go | 1 + go/cmd/vserver/secgroup/rule/rule.go | 1 + go/cmd/vserver/secgroup/secgroup.go | 1 + go/cmd/vserver/server/server.go | 1 + go/cmd/vserver/subnet/subnet.go | 1 + go/cmd/vserver/volume/volume.go | 1 + go/cmd/vserver/volumetype/volumetype.go | 1 + go/cmd/vserver/vpc/vpc.go | 1 + go/cmd/vserver/vserver.go | 2 ++ 12 files changed, 18 insertions(+) create mode 100644 .changes/next-release/bugfix-vks-uv426h3g.json diff --git a/.changes/next-release/bugfix-vks-uv426h3g.json b/.changes/next-release/bugfix-vks-uv426h3g.json new file mode 100644 index 0000000..d7f947e --- /dev/null +++ b/.changes/next-release/bugfix-vks-uv426h3g.json @@ -0,0 +1,5 @@ +{ + "type": "bugfix", + "category": "vks", + "description": "Return a non-zero exit code and 'unknown command' error for invalid subcommands of grn vks/vserver (and their groups) instead of silently printing help with exit 0" +} diff --git a/go/cmd/vks/vks.go b/go/cmd/vks/vks.go index 7b1f4c7..7b56f82 100644 --- a/go/cmd/vks/vks.go +++ b/go/cmd/vks/vks.go @@ -10,6 +10,8 @@ var VksCmd = &cobra.Command{ Use: "vks", Short: "VNG Kubernetes Service (VKS) commands", Long: "Manage VKS clusters, node groups, and related resources.", + // Reject unknown subcommands (nested groups don't error by default in cobra). + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/flavor/flavor.go b/go/cmd/vserver/flavor/flavor.go index f20a6b6..be8a6ea 100644 --- a/go/cmd/vserver/flavor/flavor.go +++ b/go/cmd/vserver/flavor/flavor.go @@ -9,6 +9,7 @@ var FlavorCmd = &cobra.Command{ Use: "flavor", Short: "Manage vServer flavors", Long: "List and inspect available vServer flavors.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/image/image.go b/go/cmd/vserver/image/image.go index abd6a25..343b153 100644 --- a/go/cmd/vserver/image/image.go +++ b/go/cmd/vserver/image/image.go @@ -9,6 +9,7 @@ var ImageCmd = &cobra.Command{ Use: "image", Short: "Manage vServer images", Long: "List available vServer images by type (os, gpu).", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/secgroup/rule/rule.go b/go/cmd/vserver/secgroup/rule/rule.go index 770fc5a..1f5be85 100644 --- a/go/cmd/vserver/secgroup/rule/rule.go +++ b/go/cmd/vserver/secgroup/rule/rule.go @@ -9,6 +9,7 @@ var RuleCmd = &cobra.Command{ Use: "rule", Short: "Manage security group rules", Long: "Create, list, and delete rules within a security group.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/secgroup/secgroup.go b/go/cmd/vserver/secgroup/secgroup.go index 4065087..a605f1c 100644 --- a/go/cmd/vserver/secgroup/secgroup.go +++ b/go/cmd/vserver/secgroup/secgroup.go @@ -10,6 +10,7 @@ var SecgroupCmd = &cobra.Command{ Use: "secgroup", Short: "Manage security groups", Long: "Create, list, and delete security groups and their rules.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/server/server.go b/go/cmd/vserver/server/server.go index 61cb7b7..7ada3a0 100644 --- a/go/cmd/vserver/server/server.go +++ b/go/cmd/vserver/server/server.go @@ -9,6 +9,7 @@ var ServerCmd = &cobra.Command{ Use: "server", Short: "Manage vServer instances", Long: "Create, list, get, and manage vServer instances.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/subnet/subnet.go b/go/cmd/vserver/subnet/subnet.go index 5223aa8..1a8f276 100644 --- a/go/cmd/vserver/subnet/subnet.go +++ b/go/cmd/vserver/subnet/subnet.go @@ -9,6 +9,7 @@ var SubnetCmd = &cobra.Command{ Use: "subnet", Short: "Manage subnets", Long: "Create, list, get, and delete subnets within a VPC.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/volume/volume.go b/go/cmd/vserver/volume/volume.go index 3fb0674..98786be 100644 --- a/go/cmd/vserver/volume/volume.go +++ b/go/cmd/vserver/volume/volume.go @@ -9,6 +9,7 @@ var VolumeCmd = &cobra.Command{ Use: "volume", Short: "Manage vServer volumes", Long: "Create, list, get, and delete vServer block storage volumes.\n\nTo see available volume types for a zone, run: grn vserver volume-type list", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/volumetype/volumetype.go b/go/cmd/vserver/volumetype/volumetype.go index d7969fb..0eefe9c 100644 --- a/go/cmd/vserver/volumetype/volumetype.go +++ b/go/cmd/vserver/volumetype/volumetype.go @@ -9,6 +9,7 @@ var VolumeTypeCmd = &cobra.Command{ Use: "volume-type", Short: "Manage vServer volume types", Long: "List available volume types for a zone.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/vpc/vpc.go b/go/cmd/vserver/vpc/vpc.go index a4d2a1f..642b7b3 100644 --- a/go/cmd/vserver/vpc/vpc.go +++ b/go/cmd/vserver/vpc/vpc.go @@ -9,6 +9,7 @@ var VpcCmd = &cobra.Command{ Use: "vpc", Short: "Manage VPCs (virtual private clouds)", Long: "Create, list, get, and delete VPC networks.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/go/cmd/vserver/vserver.go b/go/cmd/vserver/vserver.go index 4febffc..186b8f3 100644 --- a/go/cmd/vserver/vserver.go +++ b/go/cmd/vserver/vserver.go @@ -18,6 +18,8 @@ var VServerCmd = &cobra.Command{ Use: "vserver", Short: "VNG Virtual Server (vServer) commands", Long: "Manage vServer instances and related resources.", + // Reject unknown subcommands (nested groups don't error by default in cobra). + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { cmd.Help() },