From d89582f042d5fa393aee5b7809410f1ee6f381de Mon Sep 17 00:00:00 2001 From: tytv2 Date: Sat, 11 Apr 2026 23:16:08 +0700 Subject: [PATCH] feat(vks): add set-auto-upgrade-config and delete-auto-upgrade-config commands --- .../next-release/feature-vks-fzo41e6c.json | 5 + CLAUDE.md | 98 +++++++++++++++++++ .../vks/delete-auto-upgrade-config.md | 36 +++++++ docs/commands/vks/index.md | 7 ++ docs/commands/vks/set-auto-upgrade-config.md | 45 +++++++++ grncli/customizations/vks/__init__.py | 4 + .../customizations/vks/auto_upgrade_config.py | 57 +++++++++++ mkdocs.yml | 3 + 8 files changed, 255 insertions(+) create mode 100644 .changes/next-release/feature-vks-fzo41e6c.json create mode 100644 CLAUDE.md create mode 100644 docs/commands/vks/delete-auto-upgrade-config.md create mode 100644 docs/commands/vks/set-auto-upgrade-config.md create mode 100644 grncli/customizations/vks/auto_upgrade_config.py diff --git a/.changes/next-release/feature-vks-fzo41e6c.json b/.changes/next-release/feature-vks-fzo41e6c.json new file mode 100644 index 0000000..4d6ce59 --- /dev/null +++ b/.changes/next-release/feature-vks-fzo41e6c.json @@ -0,0 +1,5 @@ +{ + "type": "feature", + "category": "vks", + "description": "Add set-auto-upgrade-config and delete-auto-upgrade-config commands" +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d88f209 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,98 @@ +# CLAUDE.md — GreenNode CLI + +## Project overview + +GreenNode CLI (`grn`) is a unified command-line tool for managing GreenNode (VNG Cloud) services. Architecture cloned from AWS CLI with hand-written commands. VKS (VNG Kubernetes Service) is the first service; other product teams add their own services by following the same pattern. + +- **Repo**: `vngcloud/greennode-cli` +- **Docs**: https://vngcloud.github.io/greennode-cli/ +- **PyPI**: https://pypi.org/project/grncli/ + +## Code conventions + +- All source code text must be in **English** — error messages, descriptions, comments, docstrings, ARG_TABLE help_text +- Follow existing AWS CLI patterns: `CLIDriver` → `ServiceCommand` → `BasicCommand` +- Each command file has one class, one responsibility +- Use `display_output(result, parsed_globals)` helper for API response formatting + +## VNG Cloud API quirks + +- **IAM API uses camelCase**: `grantType`, `accessToken`, `expiresIn` (not snake_case OAuth2 standard) +- **VKS API pagination is 0-based**: page 0 = first page +- **`--version` conflict**: Use `--k8s-version` for Kubernetes version to avoid clash with global `--version` flag + +## Adding a new command + +1. Create file in `grncli/customizations/vks/.py` +2. Extend `BasicCommand` with `NAME`, `DESCRIPTION`, `ARG_TABLE` +3. Implement `_run_main(self, parsed_args, parsed_globals)` +4. Register in `grncli/customizations/vks/__init__.py` +5. Add `validate_id()` calls for any ID args used in URLs +6. Add `--dry-run` for create/update/delete commands +7. Add `--force` + confirmation prompt for delete commands + +## Adding a new service + +1. Create `grncli/customizations//` +2. Write commands extending `BasicCommand` +3. Register in `grncli/handlers.py` +4. See `grncli/customizations/vks/` for reference + +## Security rules + +- **Credential masking**: `grn configure list` and `grn configure get` must mask `client_id`/`client_secret` (show last 4 chars only) +- **Input validation**: All cluster-id and nodegroup-id args must be validated via `validators.validate_id()` before constructing URLs — prevents path traversal +- **SSL default on**: `--no-verify-ssl` must print warning to stderr +- **Tokens in memory only**: Never write tokens to disk or logs +- **Dependency pinning**: Pin to major versions (`httpx<1.0`, `PyYAML<7.0`) + +## Testing + +```bash +python -m pytest tests/ -v +``` + +- Tests must pass on Python 3.10-3.13 × Ubuntu/macOS/Windows +- Skip Unix-only tests on Windows with `@pytest.mark.skipif(platform.system() == 'Windows', ...)` + +## Git workflow + +- **Do not auto commit/push** — only change source code, user will ask for commit/push when ready +- **Branches**: `main` (production), `develop` (testing), `feat/*` or `fix/*` (feature/bug branches) +- **PRs**: feature → develop (test), feature → main (release-ready) +- **Changelog**: Add fragment via `./scripts/new-change` for every change +- **Release**: `./scripts/bump-version minor` → `git push && git push --tags` +- **Main branch is protected** — cannot push directly, must use PR + +## Documentation update rule + +**After completing any feature or bugfix, update ALL related documentation before considering the work done:** + +1. **GitHub Pages docs** (`docs/`): + - Add/update command reference page in `docs/commands/vks/.md` + - Update `docs/commands/vks/index.md` command table + - Update relevant usage guides if behavior changes (pagination, dry-run, etc.) + - Update `mkdocs.yml` nav if new pages added + +2. **CHANGELOG**: Add changelog fragment via `./scripts/new-change` + +3. **Spec** (`docs/superpowers/specs/2026-04-10-greenode-cli-design.md`): + - Update command list in Section 4 + - Update file structure in Section 2 if new files added + +This is not optional. Code without docs is not done. + +## Key files + +| File | Purpose | +|------|---------| +| `grncli/clidriver.py` | CLIDriver + ServiceCommand — main orchestrator | +| `grncli/session.py` | Config, credentials, region, endpoints, SSL, timeouts | +| `grncli/auth.py` | TokenManager — OAuth2 Client Credentials with IAM | +| `grncli/client.py` | HTTP client with retry (3x backoff) + auto token refresh | +| `grncli/customizations/commands.py` | BasicCommand base class + display_output + help system | +| `grncli/customizations/vks/validators.py` | ID format validation | +| `grncli/data/cli.json` | Global CLI options (AWS CLI style) | +| `mkdocs.yml` | Documentation site config | +| `scripts/bump-version` | Bump version + merge changelog + commit + tag | +| `scripts/new-change` | Create changelog fragment | diff --git a/docs/commands/vks/delete-auto-upgrade-config.md b/docs/commands/vks/delete-auto-upgrade-config.md new file mode 100644 index 0000000..0b0f2c9 --- /dev/null +++ b/docs/commands/vks/delete-auto-upgrade-config.md @@ -0,0 +1,36 @@ +# delete-auto-upgrade-config + +## Description + +Delete the auto-upgrade configuration for a cluster. This disables automatic Kubernetes version upgrades. + +## Synopsis + +``` +grn vks delete-auto-upgrade-config + --cluster-id + [--force] +``` + +## Options + +`--cluster-id` (required) +: The ID of the cluster. + +`--force` (optional) +: Skip the confirmation prompt. + +## Examples + +Delete auto-upgrade config with confirmation: + +```bash +grn vks delete-auto-upgrade-config --cluster-id k8s-xxxxx +# Are you sure you want to delete the auto-upgrade config? (yes/no): yes +``` + +Delete without confirmation (for scripting): + +```bash +grn vks delete-auto-upgrade-config --cluster-id k8s-xxxxx --force +``` diff --git a/docs/commands/vks/index.md b/docs/commands/vks/index.md index 53cba95..e14b0c1 100644 --- a/docs/commands/vks/index.md +++ b/docs/commands/vks/index.md @@ -28,6 +28,13 @@ grn vks [options] | [update-nodegroup](update-nodegroup.md) | Update a node group | | [delete-nodegroup](delete-nodegroup.md) | Delete a node group | +### Auto-Upgrade + +| Command | Description | +|---------|-------------| +| [set-auto-upgrade-config](set-auto-upgrade-config.md) | Configure auto-upgrade schedule for a cluster | +| [delete-auto-upgrade-config](delete-auto-upgrade-config.md) | Delete auto-upgrade config for a cluster | + ### Waiter | Command | Description | diff --git a/docs/commands/vks/set-auto-upgrade-config.md b/docs/commands/vks/set-auto-upgrade-config.md new file mode 100644 index 0000000..8ea6a2e --- /dev/null +++ b/docs/commands/vks/set-auto-upgrade-config.md @@ -0,0 +1,45 @@ +# set-auto-upgrade-config + +## Description + +Configure auto-upgrade schedule for a cluster. Sets the days and time when automatic Kubernetes version upgrades will be performed. + +## Synopsis + +``` +grn vks set-auto-upgrade-config + --cluster-id + --weekdays + --time +``` + +## Options + +`--cluster-id` (required) +: The ID of the cluster. + +`--weekdays` (required) +: Days of the week to perform auto upgrade, comma-separated. Valid values: `Mon`, `Tue`, `Wed`, `Thu`, `Fri`, `Sat`, `Sun`. Example: `Mon,Wed,Fri` + +`--time` (required) +: Time of day to perform auto upgrade in 24-hour format `HH:mm`. Example: `03:00` + +## Examples + +Set auto-upgrade to run on weekdays at 3 AM: + +```bash +grn vks set-auto-upgrade-config \ + --cluster-id k8s-xxxxx \ + --weekdays Mon,Tue,Wed,Thu,Fri \ + --time 03:00 +``` + +Set auto-upgrade to run on weekends at midnight: + +```bash +grn vks set-auto-upgrade-config \ + --cluster-id k8s-xxxxx \ + --weekdays Sat,Sun \ + --time 00:00 +``` diff --git a/grncli/customizations/vks/__init__.py b/grncli/customizations/vks/__init__.py index 89d7769..e995c7a 100644 --- a/grncli/customizations/vks/__init__.py +++ b/grncli/customizations/vks/__init__.py @@ -23,6 +23,8 @@ def _inject_vks_operations(command_table, session, **kwargs): from grncli.customizations.vks.update_nodegroup import UpdateNodegroupCommand from grncli.customizations.vks.delete_nodegroup import DeleteNodegroupCommand from grncli.customizations.vks.wait import WaitClusterActiveCommand + from grncli.customizations.vks.auto_upgrade_config import SetAutoUpgradeConfigCommand + from grncli.customizations.vks.auto_upgrade_config import DeleteAutoUpgradeConfigCommand command_table['list-clusters'] = ListClustersCommand(session) command_table['get-cluster'] = GetClusterCommand(session) @@ -35,3 +37,5 @@ def _inject_vks_operations(command_table, session, **kwargs): command_table['update-nodegroup'] = UpdateNodegroupCommand(session) command_table['delete-nodegroup'] = DeleteNodegroupCommand(session) command_table['wait-cluster-active'] = WaitClusterActiveCommand(session) + command_table['set-auto-upgrade-config'] = SetAutoUpgradeConfigCommand(session) + command_table['delete-auto-upgrade-config'] = DeleteAutoUpgradeConfigCommand(session) diff --git a/grncli/customizations/vks/auto_upgrade_config.py b/grncli/customizations/vks/auto_upgrade_config.py new file mode 100644 index 0000000..564ca98 --- /dev/null +++ b/grncli/customizations/vks/auto_upgrade_config.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from grncli.customizations.commands import BasicCommand, display_output +from grncli.customizations.vks.validators import validate_id + + +class SetAutoUpgradeConfigCommand(BasicCommand): + NAME = 'set-auto-upgrade-config' + DESCRIPTION = 'Configure auto-upgrade schedule for a cluster' + ARG_TABLE = [ + {'name': 'cluster-id', 'help_text': 'Cluster ID', 'required': True}, + {'name': 'weekdays', 'help_text': 'Days of the week (e.g. Mon,Wed,Fri)', 'required': True}, + {'name': 'time', 'help_text': 'Time of day in 24h format HH:mm (e.g. 03:00)', 'required': True}, + ] + + def _run_main(self, parsed_args, parsed_globals): + validate_id(parsed_args.cluster_id, 'cluster-id') + client = self._session.create_client('vks') + body = { + 'weekdays': parsed_args.weekdays, + 'time': parsed_args.time, + } + result = client.put( + f'/v1/clusters/{parsed_args.cluster_id}/auto-upgrade-config', + json=body, + ) + display_output(result, parsed_globals) + return 0 + + +class DeleteAutoUpgradeConfigCommand(BasicCommand): + NAME = 'delete-auto-upgrade-config' + DESCRIPTION = 'Delete auto-upgrade config for a cluster' + ARG_TABLE = [ + {'name': 'cluster-id', 'help_text': 'Cluster ID', 'required': True}, + {'name': 'force', 'help_text': 'Skip confirmation prompt', + 'action': 'store_true', 'default': False}, + ] + + def _run_main(self, parsed_args, parsed_globals): + validate_id(parsed_args.cluster_id, 'cluster-id') + client = self._session.create_client('vks') + + if not parsed_args.force: + import sys + response = input( + "Are you sure you want to delete the auto-upgrade config? (yes/no): " + ) + if response.strip().lower() != 'yes': + sys.stdout.write("Delete cancelled.\n") + return 0 + + result = client.delete( + f'/v1/clusters/{parsed_args.cluster_id}/auto-upgrade-config', + ) + display_output(result, parsed_globals) + return 0 diff --git a/mkdocs.yml b/mkdocs.yml index 141206a..42ac770 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -52,6 +52,9 @@ nav: - create-nodegroup: commands/vks/create-nodegroup.md - update-nodegroup: commands/vks/update-nodegroup.md - delete-nodegroup: commands/vks/delete-nodegroup.md + - Auto-Upgrade: + - set-auto-upgrade-config: commands/vks/set-auto-upgrade-config.md + - delete-auto-upgrade-config: commands/vks/delete-auto-upgrade-config.md - Waiter: - wait-cluster-active: commands/vks/wait-cluster-active.md - Development: