diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 67954c08f..3a5bb9f13 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,7 +36,7 @@ jobs: run: make test - name: Archive code coverage results - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ${{ env.CODE_COVERAGE_ARTIFACT_NAME }} path: ${{ env.CODE_COVERAGE_FILE_NAME }} @@ -50,7 +50,7 @@ jobs: uses: actions/checkout@v6 - name: Check GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@v7 with: args: check @@ -65,7 +65,7 @@ jobs: pull-requests: write # write permission needed to comment on PR steps: - name: Check new code coverage - uses: fgrosse/go-coverage-report@v1.2.0 + uses: fgrosse/go-coverage-report@v1.3.0 continue-on-error: true # Add this line to prevent pipeline failures in forks with: coverage-artifact-name: ${{ env.CODE_COVERAGE_ARTIFACT_NAME }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e9377a728..2f24d7955 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -35,7 +35,7 @@ jobs: cache: true - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 id: import_gpg with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} @@ -74,7 +74,7 @@ jobs: uses: samuelmeuli/action-snapcraft@v3 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@v7 with: args: release --clean env: @@ -90,7 +90,7 @@ jobs: rm -f "$GPG_KEY_PATH" - name: Upload artifacts to workflow - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: goreleaser-dist-temp path: dist @@ -110,7 +110,7 @@ jobs: # use the artifacts from the "goreleaser" job - name: Download artifacts from workflow - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: goreleaser-dist-temp path: dist @@ -119,7 +119,7 @@ jobs: run: brew install aptly - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 id: import_gpg with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@v6 - name: Download artifacts from workflow - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: goreleaser-dist-temp path: dist @@ -158,7 +158,7 @@ jobs: sudo apt-get install -y createrepo-c - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 id: import_gpg with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2ef5c2ad1..7fb43c81d 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -105,7 +105,9 @@ nfpms: license: Apache 2.0 contents: - src: LICENSE.md - dst: LICENSE.md + dst: /usr/share/doc/stackit/copyright + file_info: + mode: 0644 formats: - deb - rpm diff --git a/INSTALLATION.md b/INSTALLATION.md index 14976fe69..69756bf2d 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -188,7 +188,22 @@ Alternatively, you can install via [Homebrew](https://brew.sh/) or refer to one ### Windows -> We are currently working on distributing the CLI on a package manager for Windows. For the moment, please refer to one of the installation methods below. +#### Scoop + +The STACKIT CLI can be installed through the [Scoop](https://scoop.sh/) package manager. + +1. Install Scoop (if not already installed): + +```powershell +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression +``` + +2. Install the CLI: + +```powershell +scoop install stackit +``` ## Manual installation diff --git a/Makefile b/Makefile index 836e41ad4..436a40a80 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ build: fmt: @gofmt -s -w . - @go tool goimports -w . + @go tool golangci-lint fmt --config=${GOLANG_CI_YAML_PATH} # Lint lint-golangci-lint: diff --git a/README.md b/README.md index 8c66fa532..579b9a4d1 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/stackitcloud/stackit-cli)](https://goreportcard.com/report/github.com/stackitcloud/stackit-cli) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/stackitcloud/stackit-cli) [![GitHub License](https://img.shields.io/github/license/stackitcloud/stackit-cli)](https://www.apache.org/licenses/LICENSE-2.0) -Welcome to the STACKIT CLI, a command-line interface for [STACKIT - The German business cloud](https://www.stackit.de/en). +Welcome to the STACKIT CLI, a command-line interface for [STACKIT - The sovereign cloud for Europe](https://www.stackit.de/en). The STACKIT CLI allows you to manage your STACKIT services and resources as well as perform operations using the command-line or in scripts or automation, such as: @@ -68,28 +68,34 @@ Help is available for any command by specifying the special flag `--help` (or si Below you can find a list of the STACKIT services already available in the CLI (along with their respective command names) and the ones that are currently planned to be integrated. -| Service | CLI Commands | Status | -| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| Authorization | `project`, `organization` | :white_check_mark: | -| DNS | `dns` | :white_check_mark: | -| Infrastructure as a Service (IaaS) | `image`
`key-pair`
`network`
`network-area`
`network-interface`
`public-ip`
`quota`
`security-group`
`server`
`volume` | :white_check_mark:| -| Kubernetes Engine (SKE) | `ske` | :white_check_mark: | -| Load Balancer | `load-balancer` | :white_check_mark: | -| LogMe | `logme` | :white_check_mark: | -| MariaDB | `mariadb` | :white_check_mark: | -| MongoDB Flex | `mongodbflex` | :white_check_mark: | -| Observability | `observability` | :white_check_mark: | -| Object Storage | `object-storage` | :white_check_mark: | -| OpenSearch | `opensearch` | :white_check_mark: | -| PostgreSQL Flex | `postgresflex` | :white_check_mark: | -| RabbitMQ | `rabbitmq` | :white_check_mark: | -| Redis | `redis` | :white_check_mark: | -| Resource Manager | `project` | :white_check_mark: | -| Secrets Manager | `secrets-manager` | :white_check_mark: | -| Server Backup Management | `server backup` | :white_check_mark: | -| Server Command (Run Command) | `server command` | :white_check_mark: | -| Service Account | `service-account` | :white_check_mark: | -| SQLServer Flex | `beta sqlserverflex` | :white_check_mark: (beta) | +| Service | CLI Commands | Status | +|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------| +| Application Load Balancer | `beta alb` | :white_check_mark: (beta) | +| Authorization | `project`, `organization` | :white_check_mark: | +| DNS | `dns` | :white_check_mark: | +| Edge Cloud | `beta edge-cloud` | :white_check_mark: (beta) | +| Git | `git` | :white_check_mark: | +| Infrastructure as a Service (IaaS) | `affinity-group`
`image`
`key-pair`
`network`
`network-area`
`network-interface`
`public-ip`
`quota`
`security-group`
`server`
`volume` | :white_check_mark: | +| Intake | `beta intake` | :white_check_mark: (beta) | +| Key Management Service (KMS) | `beta kms` | :white_check_mark: (beta) | +| Kubernetes Engine (SKE) | `ske` | :white_check_mark: | +| Load Balancer | `load-balancer` | :white_check_mark: | +| LogMe | `logme` | :white_check_mark: | +| MariaDB | `mariadb` | :white_check_mark: | +| MongoDB Flex | `mongodbflex` | :white_check_mark: | +| Observability | `observability` | :white_check_mark: | +| Object Storage | `object-storage` | :white_check_mark: | +| OpenSearch | `opensearch` | :white_check_mark: | +| PostgreSQL Flex | `postgresflex` | :white_check_mark: | +| RabbitMQ | `rabbitmq` | :white_check_mark: | +| Redis | `redis` | :white_check_mark: | +| Resource Manager | `project` | :white_check_mark: | +| Secrets Manager | `secrets-manager` | :white_check_mark: | +| Server Backup Management | `server backup` | :white_check_mark: | +| Server Command (Run Command) | `server command` | :white_check_mark: | +| Service Account | `service-account` | :white_check_mark: | +| SQLServer Flex | `beta sqlserverflex` | :white_check_mark: (beta) | +| File Storage (SFS) | `beta sfs` | :white_check_mark: (beta) | ## Authentication diff --git a/docs/stackit.md b/docs/stackit.md index fe3f26296..88f1a5707 100644 --- a/docs/stackit.md +++ b/docs/stackit.md @@ -35,6 +35,7 @@ stackit [flags] * [stackit git](./stackit_git.md) - Provides functionality for STACKIT Git * [stackit image](./stackit_image.md) - Manage server images * [stackit key-pair](./stackit_key-pair.md) - Provides functionality for SSH key pairs +* [stackit kms](./stackit_kms.md) - Provides functionality for KMS * [stackit load-balancer](./stackit_load-balancer.md) - Provides functionality for Load Balancer * [stackit logme](./stackit_logme.md) - Provides functionality for LogMe * [stackit logs](./stackit_logs.md) - Provides functionality for Logs diff --git a/docs/stackit_auth_login.md b/docs/stackit_auth_login.md index 8b08bc947..3cd888bd2 100644 --- a/docs/stackit_auth_login.md +++ b/docs/stackit_auth_login.md @@ -21,7 +21,9 @@ stackit auth login [flags] ### Options ``` - -h, --help Help for "stackit auth login" + -h, --help Help for "stackit auth login" + --port int The port on which the callback server will listen to. By default, it tries to bind a port between 8000 and 8020. + When a value is specified, it will only try to use the specified port. Valid values are within the range of 8000 to 8020. ``` ### Options inherited from parent commands diff --git a/docs/stackit_beta.md b/docs/stackit_beta.md index 77c078fa6..9d62cd913 100644 --- a/docs/stackit_beta.md +++ b/docs/stackit_beta.md @@ -45,7 +45,6 @@ stackit beta [flags] * [stackit beta cdn](./stackit_beta_cdn.md) - Manage CDN resources * [stackit beta edge-cloud](./stackit_beta_edge-cloud.md) - Provides functionality for edge services. * [stackit beta intake](./stackit_beta_intake.md) - Provides functionality for intake -* [stackit beta kms](./stackit_beta_kms.md) - Provides functionality for KMS * [stackit beta sfs](./stackit_beta_sfs.md) - Provides functionality for SFS (stackit file storage) * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex diff --git a/docs/stackit_beta_intake.md b/docs/stackit_beta_intake.md index 9a3672fb8..fa29e493f 100644 --- a/docs/stackit_beta_intake.md +++ b/docs/stackit_beta_intake.md @@ -36,4 +36,5 @@ stackit beta intake [flags] * [stackit beta intake list](./stackit_beta_intake_list.md) - Lists all Intakes * [stackit beta intake runner](./stackit_beta_intake_runner.md) - Provides functionality for Intake Runners * [stackit beta intake update](./stackit_beta_intake_update.md) - Updates an Intake +* [stackit beta intake user](./stackit_beta_intake_user.md) - Provides functionality for Intake Users diff --git a/docs/stackit_beta_intake_user.md b/docs/stackit_beta_intake_user.md new file mode 100644 index 000000000..a09b51fb1 --- /dev/null +++ b/docs/stackit_beta_intake_user.md @@ -0,0 +1,38 @@ +## stackit beta intake user + +Provides functionality for Intake Users + +### Synopsis + +Provides functionality for Intake Users. + +``` +stackit beta intake user [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta intake user" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta intake](./stackit_beta_intake.md) - Provides functionality for intake +* [stackit beta intake user create](./stackit_beta_intake_user_create.md) - Creates a new Intake User +* [stackit beta intake user delete](./stackit_beta_intake_user_delete.md) - Deletes an Intake User +* [stackit beta intake user describe](./stackit_beta_intake_user_describe.md) - Shows details of an Intake User +* [stackit beta intake user list](./stackit_beta_intake_user_list.md) - Lists all Intake Users +* [stackit beta intake user update](./stackit_beta_intake_user_update.md) - Updates an Intake User + diff --git a/docs/stackit_beta_intake_user_create.md b/docs/stackit_beta_intake_user_create.md new file mode 100644 index 000000000..84ec49bdf --- /dev/null +++ b/docs/stackit_beta_intake_user_create.md @@ -0,0 +1,49 @@ +## stackit beta intake user create + +Creates a new Intake User + +### Synopsis + +Creates a new Intake User for a specific Intake. + +``` +stackit beta intake user create [flags] +``` + +### Examples + +``` + Create a new Intake User with required parameters + $ stackit beta intake user create --display-name intake-user --intake-id xxx --password "SuperSafepass123\!" + + Create a new Intake User for the dead-letter queue with labels + $ stackit beta intake user create --display-name dlq-user --intake-id xxx --password "SuperSafepass123\!" --type dead-letter --labels "env=prod" +``` + +### Options + +``` + --description string Description + --display-name string Display name + -h, --help Help for "stackit beta intake user create" + --intake-id string The UUID of the Intake to associate the user with + --labels stringToString Labels in key=value format, separated by commas (default []) + --password string Password for the user. Must contain lower, upper, number, and special characters (min 12 chars) + --type string Type of user. One of 'intake' (default) or 'dead-letter' (default "intake") +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta intake user](./stackit_beta_intake_user.md) - Provides functionality for Intake Users + diff --git a/docs/stackit_beta_intake_user_delete.md b/docs/stackit_beta_intake_user_delete.md new file mode 100644 index 000000000..cf6fad990 --- /dev/null +++ b/docs/stackit_beta_intake_user_delete.md @@ -0,0 +1,41 @@ +## stackit beta intake user delete + +Deletes an Intake User + +### Synopsis + +Deletes an Intake User. + +``` +stackit beta intake user delete USER_ID [flags] +``` + +### Examples + +``` + Delete an Intake User with ID "xxx" for Intake "yyy" + $ stackit beta intake user delete xxx --intake-id yyy +``` + +### Options + +``` + -h, --help Help for "stackit beta intake user delete" + --intake-id string Intake ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta intake user](./stackit_beta_intake_user.md) - Provides functionality for Intake Users + diff --git a/docs/stackit_beta_intake_user_describe.md b/docs/stackit_beta_intake_user_describe.md new file mode 100644 index 000000000..18f04e693 --- /dev/null +++ b/docs/stackit_beta_intake_user_describe.md @@ -0,0 +1,44 @@ +## stackit beta intake user describe + +Shows details of an Intake User + +### Synopsis + +Shows details of an Intake User. + +``` +stackit beta intake user describe USER_ID [flags] +``` + +### Examples + +``` + Get details of an Intake User with ID "xxx" for Intake "yyy" + $ stackit beta intake user describe xxx --intake-id yyy + + Get details of an Intake User with ID "xxx" in JSON format + $ stackit beta intake user describe xxx --intake-id yyy --output-format json +``` + +### Options + +``` + -h, --help Help for "stackit beta intake user describe" + --intake-id string Intake ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta intake user](./stackit_beta_intake_user.md) - Provides functionality for Intake Users + diff --git a/docs/stackit_beta_intake_user_list.md b/docs/stackit_beta_intake_user_list.md new file mode 100644 index 000000000..b1a25a14a --- /dev/null +++ b/docs/stackit_beta_intake_user_list.md @@ -0,0 +1,48 @@ +## stackit beta intake user list + +Lists all Intake Users + +### Synopsis + +Lists all Intake Users for a specific Intake. + +``` +stackit beta intake user list [flags] +``` + +### Examples + +``` + List all users for an Intake + $ stackit beta intake user list --intake-id xxx + + List all users for an Intake in JSON format + $ stackit beta intake user list --intake-id xxx --output-format json + + List up to 5 users for an Intake + $ stackit beta intake user list --intake-id xxx --limit 5 +``` + +### Options + +``` + -h, --help Help for "stackit beta intake user list" + --intake-id string Intake ID + --limit int Maximum number of entries to list +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta intake user](./stackit_beta_intake_user.md) - Provides functionality for Intake Users + diff --git a/docs/stackit_beta_intake_user_update.md b/docs/stackit_beta_intake_user_update.md new file mode 100644 index 000000000..93c591a15 --- /dev/null +++ b/docs/stackit_beta_intake_user_update.md @@ -0,0 +1,49 @@ +## stackit beta intake user update + +Updates an Intake User + +### Synopsis + +Updates an Intake User. Only the specified fields are updated. + +``` +stackit beta intake user update USER_ID [flags] +``` + +### Examples + +``` + Update the display name of an Intake User + $ stackit beta intake user update xxx --intake-id yyy --display-name "new-user-name" + + Update the password and description for an Intake User + $ stackit beta intake user update xxx --intake-id yyy --password "NewSecret123\!" --description "Updated description" +``` + +### Options + +``` + --description string Description + --display-name string Display name + -h, --help Help for "stackit beta intake user update" + --intake-id string Intake ID + --labels stringToString Labels in key=value format, separated by commas. Example: --labels "key1=value1,key2=value2". (default []) + --password string Password for the user. Must contain lower, upper, number, and special characters (min 12 chars) + --type string Type of user. One of 'intake' or 'dead-letter' +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta intake user](./stackit_beta_intake_user.md) - Provides functionality for Intake Users + diff --git a/docs/stackit_beta_kms_key.md b/docs/stackit_beta_kms_key.md deleted file mode 100644 index a22f3d97b..000000000 --- a/docs/stackit_beta_kms_key.md +++ /dev/null @@ -1,40 +0,0 @@ -## stackit beta kms key - -Manage KMS keys - -### Synopsis - -Provides functionality for key operations inside the KMS - -``` -stackit beta kms key [flags] -``` - -### Options - -``` - -h, --help Help for "stackit beta kms key" -``` - -### Options inherited from parent commands - -``` - -y, --assume-yes If set, skips all confirmation prompts - --async If set, runs the command asynchronously - -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] - -p, --project-id string Project ID - --region string Target region for region-specific requests - --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") -``` - -### SEE ALSO - -* [stackit beta kms](./stackit_beta_kms.md) - Provides functionality for KMS -* [stackit beta kms key create](./stackit_beta_kms_key_create.md) - Creates a KMS key -* [stackit beta kms key delete](./stackit_beta_kms_key_delete.md) - Deletes a KMS key -* [stackit beta kms key describe](./stackit_beta_kms_key_describe.md) - Describe a KMS key -* [stackit beta kms key import](./stackit_beta_kms_key_import.md) - Import a KMS key -* [stackit beta kms key list](./stackit_beta_kms_key_list.md) - List all KMS keys -* [stackit beta kms key restore](./stackit_beta_kms_key_restore.md) - Restore a key -* [stackit beta kms key rotate](./stackit_beta_kms_key_rotate.md) - Rotate a key - diff --git a/docs/stackit_beta_kms_version.md b/docs/stackit_beta_kms_version.md deleted file mode 100644 index baf9c5ecb..000000000 --- a/docs/stackit_beta_kms_version.md +++ /dev/null @@ -1,38 +0,0 @@ -## stackit beta kms version - -Manage KMS key versions - -### Synopsis - -Provides functionality for key version operations inside the KMS - -``` -stackit beta kms version [flags] -``` - -### Options - -``` - -h, --help Help for "stackit beta kms version" -``` - -### Options inherited from parent commands - -``` - -y, --assume-yes If set, skips all confirmation prompts - --async If set, runs the command asynchronously - -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] - -p, --project-id string Project ID - --region string Target region for region-specific requests - --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") -``` - -### SEE ALSO - -* [stackit beta kms](./stackit_beta_kms.md) - Provides functionality for KMS -* [stackit beta kms version destroy](./stackit_beta_kms_version_destroy.md) - Destroy a key version -* [stackit beta kms version disable](./stackit_beta_kms_version_disable.md) - Disable a key version -* [stackit beta kms version enable](./stackit_beta_kms_version_enable.md) - Enable a key version -* [stackit beta kms version list](./stackit_beta_kms_version_list.md) - List all key versions -* [stackit beta kms version restore](./stackit_beta_kms_version_restore.md) - Restore a key version - diff --git a/docs/stackit_beta_kms_wrapping-key.md b/docs/stackit_beta_kms_wrapping-key.md deleted file mode 100644 index 2cef6b863..000000000 --- a/docs/stackit_beta_kms_wrapping-key.md +++ /dev/null @@ -1,37 +0,0 @@ -## stackit beta kms wrapping-key - -Manage KMS wrapping keys - -### Synopsis - -Provides functionality for wrapping key operations inside the KMS - -``` -stackit beta kms wrapping-key [flags] -``` - -### Options - -``` - -h, --help Help for "stackit beta kms wrapping-key" -``` - -### Options inherited from parent commands - -``` - -y, --assume-yes If set, skips all confirmation prompts - --async If set, runs the command asynchronously - -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] - -p, --project-id string Project ID - --region string Target region for region-specific requests - --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") -``` - -### SEE ALSO - -* [stackit beta kms](./stackit_beta_kms.md) - Provides functionality for KMS -* [stackit beta kms wrapping-key create](./stackit_beta_kms_wrapping-key_create.md) - Creates a KMS wrapping key -* [stackit beta kms wrapping-key delete](./stackit_beta_kms_wrapping-key_delete.md) - Deletes a KMS wrapping key -* [stackit beta kms wrapping-key describe](./stackit_beta_kms_wrapping-key_describe.md) - Describe a KMS wrapping key -* [stackit beta kms wrapping-key list](./stackit_beta_kms_wrapping-key_list.md) - Lists all KMS wrapping keys - diff --git a/docs/stackit_config_unset.md b/docs/stackit_config_unset.md index 4f0f7f229..d76f3068c 100644 --- a/docs/stackit_config_unset.md +++ b/docs/stackit_config_unset.md @@ -27,6 +27,7 @@ stackit config unset [flags] ``` --allowed-url-domain Domain name, used for the verification of the URLs that are given in the IDP endpoint and curl commands. If unset, defaults to stackit.cloud + --assume-yes If set, skips all confirmation prompts --async Configuration option to run commands asynchronously --authorization-custom-endpoint Authorization API base URL. If unset, uses the default base URL --cdn-custom-endpoint Custom CDN endpoint URL. If unset, uses the default base URL @@ -67,12 +68,6 @@ stackit config unset [flags] --verbosity Verbosity of the CLI ``` -### Options inherited from parent commands - -``` - -y, --assume-yes If set, skips all confirmation prompts -``` - ### SEE ALSO * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options diff --git a/docs/stackit_image_list.md b/docs/stackit_image_list.md index eae2a3409..28b759eb0 100644 --- a/docs/stackit_image_list.md +++ b/docs/stackit_image_list.md @@ -13,7 +13,7 @@ stackit image list [flags] ### Examples ``` - List all images + List images in your project $ stackit image list List images with label @@ -21,11 +21,15 @@ stackit image list [flags] List the first 10 images $ stackit image list --limit=10 + + List all images + $ stackit image list --all ``` ### Options ``` + --all List all images available -h, --help Help for "stackit image list" --label-selector string Filter by label --limit int Limit the output to the first n elements diff --git a/docs/stackit_beta_kms.md b/docs/stackit_kms.md similarity index 55% rename from docs/stackit_beta_kms.md rename to docs/stackit_kms.md index e50cfd05a..d1adc5024 100644 --- a/docs/stackit_beta_kms.md +++ b/docs/stackit_kms.md @@ -1,4 +1,4 @@ -## stackit beta kms +## stackit kms Provides functionality for KMS @@ -7,13 +7,13 @@ Provides functionality for KMS Provides functionality for KMS. ``` -stackit beta kms [flags] +stackit kms [flags] ``` ### Options ``` - -h, --help Help for "stackit beta kms" + -h, --help Help for "stackit kms" ``` ### Options inherited from parent commands @@ -29,9 +29,9 @@ stackit beta kms [flags] ### SEE ALSO -* [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys -* [stackit beta kms keyring](./stackit_beta_kms_keyring.md) - Manage KMS key rings -* [stackit beta kms version](./stackit_beta_kms_version.md) - Manage KMS key versions -* [stackit beta kms wrapping-key](./stackit_beta_kms_wrapping-key.md) - Manage KMS wrapping keys +* [stackit](./stackit.md) - Manage STACKIT resources using the command line +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys +* [stackit kms keyring](./stackit_kms_keyring.md) - Manage KMS key rings +* [stackit kms version](./stackit_kms_version.md) - Manage KMS key versions +* [stackit kms wrapping-key](./stackit_kms_wrapping-key.md) - Manage KMS wrapping keys diff --git a/docs/stackit_kms_key.md b/docs/stackit_kms_key.md new file mode 100644 index 000000000..3cdfbdf00 --- /dev/null +++ b/docs/stackit_kms_key.md @@ -0,0 +1,40 @@ +## stackit kms key + +Manage KMS keys + +### Synopsis + +Provides functionality for key operations inside the KMS + +``` +stackit kms key [flags] +``` + +### Options + +``` + -h, --help Help for "stackit kms key" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit kms](./stackit_kms.md) - Provides functionality for KMS +* [stackit kms key create](./stackit_kms_key_create.md) - Creates a KMS key +* [stackit kms key delete](./stackit_kms_key_delete.md) - Deletes a KMS key +* [stackit kms key describe](./stackit_kms_key_describe.md) - Describe a KMS key +* [stackit kms key import](./stackit_kms_key_import.md) - Import a KMS key +* [stackit kms key list](./stackit_kms_key_list.md) - List all KMS keys +* [stackit kms key restore](./stackit_kms_key_restore.md) - Restore a key +* [stackit kms key rotate](./stackit_kms_key_rotate.md) - Rotate a key + diff --git a/docs/stackit_beta_kms_key_create.md b/docs/stackit_kms_key_create.md similarity index 60% rename from docs/stackit_beta_kms_key_create.md rename to docs/stackit_kms_key_create.md index 0c3114a69..dcf9716f3 100644 --- a/docs/stackit_beta_kms_key_create.md +++ b/docs/stackit_kms_key_create.md @@ -1,4 +1,4 @@ -## stackit beta kms key create +## stackit kms key create Creates a KMS key @@ -7,29 +7,29 @@ Creates a KMS key Creates a KMS key. ``` -stackit beta kms key create [flags] +stackit kms key create [flags] ``` ### Examples ``` Create a symmetric AES key (AES-256) with the name "symm-aes-gcm" under the key ring "my-keyring-id" - $ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "aes_256_gcm" --name "symm-aes-gcm" --purpose "symmetric_encrypt_decrypt" --protection "software" + $ stackit kms key create --keyring-id "my-keyring-id" --algorithm "aes_256_gcm" --name "symm-aes-gcm" --purpose "symmetric_encrypt_decrypt" --protection "software" Create an asymmetric RSA encryption key (RSA-2048) - $ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "prod-orders-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" + $ stackit kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "prod-orders-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" Create a message authentication key (HMAC-SHA512) - $ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "hmac_sha512" --name "api-mac-key" --purpose "message_authentication_code" --protection "software" + $ stackit kms key create --keyring-id "my-keyring-id" --algorithm "hmac_sha512" --name "api-mac-key" --purpose "message_authentication_code" --protection "software" Create an ECDSA P-256 key for signing & verification - $ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "ecdsa_p256_sha256" --name "signing-ecdsa-p256" --purpose "asymmetric_sign_verify" --protection "software" + $ stackit kms key create --keyring-id "my-keyring-id" --algorithm "ecdsa_p256_sha256" --name "signing-ecdsa-p256" --purpose "asymmetric_sign_verify" --protection "software" Create an import-only key (versions must be imported) - $ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "ext-managed-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --import-only + $ stackit kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "ext-managed-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --import-only Create a key and print the result as YAML - $ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "yaml-output-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --output yaml + $ stackit kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "yaml-output-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --output yaml ``` ### Options @@ -37,7 +37,7 @@ stackit beta kms key create [flags] ``` --algorithm string En-/Decryption / signing algorithm. Possible values: ["aes_256_gcm" "rsa_2048_oaep_sha256" "rsa_3072_oaep_sha256" "rsa_4096_oaep_sha256" "rsa_4096_oaep_sha512" "hmac_sha256" "hmac_sha384" "hmac_sha512" "ecdsa_p256_sha256" "ecdsa_p384_sha384" "ecdsa_p521_sha512"] --description string Optional description of the key - -h, --help Help for "stackit beta kms key create" + -h, --help Help for "stackit kms key create" --import-only States whether versions can be created or only imported --keyring-id string ID of the KMS key ring --name string The display name to distinguish multiple keys @@ -58,5 +58,5 @@ stackit beta kms key create [flags] ### SEE ALSO -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys diff --git a/docs/stackit_beta_kms_key_delete.md b/docs/stackit_kms_key_delete.md similarity index 73% rename from docs/stackit_beta_kms_key_delete.md rename to docs/stackit_kms_key_delete.md index 1f67c4ff8..4a5418843 100644 --- a/docs/stackit_beta_kms_key_delete.md +++ b/docs/stackit_kms_key_delete.md @@ -1,4 +1,4 @@ -## stackit beta kms key delete +## stackit kms key delete Deletes a KMS key @@ -7,20 +7,20 @@ Deletes a KMS key Deletes a KMS key inside a specific key ring. ``` -stackit beta kms key delete KEY_ID [flags] +stackit kms key delete KEY_ID [flags] ``` ### Examples ``` Delete a KMS key "MY_KEY_ID" inside the key ring "my-keyring-id" - $ stackit beta kms key delete "MY_KEY_ID" --keyring-id "my-keyring-id" + $ stackit kms key delete "MY_KEY_ID" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms key delete" + -h, --help Help for "stackit kms key delete" --keyring-id string ID of the KMS key ring where the key is stored ``` @@ -37,5 +37,5 @@ stackit beta kms key delete KEY_ID [flags] ### SEE ALSO -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys diff --git a/docs/stackit_beta_kms_key_describe.md b/docs/stackit_kms_key_describe.md similarity index 72% rename from docs/stackit_beta_kms_key_describe.md rename to docs/stackit_kms_key_describe.md index 05e876491..d2921c47c 100644 --- a/docs/stackit_beta_kms_key_describe.md +++ b/docs/stackit_kms_key_describe.md @@ -1,4 +1,4 @@ -## stackit beta kms key describe +## stackit kms key describe Describe a KMS key @@ -7,20 +7,20 @@ Describe a KMS key Describe a KMS key ``` -stackit beta kms key describe KEY_ID [flags] +stackit kms key describe KEY_ID [flags] ``` ### Examples ``` Describe a KMS key with ID xxx of keyring yyy - $ stackit beta kms key describe xxx --keyring-id yyy + $ stackit kms key describe xxx --keyring-id yyy ``` ### Options ``` - -h, --help Help for "stackit beta kms key describe" + -h, --help Help for "stackit kms key describe" --keyring-id string Key Ring ID ``` @@ -37,5 +37,5 @@ stackit beta kms key describe KEY_ID [flags] ### SEE ALSO -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys diff --git a/docs/stackit_beta_kms_key_import.md b/docs/stackit_kms_key_import.md similarity index 69% rename from docs/stackit_beta_kms_key_import.md rename to docs/stackit_kms_key_import.md index efc1ba47a..99953dacc 100644 --- a/docs/stackit_beta_kms_key_import.md +++ b/docs/stackit_kms_key_import.md @@ -1,4 +1,4 @@ -## stackit beta kms key import +## stackit kms key import Import a KMS key @@ -7,23 +7,23 @@ Import a KMS key After encrypting the secret with the wrapping key’s public key and Base64-encoding it, import it as a new version of the specified KMS key. ``` -stackit beta kms key import KEY_ID [flags] +stackit kms key import KEY_ID [flags] ``` ### Examples ``` Import a new version for the given KMS key "MY_KEY_ID" from literal value - $ stackit beta kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "BASE64_VALUE" --wrapping-key-id "MY_WRAPPING_KEY_ID" + $ stackit kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "BASE64_VALUE" --wrapping-key-id "MY_WRAPPING_KEY_ID" Import from a file - $ stackit beta kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "@path/to/wrapped.key.b64" --wrapping-key-id "MY_WRAPPING_KEY_ID" + $ stackit kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "@path/to/wrapped.key.b64" --wrapping-key-id "MY_WRAPPING_KEY_ID" ``` ### Options ``` - -h, --help Help for "stackit beta kms key import" + -h, --help Help for "stackit kms key import" --keyring-id string ID of the KMS key ring --wrapped-key string The wrapped key material to be imported. Base64-encoded. Pass the value directly or a file path (e.g. @path/to/wrapped.key.b64) --wrapping-key-id string The unique id of the wrapping key the key material has been wrapped with @@ -42,5 +42,5 @@ stackit beta kms key import KEY_ID [flags] ### SEE ALSO -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys diff --git a/docs/stackit_beta_kms_key_list.md b/docs/stackit_kms_key_list.md similarity index 70% rename from docs/stackit_beta_kms_key_list.md rename to docs/stackit_kms_key_list.md index 766bb0a5d..336d37a6a 100644 --- a/docs/stackit_beta_kms_key_list.md +++ b/docs/stackit_kms_key_list.md @@ -1,4 +1,4 @@ -## stackit beta kms key list +## stackit kms key list List all KMS keys @@ -7,23 +7,23 @@ List all KMS keys List all KMS keys inside a key ring. ``` -stackit beta kms key list [flags] +stackit kms key list [flags] ``` ### Examples ``` List all KMS keys for the key ring "my-keyring-id" - $ stackit beta kms key list --keyring-id "my-keyring-id" + $ stackit kms key list --keyring-id "my-keyring-id" List all KMS keys in JSON format - $ stackit beta kms key list --keyring-id "my-keyring-id" --output-format json + $ stackit kms key list --keyring-id "my-keyring-id" --output-format json ``` ### Options ``` - -h, --help Help for "stackit beta kms key list" + -h, --help Help for "stackit kms key list" --keyring-id string ID of the KMS key ring where the key is stored ``` @@ -40,5 +40,5 @@ stackit beta kms key list [flags] ### SEE ALSO -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys diff --git a/docs/stackit_beta_kms_key_restore.md b/docs/stackit_kms_key_restore.md similarity index 73% rename from docs/stackit_beta_kms_key_restore.md rename to docs/stackit_kms_key_restore.md index 9abd9a85e..ebe902801 100644 --- a/docs/stackit_beta_kms_key_restore.md +++ b/docs/stackit_kms_key_restore.md @@ -1,4 +1,4 @@ -## stackit beta kms key restore +## stackit kms key restore Restore a key @@ -7,20 +7,20 @@ Restore a key Restores the given key from deletion. ``` -stackit beta kms key restore KEY_ID [flags] +stackit kms key restore KEY_ID [flags] ``` ### Examples ``` Restore a KMS key "MY_KEY_ID" inside the key ring "my-keyring-id" that was scheduled for deletion. - $ stackit beta kms key restore "MY_KEY_ID" --keyring-id "my-keyring-id" + $ stackit kms key restore "MY_KEY_ID" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms key restore" + -h, --help Help for "stackit kms key restore" --keyring-id string ID of the KMS key ring where the key is stored ``` @@ -37,5 +37,5 @@ stackit beta kms key restore KEY_ID [flags] ### SEE ALSO -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys diff --git a/docs/stackit_beta_kms_key_rotate.md b/docs/stackit_kms_key_rotate.md similarity index 73% rename from docs/stackit_beta_kms_key_rotate.md rename to docs/stackit_kms_key_rotate.md index 7fdbbe3c5..a9152ccf3 100644 --- a/docs/stackit_beta_kms_key_rotate.md +++ b/docs/stackit_kms_key_rotate.md @@ -1,4 +1,4 @@ -## stackit beta kms key rotate +## stackit kms key rotate Rotate a key @@ -7,20 +7,20 @@ Rotate a key Rotates the given key. ``` -stackit beta kms key rotate KEY_ID [flags] +stackit kms key rotate KEY_ID [flags] ``` ### Examples ``` Rotate a KMS key "MY_KEY_ID" and increase its version inside the key ring "my-keyring-id". - $ stackit beta kms key rotate "MY_KEY_ID" --keyring-id "my-keyring-id" + $ stackit kms key rotate "MY_KEY_ID" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms key rotate" + -h, --help Help for "stackit kms key rotate" --keyring-id string ID of the KMS key ring where the key is stored ``` @@ -37,5 +37,5 @@ stackit beta kms key rotate KEY_ID [flags] ### SEE ALSO -* [stackit beta kms key](./stackit_beta_kms_key.md) - Manage KMS keys +* [stackit kms key](./stackit_kms_key.md) - Manage KMS keys diff --git a/docs/stackit_beta_kms_keyring.md b/docs/stackit_kms_keyring.md similarity index 52% rename from docs/stackit_beta_kms_keyring.md rename to docs/stackit_kms_keyring.md index 2d87f99d3..00202422e 100644 --- a/docs/stackit_beta_kms_keyring.md +++ b/docs/stackit_kms_keyring.md @@ -1,4 +1,4 @@ -## stackit beta kms keyring +## stackit kms keyring Manage KMS key rings @@ -7,13 +7,13 @@ Manage KMS key rings Provides functionality for key ring operations inside the KMS ``` -stackit beta kms keyring [flags] +stackit kms keyring [flags] ``` ### Options ``` - -h, --help Help for "stackit beta kms keyring" + -h, --help Help for "stackit kms keyring" ``` ### Options inherited from parent commands @@ -29,9 +29,9 @@ stackit beta kms keyring [flags] ### SEE ALSO -* [stackit beta kms](./stackit_beta_kms.md) - Provides functionality for KMS -* [stackit beta kms keyring create](./stackit_beta_kms_keyring_create.md) - Creates a KMS key ring -* [stackit beta kms keyring delete](./stackit_beta_kms_keyring_delete.md) - Deletes a KMS key ring -* [stackit beta kms keyring describe](./stackit_beta_kms_keyring_describe.md) - Describe a KMS key ring -* [stackit beta kms keyring list](./stackit_beta_kms_keyring_list.md) - Lists all KMS key rings +* [stackit kms](./stackit_kms.md) - Provides functionality for KMS +* [stackit kms keyring create](./stackit_kms_keyring_create.md) - Creates a KMS key ring +* [stackit kms keyring delete](./stackit_kms_keyring_delete.md) - Deletes a KMS key ring +* [stackit kms keyring describe](./stackit_kms_keyring_describe.md) - Describe a KMS key ring +* [stackit kms keyring list](./stackit_kms_keyring_list.md) - Lists all KMS key rings diff --git a/docs/stackit_beta_kms_keyring_create.md b/docs/stackit_kms_keyring_create.md similarity index 67% rename from docs/stackit_beta_kms_keyring_create.md rename to docs/stackit_kms_keyring_create.md index d02e6e13e..e07deb467 100644 --- a/docs/stackit_beta_kms_keyring_create.md +++ b/docs/stackit_kms_keyring_create.md @@ -1,4 +1,4 @@ -## stackit beta kms keyring create +## stackit kms keyring create Creates a KMS key ring @@ -7,27 +7,27 @@ Creates a KMS key ring Creates a KMS key ring. ``` -stackit beta kms keyring create [flags] +stackit kms keyring create [flags] ``` ### Examples ``` Create a KMS key ring with name "my-keyring" - $ stackit beta kms keyring create --name my-keyring + $ stackit kms keyring create --name my-keyring Create a KMS key ring with a description - $ stackit beta kms keyring create --name my-keyring --description my-description + $ stackit kms keyring create --name my-keyring --description my-description Create a KMS key ring and print the result as YAML - $ stackit beta kms keyring create --name my-keyring -o yaml + $ stackit kms keyring create --name my-keyring -o yaml ``` ### Options ``` --description string Optional description of the key ring - -h, --help Help for "stackit beta kms keyring create" + -h, --help Help for "stackit kms keyring create" --name string Name of the KMS key ring ``` @@ -44,5 +44,5 @@ stackit beta kms keyring create [flags] ### SEE ALSO -* [stackit beta kms keyring](./stackit_beta_kms_keyring.md) - Manage KMS key rings +* [stackit kms keyring](./stackit_kms_keyring.md) - Manage KMS key rings diff --git a/docs/stackit_beta_kms_keyring_delete.md b/docs/stackit_kms_keyring_delete.md similarity index 71% rename from docs/stackit_beta_kms_keyring_delete.md rename to docs/stackit_kms_keyring_delete.md index d5230f353..e60c0b44a 100644 --- a/docs/stackit_beta_kms_keyring_delete.md +++ b/docs/stackit_kms_keyring_delete.md @@ -1,4 +1,4 @@ -## stackit beta kms keyring delete +## stackit kms keyring delete Deletes a KMS key ring @@ -7,20 +7,20 @@ Deletes a KMS key ring Deletes a KMS key ring. ``` -stackit beta kms keyring delete KEYRING-ID [flags] +stackit kms keyring delete KEYRING-ID [flags] ``` ### Examples ``` Delete a KMS key ring with ID "MY_KEYRING_ID" - $ stackit beta kms keyring delete "MY_KEYRING_ID" + $ stackit kms keyring delete "MY_KEYRING_ID" ``` ### Options ``` - -h, --help Help for "stackit beta kms keyring delete" + -h, --help Help for "stackit kms keyring delete" ``` ### Options inherited from parent commands @@ -36,5 +36,5 @@ stackit beta kms keyring delete KEYRING-ID [flags] ### SEE ALSO -* [stackit beta kms keyring](./stackit_beta_kms_keyring.md) - Manage KMS key rings +* [stackit kms keyring](./stackit_kms_keyring.md) - Manage KMS key rings diff --git a/docs/stackit_beta_kms_keyring_describe.md b/docs/stackit_kms_keyring_describe.md similarity index 71% rename from docs/stackit_beta_kms_keyring_describe.md rename to docs/stackit_kms_keyring_describe.md index 9b1381dc0..f36bf9030 100644 --- a/docs/stackit_beta_kms_keyring_describe.md +++ b/docs/stackit_kms_keyring_describe.md @@ -1,4 +1,4 @@ -## stackit beta kms keyring describe +## stackit kms keyring describe Describe a KMS key ring @@ -7,20 +7,20 @@ Describe a KMS key ring Describe a KMS key ring ``` -stackit beta kms keyring describe KEYRING_ID [flags] +stackit kms keyring describe KEYRING_ID [flags] ``` ### Examples ``` Describe a KMS key ring with ID xxx - $ stackit beta kms keyring describe xxx + $ stackit kms keyring describe xxx ``` ### Options ``` - -h, --help Help for "stackit beta kms keyring describe" + -h, --help Help for "stackit kms keyring describe" ``` ### Options inherited from parent commands @@ -36,5 +36,5 @@ stackit beta kms keyring describe KEYRING_ID [flags] ### SEE ALSO -* [stackit beta kms keyring](./stackit_beta_kms_keyring.md) - Manage KMS key rings +* [stackit kms keyring](./stackit_kms_keyring.md) - Manage KMS key rings diff --git a/docs/stackit_beta_kms_keyring_list.md b/docs/stackit_kms_keyring_list.md similarity index 70% rename from docs/stackit_beta_kms_keyring_list.md rename to docs/stackit_kms_keyring_list.md index c82dae950..65f3ab323 100644 --- a/docs/stackit_beta_kms_keyring_list.md +++ b/docs/stackit_kms_keyring_list.md @@ -1,4 +1,4 @@ -## stackit beta kms keyring list +## stackit kms keyring list Lists all KMS key rings @@ -7,23 +7,23 @@ Lists all KMS key rings Lists all KMS key rings. ``` -stackit beta kms keyring list [flags] +stackit kms keyring list [flags] ``` ### Examples ``` List all KMS key rings - $ stackit beta kms keyring list + $ stackit kms keyring list List all KMS key rings in JSON format - $ stackit beta kms keyring list --output-format json + $ stackit kms keyring list --output-format json ``` ### Options ``` - -h, --help Help for "stackit beta kms keyring list" + -h, --help Help for "stackit kms keyring list" ``` ### Options inherited from parent commands @@ -39,5 +39,5 @@ stackit beta kms keyring list [flags] ### SEE ALSO -* [stackit beta kms keyring](./stackit_beta_kms_keyring.md) - Manage KMS key rings +* [stackit kms keyring](./stackit_kms_keyring.md) - Manage KMS key rings diff --git a/docs/stackit_kms_version.md b/docs/stackit_kms_version.md new file mode 100644 index 000000000..06b3f23b5 --- /dev/null +++ b/docs/stackit_kms_version.md @@ -0,0 +1,38 @@ +## stackit kms version + +Manage KMS key versions + +### Synopsis + +Provides functionality for key version operations inside the KMS + +``` +stackit kms version [flags] +``` + +### Options + +``` + -h, --help Help for "stackit kms version" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit kms](./stackit_kms.md) - Provides functionality for KMS +* [stackit kms version destroy](./stackit_kms_version_destroy.md) - Destroy a key version +* [stackit kms version disable](./stackit_kms_version_disable.md) - Disable a key version +* [stackit kms version enable](./stackit_kms_version_enable.md) - Enable a key version +* [stackit kms version list](./stackit_kms_version_list.md) - List all key versions +* [stackit kms version restore](./stackit_kms_version_restore.md) - Restore a key version + diff --git a/docs/stackit_beta_kms_version_destroy.md b/docs/stackit_kms_version_destroy.md similarity index 70% rename from docs/stackit_beta_kms_version_destroy.md rename to docs/stackit_kms_version_destroy.md index 8a189ecf2..3145408c1 100644 --- a/docs/stackit_beta_kms_version_destroy.md +++ b/docs/stackit_kms_version_destroy.md @@ -1,4 +1,4 @@ -## stackit beta kms version destroy +## stackit kms version destroy Destroy a key version @@ -7,20 +7,20 @@ Destroy a key version Removes the key material of a version. ``` -stackit beta kms version destroy VERSION_NUMBER [flags] +stackit kms version destroy VERSION_NUMBER [flags] ``` ### Examples ``` Destroy key version "42" for the key "my-key-id" inside the key ring "my-keyring-id" - $ stackit beta kms version destroy 42 --key-id "my-key-id" --keyring-id "my-keyring-id" + $ stackit kms version destroy 42 --key-id "my-key-id" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms version destroy" + -h, --help Help for "stackit kms version destroy" --key-id string ID of the key --keyring-id string ID of the KMS key ring ``` @@ -38,5 +38,5 @@ stackit beta kms version destroy VERSION_NUMBER [flags] ### SEE ALSO -* [stackit beta kms version](./stackit_beta_kms_version.md) - Manage KMS key versions +* [stackit kms version](./stackit_kms_version.md) - Manage KMS key versions diff --git a/docs/stackit_beta_kms_version_disable.md b/docs/stackit_kms_version_disable.md similarity index 70% rename from docs/stackit_beta_kms_version_disable.md rename to docs/stackit_kms_version_disable.md index c2e13a87e..0239dd4c1 100644 --- a/docs/stackit_beta_kms_version_disable.md +++ b/docs/stackit_kms_version_disable.md @@ -1,4 +1,4 @@ -## stackit beta kms version disable +## stackit kms version disable Disable a key version @@ -7,20 +7,20 @@ Disable a key version Disable the given key version. ``` -stackit beta kms version disable VERSION_NUMBER [flags] +stackit kms version disable VERSION_NUMBER [flags] ``` ### Examples ``` Disable key version "42" for the key "my-key-id" inside the key ring "my-keyring-id" - $ stackit beta kms version disable 42 --key-id "my-key-id" --keyring-id "my-keyring-id" + $ stackit kms version disable 42 --key-id "my-key-id" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms version disable" + -h, --help Help for "stackit kms version disable" --key-id string ID of the key --keyring-id string ID of the KMS key ring ``` @@ -38,5 +38,5 @@ stackit beta kms version disable VERSION_NUMBER [flags] ### SEE ALSO -* [stackit beta kms version](./stackit_beta_kms_version.md) - Manage KMS key versions +* [stackit kms version](./stackit_kms_version.md) - Manage KMS key versions diff --git a/docs/stackit_beta_kms_version_enable.md b/docs/stackit_kms_version_enable.md similarity index 70% rename from docs/stackit_beta_kms_version_enable.md rename to docs/stackit_kms_version_enable.md index 46d23bec0..bdb59e5d5 100644 --- a/docs/stackit_beta_kms_version_enable.md +++ b/docs/stackit_kms_version_enable.md @@ -1,4 +1,4 @@ -## stackit beta kms version enable +## stackit kms version enable Enable a key version @@ -7,20 +7,20 @@ Enable a key version Enable the given key version. ``` -stackit beta kms version enable VERSION_NUMBER [flags] +stackit kms version enable VERSION_NUMBER [flags] ``` ### Examples ``` Enable key version "42" for the key "my-key-id" inside the key ring "my-keyring-id" - $ stackit beta kms version enable 42 --key-id "my-key-id" --keyring-id "my-keyring-id" + $ stackit kms version enable 42 --key-id "my-key-id" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms version enable" + -h, --help Help for "stackit kms version enable" --key-id string ID of the key --keyring-id string ID of the KMS key ring ``` @@ -38,5 +38,5 @@ stackit beta kms version enable VERSION_NUMBER [flags] ### SEE ALSO -* [stackit beta kms version](./stackit_beta_kms_version.md) - Manage KMS key versions +* [stackit kms version](./stackit_kms_version.md) - Manage KMS key versions diff --git a/docs/stackit_beta_kms_version_list.md b/docs/stackit_kms_version_list.md similarity index 68% rename from docs/stackit_beta_kms_version_list.md rename to docs/stackit_kms_version_list.md index bd4a96747..15522ab18 100644 --- a/docs/stackit_beta_kms_version_list.md +++ b/docs/stackit_kms_version_list.md @@ -1,4 +1,4 @@ -## stackit beta kms version list +## stackit kms version list List all key versions @@ -7,23 +7,23 @@ List all key versions List all versions of a given key. ``` -stackit beta kms version list [flags] +stackit kms version list [flags] ``` ### Examples ``` List all key versions for the key "my-key-id" inside the key ring "my-keyring-id" - $ stackit beta kms version list --key-id "my-key-id" --keyring-id "my-keyring-id" + $ stackit kms version list --key-id "my-key-id" --keyring-id "my-keyring-id" List all key versions in JSON format - $ stackit beta kms version list --key-id "my-key-id" --keyring-id "my-keyring-id" -o json + $ stackit kms version list --key-id "my-key-id" --keyring-id "my-keyring-id" -o json ``` ### Options ``` - -h, --help Help for "stackit beta kms version list" + -h, --help Help for "stackit kms version list" --key-id string ID of the key --keyring-id string ID of the KMS key ring ``` @@ -41,5 +41,5 @@ stackit beta kms version list [flags] ### SEE ALSO -* [stackit beta kms version](./stackit_beta_kms_version.md) - Manage KMS key versions +* [stackit kms version](./stackit_kms_version.md) - Manage KMS key versions diff --git a/docs/stackit_beta_kms_version_restore.md b/docs/stackit_kms_version_restore.md similarity index 71% rename from docs/stackit_beta_kms_version_restore.md rename to docs/stackit_kms_version_restore.md index 1562d5fa2..b2bb1519c 100644 --- a/docs/stackit_beta_kms_version_restore.md +++ b/docs/stackit_kms_version_restore.md @@ -1,4 +1,4 @@ -## stackit beta kms version restore +## stackit kms version restore Restore a key version @@ -7,20 +7,20 @@ Restore a key version Restores the specified version of a key. ``` -stackit beta kms version restore VERSION_NUMBER [flags] +stackit kms version restore VERSION_NUMBER [flags] ``` ### Examples ``` Restore key version "42" for the key "my-key-id" inside the key ring "my-keyring-id" - $ stackit beta kms version restore 42 --key-id "my-key-id" --keyring-id "my-keyring-id" + $ stackit kms version restore 42 --key-id "my-key-id" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms version restore" + -h, --help Help for "stackit kms version restore" --key-id string ID of the key --keyring-id string ID of the KMS key ring ``` @@ -38,5 +38,5 @@ stackit beta kms version restore VERSION_NUMBER [flags] ### SEE ALSO -* [stackit beta kms version](./stackit_beta_kms_version.md) - Manage KMS key versions +* [stackit kms version](./stackit_kms_version.md) - Manage KMS key versions diff --git a/docs/stackit_kms_wrapping-key.md b/docs/stackit_kms_wrapping-key.md new file mode 100644 index 000000000..eec6e11a5 --- /dev/null +++ b/docs/stackit_kms_wrapping-key.md @@ -0,0 +1,37 @@ +## stackit kms wrapping-key + +Manage KMS wrapping keys + +### Synopsis + +Provides functionality for wrapping key operations inside the KMS + +``` +stackit kms wrapping-key [flags] +``` + +### Options + +``` + -h, --help Help for "stackit kms wrapping-key" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit kms](./stackit_kms.md) - Provides functionality for KMS +* [stackit kms wrapping-key create](./stackit_kms_wrapping-key_create.md) - Creates a KMS wrapping key +* [stackit kms wrapping-key delete](./stackit_kms_wrapping-key_delete.md) - Deletes a KMS wrapping key +* [stackit kms wrapping-key describe](./stackit_kms_wrapping-key_describe.md) - Describe a KMS wrapping key +* [stackit kms wrapping-key list](./stackit_kms_wrapping-key_list.md) - Lists all KMS wrapping keys + diff --git a/docs/stackit_beta_kms_wrapping-key_create.md b/docs/stackit_kms_wrapping-key_create.md similarity index 72% rename from docs/stackit_beta_kms_wrapping-key_create.md rename to docs/stackit_kms_wrapping-key_create.md index d4087bcbe..616f60ac7 100644 --- a/docs/stackit_beta_kms_wrapping-key_create.md +++ b/docs/stackit_kms_wrapping-key_create.md @@ -1,4 +1,4 @@ -## stackit beta kms wrapping-key create +## stackit kms wrapping-key create Creates a KMS wrapping key @@ -7,17 +7,17 @@ Creates a KMS wrapping key Creates a KMS wrapping key. ``` -stackit beta kms wrapping-key create [flags] +stackit kms wrapping-key create [flags] ``` ### Examples ``` Create a symmetric (RSA + AES) KMS wrapping key with name "my-wrapping-key-name" in key ring with ID "my-keyring-id" - $ stackit beta kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256_aes_256_key_wrap" --name "my-wrapping-key-name" --purpose "wrap_symmetric_key" --protection "software" + $ stackit kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256_aes_256_key_wrap" --name "my-wrapping-key-name" --purpose "wrap_symmetric_key" --protection "software" Create an asymmetric (RSA) KMS wrapping key with name "my-wrapping-key-name" in key ring with ID "my-keyring-id" - $ stackit beta kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_3072_oaep_sha256" --name "my-wrapping-key-name" --purpose "wrap_asymmetric_key" --protection "software" + $ stackit kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_3072_oaep_sha256" --name "my-wrapping-key-name" --purpose "wrap_asymmetric_key" --protection "software" ``` ### Options @@ -25,7 +25,7 @@ stackit beta kms wrapping-key create [flags] ``` --algorithm string En-/Decryption / signing algorithm. Possible values: ["rsa_2048_oaep_sha256" "rsa_3072_oaep_sha256" "rsa_4096_oaep_sha256" "rsa_4096_oaep_sha512" "rsa_2048_oaep_sha256_aes_256_key_wrap" "rsa_3072_oaep_sha256_aes_256_key_wrap" "rsa_4096_oaep_sha256_aes_256_key_wrap" "rsa_4096_oaep_sha512_aes_256_key_wrap"] --description string Optional description of the wrapping key - -h, --help Help for "stackit beta kms wrapping-key create" + -h, --help Help for "stackit kms wrapping-key create" --keyring-id string ID of the KMS key ring --name string The display name to distinguish multiple wrapping keys --protection string The underlying system that is responsible for protecting the wrapping key material. Possible values: ["wrap_symmetric_key" "wrap_asymmetric_key"] @@ -45,5 +45,5 @@ stackit beta kms wrapping-key create [flags] ### SEE ALSO -* [stackit beta kms wrapping-key](./stackit_beta_kms_wrapping-key.md) - Manage KMS wrapping keys +* [stackit kms wrapping-key](./stackit_kms_wrapping-key.md) - Manage KMS wrapping keys diff --git a/docs/stackit_beta_kms_wrapping-key_delete.md b/docs/stackit_kms_wrapping-key_delete.md similarity index 69% rename from docs/stackit_beta_kms_wrapping-key_delete.md rename to docs/stackit_kms_wrapping-key_delete.md index 0dfd43a03..7504c1782 100644 --- a/docs/stackit_beta_kms_wrapping-key_delete.md +++ b/docs/stackit_kms_wrapping-key_delete.md @@ -1,4 +1,4 @@ -## stackit beta kms wrapping-key delete +## stackit kms wrapping-key delete Deletes a KMS wrapping key @@ -7,20 +7,20 @@ Deletes a KMS wrapping key Deletes a KMS wrapping key inside a specific key ring. ``` -stackit beta kms wrapping-key delete WRAPPING_KEY_ID [flags] +stackit kms wrapping-key delete WRAPPING_KEY_ID [flags] ``` ### Examples ``` Delete a KMS wrapping key "MY_WRAPPING_KEY_ID" inside the key ring "my-keyring-id" - $ stackit beta kms wrapping-key delete "MY_WRAPPING_KEY_ID" --keyring-id "my-keyring-id" + $ stackit kms wrapping-key delete "MY_WRAPPING_KEY_ID" --keyring-id "my-keyring-id" ``` ### Options ``` - -h, --help Help for "stackit beta kms wrapping-key delete" + -h, --help Help for "stackit kms wrapping-key delete" --keyring-id string ID of the KMS key ring where the wrapping key is stored ``` @@ -37,5 +37,5 @@ stackit beta kms wrapping-key delete WRAPPING_KEY_ID [flags] ### SEE ALSO -* [stackit beta kms wrapping-key](./stackit_beta_kms_wrapping-key.md) - Manage KMS wrapping keys +* [stackit kms wrapping-key](./stackit_kms_wrapping-key.md) - Manage KMS wrapping keys diff --git a/docs/stackit_beta_kms_wrapping-key_describe.md b/docs/stackit_kms_wrapping-key_describe.md similarity index 68% rename from docs/stackit_beta_kms_wrapping-key_describe.md rename to docs/stackit_kms_wrapping-key_describe.md index 6e82cd595..2d1f484b4 100644 --- a/docs/stackit_beta_kms_wrapping-key_describe.md +++ b/docs/stackit_kms_wrapping-key_describe.md @@ -1,4 +1,4 @@ -## stackit beta kms wrapping-key describe +## stackit kms wrapping-key describe Describe a KMS wrapping key @@ -7,20 +7,20 @@ Describe a KMS wrapping key Describe a KMS wrapping key ``` -stackit beta kms wrapping-key describe WRAPPING_KEY_ID [flags] +stackit kms wrapping-key describe WRAPPING_KEY_ID [flags] ``` ### Examples ``` Describe a KMS wrapping key with ID xxx of keyring yyy - $ stackit beta kms wrappingkey describe xxx --keyring-id yyy + $ stackit kms wrapping-key describe xxx --keyring-id yyy ``` ### Options ``` - -h, --help Help for "stackit beta kms wrapping-key describe" + -h, --help Help for "stackit kms wrapping-key describe" --keyring-id string Key Ring ID ``` @@ -37,5 +37,5 @@ stackit beta kms wrapping-key describe WRAPPING_KEY_ID [flags] ### SEE ALSO -* [stackit beta kms wrapping-key](./stackit_beta_kms_wrapping-key.md) - Manage KMS wrapping keys +* [stackit kms wrapping-key](./stackit_kms_wrapping-key.md) - Manage KMS wrapping keys diff --git a/docs/stackit_beta_kms_wrapping-key_list.md b/docs/stackit_kms_wrapping-key_list.md similarity index 67% rename from docs/stackit_beta_kms_wrapping-key_list.md rename to docs/stackit_kms_wrapping-key_list.md index f17c23212..bc9d5dce0 100644 --- a/docs/stackit_beta_kms_wrapping-key_list.md +++ b/docs/stackit_kms_wrapping-key_list.md @@ -1,4 +1,4 @@ -## stackit beta kms wrapping-key list +## stackit kms wrapping-key list Lists all KMS wrapping keys @@ -7,23 +7,23 @@ Lists all KMS wrapping keys Lists all KMS wrapping keys inside a key ring. ``` -stackit beta kms wrapping-key list [flags] +stackit kms wrapping-key list [flags] ``` ### Examples ``` List all KMS wrapping keys for the key ring "my-keyring-id" - $ stackit beta kms wrapping-key list --keyring-id "my-keyring-id" + $ stackit kms wrapping-key list --keyring-id "my-keyring-id" List all KMS wrapping keys in JSON format - $ stackit beta kms wrapping-key list --keyring-id "my-keyring-id" --output-format json + $ stackit kms wrapping-key list --keyring-id "my-keyring-id" --output-format json ``` ### Options ``` - -h, --help Help for "stackit beta kms wrapping-key list" + -h, --help Help for "stackit kms wrapping-key list" --keyring-id string ID of the KMS key ring where the key is stored ``` @@ -40,5 +40,5 @@ stackit beta kms wrapping-key list [flags] ### SEE ALSO -* [stackit beta kms wrapping-key](./stackit_beta_kms_wrapping-key.md) - Manage KMS wrapping keys +* [stackit kms wrapping-key](./stackit_kms_wrapping-key.md) - Manage KMS wrapping keys diff --git a/docs/stackit_network-area.md b/docs/stackit_network-area.md index 6f2d751f8..e99e52ad1 100644 --- a/docs/stackit_network-area.md +++ b/docs/stackit_network-area.md @@ -37,5 +37,6 @@ stackit network-area [flags] * [stackit network-area network-range](./stackit_network-area_network-range.md) - Provides functionality for network ranges in STACKIT Network Areas * [stackit network-area region](./stackit_network-area_region.md) - Provides functionality for regional configuration of STACKIT Network Area (SNA) * [stackit network-area route](./stackit_network-area_route.md) - Provides functionality for static routes in STACKIT Network Areas +* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes * [stackit network-area update](./stackit_network-area_update.md) - Updates a STACKIT Network Area (SNA) diff --git a/docs/stackit_network-area_routing-table.md b/docs/stackit_network-area_routing-table.md new file mode 100644 index 000000000..d1aefa50a --- /dev/null +++ b/docs/stackit_network-area_routing-table.md @@ -0,0 +1,42 @@ +## stackit network-area routing-table + +Manage routing-tables and its according routes + +### Synopsis + +Manage routing-tables and their associated routes. + +This API is currently available only to selected customers. +To request access, please contact your account manager or submit a support ticket. + +``` +stackit network-area routing-table [flags] +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area](./stackit_network-area.md) - Provides functionality for STACKIT Network Area (SNA) +* [stackit network-area routing-table create](./stackit_network-area_routing-table_create.md) - Creates a routing-table +* [stackit network-area routing-table delete](./stackit_network-area_routing-table_delete.md) - Deletes a routing-table +* [stackit network-area routing-table describe](./stackit_network-area_routing-table_describe.md) - Describes a routing-table +* [stackit network-area routing-table list](./stackit_network-area_routing-table_list.md) - Lists all routing-tables +* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table +* [stackit network-area routing-table update](./stackit_network-area_routing-table_update.md) - Updates a routing-table + diff --git a/docs/stackit_network-area_routing-table_create.md b/docs/stackit_network-area_routing-table_create.md new file mode 100644 index 000000000..b926dcc7d --- /dev/null +++ b/docs/stackit_network-area_routing-table_create.md @@ -0,0 +1,56 @@ +## stackit network-area routing-table create + +Creates a routing-table + +### Synopsis + +Creates a routing-table. + +``` +stackit network-area routing-table create [flags] +``` + +### Examples + +``` + Create a routing-table with name "rt" + $ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt" + + Create a routing-table with name "rt" and description "some description" + $ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt" --description "some description" + + Create a routing-table with name "rt" with system routes disabled + $ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt" --system-routes=false + + Create a routing-table with name "rt" with dynamic routes disabled + $ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt" --dynamic-routes=false +``` + +### Options + +``` + --description string Description of the routing-table + --dynamic-routes If set to false, prevents dynamic routes from propagating to the routing table. (default true) + -h, --help Help for "stackit network-area routing-table create" + --labels stringToString Key=value labels (default []) + --name string Name of the routing-table + --network-area-id string Network-Area ID + --organization-id string Organization ID + --system-routes If set to false, disables routes for project-to-project communication. (default true) +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes + diff --git a/docs/stackit_network-area_routing-table_delete.md b/docs/stackit_network-area_routing-table_delete.md new file mode 100644 index 000000000..59a38395b --- /dev/null +++ b/docs/stackit_network-area_routing-table_delete.md @@ -0,0 +1,42 @@ +## stackit network-area routing-table delete + +Deletes a routing-table + +### Synopsis + +Deletes a routing-table + +``` +stackit network-area routing-table delete ROUTING_TABLE_ID [flags] +``` + +### Examples + +``` + Delete a routing-table with ID "xxx" + $ stackit network-area routing-table delete xxx --organization-id yyy --network-area-id zzz +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table delete" + --network-area-id string Network-Area ID + --organization-id string Organization ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes + diff --git a/docs/stackit_network-area_routing-table_describe.md b/docs/stackit_network-area_routing-table_describe.md new file mode 100644 index 000000000..b608ce830 --- /dev/null +++ b/docs/stackit_network-area_routing-table_describe.md @@ -0,0 +1,42 @@ +## stackit network-area routing-table describe + +Describes a routing-table + +### Synopsis + +Describes a routing-table + +``` +stackit network-area routing-table describe ROUTING_TABLE_ID [flags] +``` + +### Examples + +``` + Describe a routing-table + $ stackit network-area routing-table describe xxx --organization-id xxx --network-area-id yyy +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table describe" + --network-area-id string Network-Area ID + --organization-id string Organization ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes + diff --git a/docs/stackit_network-area_routing-table_list.md b/docs/stackit_network-area_routing-table_list.md new file mode 100644 index 000000000..5b7a277d4 --- /dev/null +++ b/docs/stackit_network-area_routing-table_list.md @@ -0,0 +1,50 @@ +## stackit network-area routing-table list + +Lists all routing-tables + +### Synopsis + +Lists all routing-tables + +``` +stackit network-area routing-table list [flags] +``` + +### Examples + +``` + List all routing-tables + $ stackit network-area routing-table list --organization-id xxx --network-area-id yyy + + List all routing-tables with labels + $ stackit network-area routing-table list --label-selector env=dev,env=rc --organization-id xxx --network-area-id yyy + + List all routing-tables with labels and set limit to 10 + $ stackit network-area routing-table list --label-selector env=dev,env=rc --limit 10 --organization-id xxx --network-area-id yyy +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table list" + --label-selector string Filter by label + --limit int Maximum number of entries to list + --network-area-id string Network-Area ID + --organization-id string Organization ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes + diff --git a/docs/stackit_network-area_routing-table_route.md b/docs/stackit_network-area_routing-table_route.md new file mode 100644 index 000000000..f23144acf --- /dev/null +++ b/docs/stackit_network-area_routing-table_route.md @@ -0,0 +1,38 @@ +## stackit network-area routing-table route + +Manages routes of a routing-table + +### Synopsis + +Manages routes of a routing-table + +``` +stackit network-area routing-table route [flags] +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table route" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes +* [stackit network-area routing-table route create](./stackit_network-area_routing-table_route_create.md) - Creates a route in a routing-table +* [stackit network-area routing-table route delete](./stackit_network-area_routing-table_route_delete.md) - Deletes a route within a routing-table +* [stackit network-area routing-table route describe](./stackit_network-area_routing-table_route_describe.md) - Describes a route within a routing-table +* [stackit network-area routing-table route list](./stackit_network-area_routing-table_route_list.md) - Lists all routes within a routing-table +* [stackit network-area routing-table route update](./stackit_network-area_routing-table_route_update.md) - Updates a route in a routing-table + diff --git a/docs/stackit_network-area_routing-table_route_create.md b/docs/stackit_network-area_routing-table_route_create.md new file mode 100644 index 000000000..eb3c3ad52 --- /dev/null +++ b/docs/stackit_network-area_routing-table_route_create.md @@ -0,0 +1,54 @@ +## stackit network-area routing-table route create + +Creates a route in a routing-table + +### Synopsis + +Creates a route in a routing-table. + +``` +stackit network-area routing-table route create [flags] +``` + +### Examples + +``` + Create a route with CIDRv4 destination and IPv4 nexthop + $ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv4 --destination-value --nexthop-type ipv4 --nexthop-value + + Create a route with CIDRv6 destination and IPv6 nexthop + $ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value --nexthop-type ipv6 --nexthop-value + + Create a route with CIDRv6 destination and Nexthop Internet + $ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value --nexthop-type internet +``` + +### Options + +``` + --destination-type string Destination type + --destination-value string Destination value + -h, --help Help for "stackit network-area routing-table route create" + --labels stringToString Key=value labels (default []) + --network-area-id string Network-Area ID + --nexthop-type string Next hop type + --nexthop-value string NextHop value + --organization-id string Organization ID + --routing-table-id string Routing-Table ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table + diff --git a/docs/stackit_network-area_routing-table_route_delete.md b/docs/stackit_network-area_routing-table_route_delete.md new file mode 100644 index 000000000..5f15e61d0 --- /dev/null +++ b/docs/stackit_network-area_routing-table_route_delete.md @@ -0,0 +1,43 @@ +## stackit network-area routing-table route delete + +Deletes a route within a routing-table + +### Synopsis + +Deletes a route within a routing-table + +``` +stackit network-area routing-table route delete routing-table-id [flags] +``` + +### Examples + +``` + Deletes a route within a routing-table + $ stackit network-area routing-table route delete xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table route delete" + --network-area-id string Network-Area ID + --organization-id string Organization ID + --routing-table-id string Routing-Table ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table + diff --git a/docs/stackit_network-area_routing-table_route_describe.md b/docs/stackit_network-area_routing-table_route_describe.md new file mode 100644 index 000000000..9b50da817 --- /dev/null +++ b/docs/stackit_network-area_routing-table_route_describe.md @@ -0,0 +1,43 @@ +## stackit network-area routing-table route describe + +Describes a route within a routing-table + +### Synopsis + +Describes a route within a routing-table + +``` +stackit network-area routing-table route describe ROUTE_ID [flags] +``` + +### Examples + +``` + Describe a route within a routing-table + $ stackit network-area routing-table route describe xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table route describe" + --network-area-id string Network-Area ID + --organization-id string Organization ID + --routing-table-id string Routing-Table ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table + diff --git a/docs/stackit_network-area_routing-table_route_list.md b/docs/stackit_network-area_routing-table_route_list.md new file mode 100644 index 000000000..901b8d593 --- /dev/null +++ b/docs/stackit_network-area_routing-table_route_list.md @@ -0,0 +1,51 @@ +## stackit network-area routing-table route list + +Lists all routes within a routing-table + +### Synopsis + +Lists all routes within a routing-table + +``` +stackit network-area routing-table route list [flags] +``` + +### Examples + +``` + List all routes within a routing-table + $ stackit network-area routing-table route list --routing-table-id xxx --organization-id yyy --network-area-id zzz + + List all routes within a routing-table with labels + $ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc + + List all routes within a routing-tables with labels and limit to 10 + $ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table route list" + --label-selector string Filter by label + --limit int Maximum number of entries to list + --network-area-id string Network-Area ID + --organization-id string Organization ID + --routing-table-id string Routing-Table ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table + diff --git a/docs/stackit_network-area_routing-table_route_update.md b/docs/stackit_network-area_routing-table_route_update.md new file mode 100644 index 000000000..22bdb11ad --- /dev/null +++ b/docs/stackit_network-area_routing-table_route_update.md @@ -0,0 +1,44 @@ +## stackit network-area routing-table route update + +Updates a route in a routing-table + +### Synopsis + +Updates a route in a routing-table. + +``` +stackit network-area routing-table route update ROUTE_ID [flags] +``` + +### Examples + +``` + Updates the label(s) of a route with ID "xxx" in a routing-table ID "xxx" in organization with ID "yyy" and network-area with ID "zzz" + $ stackit network-area routing-table route update xxx --labels key=value,foo=bar --routing-table-id xxx --organization-id yyy --network-area-id zzz +``` + +### Options + +``` + -h, --help Help for "stackit network-area routing-table route update" + --labels stringToString Labels are key-value string pairs which can be attached to a route. A label can be provided with the format key=value and the flag can be used multiple times to provide a list of labels (default []) + --network-area-id string Network-Area ID + --organization-id string Organization ID + --routing-table-id string Routing-Table ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table + diff --git a/docs/stackit_network-area_routing-table_update.md b/docs/stackit_network-area_routing-table_update.md new file mode 100644 index 000000000..917565e38 --- /dev/null +++ b/docs/stackit_network-area_routing-table_update.md @@ -0,0 +1,59 @@ +## stackit network-area routing-table update + +Updates a routing-table + +### Synopsis + +Updates a routing-table. + +``` +stackit network-area routing-table update ROUTING_TABLE_ID [flags] +``` + +### Examples + +``` + Updates the label(s) of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz" + $ stackit network-area routing-table update xxx --labels key=value,foo=bar --organization-id yyy --network-area-id zzz + + Updates the name of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz" + $ stackit network-area routing-table update xxx --name foo --organization-id yyy --network-area-id zzz + + Updates the description of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz" + $ stackit network-area routing-table update xxx --description foo --organization-id yyy --network-area-id zzz + + Disables the dynamic routes of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz" + $ stackit network-area routing-table update xxx --organization-id yyy --network-area-id zzz --dynamic-routes=false + + Disables the system routes of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz" + $ stackit network-area routing-table update xxx --organization-id yyy --network-area-id zzz --system-routes=false +``` + +### Options + +``` + --description string Description of the routing-table + --dynamic-routes If set to false, prevents dynamic routes from propagating to the routing table. + -h, --help Help for "stackit network-area routing-table update" + --labels stringToString Key=value labels (default []) + --name string Name of the routing-table + --network-area-id string Network-Area ID + --organization-id string Organization ID + --system-routes If set to false, disables routes for project-to-project communication. +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes + diff --git a/docs/stackit_network_create.md b/docs/stackit_network_create.md index 146264977..44934bee3 100644 --- a/docs/stackit_network_create.md +++ b/docs/stackit_network_create.md @@ -30,6 +30,9 @@ stackit network create [flags] Create an IPv6 network with name "network-1" with DNS name servers, a prefix and a gateway $ stackit network create --name network-1 --ipv6-dns-name-servers "2001:4860:4860::8888,2001:4860:4860::8844" --ipv6-prefix "2001:4860:4860::8888" --ipv6-gateway "2001:4860:4860::8888" + + Create a network with name "network-1" and attach routing-table "xxx" + $ stackit network create --name network-1 --routing-table-id xxx ``` ### Options @@ -49,6 +52,7 @@ stackit network create [flags] --no-ipv4-gateway If set to true, the network doesn't have an IPv4 gateway --no-ipv6-gateway If set to true, the network doesn't have an IPv6 gateway --non-routed If set to true, the network is not routed and therefore not accessible from other networks + --routing-table-id string The ID of the routing-table for the network ``` ### Options inherited from parent commands diff --git a/docs/stackit_network_update.md b/docs/stackit_network_update.md index 313ce68fa..7069b26d2 100644 --- a/docs/stackit_network_update.md +++ b/docs/stackit_network_update.md @@ -24,6 +24,9 @@ stackit network update NETWORK_ID [flags] Update IPv6 network with ID "xxx" with new name "network-1-new", new gateway and new DNS name servers $ stackit network update xxx --name network-1-new --ipv6-dns-name-servers "2001:4860:4860::8888" --ipv6-gateway "2001:4860:4860::8888" + + Update network with ID "xxx" with new routing-table id "xxx" + $ stackit network update xxx --routing-table-id xxx ``` ### Options @@ -38,6 +41,7 @@ stackit network update NETWORK_ID [flags] -n, --name string Network name --no-ipv4-gateway If set to true, the network doesn't have an IPv4 gateway --no-ipv6-gateway If set to true, the network doesn't have an IPv6 gateway + --routing-table-id string The ID of the routing-table for the network ``` ### Options inherited from parent commands diff --git a/docs/stackit_object-storage.md b/docs/stackit_object-storage.md index 5caa02380..bae7c2496 100644 --- a/docs/stackit_object-storage.md +++ b/docs/stackit_object-storage.md @@ -31,6 +31,7 @@ stackit object-storage [flags] * [stackit](./stackit.md) - Manage STACKIT resources using the command line * [stackit object-storage bucket](./stackit_object-storage_bucket.md) - Provides functionality for Object Storage buckets +* [stackit object-storage compliance-lock](./stackit_object-storage_compliance-lock.md) - Provides functionality to manage Object Storage compliance lock * [stackit object-storage credentials](./stackit_object-storage_credentials.md) - Provides functionality for Object Storage credentials * [stackit object-storage credentials-group](./stackit_object-storage_credentials-group.md) - Provides functionality for Object Storage credentials group * [stackit object-storage disable](./stackit_object-storage_disable.md) - Disables Object Storage for a project diff --git a/docs/stackit_object-storage_bucket_create.md b/docs/stackit_object-storage_bucket_create.md index 1c07eb540..58f14a21a 100644 --- a/docs/stackit_object-storage_bucket_create.md +++ b/docs/stackit_object-storage_bucket_create.md @@ -15,12 +15,16 @@ stackit object-storage bucket create BUCKET_NAME [flags] ``` Create an Object Storage bucket with name "my-bucket" $ stackit object-storage bucket create my-bucket + + Create an Object Storage bucket with enabled object-lock + $ stackit object-storage bucket create my-bucket --object-lock-enabled ``` ### Options ``` - -h, --help Help for "stackit object-storage bucket create" + -h, --help Help for "stackit object-storage bucket create" + --object-lock-enabled is the object-lock enabled for the bucket ``` ### Options inherited from parent commands diff --git a/docs/stackit_object-storage_compliance-lock.md b/docs/stackit_object-storage_compliance-lock.md new file mode 100644 index 000000000..435807d29 --- /dev/null +++ b/docs/stackit_object-storage_compliance-lock.md @@ -0,0 +1,36 @@ +## stackit object-storage compliance-lock + +Provides functionality to manage Object Storage compliance lock + +### Synopsis + +Provides functionality to manage Object Storage compliance lock. + +``` +stackit object-storage compliance-lock [flags] +``` + +### Options + +``` + -h, --help Help for "stackit object-storage compliance-lock" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit object-storage](./stackit_object-storage.md) - Provides functionality for Object Storage +* [stackit object-storage compliance-lock describe](./stackit_object-storage_compliance-lock_describe.md) - Describe object storage compliance lock +* [stackit object-storage compliance-lock lock](./stackit_object-storage_compliance-lock_lock.md) - Create object storage compliance lock +* [stackit object-storage compliance-lock unlock](./stackit_object-storage_compliance-lock_unlock.md) - Delete object storage compliance lock + diff --git a/docs/stackit_object-storage_compliance-lock_describe.md b/docs/stackit_object-storage_compliance-lock_describe.md new file mode 100644 index 000000000..393b034da --- /dev/null +++ b/docs/stackit_object-storage_compliance-lock_describe.md @@ -0,0 +1,40 @@ +## stackit object-storage compliance-lock describe + +Describe object storage compliance lock + +### Synopsis + +Describe object storage compliance lock. + +``` +stackit object-storage compliance-lock describe [flags] +``` + +### Examples + +``` + Describe object storage compliance lock + $ stackit object-storage compliance-lock describe +``` + +### Options + +``` + -h, --help Help for "stackit object-storage compliance-lock describe" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit object-storage compliance-lock](./stackit_object-storage_compliance-lock.md) - Provides functionality to manage Object Storage compliance lock + diff --git a/docs/stackit_object-storage_compliance-lock_lock.md b/docs/stackit_object-storage_compliance-lock_lock.md new file mode 100644 index 000000000..98a55265d --- /dev/null +++ b/docs/stackit_object-storage_compliance-lock_lock.md @@ -0,0 +1,40 @@ +## stackit object-storage compliance-lock lock + +Create object storage compliance lock + +### Synopsis + +Create object storage compliance lock. + +``` +stackit object-storage compliance-lock lock [flags] +``` + +### Examples + +``` + Create object storage compliance lock + $ stackit object-storage compliance-lock lock +``` + +### Options + +``` + -h, --help Help for "stackit object-storage compliance-lock lock" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit object-storage compliance-lock](./stackit_object-storage_compliance-lock.md) - Provides functionality to manage Object Storage compliance lock + diff --git a/docs/stackit_object-storage_compliance-lock_unlock.md b/docs/stackit_object-storage_compliance-lock_unlock.md new file mode 100644 index 000000000..5666c3a40 --- /dev/null +++ b/docs/stackit_object-storage_compliance-lock_unlock.md @@ -0,0 +1,40 @@ +## stackit object-storage compliance-lock unlock + +Delete object storage compliance lock + +### Synopsis + +Delete object storage compliance lock. + +``` +stackit object-storage compliance-lock unlock [flags] +``` + +### Examples + +``` + Delete object storage compliance lock + $ stackit object-storage compliance-lock unlock +``` + +### Options + +``` + -h, --help Help for "stackit object-storage compliance-lock unlock" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit object-storage compliance-lock](./stackit_object-storage_compliance-lock.md) - Provides functionality to manage Object Storage compliance lock + diff --git a/docs/stackit_organization.md b/docs/stackit_organization.md index f1bbaedde..ad124b975 100644 --- a/docs/stackit_organization.md +++ b/docs/stackit_organization.md @@ -31,6 +31,8 @@ stackit organization [flags] ### SEE ALSO * [stackit](./stackit.md) - Manage STACKIT resources using the command line +* [stackit organization describe](./stackit_organization_describe.md) - Show an organization +* [stackit organization list](./stackit_organization_list.md) - Lists all organizations * [stackit organization member](./stackit_organization_member.md) - Manages organization members * [stackit organization role](./stackit_organization_role.md) - Manages organization roles diff --git a/docs/stackit_organization_describe.md b/docs/stackit_organization_describe.md new file mode 100644 index 000000000..795cf38e5 --- /dev/null +++ b/docs/stackit_organization_describe.md @@ -0,0 +1,43 @@ +## stackit organization describe + +Show an organization + +### Synopsis + +Show an organization. + +``` +stackit organization describe [flags] +``` + +### Examples + +``` + Describe the organization with the organization uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + $ stackit organization describe xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + + Describe the organization with the container id "foo-bar-organization" + $ stackit organization describe foo-bar-organization +``` + +### Options + +``` + -h, --help Help for "stackit organization describe" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit organization](./stackit_organization.md) - Manages organizations + diff --git a/docs/stackit_organization_list.md b/docs/stackit_organization_list.md new file mode 100644 index 000000000..a258807ba --- /dev/null +++ b/docs/stackit_organization_list.md @@ -0,0 +1,44 @@ +## stackit organization list + +Lists all organizations + +### Synopsis + +Lists all organizations. + +``` +stackit organization list [flags] +``` + +### Examples + +``` + Lists organizations for your user + $ stackit organization list + + Lists the first 10 organizations + $ stackit organization list --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit organization list" + --limit int Maximum number of entries to list (default 50) +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit organization](./stackit_organization.md) - Manages organizations + diff --git a/docs/stackit_secrets-manager_instance_create.md b/docs/stackit_secrets-manager_instance_create.md index 379de7785..65108008a 100644 --- a/docs/stackit_secrets-manager_instance_create.md +++ b/docs/stackit_secrets-manager_instance_create.md @@ -18,14 +18,21 @@ stackit secrets-manager instance create [flags] Create a Secrets Manager instance with name "my-instance" and specify IP range which is allowed to access it $ stackit secrets-manager instance create --name my-instance --acl 1.2.3.0/24 + + Create a Secrets Manager instance with name "my-instance" and configure KMS key options + $ stackit secrets-manager instance create --name my-instance --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud ``` ### Options ``` - --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) - -h, --help Help for "stackit secrets-manager instance create" - -n, --name string Instance name + --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) + -h, --help Help for "stackit secrets-manager instance create" + --kms-key-id string ID of the KMS key to use for encryption + --kms-key-version int Version of the KMS key + --kms-keyring-id string ID of the KMS key ring + --kms-service-account-email string Service account email for KMS access + -n, --name string Instance name ``` ### Options inherited from parent commands diff --git a/docs/stackit_secrets-manager_instance_update.md b/docs/stackit_secrets-manager_instance_update.md index cf40d3c1a..c000c7cad 100644 --- a/docs/stackit_secrets-manager_instance_update.md +++ b/docs/stackit_secrets-manager_instance_update.md @@ -13,15 +13,29 @@ stackit secrets-manager instance update INSTANCE_ID [flags] ### Examples ``` + Update the name of a Secrets Manager instance with ID "xxx" + $ stackit secrets-manager instance update xxx --name my-new-name + Update the range of IPs allowed to access a Secrets Manager instance with ID "xxx" $ stackit secrets-manager instance update xxx --acl 1.2.3.0/24 + + Update the name and ACLs of a Secrets Manager instance with ID "xxx" + $ stackit secrets-manager instance update xxx --name my-new-name --acl 1.2.3.0/24 + + Update the KMS key settings of a Secrets Manager instance with ID "xxx" + $ stackit secrets-manager instance update xxx --name my-instance --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud ``` ### Options ``` - --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) - -h, --help Help for "stackit secrets-manager instance update" + --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) + -h, --help Help for "stackit secrets-manager instance update" + --kms-key-id string ID of the KMS key to use for encryption + --kms-key-version int Version of the KMS key + --kms-keyring-id string ID of the KMS key ring + --kms-service-account-email string Service account email for KMS access + -n, --name string Instance name ``` ### Options inherited from parent commands diff --git a/docs/stackit_server.md b/docs/stackit_server.md index 83bf55541..267a8df3b 100644 --- a/docs/stackit_server.md +++ b/docs/stackit_server.md @@ -46,6 +46,7 @@ stackit server [flags] * [stackit server reboot](./stackit_server_reboot.md) - Reboots a server * [stackit server rescue](./stackit_server_rescue.md) - Rescues an existing server * [stackit server resize](./stackit_server_resize.md) - Resizes the server to the given machine type +* [stackit server security-group](./stackit_server_security-group.md) - Allows attaching/detaching security groups to servers * [stackit server service-account](./stackit_server_service-account.md) - Allows attaching/detaching service accounts to servers * [stackit server start](./stackit_server_start.md) - Starts an existing server or allocates the server if deallocated * [stackit server stop](./stackit_server_stop.md) - Stops an existing server diff --git a/docs/stackit_server_machine-type_list.md b/docs/stackit_server_machine-type_list.md index fd2ed7afe..127563908 100644 --- a/docs/stackit_server_machine-type_list.md +++ b/docs/stackit_server_machine-type_list.md @@ -21,13 +21,20 @@ stackit server machine-type list [flags] List the first 10 machine types $ stackit server machine-type list --limit=10 + + List machine types with exactly 2 vCPUs + $ stackit server machine-type list --filter="vcpus==2" + + List machine types with at least 2 vCPUs and 2048 MB RAM + $ stackit server machine-type list --filter="vcpus >= 2 && ram >= 2048" ``` ### Options ``` - -h, --help Help for "stackit server machine-type list" - --limit int Limit the output to the first n elements + --filter string Filter resources by fields. A subset of expr-lang is supported. See https://expr-lang.org/docs/language-definition for usage details + -h, --help Help for "stackit server machine-type list" + --limit int Limit the output to the first n elements ``` ### Options inherited from parent commands diff --git a/docs/stackit_server_security-group.md b/docs/stackit_server_security-group.md new file mode 100644 index 000000000..b44ce57e4 --- /dev/null +++ b/docs/stackit_server_security-group.md @@ -0,0 +1,35 @@ +## stackit server security-group + +Allows attaching/detaching security groups to servers + +### Synopsis + +Allows attaching/detaching security groups to servers. + +``` +stackit server security-group [flags] +``` + +### Options + +``` + -h, --help Help for "stackit server security-group" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit server](./stackit_server.md) - Provides functionality for servers +* [stackit server security-group attach](./stackit_server_security-group_attach.md) - Attaches a security group to a server +* [stackit server security-group detach](./stackit_server_security-group_detach.md) - Detaches a security group from a server + diff --git a/docs/stackit_server_security-group_attach.md b/docs/stackit_server_security-group_attach.md new file mode 100644 index 000000000..c42466381 --- /dev/null +++ b/docs/stackit_server_security-group_attach.md @@ -0,0 +1,42 @@ +## stackit server security-group attach + +Attaches a security group to a server + +### Synopsis + +Attaches a security group to a server. + +``` +stackit server security-group attach [flags] +``` + +### Examples + +``` + Attach a security group with ID "xxx" to a server with ID "yyy" + $ stackit server security-group attach --server-id yyy --security-group-id xxx +``` + +### Options + +``` + -h, --help Help for "stackit server security-group attach" + --security-group-id string Security Group ID + --server-id string Server ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit server security-group](./stackit_server_security-group.md) - Allows attaching/detaching security groups to servers + diff --git a/docs/stackit_server_security-group_detach.md b/docs/stackit_server_security-group_detach.md new file mode 100644 index 000000000..493eee69f --- /dev/null +++ b/docs/stackit_server_security-group_detach.md @@ -0,0 +1,42 @@ +## stackit server security-group detach + +Detaches a security group from a server + +### Synopsis + +Detaches a security group from a server. + +``` +stackit server security-group detach [flags] +``` + +### Examples + +``` + Detach a security group with ID "xxx" from a server with ID "yyy" + $ stackit server security-group detach --server-id yyy --security-group-id xxx +``` + +### Options + +``` + -h, --help Help for "stackit server security-group detach" + --security-group-id string Security Group ID + --server-id string Server ID +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit server security-group](./stackit_server_security-group.md) - Allows attaching/detaching security groups to servers + diff --git a/docs/stackit_ske_kubeconfig_create.md b/docs/stackit_ske_kubeconfig_create.md index 476d50bab..5717a31f8 100644 --- a/docs/stackit_ske_kubeconfig_create.md +++ b/docs/stackit_ske_kubeconfig_create.md @@ -4,9 +4,9 @@ Creates or update a kubeconfig for a SKE cluster ### Synopsis -Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exists in the kubeconfig file the information will be updated. +Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster. By default an admin kubeconfig is created. Use the `--idp` option to create an IDP kubeconfig that authenticates via the STACKIT IDP. -By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created. +If the config exists in the kubeconfig file the information will be updated. By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created. You can override this behavior by specifying a custom filepath using the --filepath flag or by setting the KUBECONFIG env variable (fallback). An expiration time can be set for the kubeconfig. The expiration time is set in seconds(s), minutes(m), hours(h), days(d) or months(M). Default is 1h. @@ -20,25 +20,28 @@ stackit ske kubeconfig create CLUSTER_NAME [flags] ### Examples ``` - Create or update a kubeconfig for the SKE cluster with name "my-cluster. If the config exits in the kubeconfig file the information will be updated." - $ stackit ske kubeconfig create my-cluster - - Get a login kubeconfig for the SKE cluster with name "my-cluster". This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command. + Get a short-lived admin kubeconfig for the SKE cluster with name "my-cluster". This kubeconfig does not contain any credentials and instead obtains valid admin credentials via the `stackit ske kubeconfig login` command. $ stackit ske kubeconfig create my-cluster --login - Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days. If the config exits in the kubeconfig file the information will be updated. + Get an IDP kubeconfig for the SKE cluster with name "my-cluster". This kubeconfig does not grant permissions in the cluster by default and obtains credentials on-demand via the `stackit ske kubeconfig login` command. + $ stackit ske kubeconfig create my-cluster --idp + + Create or update a short-lived admin kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath. If the config exits in the kubeconfig file the information will be updated. + $ stackit ske kubeconfig create my-cluster --login --filepath /path/to/config + + Create or update an admin kubeconfig for the SKE cluster with name "my-cluster". If the config exits in the kubeconfig file the information will be updated." + $ stackit ske kubeconfig create my-cluster + + Create an admin kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days. If the config exits in the kubeconfig file the information will be updated. $ stackit ske kubeconfig create my-cluster --expiration 30d - Create or update a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months. If the config exits in the kubeconfig file the information will be updated. + Create or update an admin kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months. If the config exits in the kubeconfig file the information will be updated. $ stackit ske kubeconfig create my-cluster --expiration 2M - Create or update a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath. If the config exits in the kubeconfig file the information will be updated. - $ stackit ske kubeconfig create my-cluster --filepath /path/to/config - - Get a kubeconfig for the SKE cluster with name "my-cluster" without writing it to a file and format the output as json + Get an admin kubeconfig for the SKE cluster with name "my-cluster" without writing it to a file and format the output as json $ stackit ske kubeconfig create my-cluster --disable-writing --output-format json - Create a kubeconfig for the SKE cluster with name "my-cluster. It will OVERWRITE your current kubeconfig file." + Create an admin kubeconfig for the SKE cluster with name "my-cluster". It will OVERWRITE your current kubeconfig file. $ stackit ske kubeconfig create my-cluster --overwrite true ``` @@ -49,7 +52,8 @@ stackit ske kubeconfig create CLUSTER_NAME [flags] -e, --expiration string Expiration time for the kubeconfig in seconds(s), minutes(m), hours(h), days(d) or months(M). Example: 30d. By default, expiration time is 1h --filepath string Path to create the kubeconfig file. Will fall back to KUBECONFIG env variable if not set. In case both aren't set, the kubeconfig is created as file named 'config' in the .kube folder in the user's home directory. -h, --help Help for "stackit ske kubeconfig create" - -l, --login Create a login kubeconfig that obtains valid credentials via the STACKIT CLI. This flag is mutually exclusive with the expiration flag. + --idp Create a non-admin kubeconfig that uses the STACKIT IDP to obtain credentials. + -l, --login Create a short-lived admin kubeconfig that obtains valid credentials via the STACKIT CLI. This flag is mutually exclusive with the expiration flag. --overwrite Overwrite the kubeconfig file. ``` diff --git a/docs/stackit_ske_kubeconfig_login.md b/docs/stackit_ske_kubeconfig_login.md index 0b5441533..2b9956717 100644 --- a/docs/stackit_ske_kubeconfig_login.md +++ b/docs/stackit_ske_kubeconfig_login.md @@ -5,8 +5,8 @@ Login plugin for kubernetes clients ### Synopsis Login plugin for kubernetes clients, that creates short-lived credentials to authenticate against a STACKIT Kubernetes Engine (SKE) cluster. -First you need to obtain a kubeconfig for use with the login command (first example). -Secondly you use the kubeconfig with your chosen Kubernetes client (second example), the client will automatically retrieve the credentials via the STACKIT CLI. +First you need to obtain a kubeconfig for use with the login command (first or second example). +Secondly you use the kubeconfig with your chosen Kubernetes client (third example), the client will automatically retrieve the credentials via the STACKIT CLI. ``` stackit ske kubeconfig login [flags] @@ -15,9 +15,12 @@ stackit ske kubeconfig login [flags] ### Examples ``` - Get a login kubeconfig for the SKE cluster with name "my-cluster". This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command. + Get an admin, login kubeconfig for the SKE cluster with name "my-cluster". This kubeconfig does not contain any credentials and instead obtains valid admin credentials via the `stackit ske kubeconfig login` command. $ stackit ske kubeconfig create my-cluster --login + Get an IDP kubeconfig for the SKE cluster with name "my-cluster". This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command. + $ stackit ske kubeconfig create my-cluster --idp + Use the previously saved kubeconfig to authenticate to the SKE cluster, in this case with kubectl. $ kubectl cluster-info $ kubectl get pods @@ -27,6 +30,7 @@ stackit ske kubeconfig login [flags] ``` -h, --help Help for "stackit ske kubeconfig login" + --idp Use the STACKIT IdP for authentication to the cluster. ``` ### Options inherited from parent commands diff --git a/docs/stackit_ske_options.md b/docs/stackit_ske_options.md index 76afbe93c..2590f989c 100644 --- a/docs/stackit_ske_options.md +++ b/docs/stackit_ske_options.md @@ -4,6 +4,7 @@ Lists SKE provider options ### Synopsis +Command "options" is deprecated, use the subcommands instead. Lists STACKIT Kubernetes Engine (SKE) provider options (availability zones, Kubernetes versions, machine images and types, volume types). Pass one or more flags to filter what categories are shown. @@ -11,28 +12,10 @@ Pass one or more flags to filter what categories are shown. stackit ske options [flags] ``` -### Examples - -``` - List SKE options for all categories - $ stackit ske options - - List SKE options regarding Kubernetes versions only - $ stackit ske options --kubernetes-versions - - List SKE options regarding Kubernetes versions and machine images - $ stackit ske options --kubernetes-versions --machine-images -``` - ### Options ``` - --availability-zones Lists availability zones - -h, --help Help for "stackit ske options" - --kubernetes-versions Lists supported kubernetes versions - --machine-images Lists supported machine images - --machine-types Lists supported machine types - --volume-types Lists supported volume types + -h, --help Help for "stackit ske options" ``` ### Options inherited from parent commands @@ -49,4 +32,9 @@ stackit ske options [flags] ### SEE ALSO * [stackit ske](./stackit_ske.md) - Provides functionality for SKE +* [stackit ske options availability-zones](./stackit_ske_options_availability-zones.md) - Lists SKE provider options for availability-zones +* [stackit ske options kubernetes-versions](./stackit_ske_options_kubernetes-versions.md) - Lists SKE provider options for kubernetes-versions +* [stackit ske options machine-images](./stackit_ske_options_machine-images.md) - Lists SKE provider options for machine-images +* [stackit ske options machine-types](./stackit_ske_options_machine-types.md) - Lists SKE provider options for machine-types +* [stackit ske options volume-types](./stackit_ske_options_volume-types.md) - Lists SKE provider options for volume-types diff --git a/docs/stackit_ske_options_availability-zones.md b/docs/stackit_ske_options_availability-zones.md new file mode 100644 index 000000000..4bf77c67f --- /dev/null +++ b/docs/stackit_ske_options_availability-zones.md @@ -0,0 +1,40 @@ +## stackit ske options availability-zones + +Lists SKE provider options for availability-zones + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones. + +``` +stackit ske options availability-zones [flags] +``` + +### Examples + +``` + List SKE options for availability-zones + $ stackit ske options availability-zones +``` + +### Options + +``` + -h, --help Help for "stackit ske options availability-zones" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_kubernetes-versions.md b/docs/stackit_ske_options_kubernetes-versions.md new file mode 100644 index 000000000..a2dd50edd --- /dev/null +++ b/docs/stackit_ske_options_kubernetes-versions.md @@ -0,0 +1,44 @@ +## stackit ske options kubernetes-versions + +Lists SKE provider options for kubernetes-versions + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions. + +``` +stackit ske options kubernetes-versions [flags] +``` + +### Examples + +``` + List SKE options for kubernetes-versions + $ stackit ske options kubernetes-versions + + List SKE options for supported kubernetes-versions + $ stackit ske options kubernetes-versions --supported +``` + +### Options + +``` + -h, --help Help for "stackit ske options kubernetes-versions" + --supported List supported versions only +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_machine-images.md b/docs/stackit_ske_options_machine-images.md new file mode 100644 index 000000000..f6deb67db --- /dev/null +++ b/docs/stackit_ske_options_machine-images.md @@ -0,0 +1,40 @@ +## stackit ske options machine-images + +Lists SKE provider options for machine-images + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images. + +``` +stackit ske options machine-images [flags] +``` + +### Examples + +``` + List SKE options for machine-images + $ stackit ske options machine-images +``` + +### Options + +``` + -h, --help Help for "stackit ske options machine-images" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_machine-types.md b/docs/stackit_ske_options_machine-types.md new file mode 100644 index 000000000..333384fc3 --- /dev/null +++ b/docs/stackit_ske_options_machine-types.md @@ -0,0 +1,40 @@ +## stackit ske options machine-types + +Lists SKE provider options for machine-types + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types. + +``` +stackit ske options machine-types [flags] +``` + +### Examples + +``` + List SKE options for machine-types + $ stackit ske options machine-types +``` + +### Options + +``` + -h, --help Help for "stackit ske options machine-types" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_volume-types.md b/docs/stackit_ske_options_volume-types.md new file mode 100644 index 000000000..aeea921dc --- /dev/null +++ b/docs/stackit_ske_options_volume-types.md @@ -0,0 +1,40 @@ +## stackit ske options volume-types + +Lists SKE provider options for volume-types + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types. + +``` +stackit ske options volume-types [flags] +``` + +### Examples + +``` + List SKE options for volume-types + $ stackit ske options volume-types +``` + +### Options + +``` + -h, --help Help for "stackit ske options volume-types" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/go.mod b/go.mod index e04900652..3c1c07cf6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/stackitcloud/stackit-cli -go 1.24.0 +go 1.25.0 require ( github.com/fatih/color v1.18.0 @@ -10,40 +10,40 @@ require ( github.com/google/uuid v1.6.0 github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/jedib0t/go-pretty/v6 v6.7.8 - github.com/lmittmann/tint v1.1.2 + github.com/lmittmann/tint v1.1.3 github.com/mattn/go-colorable v0.1.14 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 - github.com/stackitcloud/stackit-sdk-go/core v0.21.1 - github.com/stackitcloud/stackit-sdk-go/services/alb v0.9.3 - github.com/stackitcloud/stackit-sdk-go/services/authorization v0.11.3 - github.com/stackitcloud/stackit-sdk-go/services/cdn v1.9.4 + github.com/stackitcloud/stackit-sdk-go/core v0.23.0 + github.com/stackitcloud/stackit-sdk-go/services/alb v0.10.0 + github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0 + github.com/stackitcloud/stackit-sdk-go/services/cdn v1.10.0 github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.6 github.com/stackitcloud/stackit-sdk-go/services/edge v0.4.3 github.com/stackitcloud/stackit-sdk-go/services/git v0.10.3 - github.com/stackitcloud/stackit-sdk-go/services/iaas v1.3.0 + github.com/stackitcloud/stackit-sdk-go/services/iaas v1.3.5 github.com/stackitcloud/stackit-sdk-go/services/intake v0.4.4 github.com/stackitcloud/stackit-sdk-go/services/logs v0.5.2 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.8 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.6 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.3.5 - github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.18.2 + github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.18.5 github.com/stackitcloud/stackit-sdk-go/services/runcommand v1.4.3 github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.14.3 github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.8 github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.6 - github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.11.6 + github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.12.0 github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.7 - github.com/stackitcloud/stackit-sdk-go/services/ske v1.6.3 + github.com/stackitcloud/stackit-sdk-go/services/ske v1.11.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.4.3 github.com/zalando/go-keyring v0.2.6 - golang.org/x/mod v0.32.0 - golang.org/x/oauth2 v0.34.0 - golang.org/x/term v0.39.0 - golang.org/x/text v0.33.0 - k8s.io/apimachinery v0.34.2 - k8s.io/client-go v0.34.2 + golang.org/x/mod v0.33.0 + golang.org/x/oauth2 v0.35.0 + golang.org/x/term v0.40.0 + golang.org/x/text v0.34.0 + k8s.io/apimachinery v0.35.2 + k8s.io/client-go v0.35.1 ) require ( @@ -231,7 +231,7 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/sync v0.19.0 // indirect @@ -239,6 +239,7 @@ require ( golang.org/x/tools v0.41.0 // indirect google.golang.org/protobuf v1.36.8 // indirect honnef.co/go/tools v0.6.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect mvdan.cc/gofumpt v0.9.2 // indirect mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect sigs.k8s.io/randfill v1.0.0 // indirect @@ -252,7 +253,6 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect @@ -265,22 +265,22 @@ require ( github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/stackitcloud/stackit-sdk-go/services/kms v1.2.0 - github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.7.3 + github.com/stackitcloud/stackit-sdk-go/services/kms v1.3.2 + github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.8.0 github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.6 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.6 - github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.4.5 - github.com/stackitcloud/stackit-sdk-go/services/observability v0.16.3 - github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.6 - github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.3 - github.com/stackitcloud/stackit-sdk-go/services/sfs v0.3.0 + github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.7.0 + github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0 + github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.26.0 + github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.6 + github.com/stackitcloud/stackit-sdk-go/services/sfs v0.4.0 github.com/subosito/gotenv v1.6.0 // indirect - golang.org/x/sys v0.40.0 // indirect + golang.org/x/sys v0.41.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect + k8s.io/api v0.35.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 230c982dc..23665a67e 100644 --- a/go.sum +++ b/go.sum @@ -250,8 +250,6 @@ github.com/godoc-lint/godoc-lint v0.11.1/go.mod h1:BAqayheFSuZrEAqCRxgw9MyvsM+S/ github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -397,7 +395,6 @@ github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -433,8 +430,8 @@ github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= -github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I= +github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= @@ -504,7 +501,6 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -600,28 +596,28 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stackitcloud/stackit-sdk-go/core v0.21.1 h1:Y/PcAgM7DPYMNqum0MLv4n1mF9ieuevzcCIZYQfm3Ts= -github.com/stackitcloud/stackit-sdk-go/core v0.21.1/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI= -github.com/stackitcloud/stackit-sdk-go/services/alb v0.9.3 h1:X82TZfc6lg8ZoYdckiv5+OsV0d+81Q2TFMJh1TfxGWk= -github.com/stackitcloud/stackit-sdk-go/services/alb v0.9.3/go.mod h1:V6+MolxM/M2FWyWZA+FRFKEzzUe10MU9eEVfMvxHGi8= -github.com/stackitcloud/stackit-sdk-go/services/authorization v0.11.3 h1:JMKEeNQpA+Mb1DRpY3MRQL3pko5JjUWGrevN0xOrx+4= -github.com/stackitcloud/stackit-sdk-go/services/authorization v0.11.3/go.mod h1:uYI9pHAA2g84jJN25ejFUxa0/JtfpPZqMDkctQ1BzJk= -github.com/stackitcloud/stackit-sdk-go/services/cdn v1.9.4 h1:6xBogGvhlw/F74g2aK1iSRoOUdiP37pjRK9DTgNZF3o= -github.com/stackitcloud/stackit-sdk-go/services/cdn v1.9.4/go.mod h1:vvWMnaLTsgAj9bTUwrCkC9NN+U4NCXMCOAkU8ExlReE= +github.com/stackitcloud/stackit-sdk-go/core v0.23.0 h1:zPrOhf3Xe47rKRs1fg/AqKYUiJJRYjdcv+3qsS50mEs= +github.com/stackitcloud/stackit-sdk-go/core v0.23.0/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI= +github.com/stackitcloud/stackit-sdk-go/services/alb v0.10.0 h1:V9+885qkSv621rZZatg1YE5ENM1ElALxQDJsh+hDIUg= +github.com/stackitcloud/stackit-sdk-go/services/alb v0.10.0/go.mod h1:V6+MolxM/M2FWyWZA+FRFKEzzUe10MU9eEVfMvxHGi8= +github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0 h1:HxPgBu04j5tj6nfZ2r0l6v4VXC0/tYOGe4sA5Addra8= +github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0/go.mod h1:uYI9pHAA2g84jJN25ejFUxa0/JtfpPZqMDkctQ1BzJk= +github.com/stackitcloud/stackit-sdk-go/services/cdn v1.10.0 h1:YALzjYAApyQMKyt4C2LKhPRZHa6brmbFeKuuwl+KOTs= +github.com/stackitcloud/stackit-sdk-go/services/cdn v1.10.0/go.mod h1:915b/lJgDikYFEoRQ8wc8aCtPvUCceYk7gGm9nViJe0= github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.6 h1:GBRb49x5Nax/oQQaaf2F3kKwv8DQQOL0TQOC0C/v/Ew= github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.6/go.mod h1:IX9iL3MigDZUmzwswTJMfYvyi118KAHrFMfjJUy5NYk= github.com/stackitcloud/stackit-sdk-go/services/edge v0.4.3 h1:TxChb2qbO82JiQEBYClSSD5HZxqKeKJ6dIvkEUCJmbs= github.com/stackitcloud/stackit-sdk-go/services/edge v0.4.3/go.mod h1:KVWvQHb7CQLD9DzA4Np3WmakiCCsrHaCXvFEnOQ7nPk= github.com/stackitcloud/stackit-sdk-go/services/git v0.10.3 h1:VIjkSofZz9utOOkBdNZCIb07P/JdKc1kHV1P8Rq9dLc= github.com/stackitcloud/stackit-sdk-go/services/git v0.10.3/go.mod h1:EJk1Ss9GTel2NPIu/w3+x9XcQcEd2k3ibea5aQDzVhQ= -github.com/stackitcloud/stackit-sdk-go/services/iaas v1.3.0 h1:U/x0tc487X9msMS5yZYjrBAAKrCx87Trmt0kh8JiARA= -github.com/stackitcloud/stackit-sdk-go/services/iaas v1.3.0/go.mod h1:6+5+RCDfU7eQN3+/SGdOtx7Bq9dEa2FrHz/jflgY1M4= +github.com/stackitcloud/stackit-sdk-go/services/iaas v1.3.5 h1:W57+XRa8wTLsi5CV9Tqa7mGgt/PvlRM//RurXSmvII8= +github.com/stackitcloud/stackit-sdk-go/services/iaas v1.3.5/go.mod h1:lTWjW57eAq1bwfM6nsNinhoBr3MHFW/GaFasdAsYfDM= github.com/stackitcloud/stackit-sdk-go/services/intake v0.4.4 h1:cbXM7jUBCL7A5zxJKFWolRIDl45sdJMMMAzeumeIEOA= github.com/stackitcloud/stackit-sdk-go/services/intake v0.4.4/go.mod h1:z+7KKZf0uHXU/Kb4CRs/oaBrXRJ01LpiD0OH11MXLOk= -github.com/stackitcloud/stackit-sdk-go/services/kms v1.2.0 h1:Ar2n9GKmrTN80G/Ta1R+fL5aX5nEoxL6ODVJl3emzho= -github.com/stackitcloud/stackit-sdk-go/services/kms v1.2.0/go.mod h1:sHMFoYvVrkRZcH13DkLvp48nW+ssRVVVuwqJHDGpa5M= -github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.7.3 h1:d/qIj+XNaqByVbLvwpWoA0Ekv0yrONWyNswg4/jGX7Y= -github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.7.3/go.mod h1:ClPE4TOM1FeaJiwTXvApq4gWaSgTLq6nU3PPHAIQDN4= +github.com/stackitcloud/stackit-sdk-go/services/kms v1.3.2 h1:2ulSL2IkIAKND59eAjbEhVkOoBMyvm48ojwz1a3t0U0= +github.com/stackitcloud/stackit-sdk-go/services/kms v1.3.2/go.mod h1:cuIaMMiHeHQsbvy7BOFMutoV3QtN+ZBx7Tg3GmYUw7s= +github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.8.0 h1:DxrN85V738CRLynu6MULQHO+OXyYnkhVPgoZKULfFIs= +github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.8.0/go.mod h1:ClPE4TOM1FeaJiwTXvApq4gWaSgTLq6nU3PPHAIQDN4= github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.6 h1:4x30lC+YBmo7XpsAzTn0W+C/oP5flnLVgIh5u3O/P0o= github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.6/go.mod h1:ewaYUiZcBTSS6urE5zEJBPCqxu70w2IjnBHCvnKdFKE= github.com/stackitcloud/stackit-sdk-go/services/logs v0.5.2 h1:vr4atxFRT+EL+DqONMT5R44f7AzEMbePa9U7PEE0THU= @@ -630,20 +626,20 @@ github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.6 h1:Y/byRjX2u/OZl github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.6/go.mod h1:sY66ZgCgBc1mScPV95ek5WtUEGYizdP1RMsGaqbdbhw= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.8 h1:S7t4wcT6SN8ZzdoY8d6VbF903zFpGjzqrU0FN27rJPg= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.8/go.mod h1:CdrhFUsBO7/iJleCc2yQjDChIbG6YaxKNBQRNCjgcF4= -github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.4.5 h1:4gpvB6t7d2lLjInoTxcvjL9jCpBl5EDfYe5yUtR1MvA= -github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.4.5/go.mod h1:Bdcd59sRySyhfSUCy+5BNkp5w9PECmrywdvt+ORMKnI= -github.com/stackitcloud/stackit-sdk-go/services/observability v0.16.3 h1:J/R6iBALMiNpg+JDEx1adH50TM4UTF/KBYFuHENMKIs= -github.com/stackitcloud/stackit-sdk-go/services/observability v0.16.3/go.mod h1:9KdrXC5JS30Ay3mR0adb3vNdhca+qxiy/cPF5P4wehQ= +github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.7.0 h1:UxnbsKm6PQV8Gudw/EhySaEh9q1xSaTG8mzJz1EvhnE= +github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.7.0/go.mod h1:RFL4h6JZvpsyFYbdJ3+eINEkletzJQTfrPdd+yPT/fU= +github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0 h1:LGwCvvST0fwUgZ6bOxYIfu45qqTgv421ZS07UhKjZL8= +github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0/go.mod h1:9KdrXC5JS30Ay3mR0adb3vNdhca+qxiy/cPF5P4wehQ= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.6 h1:oTVx1+O177Ojn8OvXIOUbRSwtx7L59jhxDPrZEQFOfQ= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.6/go.mod h1:6ZBeCCY6qG8w1oK7osf61Egyv3mp7Ahv6GDGxiarDGo= github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.3.5 h1:H67e3KnHQx954yI8fuQmxXwRf/myqAdLg2KvxImp00g= github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.3.5/go.mod h1:xmAWk9eom8wznvLuLfm0F4xyeiBX8LaggXsKFmos+dw= -github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.6 h1:+TFfl1ON/uM5aO0FdkBNYKBa7vx0zrCYVtX6zvqQYBA= -github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.6/go.mod h1:hnhvlLX1Y71R8KIQqLBeoSZqkU5ZJOG0J4wz0LeUdaw= -github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.3 h1:AcJSIEu1QCzRughJLzVjRP5ICop0DkvV2TgFb9LS7/c= -github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.3/go.mod h1:DLXqpz1WhmOergfOLMJ4pybozz33ysOZNIO7fv9Wtfc= -github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.18.2 h1:VDIXOvRNmSYMeF0qQ2+w4/ez04YutVDz73hSMuuOJ54= -github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.18.2/go.mod h1:9zyEzPL4DnmU/SHq+SuMWTSO5BPxM1Z4g8Fp28n00ds= +github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.26.0 h1:/8lmviszgrB+0Cz7HdhFELyTiTeqIs7LfnI6sNX4rW8= +github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.26.0/go.mod h1:hnhvlLX1Y71R8KIQqLBeoSZqkU5ZJOG0J4wz0LeUdaw= +github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.6 h1:CXM9cZ9WeTyJd+Aw/hnJnDsKRVAQi4qgtd0RJ3zoPwo= +github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.6/go.mod h1:KJNceOHRefjku1oVBoHG7idCS/SeW42WJ+55bN3AxrQ= +github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.18.5 h1:MZ5aTO2NQ1Jecmi67ByGskve5nKXHl91fE+z+vFjxt4= +github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.18.5/go.mod h1:CJLmdqWvJm5/3+lXPDKu8k4WXs2UG8euGoqQX5xE79k= github.com/stackitcloud/stackit-sdk-go/services/runcommand v1.4.3 h1:AiGNJmpQ/f9cglaIQQ4SyePbtCI3K1DQLNvqVN9jKSo= github.com/stackitcloud/stackit-sdk-go/services/runcommand v1.4.3/go.mod h1:U/q0V89fvCF2O1ZJfi68/Chie9YY/5s7xBHI1Klq7wA= github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.14.3 h1:3hZSg3z+4AXa5LbR2Vl38VmSA83ABItE63E53LuyWv8= @@ -652,14 +648,14 @@ github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.8 h1:LLyANBzE8 github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.8/go.mod h1:/bmg57XZu+bGczzcoumrukiGMPGzI2mOyTT4BVIQUBs= github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.6 h1:sQ3fdtUjgIL2Ul8nRYVVacHOwi5aSMTGGbYVL30oQBU= github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.6/go.mod h1:3fjlL+9YtuI9Oocl1ZeYIK48ImtY4DwPggFhqAygr7o= -github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.11.6 h1:WU76mZkJP6diMDjGFqM8On6fZhUDmGcy6ppX0+kWx9Y= -github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.11.6/go.mod h1:hRllU+yEJM6ovrLeXwVeT5hI70ftPKjX4z/Nj8TZqJw= +github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.12.0 h1:l1EDIlXce2C8JcbBDHVa6nZ4SjPTqmnALTgrhms+NKI= +github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.12.0/go.mod h1:EXq8/J7t9p8zPmdIq+atuxyAbnQwxrQT18fI+Qpv98k= github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.7 h1:M2PYLF8k3zmAwYWSKfUiCTNTXr7ROGuJganVVEQA3YI= github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.7/go.mod h1:jitkQuP2K/SH63Qor0C4pcqz1GDCy/lK2H4t8/VDse4= -github.com/stackitcloud/stackit-sdk-go/services/sfs v0.3.0 h1:4567q2dFp3Hw+5Kx+NLDKDcMWsRHMazTlh5aBrhlkXs= -github.com/stackitcloud/stackit-sdk-go/services/sfs v0.3.0/go.mod h1:r5lBwzJpJe2xBIYctkVIIpaZ41Y6vUEpkmsWR2VoQJs= -github.com/stackitcloud/stackit-sdk-go/services/ske v1.6.3 h1:c+nQMvSml08cdRF1kE24vCw0r/l56olP/svQyhcnKOs= -github.com/stackitcloud/stackit-sdk-go/services/ske v1.6.3/go.mod h1:1Jr+ImrmPERxbYnlTy6O2aSZYNnREf2qQyysv6YC1RY= +github.com/stackitcloud/stackit-sdk-go/services/sfs v0.4.0 h1:ofdGO2dGH6ywKbIVxaxRVal3jWX9WlcHSm5BTud5bC4= +github.com/stackitcloud/stackit-sdk-go/services/sfs v0.4.0/go.mod h1:r5lBwzJpJe2xBIYctkVIIpaZ41Y6vUEpkmsWR2VoQJs= +github.com/stackitcloud/stackit-sdk-go/services/ske v1.11.0 h1:QoKyQPe8FqDqJLNgE5uRlZ/y1c1GUxjV1DDLu5QEBD8= +github.com/stackitcloud/stackit-sdk-go/services/ske v1.11.0/go.mod h1:KhVYCR58wETqdI7Quwhe3OR3BhB2T/b7DzaMsfDnr8g= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.4.3 h1:AQrcr+qeIuZob+3TT2q1L4WOPtpsu5SEpkTnOUHDqfE= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.4.3/go.mod h1:8BBGC69WFXWWmKgzSjgE4HvsI7pEgO0RN2cASwuPJ18= github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= @@ -744,8 +740,8 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -800,8 +796,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -848,8 +844,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -917,8 +913,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo= golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -927,8 +923,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -939,8 +935,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -984,12 +980,10 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -1092,8 +1086,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1114,18 +1108,18 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= +k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= +k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8= +k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= +k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= @@ -1133,8 +1127,8 @@ mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6y rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= diff --git a/golang-ci.yaml b/golang-ci.yaml index 0a367d55a..3026ce631 100644 --- a/golang-ci.yaml +++ b/golang-ci.yaml @@ -32,6 +32,7 @@ linters: - typeDefFirst - ifElseChain - dupImport # https://github.com/go-critic/go-critic/issues/845 + - rangeValCopy enabled-tags: - performance - style @@ -75,6 +76,11 @@ formatters: enable: - gofmt - goimports + settings: + goimports: + # put imports beginning with prefix after 3rd-party packages; + # it's a comma-separated list of prefixes + local-prefixes: github.com/stackitcloud/stackit-cli exclusions: generated: lax paths: diff --git a/internal/cmd/affinity-groups/affinity-groups.go b/internal/cmd/affinity-groups/affinity-groups.go index f8fe78433..0287d8713 100644 --- a/internal/cmd/affinity-groups/affinity-groups.go +++ b/internal/cmd/affinity-groups/affinity-groups.go @@ -2,6 +2,7 @@ package affinity_groups import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/affinity-groups/create" "github.com/stackitcloud/stackit-cli/internal/cmd/affinity-groups/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/affinity-groups/describe" diff --git a/internal/cmd/affinity-groups/create/create.go b/internal/cmd/affinity-groups/create/create.go index d8b0d5a1b..0a38cd35f 100644 --- a/internal/cmd/affinity-groups/create/create.go +++ b/internal/cmd/affinity-groups/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/affinity-groups/create/create_test.go b/internal/cmd/affinity-groups/create/create_test.go index 82dd23ef4..8cdba240a 100644 --- a/internal/cmd/affinity-groups/create/create_test.go +++ b/internal/cmd/affinity-groups/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/affinity-groups/delete/delete.go b/internal/cmd/affinity-groups/delete/delete.go index ba3f83efc..d37005653 100644 --- a/internal/cmd/affinity-groups/delete/delete.go +++ b/internal/cmd/affinity-groups/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/affinity-groups/delete/delete_test.go b/internal/cmd/affinity-groups/delete/delete_test.go index b059eb50c..00cb16756 100644 --- a/internal/cmd/affinity-groups/delete/delete_test.go +++ b/internal/cmd/affinity-groups/delete/delete_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/affinity-groups/describe/describe.go b/internal/cmd/affinity-groups/describe/describe.go index 52465a976..7fda51352 100644 --- a/internal/cmd/affinity-groups/describe/describe.go +++ b/internal/cmd/affinity-groups/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/affinity-groups/describe/describe_test.go b/internal/cmd/affinity-groups/describe/describe_test.go index ac751003b..966df5964 100644 --- a/internal/cmd/affinity-groups/describe/describe_test.go +++ b/internal/cmd/affinity-groups/describe/describe_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/affinity-groups/list/list.go b/internal/cmd/affinity-groups/list/list.go index 0a2e3013f..fe9abad60 100644 --- a/internal/cmd/affinity-groups/list/list.go +++ b/internal/cmd/affinity-groups/list/list.go @@ -10,6 +10,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/affinity-groups/list/list_test.go b/internal/cmd/affinity-groups/list/list_test.go index 432640085..c872f4b45 100644 --- a/internal/cmd/affinity-groups/list/list_test.go +++ b/internal/cmd/affinity-groups/list/list_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/auth/activate-service-account/activate_service_account.go b/internal/cmd/auth/activate-service-account/activate_service_account.go index 3b87d23f5..b69de90d7 100644 --- a/internal/cmd/auth/activate-service-account/activate_service_account.go +++ b/internal/cmd/auth/activate-service-account/activate_service_account.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/viper" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/config" diff --git a/internal/cmd/auth/activate-service-account/activate_service_account_test.go b/internal/cmd/auth/activate-service-account/activate_service_account_test.go index 026ba8dce..22a777ac4 100644 --- a/internal/cmd/auth/activate-service-account/activate_service_account_test.go +++ b/internal/cmd/auth/activate-service-account/activate_service_account_test.go @@ -6,9 +6,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/spf13/viper" + "github.com/zalando/go-keyring" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/config" - "github.com/zalando/go-keyring" ) var testTokenCustomEndpoint = "token_url" diff --git a/internal/cmd/auth/get-access-token/get_access_token.go b/internal/cmd/auth/get-access-token/get_access_token.go index a3c1246e6..bf97df4ec 100644 --- a/internal/cmd/auth/get-access-token/get_access_token.go +++ b/internal/cmd/auth/get-access-token/get_access_token.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" diff --git a/internal/cmd/auth/login/login.go b/internal/cmd/auth/login/login.go index 23efd0a4e..8a03d19af 100644 --- a/internal/cmd/auth/login/login.go +++ b/internal/cmd/auth/login/login.go @@ -3,15 +3,24 @@ package login import ( "fmt" - "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" ) +const ( + portFlag = "port" +) + +type inputModel struct { + Port *int +} + func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "login", @@ -25,8 +34,16 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `Login to the STACKIT CLI. This command will open a browser window where you can login to your STACKIT account`, "$ stackit auth login"), ), - RunE: func(_ *cobra.Command, _ []string) error { - err := auth.AuthorizeUser(params.Printer, false) + RunE: func(cmd *cobra.Command, args []string) error { + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + err = auth.AuthorizeUser(params.Printer, auth.UserAuthConfig{ + IsReauthentication: false, + Port: model.Port, + }) if err != nil { return fmt.Errorf("authorization failed: %w", err) } @@ -36,5 +53,28 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return nil }, } + configureFlags(cmd) return cmd } + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int(portFlag, 0, + "The port on which the callback server will listen to. By default, it tries to bind a port between 8000 and 8020.\n"+ + "When a value is specified, it will only try to use the specified port. Valid values are within the range of 8000 to 8020.", + ) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + port := flags.FlagToIntPointer(p, cmd, portFlag) + // For the CLI client only callback URLs with localhost:[8000-8020] are valid. Additional callbacks must be enabled in the backend. + if port != nil && (*port < 8000 || 8020 < *port) { + return nil, fmt.Errorf("port must be between 8000 and 8020") + } + + model := inputModel{ + Port: port, + } + + p.DebugInputModel(model) + return &model, nil +} diff --git a/internal/cmd/auth/login/login_test.go b/internal/cmd/auth/login/login_test.go new file mode 100644 index 000000000..823fa863e --- /dev/null +++ b/internal/cmd/auth/login/login_test.go @@ -0,0 +1,93 @@ +package login + +import ( + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + portFlag: "8010", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + Port: utils.Ptr(8010), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + argValues []string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: &inputModel{ + Port: nil, + }, + }, + { + description: "lower limit", + flagValues: map[string]string{ + portFlag: "8000", + }, + isValid: true, + expectedModel: &inputModel{ + Port: utils.Ptr(8000), + }, + }, + { + description: "below lower limit is not valid ", + flagValues: map[string]string{ + portFlag: "7999", + }, + isValid: false, + }, + { + description: "upper limit", + flagValues: map[string]string{ + portFlag: "8020", + }, + isValid: true, + expectedModel: &inputModel{ + Port: utils.Ptr(8020), + }, + }, + { + description: "above upper limit is not valid ", + flagValues: map[string]string{ + portFlag: "8021", + }, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} diff --git a/internal/cmd/beta/alb/create/create.go b/internal/cmd/beta/alb/create/create.go index 2232d7aad..5e7989f4e 100644 --- a/internal/cmd/beta/alb/create/create.go +++ b/internal/cmd/beta/alb/create/create.go @@ -11,6 +11,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/goccy/go-yaml" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -84,13 +85,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating loadbalancer") - _, err = wait.CreateOrUpdateLoadbalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *resp.Name).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating loadbalancer", func() error { + _, err := wait.CreateOrUpdateLoadbalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *resp.Name).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for loadbalancer creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, resp) diff --git a/internal/cmd/beta/alb/create/create_test.go b/internal/cmd/beta/alb/create/create_test.go index c99f4a859..6641a2fc2 100644 --- a/internal/cmd/beta/alb/create/create_test.go +++ b/internal/cmd/beta/alb/create/create_test.go @@ -12,11 +12,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/alb" ) //go:embed testdata/testconfig.json diff --git a/internal/cmd/beta/alb/list/list.go b/internal/cmd/beta/alb/list/list.go index bfeb711c6..1967f706e 100644 --- a/internal/cmd/beta/alb/list/list.go +++ b/internal/cmd/beta/alb/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/alb/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/alb" ) type inputModel struct { diff --git a/internal/cmd/beta/alb/list/list_test.go b/internal/cmd/beta/alb/list/list_test.go index b579d45a6..b0524705a 100644 --- a/internal/cmd/beta/alb/list/list_test.go +++ b/internal/cmd/beta/alb/list/list_test.go @@ -10,6 +10,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" diff --git a/internal/cmd/beta/alb/observability-credentials/delete/delete_test.go b/internal/cmd/beta/alb/observability-credentials/delete/delete_test.go index 951846c66..20d9e9b51 100644 --- a/internal/cmd/beta/alb/observability-credentials/delete/delete_test.go +++ b/internal/cmd/beta/alb/observability-credentials/delete/delete_test.go @@ -6,9 +6,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/alb" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/beta/alb/observability-credentials/observability-credentials.go b/internal/cmd/beta/alb/observability-credentials/observability-credentials.go index 0e05ae183..2c0b7d461 100644 --- a/internal/cmd/beta/alb/observability-credentials/observability-credentials.go +++ b/internal/cmd/beta/alb/observability-credentials/observability-credentials.go @@ -2,6 +2,7 @@ package credentials import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" add "github.com/stackitcloud/stackit-cli/internal/cmd/beta/alb/observability-credentials/add" diff --git a/internal/cmd/beta/alb/plans/plans.go b/internal/cmd/beta/alb/plans/plans.go index c38dd9b70..bd672fd23 100644 --- a/internal/cmd/beta/alb/plans/plans.go +++ b/internal/cmd/beta/alb/plans/plans.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/alb/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/alb" ) type inputModel struct { diff --git a/internal/cmd/beta/alb/pool/update/update.go b/internal/cmd/beta/alb/pool/update/update.go index a98d5f822..c20aac785 100644 --- a/internal/cmd/beta/alb/pool/update/update.go +++ b/internal/cmd/beta/alb/pool/update/update.go @@ -11,6 +11,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/goccy/go-yaml" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/beta/alb/pool/update/update_test.go b/internal/cmd/beta/alb/pool/update/update_test.go index 771cf6ce1..09af0fc92 100644 --- a/internal/cmd/beta/alb/pool/update/update_test.go +++ b/internal/cmd/beta/alb/pool/update/update_test.go @@ -12,11 +12,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/alb" ) //go:embed testdata/testconfig.json diff --git a/internal/cmd/beta/alb/quotas/quotas.go b/internal/cmd/beta/alb/quotas/quotas.go index 29f1c3bfd..3f77bbaa0 100644 --- a/internal/cmd/beta/alb/quotas/quotas.go +++ b/internal/cmd/beta/alb/quotas/quotas.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/alb/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/alb" ) type inputModel struct { diff --git a/internal/cmd/beta/alb/template/template.go b/internal/cmd/beta/alb/template/template.go index 67819b173..c91289a2c 100644 --- a/internal/cmd/beta/alb/template/template.go +++ b/internal/cmd/beta/alb/template/template.go @@ -10,6 +10,8 @@ import ( "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/alb" ) const ( diff --git a/internal/cmd/beta/alb/update/update.go b/internal/cmd/beta/alb/update/update.go index 1221cba4f..006c027d4 100644 --- a/internal/cmd/beta/alb/update/update.go +++ b/internal/cmd/beta/alb/update/update.go @@ -11,6 +11,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/goccy/go-yaml" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -90,14 +91,14 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("updating loadbalancer") - _, err = wait.CreateOrUpdateLoadbalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *resp.Name). - WaitWithContext(ctx) + err := spinner.Run(params.Printer, "updating loadbalancer", func() error { + _, err = wait.CreateOrUpdateLoadbalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *resp.Name). + WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for loadbalancer update: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, resp) diff --git a/internal/cmd/beta/alb/update/update_test.go b/internal/cmd/beta/alb/update/update_test.go index d4ccb0788..84220cfdc 100644 --- a/internal/cmd/beta/alb/update/update_test.go +++ b/internal/cmd/beta/alb/update/update_test.go @@ -12,11 +12,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/alb" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/alb" ) //go:embed testdata/testconfig.json diff --git a/internal/cmd/beta/beta.go b/internal/cmd/beta/beta.go index ebc082da7..1bcb3ae55 100644 --- a/internal/cmd/beta/beta.go +++ b/internal/cmd/beta/beta.go @@ -9,7 +9,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/beta/cdn" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/edge" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sfs" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -47,6 +46,5 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(alb.NewCmd(params)) cmd.AddCommand(edge.NewCmd(params)) cmd.AddCommand(intake.NewCmd(params)) - cmd.AddCommand(kms.NewCmd(params)) cmd.AddCommand(cdn.NewCmd(params)) } diff --git a/internal/cmd/beta/cdn/cdn.go b/internal/cmd/beta/cdn/cdn.go index 257633ef0..09d05af76 100644 --- a/internal/cmd/beta/cdn/cdn.go +++ b/internal/cmd/beta/cdn/cdn.go @@ -2,6 +2,7 @@ package cdn import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/cdn/distribution" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" diff --git a/internal/cmd/beta/cdn/distribution/create/create.go b/internal/cmd/beta/cdn/distribution/create/create.go index 19c5d3ca8..d372af381 100644 --- a/internal/cmd/beta/cdn/distribution/create/create.go +++ b/internal/cmd/beta/cdn/distribution/create/create.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( cdnUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/cdn/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) const ( diff --git a/internal/cmd/beta/cdn/distribution/create/create_test.go b/internal/cmd/beta/cdn/distribution/create/create_test.go index 4276f34a7..8d2140e6e 100644 --- a/internal/cmd/beta/cdn/distribution/create/create_test.go +++ b/internal/cmd/beta/cdn/distribution/create/create_test.go @@ -10,14 +10,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "k8s.io/utils/ptr" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" - "k8s.io/utils/ptr" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/cdn/distribution/delete/delete.go b/internal/cmd/beta/cdn/distribution/delete/delete.go index 78baebdc4..beffc6f48 100644 --- a/internal/cmd/beta/cdn/distribution/delete/delete.go +++ b/internal/cmd/beta/cdn/distribution/delete/delete.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" @@ -13,7 +15,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/cdn/client" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) const argDistributionID = "DISTRIBUTION_ID" diff --git a/internal/cmd/beta/cdn/distribution/delete/delete_test.go b/internal/cmd/beta/cdn/distribution/delete/delete_test.go index 03ec87f46..629110c8b 100644 --- a/internal/cmd/beta/cdn/distribution/delete/delete_test.go +++ b/internal/cmd/beta/cdn/distribution/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/cdn/distribution/describe/describe.go b/internal/cmd/beta/cdn/distribution/describe/describe.go index 9d4897f72..7443553ee 100644 --- a/internal/cmd/beta/cdn/distribution/describe/describe.go +++ b/internal/cmd/beta/cdn/distribution/describe/describe.go @@ -7,6 +7,9 @@ import ( "strings" "github.com/spf13/cobra" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,8 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) const distributionIDArg = "DISTRIBUTION_ID_ARG" diff --git a/internal/cmd/beta/cdn/distribution/describe/describe_test.go b/internal/cmd/beta/cdn/distribution/describe/describe_test.go index 78037f8d1..640fab303 100644 --- a/internal/cmd/beta/cdn/distribution/describe/describe_test.go +++ b/internal/cmd/beta/cdn/distribution/describe/describe_test.go @@ -10,12 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/cdn/distribution/distribution.go b/internal/cmd/beta/cdn/distribution/distribution.go index defb471c2..c6cb8e018 100644 --- a/internal/cmd/beta/cdn/distribution/distribution.go +++ b/internal/cmd/beta/cdn/distribution/distribution.go @@ -2,6 +2,7 @@ package distribution import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/cdn/distribution/create" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/cdn/distribution/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/cdn/distribution/describe" diff --git a/internal/cmd/beta/cdn/distribution/list/list.go b/internal/cmd/beta/cdn/distribution/list/list.go index aae2b0db0..41117b279 100644 --- a/internal/cmd/beta/cdn/distribution/list/list.go +++ b/internal/cmd/beta/cdn/distribution/list/list.go @@ -7,6 +7,9 @@ import ( "strings" "github.com/spf13/cobra" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,8 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) type inputModel struct { diff --git a/internal/cmd/beta/cdn/distribution/list/list_test.go b/internal/cmd/beta/cdn/distribution/list/list_test.go index 8e5d71aa9..7eae777a6 100644 --- a/internal/cmd/beta/cdn/distribution/list/list_test.go +++ b/internal/cmd/beta/cdn/distribution/list/list_test.go @@ -13,13 +13,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/cdn/distribution/update/update.go b/internal/cmd/beta/cdn/distribution/update/update.go index 0c6662e60..5bb66942f 100644 --- a/internal/cmd/beta/cdn/distribution/update/update.go +++ b/internal/cmd/beta/cdn/distribution/update/update.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( cdnUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/cdn/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) const ( diff --git a/internal/cmd/beta/cdn/distribution/update/update_test.go b/internal/cmd/beta/cdn/distribution/update/update_test.go index bf3dd11f5..915f908d7 100644 --- a/internal/cmd/beta/cdn/distribution/update/update_test.go +++ b/internal/cmd/beta/cdn/distribution/update/update_test.go @@ -9,13 +9,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "k8s.io/utils/ptr" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" - "k8s.io/utils/ptr" ) const testCacheDuration = "P1DT12H" diff --git a/internal/cmd/beta/edge/edge.go b/internal/cmd/beta/edge/edge.go index 11d1b1e16..35b5e0575 100644 --- a/internal/cmd/beta/edge/edge.go +++ b/internal/cmd/beta/edge/edge.go @@ -5,6 +5,7 @@ package edge import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/edge/instance" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/edge/kubeconfig" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/edge/plans" diff --git a/internal/cmd/beta/edge/instance/create/create.go b/internal/cmd/beta/edge/instance/create/create.go index 72f7ec2d2..6faac9cc0 100755 --- a/internal/cmd/beta/edge/instance/create/create.go +++ b/internal/cmd/beta/edge/instance/create/create.go @@ -8,6 +8,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -21,8 +24,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" - "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" ) // Command constructor @@ -87,19 +88,18 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - // The waiter handler needs a concrete client type. We can safely cast here as the real implementation will always match. - client, ok := apiClient.(*edge.APIClient) - if !ok { - return fmt.Errorf("failed to configure API client") - } - _, err = wait.CreateOrUpdateInstanceWaitHandler(ctx, client, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) - + err := spinner.Run(params.Printer, "Creating instance", func() error { + // The waiter handler needs a concrete concreteClient type. We can safely cast here as the real implementation will always match. + concreteClient, ok := apiClient.(*edge.APIClient) + if !ok { + return fmt.Errorf("failed to configure API concreteClient") + } + _, err = wait.CreateOrUpdateInstanceWaitHandler(ctx, concreteClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for edge instance creation: %w", err) } - s.Stop() } // Handle output to printer diff --git a/internal/cmd/beta/edge/instance/create/create_test.go b/internal/cmd/beta/edge/instance/create/create_test.go index e4ce1482d..7861a87c8 100755 --- a/internal/cmd/beta/edge/instance/create/create_test.go +++ b/internal/cmd/beta/edge/instance/create/create_test.go @@ -11,6 +11,8 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -19,7 +21,6 @@ import ( commonInstance "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/instance" testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/edge/instance/delete/delete.go b/internal/cmd/beta/edge/instance/delete/delete.go index d6650e7e6..c6998e782 100755 --- a/internal/cmd/beta/edge/instance/delete/delete.go +++ b/internal/cmd/beta/edge/instance/delete/delete.go @@ -8,6 +8,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -20,8 +23,6 @@ import ( commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/edge" - "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" ) // Struct to model user input (arguments and/or flags) @@ -110,25 +111,26 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled operationState := "Triggered deletion of" if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - // Determine identifier and waiter to use - waiterFactory, err := getWaiterFactory(ctx, model) - if err != nil { + err := spinner.Run(params.Printer, "Deleting instance", func() error { + // Determine identifier and waiter to use + waiterFactory, err := getWaiterFactory(ctx, model) + if err != nil { + return err + } + // The waiter factory needs a concrete concreteClient type. We can safely cast here as the real implementation will always match. + concreteClient, ok := apiClient.(*edge.APIClient) + if !ok { + return fmt.Errorf("failed to configure API concreteClient") + } + waiter := waiterFactory(concreteClient) + _, err = waiter.WaitWithContext(ctx) return err - } - // The waiter factory needs a concrete client type. We can safely cast here as the real implementation will always match. - client, ok := apiClient.(*edge.APIClient) - if !ok { - return fmt.Errorf("failed to configure API client") - } - waiter := waiterFactory(client) + }) - if _, err = waiter.WaitWithContext(ctx); err != nil { + if err != nil { return fmt.Errorf("wait for edge instance deletion: %w", err) } operationState = "Deleted" - s.Stop() } params.Printer.Info("%s instance with %q %q of project %q.\n", operationState, model.identifier.Flag, model.identifier.Value, projectLabel) diff --git a/internal/cmd/beta/edge/instance/delete/delete_test.go b/internal/cmd/beta/edge/instance/delete/delete_test.go index c15a3e7d7..3e72a71d1 100755 --- a/internal/cmd/beta/edge/instance/delete/delete_test.go +++ b/internal/cmd/beta/edge/instance/delete/delete_test.go @@ -11,6 +11,9 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -19,8 +22,6 @@ import ( commonInstance "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/instance" commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/edge/instance/describe/describe.go b/internal/cmd/beta/edge/instance/describe/describe.go index 5a7d85ed6..d4ac397b5 100755 --- a/internal/cmd/beta/edge/instance/describe/describe.go +++ b/internal/cmd/beta/edge/instance/describe/describe.go @@ -8,6 +8,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -20,7 +22,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type inputModel struct { diff --git a/internal/cmd/beta/edge/instance/describe/describe_test.go b/internal/cmd/beta/edge/instance/describe/describe_test.go index 913a9c221..a660532f9 100755 --- a/internal/cmd/beta/edge/instance/describe/describe_test.go +++ b/internal/cmd/beta/edge/instance/describe/describe_test.go @@ -11,6 +11,9 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -20,8 +23,6 @@ import ( commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/edge/instance/list/list.go b/internal/cmd/beta/edge/instance/list/list.go index 6a589bfdd..3b728ee2d 100755 --- a/internal/cmd/beta/edge/instance/list/list.go +++ b/internal/cmd/beta/edge/instance/list/list.go @@ -8,6 +8,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) const ( diff --git a/internal/cmd/beta/edge/instance/list/list_test.go b/internal/cmd/beta/edge/instance/list/list_test.go index 3b5adc6dc..cf346b7fb 100755 --- a/internal/cmd/beta/edge/instance/list/list_test.go +++ b/internal/cmd/beta/edge/instance/list/list_test.go @@ -10,6 +10,8 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -17,7 +19,6 @@ import ( testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/edge/instance/update/update.go b/internal/cmd/beta/edge/instance/update/update.go index 28ec3437a..2a6d3f6dd 100755 --- a/internal/cmd/beta/edge/instance/update/update.go +++ b/internal/cmd/beta/edge/instance/update/update.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -21,9 +22,10 @@ import ( commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-sdk-go/services/edge" "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" + + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" ) // Struct to model user input (arguments and/or flags) @@ -120,25 +122,26 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if !model.Async { // Wait for async operation, if async mode not enabled // Show spinner while waiting - s := spinner.New(params.Printer) - s.Start("Updating instance") - // Determine identifier and waiter to use - waiterFactory, err := getWaiterFactory(ctx, model) - if err != nil { + err := spinner.Run(params.Printer, "Updating instance", func() error { + // Determine identifier and waiter to use + waiterFactory, err := getWaiterFactory(ctx, model) + if err != nil { + return err + } + // The waiter handler needs a concrete concreteClient type. We can safely cast here as the real implementation will always match. + concreteClient, ok := apiClient.(*edge.APIClient) + if !ok { + return fmt.Errorf("failed to configure API concreteClient") + } + waiter := waiterFactory(concreteClient) + _, err = waiter.WaitWithContext(ctx) return err - } - // The waiter handler needs a concrete client type. We can safely cast here as the real implementation will always match. - client, ok := apiClient.(*edge.APIClient) - if !ok { - return fmt.Errorf("failed to configure API client") - } - waiter := waiterFactory(client) + }) - if _, err = waiter.WaitWithContext(ctx); err != nil { + if err != nil { return fmt.Errorf("wait for edge instance update: %w", err) } operationState = "Updated" - s.Stop() } params.Printer.Info("%s instance with %q %q of project %q.\n", operationState, model.identifier.Flag, model.identifier.Value, projectLabel) diff --git a/internal/cmd/beta/edge/instance/update/update_test.go b/internal/cmd/beta/edge/instance/update/update_test.go index 61e78d50d..c5e3e92a8 100755 --- a/internal/cmd/beta/edge/instance/update/update_test.go +++ b/internal/cmd/beta/edge/instance/update/update_test.go @@ -11,6 +11,9 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -19,8 +22,6 @@ import ( commonInstance "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/instance" commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/edge/kubeconfig/create/create.go b/internal/cmd/beta/edge/kubeconfig/create/create.go index b22b7a1b3..dd38b8322 100755 --- a/internal/cmd/beta/edge/kubeconfig/create/create.go +++ b/internal/cmd/beta/edge/kubeconfig/create/create.go @@ -10,6 +10,10 @@ import ( "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -22,9 +26,6 @@ import ( commonKubeconfig "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/kubeconfig" commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" - "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" ) type inputModel struct { diff --git a/internal/cmd/beta/edge/kubeconfig/create/create_test.go b/internal/cmd/beta/edge/kubeconfig/create/create_test.go index 1a353e303..65ef09fda 100755 --- a/internal/cmd/beta/edge/kubeconfig/create/create_test.go +++ b/internal/cmd/beta/edge/kubeconfig/create/create_test.go @@ -12,6 +12,9 @@ import ( "github.com/goccy/go-yaml" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -23,8 +26,6 @@ import ( testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/edge/plans/list/list.go b/internal/cmd/beta/edge/plans/list/list.go index ad4c3e178..e8b8607fd 100755 --- a/internal/cmd/beta/edge/plans/list/list.go +++ b/internal/cmd/beta/edge/plans/list/list.go @@ -8,6 +8,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) // User input struct for the command diff --git a/internal/cmd/beta/edge/plans/list/list_test.go b/internal/cmd/beta/edge/plans/list/list_test.go index d2fcb595f..084e7e8e8 100755 --- a/internal/cmd/beta/edge/plans/list/list_test.go +++ b/internal/cmd/beta/edge/plans/list/list_test.go @@ -10,6 +10,8 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -17,7 +19,6 @@ import ( testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/edge/token/create/create.go b/internal/cmd/beta/edge/token/create/create.go index f28e196ab..cec854bcc 100755 --- a/internal/cmd/beta/edge/token/create/create.go +++ b/internal/cmd/beta/edge/token/create/create.go @@ -8,6 +8,10 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -20,9 +24,6 @@ import ( commonKubeconfig "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/kubeconfig" commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/edge" - "github.com/stackitcloud/stackit-sdk-go/services/edge/wait" ) type inputModel struct { diff --git a/internal/cmd/beta/edge/token/create/create_test.go b/internal/cmd/beta/edge/token/create/create_test.go index c41e62044..c380da959 100755 --- a/internal/cmd/beta/edge/token/create/create_test.go +++ b/internal/cmd/beta/edge/token/create/create_test.go @@ -11,6 +11,9 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -21,8 +24,6 @@ import ( commonValidation "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/validation" testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/intake/create/create.go b/internal/cmd/beta/intake/create/create.go index 820e13b90..00d54349e 100644 --- a/internal/cmd/beta/intake/create/create.go +++ b/internal/cmd/beta/intake/create/create.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" - "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" ) const ( @@ -111,13 +112,13 @@ func NewCmd(p *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(p.Printer) - s.Start("Creating STACKIT Intake instance") - _, err = wait.CreateOrUpdateIntakeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, resp.GetId()).WaitWithContext(ctx) + err := spinner.Run(p.Printer, "Creating STACKIT Intake instance", func() error { + _, err = wait.CreateOrUpdateIntakeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, resp.GetId()).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for STACKIT Instance creation: %w", err) } - s.Stop() } return outputResult(p.Printer, model, projectLabel, resp) diff --git a/internal/cmd/beta/intake/create/create_test.go b/internal/cmd/beta/intake/create/create_test.go index 38a0febe3..9c6c37213 100644 --- a/internal/cmd/beta/intake/create/create_test.go +++ b/internal/cmd/beta/intake/create/create_test.go @@ -8,12 +8,13 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) // Define a unique key for the context to avoid collisions diff --git a/internal/cmd/beta/intake/delete/delete.go b/internal/cmd/beta/intake/delete/delete.go index b55d9997e..f49118dfe 100644 --- a/internal/cmd/beta/intake/delete/delete.go +++ b/internal/cmd/beta/intake/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) const ( @@ -68,13 +69,13 @@ func NewCmd(p *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(p.Printer) - s.Start("Deleting STACKIT Intake instance") - _, err = wait.DeleteIntakeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.IntakeId).WaitWithContext(ctx) + err := spinner.Run(p.Printer, "Deleting STACKIT Intake instance", func() error { + _, err = wait.DeleteIntakeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.IntakeId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for STACKIT Instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/beta/intake/delete/delete_test.go b/internal/cmd/beta/intake/delete/delete_test.go index 641c74866..ce673fabc 100644 --- a/internal/cmd/beta/intake/delete/delete_test.go +++ b/internal/cmd/beta/intake/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) // Define a unique key for the context to avoid collisions diff --git a/internal/cmd/beta/intake/describe/describe_test.go b/internal/cmd/beta/intake/describe/describe_test.go index 37c6d3c47..c2961c5b4 100644 --- a/internal/cmd/beta/intake/describe/describe_test.go +++ b/internal/cmd/beta/intake/describe/describe_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/intake/intake.go b/internal/cmd/beta/intake/intake.go index 528a9e392..8e90bddd6 100644 --- a/internal/cmd/beta/intake/intake.go +++ b/internal/cmd/beta/intake/intake.go @@ -2,12 +2,14 @@ package intake import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/create" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/describe" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/list" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/runner" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/update" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/user" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -28,6 +30,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(runner.NewCmd(params)) + cmd.AddCommand(user.NewCmd(params)) // Intake instance subcommands cmd.AddCommand(create.NewCmd(params)) diff --git a/internal/cmd/beta/intake/list/list_test.go b/internal/cmd/beta/intake/list/list_test.go index e57e733e8..15b5e714a 100644 --- a/internal/cmd/beta/intake/list/list_test.go +++ b/internal/cmd/beta/intake/list/list_test.go @@ -9,12 +9,13 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/intake/runner/create/create.go b/internal/cmd/beta/intake/runner/create/create.go index 72cff3b29..b64a116f9 100644 --- a/internal/cmd/beta/intake/runner/create/create.go +++ b/internal/cmd/beta/intake/runner/create/create.go @@ -88,13 +88,13 @@ func NewCmd(p *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(p.Printer) - s.Start("Creating STACKIT Intake Runner") - _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, resp.GetId()).WaitWithContext(ctx) + err := spinner.Run(p.Printer, "Creating STACKIT Intake Runner", func() error { + _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, resp.GetId()).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for STACKIT Intake Runner creation: %w", err) } - s.Stop() } return outputResult(p.Printer, model, projectLabel, resp) diff --git a/internal/cmd/beta/intake/runner/create/create_test.go b/internal/cmd/beta/intake/runner/create/create_test.go index c4f16995b..852e7339b 100644 --- a/internal/cmd/beta/intake/runner/create/create_test.go +++ b/internal/cmd/beta/intake/runner/create/create_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) // Define a unique key for the context to avoid collisions diff --git a/internal/cmd/beta/intake/runner/delete/delete.go b/internal/cmd/beta/intake/runner/delete/delete.go index 92d5b1acf..5c7277bff 100644 --- a/internal/cmd/beta/intake/runner/delete/delete.go +++ b/internal/cmd/beta/intake/runner/delete/delete.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" - "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" ) const ( @@ -68,13 +69,13 @@ func NewCmd(p *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(p.Printer) - s.Start("Deleting STACKIT Intake Runner") - _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.RunnerId).WaitWithContext(ctx) + err := spinner.Run(p.Printer, "Deleting STACKIT Intake Runner", func() error { + _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.RunnerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for STACKIT Intake Runner deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/beta/intake/runner/delete/delete_test.go b/internal/cmd/beta/intake/runner/delete/delete_test.go index b99edac92..ec5dc76f9 100644 --- a/internal/cmd/beta/intake/runner/delete/delete_test.go +++ b/internal/cmd/beta/intake/runner/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) // Define a unique key for the context to avoid collisions diff --git a/internal/cmd/beta/intake/runner/describe/describe_test.go b/internal/cmd/beta/intake/runner/describe/describe_test.go index 1cb034e04..a9f6ff778 100644 --- a/internal/cmd/beta/intake/runner/describe/describe_test.go +++ b/internal/cmd/beta/intake/runner/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/intake/runner/list/list.go b/internal/cmd/beta/intake/runner/list/list.go index c9bdc9acd..81d1afa2d 100644 --- a/internal/cmd/beta/intake/runner/list/list.go +++ b/internal/cmd/beta/intake/runner/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) const ( diff --git a/internal/cmd/beta/intake/runner/list/list_test.go b/internal/cmd/beta/intake/runner/list/list_test.go index bbce39c3e..eed227010 100644 --- a/internal/cmd/beta/intake/runner/list/list_test.go +++ b/internal/cmd/beta/intake/runner/list/list_test.go @@ -11,11 +11,12 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/intake/runner/runner.go b/internal/cmd/beta/intake/runner/runner.go index f923d96ff..4c6d58607 100644 --- a/internal/cmd/beta/intake/runner/runner.go +++ b/internal/cmd/beta/intake/runner/runner.go @@ -2,6 +2,7 @@ package runner import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/runner/create" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/runner/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/runner/describe" @@ -20,12 +21,16 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Args: args.NoArgs, Run: utils.CmdHelp, } + + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { // Pass the params down to each action command cmd.AddCommand(create.NewCmd(params)) cmd.AddCommand(delete.NewCmd(params)) cmd.AddCommand(describe.NewCmd(params)) cmd.AddCommand(list.NewCmd(params)) cmd.AddCommand(update.NewCmd(params)) - - return cmd } diff --git a/internal/cmd/beta/intake/runner/update/update.go b/internal/cmd/beta/intake/runner/update/update.go index a5f5bb55a..f59020818 100644 --- a/internal/cmd/beta/intake/runner/update/update.go +++ b/internal/cmd/beta/intake/runner/update/update.go @@ -8,6 +8,9 @@ import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,8 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" - "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" ) const ( @@ -86,13 +87,13 @@ func NewCmd(p *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(p.Printer) - s.Start("Updating STACKIT Intake Runner") - _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.RunnerId).WaitWithContext(ctx) + err := spinner.Run(p.Printer, "Updating STACKIT Intake Runner", func() error { + _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.RunnerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for STACKIT Intake Runner update: %w", err) } - s.Stop() } return outputResult(p.Printer, model, projectLabel, resp) diff --git a/internal/cmd/beta/intake/runner/update/update_test.go b/internal/cmd/beta/intake/runner/update/update_test.go index b702ede40..3b1161ee1 100644 --- a/internal/cmd/beta/intake/runner/update/update_test.go +++ b/internal/cmd/beta/intake/runner/update/update_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/intake/update/update.go b/internal/cmd/beta/intake/update/update.go index e531e2c91..c21640e5d 100644 --- a/internal/cmd/beta/intake/update/update.go +++ b/internal/cmd/beta/intake/update/update.go @@ -106,13 +106,13 @@ func NewCmd(p *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(p.Printer) - s.Start("Updating STACKIT Intake Runner instance") - _, err = wait.CreateOrUpdateIntakeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.IntakeId).WaitWithContext(ctx) + err := spinner.Run(p.Printer, "Updating STACKIT Intake Runner instance", func() error { + _, err = wait.CreateOrUpdateIntakeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.IntakeId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for STACKIT Instance creation: %w", err) } - s.Stop() } return outputResult(p.Printer, model, projectLabel, resp) diff --git a/internal/cmd/beta/intake/update/update_test.go b/internal/cmd/beta/intake/update/update_test.go index 6d6635cb0..94602f885 100644 --- a/internal/cmd/beta/intake/update/update_test.go +++ b/internal/cmd/beta/intake/update/update_test.go @@ -7,12 +7,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/intake/user/create/create.go b/internal/cmd/beta/intake/user/create/create.go new file mode 100644 index 000000000..436560060 --- /dev/null +++ b/internal/cmd/beta/intake/user/create/create.go @@ -0,0 +1,174 @@ +package create + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + displayNameFlag = "display-name" + intakeIdFlag = "intake-id" + passwordFlag = "password" + userTypeFlag = "type" + descriptionFlag = "description" + labelsFlag = "labels" +) + +// inputModel struct holds all the input parameters for the command +type inputModel struct { + *globalflags.GlobalFlagModel + DisplayName *string + IntakeId *string + Password *string + UserType *string + Description *string + Labels *map[string]string +} + +func NewCmd(p *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates a new Intake User", + Long: "Creates a new Intake User for a specific Intake.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create a new Intake User with required parameters`, + `$ stackit beta intake user create --display-name intake-user --intake-id xxx --password "SuperSafepass123\!"`), + examples.NewExample( + `Create a new Intake User for the dead-letter queue with labels`, + `$ stackit beta intake user create --display-name dlq-user --intake-id xxx --password "SuperSafepass123\!" --type dead-letter --labels "env=prod"`), + ), + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(p.Printer, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p.Printer, p.CliVersion) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, p.Printer, p.CliVersion, cmd) + if err != nil { + p.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + prompt := fmt.Sprintf("Are you sure you want to create an Intake User for project %q?", projectLabel) + err = p.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("create Intake User: %w", err) + } + + // Wait for async operation, if async mode not enabled + if !model.Async { + err := spinner.Run(p.Printer, "Creating STACKIT Intake User", func() error { + _, err = wait.CreateOrUpdateIntakeUserWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *model.IntakeId, resp.GetId()).WaitWithContext(ctx) + return err + }) + if err != nil { + return fmt.Errorf("wait for STACKIT Intake User creation: %w", err) + } + } + + return outputResult(p.Printer, model, projectLabel, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(displayNameFlag, "", "Display name") + cmd.Flags().Var(flags.UUIDFlag(), intakeIdFlag, "The UUID of the Intake to associate the user with") + cmd.Flags().String(passwordFlag, "", "Password for the user. Must contain lower, upper, number, and special characters (min 12 chars)") + cmd.Flags().String(userTypeFlag, string(intake.USERTYPE_INTAKE), "Type of user. One of 'intake' (default) or 'dead-letter'") + cmd.Flags().String(descriptionFlag, "", "Description") + cmd.Flags().StringToString(labelsFlag, nil, "Labels in key=value format, separated by commas") + + err := flags.MarkFlagsRequired(cmd, displayNameFlag, intakeIdFlag, passwordFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + DisplayName: flags.FlagToStringPointer(p, cmd, displayNameFlag), + IntakeId: flags.FlagToStringPointer(p, cmd, intakeIdFlag), + Password: flags.FlagToStringPointer(p, cmd, passwordFlag), + UserType: flags.FlagToStringPointer(p, cmd, userTypeFlag), + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelsFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *intake.APIClient) intake.ApiCreateIntakeUserRequest { + req := apiClient.CreateIntakeUser(ctx, model.ProjectId, model.Region, *model.IntakeId) + + var userType *intake.UserType + if model.UserType != nil { + userType = utils.Ptr(intake.UserType(*model.UserType)) + } + + payload := intake.CreateIntakeUserPayload{ + DisplayName: model.DisplayName, + Password: model.Password, + Type: userType, + Description: model.Description, + Labels: model.Labels, + } + + req = req.CreateIntakeUserPayload(payload) + return req +} + +func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp *intake.IntakeUserResponse) error { + return p.OutputResult(model.OutputFormat, resp, func() error { + if resp == nil { + p.Outputf("Triggered creation of Intake User for project %q, but no user ID was returned.\n", projectLabel) + return nil + } + + operationState := "Created" + if model.Async { + operationState = "Triggered creation of" + } + p.Outputf("%s Intake User for project %q. User ID: %s\n", operationState, projectLabel, utils.PtrString(resp.Id)) + return nil + }) +} diff --git a/internal/cmd/beta/intake/user/create/create_test.go b/internal/cmd/beta/intake/user/create/create_test.go new file mode 100644 index 000000000..4d28c92c7 --- /dev/null +++ b/internal/cmd/beta/intake/user/create/create_test.go @@ -0,0 +1,294 @@ +package create + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +// Define a unique key for the context to avoid collisions +type testCtxKey struct{} + +const ( + testRegion = "eu01" + testDisplayName = "testuser" + testPassword = "Secret12345!" + testUserType = "intake" + testDescription = "This is a test user" + testLabelsString = "env=test,team=dev" +) + +var ( + // testCtx dummy context for testing purposes + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + // testClient mock API client + testClient = &intake.APIClient{} + testProjectId = uuid.NewString() + testIntakeId = uuid.NewString() + + testLabels = map[string]string{"env": "test", "team": "dev"} +) + +// fixtureFlagValues generates a map of flag values for tests +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + displayNameFlag: testDisplayName, + intakeIdFlag: testIntakeId, + passwordFlag: testPassword, + userTypeFlag: testUserType, + descriptionFlag: testDescription, + labelsFlag: testLabelsString, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +// fixtureInputModel generates an input model for tests +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + DisplayName: utils.Ptr(testDisplayName), + IntakeId: utils.Ptr(testIntakeId), + Password: utils.Ptr(testPassword), + UserType: utils.Ptr(testUserType), + Description: utils.Ptr(testDescription), + Labels: utils.Ptr(testLabels), + } + for _, mod := range mods { + mod(model) + } + return model +} + +// fixtureCreatePayload generates a CreateIntakeUserPayload for tests +func fixtureCreatePayload(mods ...func(payload *intake.CreateIntakeUserPayload)) intake.CreateIntakeUserPayload { + userType := intake.UserType(testUserType) + payload := intake.CreateIntakeUserPayload{ + DisplayName: utils.Ptr(testDisplayName), + Password: utils.Ptr(testPassword), + Type: &userType, + Description: utils.Ptr(testDescription), + Labels: utils.Ptr(testLabels), + } + for _, mod := range mods { + mod(&payload) + } + return payload +} + +// fixtureRequest generates an API request for tests +func fixtureRequest(mods ...func(request *intake.ApiCreateIntakeUserRequest)) intake.ApiCreateIntakeUserRequest { + request := testClient.CreateIntakeUser(testCtx, testProjectId, testRegion, testIntakeId) + request = request.CreateIntakeUserPayload(fixtureCreatePayload()) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "intake id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, intakeIdFlag) + }), + isValid: false, + }, + { + description: "display name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, displayNameFlag) + }), + isValid: false, + }, + { + description: "password missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, passwordFlag) + }), + isValid: false, + }, + { + description: "required fields only", + flagValues: map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + displayNameFlag: testDisplayName, + intakeIdFlag: testIntakeId, + passwordFlag: testPassword, + userTypeFlag: testUserType, + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Description = nil + model.Labels = nil + // UserType has a default value in the command definition, so it should still be populated + model.UserType = utils.Ptr(string(intake.USERTYPE_INTAKE)) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, func(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + return parseInput(p, cmd) + }, tt.expectedModel, nil, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest intake.ApiCreateIntakeUserRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "no optionals", + model: fixtureInputModel(func(model *inputModel) { + model.Description = nil + model.Labels = nil + model.UserType = nil + }), + expectedRequest: fixtureRequest(func(request *intake.ApiCreateIntakeUserRequest) { + *request = (*request).CreateIntakeUserPayload(fixtureCreatePayload(func(payload *intake.CreateIntakeUserPayload) { + payload.Description = nil + payload.Labels = nil + payload.Type = nil + })) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + projectLabel string + resp *intake.IntakeUserResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default output", + args: args{ + model: fixtureInputModel(), + projectLabel: "my-project", + resp: &intake.IntakeUserResponse{Id: utils.Ptr("user-id-123")}, + }, + wantErr: false, + }, + { + name: "default output - async", + args: args{ + model: fixtureInputModel(func(model *inputModel) { + model.Async = true + }), + projectLabel: "my-project", + resp: &intake.IntakeUserResponse{Id: utils.Ptr("user-id-123")}, + }, + wantErr: false, + }, + { + name: "json output", + args: args{ + model: fixtureInputModel(func(model *inputModel) { + model.OutputFormat = print.JSONOutputFormat + }), + resp: &intake.IntakeUserResponse{Id: utils.Ptr("user-id-123")}, + }, + wantErr: false, + }, + { + name: "nil response - default output", + args: args{ + model: fixtureInputModel(), + resp: nil, + }, + wantErr: false, + }, + { + name: "nil response - json output", + args: args{ + model: fixtureInputModel(func(model *inputModel) { + model.OutputFormat = print.JSONOutputFormat + }), + resp: nil, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.projectLabel, tt.args.resp); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/intake/user/delete/delete.go b/internal/cmd/beta/intake/user/delete/delete.go new file mode 100644 index 000000000..f6d43b51d --- /dev/null +++ b/internal/cmd/beta/intake/user/delete/delete.go @@ -0,0 +1,126 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + userIdArg = "USER_ID" + intakeIdFlag = "intake-id" +) + +// inputModel struct holds all the input parameters for the command +type inputModel struct { + *globalflags.GlobalFlagModel + IntakeId string + UserId string +} + +// NewCmd creates a new cobra command for deleting an Intake User +func NewCmd(p *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", userIdArg), + Short: "Deletes an Intake User", + Long: "Deletes an Intake User.", + Args: args.SingleArg(userIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Delete an Intake User with ID "xxx" for Intake "yyy"`, + `$ stackit beta intake user delete xxx --intake-id yyy`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p.Printer, p.CliVersion) + if err != nil { + return err + } + + prompt := fmt.Sprintf("Are you sure you want to delete Intake User %q?", model.UserId) + err = p.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + if err = req.Execute(); err != nil { + return fmt.Errorf("delete Intake User: %w", err) + } + + // Wait for async operation, if async mode not enabled + if !model.Async { + err := spinner.Run(p.Printer, "Deleting STACKIT Intake User", func() error { + _, err = wait.DeleteIntakeUserWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.IntakeId, model.UserId).WaitWithContext(ctx) + return err + }) + if err != nil { + return fmt.Errorf("wait for STACKIT Intake User deletion: %w", err) + } + } + + operationState := "Deleted" + if model.Async { + operationState = "Triggered deletion of" + } + p.Printer.Outputf("%s STACKIT Intake User %s\n", operationState, model.UserId) + + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), intakeIdFlag, "Intake ID") + + err := flags.MarkFlagsRequired(cmd, intakeIdFlag) + cobra.CheckErr(err) +} + +// parseInput parses the command arguments and flags into a standardized model +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + userId := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + IntakeId: flags.FlagToStringValue(p, cmd, intakeIdFlag), + UserId: userId, + } + + p.DebugInputModel(model) + return &model, nil +} + +// buildRequest creates the API request to delete an Intake User +func buildRequest(ctx context.Context, model *inputModel, apiClient *intake.APIClient) intake.ApiDeleteIntakeUserRequest { + req := apiClient.DeleteIntakeUser(ctx, model.ProjectId, model.Region, model.IntakeId, model.UserId) + return req +} diff --git a/internal/cmd/beta/intake/user/delete/delete_test.go b/internal/cmd/beta/intake/user/delete/delete_test.go new file mode 100644 index 000000000..9aa042552 --- /dev/null +++ b/internal/cmd/beta/intake/user/delete/delete_test.go @@ -0,0 +1,175 @@ +package delete + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +// Define a unique key for the context to avoid collisions +type testCtxKey struct{} + +const ( + testRegion = "eu01" +) + +var ( + // testCtx is a dummy context for testing purposes + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + // testClient is a mock API client + testClient = &intake.APIClient{} + testProjectId = uuid.NewString() + testIntakeId = uuid.NewString() + testUserId = uuid.NewString() +) + +// fixtureArgValues generates a slice of arguments for tests +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testUserId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +// fixtureFlagValues generates a map of flag values for tests +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + intakeIdFlag: testIntakeId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +// fixtureInputModel generates an input model for tests +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + IntakeId: testIntakeId, + UserId: testUserId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +// fixtureRequest generates an API request for tests +func fixtureRequest(mods ...func(request *intake.ApiDeleteIntakeUserRequest)) intake.ApiDeleteIntakeUserRequest { + request := testClient.DeleteIntakeUser(testCtx, testProjectId, testRegion, testIntakeId, testUserId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "no flag values", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "intake id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, intakeIdFlag) + }), + isValid: false, + }, + { + description: "intake id invalid", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[intakeIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "user id invalid", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest intake.ApiDeleteIntakeUserRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/beta/intake/user/describe/describe.go b/internal/cmd/beta/intake/user/describe/describe.go new file mode 100644 index 000000000..5a12896aa --- /dev/null +++ b/internal/cmd/beta/intake/user/describe/describe.go @@ -0,0 +1,135 @@ +package describe + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + userIdArg = "USER_ID" + intakeIdFlag = "intake-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + IntakeId string + UserId string +} + +func NewCmd(p *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("describe %s", userIdArg), + Short: "Shows details of an Intake User", + Long: "Shows details of an Intake User.", + Args: args.SingleArg(userIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Get details of an Intake User with ID "xxx" for Intake "yyy"`, + `$ stackit beta intake user describe xxx --intake-id yyy`), + examples.NewExample( + `Get details of an Intake User with ID "xxx" in JSON format`, + `$ stackit beta intake user describe xxx --intake-id yyy --output-format json`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p.Printer, p.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get Intake User: %w", err) + } + + return outputResult(p.Printer, model.OutputFormat, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), intakeIdFlag, "Intake ID") + + err := flags.MarkFlagsRequired(cmd, intakeIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + userId := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + IntakeId: flags.FlagToStringValue(p, cmd, intakeIdFlag), + UserId: userId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *intake.APIClient) intake.ApiGetIntakeUserRequest { + req := apiClient.GetIntakeUser(ctx, model.ProjectId, model.Region, model.IntakeId, model.UserId) + return req +} + +func outputResult(p *print.Printer, outputFormat string, user *intake.IntakeUserResponse) error { + if user == nil { + return fmt.Errorf("received nil response, could not display details") + } + + return p.OutputResult(outputFormat, user, func() error { + table := tables.NewTable() + table.SetHeader("Attribute", "Value") + + table.AddRow("ID", user.GetId()) + table.AddRow("Name", user.GetDisplayName()) + table.AddRow("State", user.GetState()) + + if user.Type != nil { + table.AddRow("Type", *user.Type) + } + + table.AddRow("Username", user.GetUser()) + table.AddRow("Created", user.GetCreateTime()) + table.AddRow("Labels", user.GetLabels()) + + if description := user.GetDescription(); description != "" { + table.AddRow("Description", description) + } + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + }) +} diff --git a/internal/cmd/beta/intake/user/describe/describe_test.go b/internal/cmd/beta/intake/user/describe/describe_test.go new file mode 100644 index 000000000..b3aacd8bb --- /dev/null +++ b/internal/cmd/beta/intake/user/describe/describe_test.go @@ -0,0 +1,212 @@ +package describe + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" +) + +type testCtxKey struct{} + +const ( + testRegion = "eu01" +) + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &intake.APIClient{} + testProjectId = uuid.NewString() + testIntakeId = uuid.NewString() + testUserId = uuid.NewString() +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testUserId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + intakeIdFlag: testIntakeId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + IntakeId: testIntakeId, + UserId: testUserId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *intake.ApiGetIntakeUserRequest)) intake.ApiGetIntakeUserRequest { + request := testClient.GetIntakeUser(testCtx, testProjectId, testRegion, testIntakeId, testUserId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "no flag values", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "intake id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, intakeIdFlag) + }), + isValid: false, + }, + { + description: "intake id invalid", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[intakeIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "user id invalid", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest intake.ApiGetIntakeUserRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + user *intake.IntakeUserResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default output", + args: args{outputFormat: "default", user: &intake.IntakeUserResponse{}}, + wantErr: false, + }, + { + name: "json output", + args: args{outputFormat: print.JSONOutputFormat, user: &intake.IntakeUserResponse{}}, + wantErr: false, + }, + { + name: "yaml output", + args: args{outputFormat: print.YAMLOutputFormat, user: &intake.IntakeUserResponse{}}, + wantErr: false, + }, + { + name: "nil user", + args: args{user: nil}, + wantErr: true, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.user); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/intake/user/list/list.go b/internal/cmd/beta/intake/user/list/list.go new file mode 100644 index 000000000..3c1f57520 --- /dev/null +++ b/internal/cmd/beta/intake/user/list/list.go @@ -0,0 +1,158 @@ +package list + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + intakeIdFlag = "intake-id" + limitFlag = "limit" +) + +// inputModel struct holds all the input parameters for the command +type inputModel struct { + *globalflags.GlobalFlagModel + IntakeId *string + Limit *int64 +} + +// NewCmd creates a new cobra command for listing Intake Users +func NewCmd(p *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all Intake Users", + Long: "Lists all Intake Users for a specific Intake.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List all users for an Intake`, + `$ stackit beta intake user list --intake-id xxx`), + examples.NewExample( + `List all users for an Intake in JSON format`, + `$ stackit beta intake user list --intake-id xxx --output-format json`), + examples.NewExample( + `List up to 5 users for an Intake`, + `$ stackit beta intake user list --intake-id xxx --limit 5`), + ), + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(p.Printer, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p.Printer, p.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("list Intake Users: %w", err) + } + users := resp.GetIntakeUsers() + + // Truncate output + if model.Limit != nil && len(users) > int(*model.Limit) { + users = users[:*model.Limit] + } + + projectLabel := model.ProjectId + if len(users) == 0 { + projectLabel, err = projectname.GetProjectName(ctx, p.Printer, p.CliVersion, cmd) + if err != nil { + p.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + } + } + + return outputResult(p.Printer, model.OutputFormat, projectLabel, *model.IntakeId, users) + }, + } + configureFlags(cmd) + return cmd +} + +// configureFlags adds the flags to the command +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), intakeIdFlag, "Intake ID") + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + + err := flags.MarkFlagsRequired(cmd, intakeIdFlag) + cobra.CheckErr(err) +} + +// parseInput parses the command flags into a standardized model +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &cliErr.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + IntakeId: flags.FlagToStringPointer(p, cmd, intakeIdFlag), + Limit: limit, + } + + p.DebugInputModel(model) + return &model, nil +} + +// buildRequest creates the API request to list Intake Users +func buildRequest(ctx context.Context, model *inputModel, apiClient *intake.APIClient) intake.ApiListIntakeUsersRequest { + req := apiClient.ListIntakeUsers(ctx, model.ProjectId, model.Region, *model.IntakeId) + return req +} + +// outputResult formats the API response and prints it to the console +func outputResult(p *print.Printer, outputFormat, projectLabel, intakeId string, users []intake.IntakeUserResponse) error { + return p.OutputResult(outputFormat, users, func() error { + if len(users) == 0 { + p.Outputf("No intake users found for intake %q in project %q\n", intakeId, projectLabel) + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "DISPLAY NAME", "TYPE", "STATE") + for _, user := range users { + table.AddRow( + user.GetId(), + user.GetDisplayName(), + utils.PtrString(user.Type), + user.GetState(), + ) + } + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/beta/intake/user/list/list_test.go b/internal/cmd/beta/intake/user/list/list_test.go new file mode 100644 index 000000000..f83246a44 --- /dev/null +++ b/internal/cmd/beta/intake/user/list/list_test.go @@ -0,0 +1,226 @@ +package list + +import ( + "context" + "strconv" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +const ( + testRegion = "eu01" +) + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &intake.APIClient{} + testProjectId = uuid.NewString() + testIntakeId = uuid.NewString() + testLimit = int64(5) +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + intakeIdFlag: testIntakeId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + IntakeId: utils.Ptr(testIntakeId), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *intake.ApiListIntakeUsersRequest)) intake.ApiListIntakeUsersRequest { + request := testClient.ListIntakeUsers(testCtx, testProjectId, testRegion, testIntakeId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "with limit", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = strconv.FormatInt(testLimit, 10) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Limit = utils.Ptr(testLimit) + }), + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "intake id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, intakeIdFlag) + }), + isValid: false, + }, + { + description: "intake id invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[intakeIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "limit is zero", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + { + description: "limit is negative", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "-1" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, func(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + return parseInput(p, cmd) + }, tt.expectedModel, nil, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest intake.ApiListIntakeUsersRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + projectLabel string + intakeId string + users []intake.IntakeUserResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default output", + args: args{outputFormat: "default", intakeId: testIntakeId, users: []intake.IntakeUserResponse{}}, + wantErr: false, + }, + { + name: "json output", + args: args{outputFormat: print.JSONOutputFormat, intakeId: testIntakeId, users: []intake.IntakeUserResponse{}}, + wantErr: false, + }, + { + name: "empty slice", + args: args{intakeId: testIntakeId, users: []intake.IntakeUserResponse{}}, + wantErr: false, + }, + { + name: "nil slice", + args: args{intakeId: testIntakeId, users: nil}, + wantErr: false, + }, + { + name: "empty user in slice", + args: args{ + intakeId: testIntakeId, + users: []intake.IntakeUserResponse{{}}, + }, + wantErr: false, + }, + { + name: "with project label", + args: args{ + projectLabel: "my-project", + intakeId: testIntakeId, + users: []intake.IntakeUserResponse{}, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.projectLabel, tt.args.intakeId, tt.args.users); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/intake/user/update/update.go b/internal/cmd/beta/intake/user/update/update.go new file mode 100644 index 000000000..a2b1b881c --- /dev/null +++ b/internal/cmd/beta/intake/user/update/update.go @@ -0,0 +1,170 @@ +package update + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/intake/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + userIdArg = "USER_ID" + + intakeIdFlag = "intake-id" + displayNameFlag = "display-name" + descriptionFlag = "description" + passwordFlag = "password" + userTypeFlag = "type" + labelsFlag = "labels" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + IntakeId string + UserId string + DisplayName *string + Description *string + Password *string + UserType *string + Labels *map[string]string +} + +func NewCmd(p *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("update %s", userIdArg), + Short: "Updates an Intake User", + Long: "Updates an Intake User. Only the specified fields are updated.", + Args: args.SingleArg(userIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Update the display name of an Intake User`, + `$ stackit beta intake user update xxx --intake-id yyy --display-name "new-user-name"`), + examples.NewExample( + `Update the password and description for an Intake User`, + `$ stackit beta intake user update xxx --intake-id yyy --password "NewSecret123\!" --description "Updated description"`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p.Printer, p.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("update Intake User: %w", err) + } + + // Wait for async operation, if async mode not enabled + if !model.Async { + err := spinner.Run(p.Printer, "Updating STACKIT Intake User", func() error { + _, err = wait.CreateOrUpdateIntakeUserWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.IntakeId, model.UserId).WaitWithContext(ctx) + return err + }) + + if err != nil { + return fmt.Errorf("wait for STACKIT Intake User update: %w", err) + } + } + + return outputResult(p.Printer, model, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), intakeIdFlag, "Intake ID") + cmd.Flags().String(displayNameFlag, "", "Display name") + cmd.Flags().String(descriptionFlag, "", "Description") + cmd.Flags().String(passwordFlag, "", "Password for the user. Must contain lower, upper, number, and special characters (min 12 chars)") + cmd.Flags().String(userTypeFlag, "", "Type of user. One of 'intake' or 'dead-letter'") + cmd.Flags().StringToString(labelsFlag, nil, `Labels in key=value format, separated by commas. Example: --labels "key1=value1,key2=value2".`) + + err := flags.MarkFlagsRequired(cmd, intakeIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + userId := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := &inputModel{ + GlobalFlagModel: globalFlags, + IntakeId: flags.FlagToStringValue(p, cmd, intakeIdFlag), + UserId: userId, + DisplayName: flags.FlagToStringPointer(p, cmd, displayNameFlag), + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Password: flags.FlagToStringPointer(p, cmd, passwordFlag), + UserType: flags.FlagToStringPointer(p, cmd, userTypeFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelsFlag), + } + + if model.DisplayName == nil && model.Description == nil && model.Password == nil && model.UserType == nil && model.Labels == nil { + return nil, &cliErr.EmptyUpdateError{} + } + + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *intake.APIClient) intake.ApiUpdateIntakeUserRequest { + req := apiClient.UpdateIntakeUser(ctx, model.ProjectId, model.Region, model.IntakeId, model.UserId) + + payload := intake.UpdateIntakeUserPayload{ + DisplayName: model.DisplayName, + Description: model.Description, + Password: model.Password, + Labels: model.Labels, + } + + if model.UserType != nil { + userType := intake.UserType(*model.UserType) + payload.Type = &userType + } + + req = req.UpdateIntakeUserPayload(payload) + return req +} + +func outputResult(p *print.Printer, model *inputModel, resp *intake.IntakeUserResponse) error { + return p.OutputResult(model.OutputFormat, resp, func() error { + if resp == nil { + p.Outputf("Triggered update of Intake User for intake %q, but no user ID was returned.\n", model.IntakeId) + return nil + } + + operationState := "Updated" + if model.Async { + operationState = "Triggered update of" + } + p.Outputf("%s Intake User for intake %q. User ID: %s\n", operationState, model.IntakeId, utils.PtrString(resp.Id)) + return nil + }) +} diff --git a/internal/cmd/beta/intake/user/update/update_test.go b/internal/cmd/beta/intake/user/update/update_test.go new file mode 100644 index 000000000..f887ac4a0 --- /dev/null +++ b/internal/cmd/beta/intake/user/update/update_test.go @@ -0,0 +1,260 @@ +package update + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +const ( + testRegion = "eu01" +) + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &intake.APIClient{} + testProjectId = uuid.NewString() + testIntakeId = uuid.NewString() + testUserId = uuid.NewString() +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{testUserId} + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + intakeIdFlag: testIntakeId, + displayNameFlag: "new-display-name", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + IntakeId: testIntakeId, + UserId: testUserId, + DisplayName: utils.Ptr("new-display-name"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *intake.ApiUpdateIntakeUserRequest)) intake.ApiUpdateIntakeUserRequest { + request := testClient.UpdateIntakeUser(testCtx, testProjectId, testRegion, testIntakeId, testUserId) + payload := intake.UpdateIntakeUserPayload{ + DisplayName: utils.Ptr("new-display-name"), + } + request = request.UpdateIntakeUserPayload(payload) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no optional flags provided", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + intakeIdFlag: testIntakeId, + }, + isValid: false, + }, + { + description: "update all fields", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[descriptionFlag] = "new description" + flagValues[labelsFlag] = "env=prod,team=sre" + flagValues[userTypeFlag] = "dead-letter" + flagValues[passwordFlag] = "NewSecret123!" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Description = utils.Ptr("new description") + model.Labels = utils.Ptr(map[string]string{"env": "prod", "team": "sre"}) + model.UserType = utils.Ptr("dead-letter") + model.Password = utils.Ptr("NewSecret123!") + }), + }, + { + description: "no args", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "intake-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, intakeIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedReq intake.ApiUpdateIntakeUserRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedReq: fixtureRequest(), + }, + { + description: "update description", + model: fixtureInputModel(func(model *inputModel) { + model.DisplayName = nil + model.Description = utils.Ptr("new-desc") + }), + expectedReq: fixtureRequest(func(request *intake.ApiUpdateIntakeUserRequest) { + payload := intake.UpdateIntakeUserPayload{ + Description: utils.Ptr("new-desc"), + } + *request = (*request).UpdateIntakeUserPayload(payload) + }), + }, + { + description: "update all fields", + model: fixtureInputModel(func(model *inputModel) { + model.DisplayName = utils.Ptr("another-name") + model.Description = utils.Ptr("final-desc") + model.Labels = utils.Ptr(map[string]string{"a": "b"}) + model.UserType = utils.Ptr("dead-letter") + model.Password = utils.Ptr("Secret123!") + }), + expectedReq: fixtureRequest(func(request *intake.ApiUpdateIntakeUserRequest) { + userType := intake.UserType("dead-letter") + payload := intake.UpdateIntakeUserPayload{ + DisplayName: utils.Ptr("another-name"), + Description: utils.Ptr("final-desc"), + Labels: utils.Ptr(map[string]string{"a": "b"}), + Type: &userType, + Password: utils.Ptr("Secret123!"), + } + *request = (*request).UpdateIntakeUserPayload(payload) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(tt.expectedReq, request, + cmp.AllowUnexported(request), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + projectLabel string + intakeId string + resp *intake.IntakeUserResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default output", + args: args{outputFormat: "default", projectLabel: "my-project", intakeId: "intake-id-123", resp: &intake.IntakeUserResponse{}}, + wantErr: false, + }, + { + name: "json output", + args: args{outputFormat: print.JSONOutputFormat, resp: &intake.IntakeUserResponse{Id: utils.Ptr("user-id-123")}}, + wantErr: false, + }, + { + name: "nil response", + args: args{outputFormat: print.JSONOutputFormat, resp: nil}, + wantErr: false, + }, + { + name: "nil response - default output", + args: args{outputFormat: "default", resp: nil}, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{OutputFormat: tt.args.outputFormat}, IntakeId: tt.args.intakeId}, tt.args.resp); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/intake/user/user.go b/internal/cmd/beta/intake/user/user.go new file mode 100644 index 000000000..6e53c457b --- /dev/null +++ b/internal/cmd/beta/intake/user/user.go @@ -0,0 +1,36 @@ +package user + +import ( + "github.com/spf13/cobra" + + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/user/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/user/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/user/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/user/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake/user/update" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "user", + Short: "Provides functionality for Intake Users", + Long: "Provides functionality for Intake Users.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + // Pass the params down to each action command + cmd.AddCommand(create.NewCmd(params)) + cmd.AddCommand(delete.NewCmd(params)) + cmd.AddCommand(describe.NewCmd(params)) + cmd.AddCommand(list.NewCmd(params)) + cmd.AddCommand(update.NewCmd(params)) +} diff --git a/internal/cmd/beta/sfs/export-policy/create/create.go b/internal/cmd/beta/sfs/export-policy/create/create.go index 8732825aa..e640862df 100644 --- a/internal/cmd/beta/sfs/export-policy/create/create.go +++ b/internal/cmd/beta/sfs/export-policy/create/create.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/sfs/client" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/export-policy/create/create_test.go b/internal/cmd/beta/sfs/export-policy/create/create_test.go index f94074b1a..d019f6bd4 100644 --- a/internal/cmd/beta/sfs/export-policy/create/create_test.go +++ b/internal/cmd/beta/sfs/export-policy/create/create_test.go @@ -7,12 +7,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/export-policy/delete/delete.go b/internal/cmd/beta/sfs/export-policy/delete/delete.go index 8ae903d23..a02a994fc 100644 --- a/internal/cmd/beta/sfs/export-policy/delete/delete.go +++ b/internal/cmd/beta/sfs/export-policy/delete/delete.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( sfsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sfs/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const exportPolicyIdArg = "EXPORT_POLICY_ID" diff --git a/internal/cmd/beta/sfs/export-policy/delete/delete_test.go b/internal/cmd/beta/sfs/export-policy/delete/delete_test.go index c38c47ae6..77ea7870a 100644 --- a/internal/cmd/beta/sfs/export-policy/delete/delete_test.go +++ b/internal/cmd/beta/sfs/export-policy/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/export-policy/describe/describe.go b/internal/cmd/beta/sfs/export-policy/describe/describe.go index a9bc7b9b3..4cbed0c22 100644 --- a/internal/cmd/beta/sfs/export-policy/describe/describe.go +++ b/internal/cmd/beta/sfs/export-policy/describe/describe.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const exportPolicyIdArg = "EXPORT_POLICY_ID" diff --git a/internal/cmd/beta/sfs/export-policy/describe/describe_test.go b/internal/cmd/beta/sfs/export-policy/describe/describe_test.go index 73e711879..6b991a0b0 100644 --- a/internal/cmd/beta/sfs/export-policy/describe/describe_test.go +++ b/internal/cmd/beta/sfs/export-policy/describe/describe_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/export-policy/list/list.go b/internal/cmd/beta/sfs/export-policy/list/list.go index 7620dfc33..186f07106 100644 --- a/internal/cmd/beta/sfs/export-policy/list/list.go +++ b/internal/cmd/beta/sfs/export-policy/list/list.go @@ -6,6 +6,8 @@ import ( "strconv" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/export-policy/list/list_test.go b/internal/cmd/beta/sfs/export-policy/list/list_test.go index e2a73ace0..d9e463a46 100644 --- a/internal/cmd/beta/sfs/export-policy/list/list_test.go +++ b/internal/cmd/beta/sfs/export-policy/list/list_test.go @@ -8,12 +8,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/export-policy/update/update.go b/internal/cmd/beta/sfs/export-policy/update/update.go index a0ab0a6dc..5b0e1457a 100644 --- a/internal/cmd/beta/sfs/export-policy/update/update.go +++ b/internal/cmd/beta/sfs/export-policy/update/update.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( sfsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sfs/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/export-policy/update/update_test.go b/internal/cmd/beta/sfs/export-policy/update/update_test.go index 0ca322850..b3c37080c 100644 --- a/internal/cmd/beta/sfs/export-policy/update/update_test.go +++ b/internal/cmd/beta/sfs/export-policy/update/update_test.go @@ -7,12 +7,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/performance-class/list/list.go b/internal/cmd/beta/sfs/performance-class/list/list.go index 0ca22c392..c16ed52e9 100644 --- a/internal/cmd/beta/sfs/performance-class/list/list.go +++ b/internal/cmd/beta/sfs/performance-class/list/list.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) type inputModel struct { diff --git a/internal/cmd/beta/sfs/performance-class/list/list_test.go b/internal/cmd/beta/sfs/performance-class/list/list_test.go index 655159342..696b47e53 100644 --- a/internal/cmd/beta/sfs/performance-class/list/list_test.go +++ b/internal/cmd/beta/sfs/performance-class/list/list_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/resource-pool/create/create.go b/internal/cmd/beta/sfs/resource-pool/create/create.go index 4b97f16bb..38df9acf1 100644 --- a/internal/cmd/beta/sfs/resource-pool/create/create.go +++ b/internal/cmd/beta/sfs/resource-pool/create/create.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" - "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" ) const ( @@ -100,16 +101,16 @@ The available performance class values can be obtained by running: // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Create resource pool") - _, err = wait.CreateResourcePoolWaitHandler(ctx, apiClient, model.ProjectId, model.Region, resourcePoolId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Create resource pool", func() error { + _, err = wait.CreateResourcePoolWaitHandler(ctx, apiClient, model.ProjectId, model.Region, resourcePoolId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for resource pool creation: %w", err) } - s.Stop() } - return outputResult(params.Printer, model.OutputFormat, projectLabel, resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) }, } configureFlags(cmd) @@ -170,13 +171,17 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, return &model, nil } -func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *sfs.CreateResourcePoolResponse) error { +func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, resp *sfs.CreateResourcePoolResponse) error { return p.OutputResult(outputFormat, resp, func() error { if resp == nil || resp.ResourcePool == nil { p.Outputln("Resource pool response is empty") return nil } - p.Outputf("Created resource pool for project %q. Resource pool ID: %s\n", projectLabel, utils.PtrString(resp.ResourcePool.Id)) + operationState := "Created" + if async { + operationState = "Triggered creation of" + } + p.Outputf("%s resource pool for project %q. Resource pool ID: %s\n", operationState, projectLabel, utils.PtrString(resp.ResourcePool.Id)) return nil }) } diff --git a/internal/cmd/beta/sfs/resource-pool/create/create_test.go b/internal/cmd/beta/sfs/resource-pool/create/create_test.go index 5dd65f643..c9ac9ee8a 100644 --- a/internal/cmd/beta/sfs/resource-pool/create/create_test.go +++ b/internal/cmd/beta/sfs/resource-pool/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) type testCtxKey struct{} @@ -252,6 +253,7 @@ func TestBuildRequest(t *testing.T) { func TestOutputResult(t *testing.T) { type args struct { outputFormat string + async bool projectLabel string resp *sfs.CreateResourcePoolResponse } @@ -287,7 +289,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.outputFormat, tt.args.projectLabel, tt.args.resp); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.resp); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/beta/sfs/resource-pool/delete/delete.go b/internal/cmd/beta/sfs/resource-pool/delete/delete.go index 6883c3987..f67d92976 100644 --- a/internal/cmd/beta/sfs/resource-pool/delete/delete.go +++ b/internal/cmd/beta/sfs/resource-pool/delete/delete.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" - "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" ) const ( @@ -73,16 +74,16 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Delete resource pool") - _, err = wait.DeleteResourcePoolWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Delete resource pool", func() error { + _, err = wait.DeleteResourcePoolWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for resource pool deletion: %w", err) } - s.Stop() } - return outputResult(params.Printer, model.OutputFormat, resourcePoolName, resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, resourcePoolName, resp) }, } return cmd @@ -110,9 +111,13 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return &model, nil } -func outputResult(p *print.Printer, outputFormat, resourcePoolName string, response map[string]interface{}) error { +func outputResult(p *print.Printer, outputFormat string, async bool, resourcePoolName string, response map[string]interface{}) error { return p.OutputResult(outputFormat, response, func() error { - p.Outputf("Deleted resource pool %q\n", resourcePoolName) + operationState := "Deleted" + if async { + operationState = "Triggered deletion of" + } + p.Outputf("%s resource pool %q\n", operationState, resourcePoolName) return nil }) } diff --git a/internal/cmd/beta/sfs/resource-pool/delete/delete_test.go b/internal/cmd/beta/sfs/resource-pool/delete/delete_test.go index b41ed2626..439726ffd 100644 --- a/internal/cmd/beta/sfs/resource-pool/delete/delete_test.go +++ b/internal/cmd/beta/sfs/resource-pool/delete/delete_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) type testCtxKey struct{} @@ -175,6 +176,7 @@ func TestBuildRequest(t *testing.T) { func TestOutputResult(t *testing.T) { type args struct { outputFormat string + async bool resourcePoolName string response map[string]interface{} } @@ -201,7 +203,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.outputFormat, tt.args.resourcePoolName, tt.args.response); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.resourcePoolName, tt.args.response); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/beta/sfs/resource-pool/describe/describe.go b/internal/cmd/beta/sfs/resource-pool/describe/describe.go index 0567b950e..c428b53e1 100644 --- a/internal/cmd/beta/sfs/resource-pool/describe/describe.go +++ b/internal/cmd/beta/sfs/resource-pool/describe/describe.go @@ -6,6 +6,8 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/resource-pool/describe/describe_test.go b/internal/cmd/beta/sfs/resource-pool/describe/describe_test.go index 8fddcd529..8f5b832ab 100644 --- a/internal/cmd/beta/sfs/resource-pool/describe/describe_test.go +++ b/internal/cmd/beta/sfs/resource-pool/describe/describe_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sfs/resource-pool/list/list.go b/internal/cmd/beta/sfs/resource-pool/list/list.go index d62dbd7b6..d70ebf5a8 100644 --- a/internal/cmd/beta/sfs/resource-pool/list/list.go +++ b/internal/cmd/beta/sfs/resource-pool/list/list.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/resource-pool/list/list_test.go b/internal/cmd/beta/sfs/resource-pool/list/list_test.go index 4670a7856..082022e68 100644 --- a/internal/cmd/beta/sfs/resource-pool/list/list_test.go +++ b/internal/cmd/beta/sfs/resource-pool/list/list_test.go @@ -7,12 +7,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sfs/resource-pool/update/update.go b/internal/cmd/beta/sfs/resource-pool/update/update.go index a276fb91e..f70b6d7da 100644 --- a/internal/cmd/beta/sfs/resource-pool/update/update.go +++ b/internal/cmd/beta/sfs/resource-pool/update/update.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,8 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" - "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" ) const ( @@ -101,16 +102,16 @@ The available performance class values can be obtained by running: // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Update resource pool") - _, err = wait.UpdateResourcePoolWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Update resource pool", func() error { + _, err = wait.UpdateResourcePoolWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for resource pool update: %w", err) } - s.Stop() } - return outputResult(params.Printer, model.OutputFormat, resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, resp) }, } configureFlags(cmd) @@ -165,13 +166,17 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return &model, nil } -func outputResult(p *print.Printer, outputFormat string, resp *sfs.UpdateResourcePoolResponse) error { +func outputResult(p *print.Printer, outputFormat string, async bool, resp *sfs.UpdateResourcePoolResponse) error { return p.OutputResult(outputFormat, resp, func() error { if resp == nil || resp.ResourcePool == nil { p.Outputln("Resource pool response is empty") return nil } - p.Outputf("Updated resource pool %s\n", utils.PtrString(resp.ResourcePool.Name)) + operationState := "Updated" + if async { + operationState = "Triggered update of" + } + p.Outputf("%s resource pool %s\n", operationState, utils.PtrString(resp.ResourcePool.Name)) return nil }) } diff --git a/internal/cmd/beta/sfs/resource-pool/update/update_test.go b/internal/cmd/beta/sfs/resource-pool/update/update_test.go index c94b79b92..0ed674765 100644 --- a/internal/cmd/beta/sfs/resource-pool/update/update_test.go +++ b/internal/cmd/beta/sfs/resource-pool/update/update_test.go @@ -10,12 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) type testCtxKey struct{} @@ -308,6 +309,7 @@ func TestBuildRequest(t *testing.T) { func TestOutputResult(t *testing.T) { type args struct { outputFormat string + async bool resp *sfs.UpdateResourcePoolResponse } tests := []struct { @@ -353,7 +355,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.outputFormat, tt.args.resp); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.resp); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/beta/sfs/share/create/create.go b/internal/cmd/beta/sfs/share/create/create.go index c2b2246b8..3a565f197 100644 --- a/internal/cmd/beta/sfs/share/create/create.go +++ b/internal/cmd/beta/sfs/share/create/create.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" - "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" ) const ( @@ -91,13 +92,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating share") - _, err = wait.CreateShareWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId, shareId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating share", func() error { + _, err = wait.CreateShareWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId, shareId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("waiting for share creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, resourcePoolLabel, resp) diff --git a/internal/cmd/beta/sfs/share/create/create_test.go b/internal/cmd/beta/sfs/share/create/create_test.go index f535093e3..bbbf1ba42 100644 --- a/internal/cmd/beta/sfs/share/create/create_test.go +++ b/internal/cmd/beta/sfs/share/create/create_test.go @@ -8,12 +8,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/share/delete/delete.go b/internal/cmd/beta/sfs/share/delete/delete.go index aed603d52..c9086fe77 100644 --- a/internal/cmd/beta/sfs/share/delete/delete.go +++ b/internal/cmd/beta/sfs/share/delete/delete.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" - "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" ) const ( @@ -80,13 +81,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting share") - _, err = wait.DeleteShareWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId, model.ShareId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting share", func() error { + _, err = wait.DeleteShareWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId, model.ShareId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("waiting for share deletion: %w", err) } - s.Stop() } operation := "Deleted" diff --git a/internal/cmd/beta/sfs/share/delete/delete_test.go b/internal/cmd/beta/sfs/share/delete/delete_test.go index 60040fc44..a6526653b 100644 --- a/internal/cmd/beta/sfs/share/delete/delete_test.go +++ b/internal/cmd/beta/sfs/share/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/share/describe/describe.go b/internal/cmd/beta/sfs/share/describe/describe.go index f593f9122..9fb2b1f06 100644 --- a/internal/cmd/beta/sfs/share/describe/describe.go +++ b/internal/cmd/beta/sfs/share/describe/describe.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/share/describe/describe_test.go b/internal/cmd/beta/sfs/share/describe/describe_test.go index 889117485..4a5cc4d37 100644 --- a/internal/cmd/beta/sfs/share/describe/describe_test.go +++ b/internal/cmd/beta/sfs/share/describe/describe_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/share/list/list.go b/internal/cmd/beta/sfs/share/list/list.go index e8945cd04..9f0aa1386 100644 --- a/internal/cmd/beta/sfs/share/list/list.go +++ b/internal/cmd/beta/sfs/share/list/list.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/share/list/list_test.go b/internal/cmd/beta/sfs/share/list/list_test.go index 7cff82a0d..8c5ba498a 100644 --- a/internal/cmd/beta/sfs/share/list/list_test.go +++ b/internal/cmd/beta/sfs/share/list/list_test.go @@ -8,12 +8,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/share/update/update.go b/internal/cmd/beta/sfs/share/update/update.go index 7f368fa3d..a13257c81 100644 --- a/internal/cmd/beta/sfs/share/update/update.go +++ b/internal/cmd/beta/sfs/share/update/update.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" - "github.com/stackitcloud/stackit-sdk-go/services/sfs/wait" ) const ( @@ -96,13 +97,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating share") - _, err = wait.UpdateShareWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId, model.ShareId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating share", func() error { + _, err = wait.UpdateShareWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ResourcePoolId, model.ShareId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("waiting for share update: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, resourcePoolLabel, resp) diff --git a/internal/cmd/beta/sfs/share/update/update_test.go b/internal/cmd/beta/sfs/share/update/update_test.go index 99494e905..5a108d9c4 100644 --- a/internal/cmd/beta/sfs/share/update/update_test.go +++ b/internal/cmd/beta/sfs/share/update/update_test.go @@ -8,12 +8,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/snapshot/create/create.go b/internal/cmd/beta/sfs/snapshot/create/create.go index c531009de..5344ec33a 100644 --- a/internal/cmd/beta/sfs/snapshot/create/create.go +++ b/internal/cmd/beta/sfs/snapshot/create/create.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( sfsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sfs/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/snapshot/create/create_test.go b/internal/cmd/beta/sfs/snapshot/create/create_test.go index 9ac34a9a8..209e11c18 100644 --- a/internal/cmd/beta/sfs/snapshot/create/create_test.go +++ b/internal/cmd/beta/sfs/snapshot/create/create_test.go @@ -7,12 +7,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/snapshot/delete/delete.go b/internal/cmd/beta/sfs/snapshot/delete/delete.go index 15e0cec17..fcc74be46 100644 --- a/internal/cmd/beta/sfs/snapshot/delete/delete.go +++ b/internal/cmd/beta/sfs/snapshot/delete/delete.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/sfs/client" sfsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sfs/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/snapshot/delete/delete_test.go b/internal/cmd/beta/sfs/snapshot/delete/delete_test.go index 3a3048b64..db010e22a 100644 --- a/internal/cmd/beta/sfs/snapshot/delete/delete_test.go +++ b/internal/cmd/beta/sfs/snapshot/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/snapshot/describe/describe.go b/internal/cmd/beta/sfs/snapshot/describe/describe.go index 4d45233fd..123e60172 100644 --- a/internal/cmd/beta/sfs/snapshot/describe/describe.go +++ b/internal/cmd/beta/sfs/snapshot/describe/describe.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/snapshot/describe/describe_test.go b/internal/cmd/beta/sfs/snapshot/describe/describe_test.go index f307c1f19..c477bceb7 100644 --- a/internal/cmd/beta/sfs/snapshot/describe/describe_test.go +++ b/internal/cmd/beta/sfs/snapshot/describe/describe_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sfs/snapshot/list/list.go b/internal/cmd/beta/sfs/snapshot/list/list.go index d2caf7b58..cfc0347b8 100644 --- a/internal/cmd/beta/sfs/snapshot/list/list.go +++ b/internal/cmd/beta/sfs/snapshot/list/list.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) const ( diff --git a/internal/cmd/beta/sfs/snapshot/list/list_test.go b/internal/cmd/beta/sfs/snapshot/list/list_test.go index 3c63ed05c..5678f3a55 100644 --- a/internal/cmd/beta/sfs/snapshot/list/list_test.go +++ b/internal/cmd/beta/sfs/snapshot/list/list_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/beta/sqlserverflex/database/create/create.go b/internal/cmd/beta/sqlserverflex/database/create/create.go index 9a2b8c2e0..6539b79e1 100644 --- a/internal/cmd/beta/sqlserverflex/database/create/create.go +++ b/internal/cmd/beta/sqlserverflex/database/create/create.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" "github.com/spf13/cobra" ) @@ -68,14 +69,12 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Call API req := buildRequest(ctx, model, apiClient) - s := spinner.New(params.Printer) - s.Start("Creating database") - resp, err := req.Execute() + resp, err := spinner.Run2(params.Printer, "Creating database", func() (*sqlserverflex.CreateDatabaseResponse, error) { + return req.Execute() + }) if err != nil { - s.StopWithError() return fmt.Errorf("create SQLServer Flex database: %w", err) } - s.Stop() return outputResult(params.Printer, model.OutputFormat, model.DatabaseName, resp) }, diff --git a/internal/cmd/beta/sqlserverflex/database/create/create_test.go b/internal/cmd/beta/sqlserverflex/database/create/create_test.go index f33b7623c..e951a1506 100644 --- a/internal/cmd/beta/sqlserverflex/database/create/create_test.go +++ b/internal/cmd/beta/sqlserverflex/database/create/create_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/database/delete/delete.go b/internal/cmd/beta/sqlserverflex/database/delete/delete.go index 3408cc85d..ae8148ec2 100644 --- a/internal/cmd/beta/sqlserverflex/database/delete/delete.go +++ b/internal/cmd/beta/sqlserverflex/database/delete/delete.go @@ -66,14 +66,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Call API req := buildRequest(ctx, model, apiClient) - s := spinner.New(params.Printer) - s.Start("Deleting database") - err = req.Execute() + err = spinner.Run(params.Printer, "Deleting database", func() error { + err := req.Execute() + return err + }) if err != nil { - s.StopWithError() return fmt.Errorf("delete SQLServer Flex database: %w", err) } - s.Stop() params.Printer.Info("Deleted database %q\n", model.DatabaseName) return nil diff --git a/internal/cmd/beta/sqlserverflex/database/delete/delete_test.go b/internal/cmd/beta/sqlserverflex/database/delete/delete_test.go index ab137dcd0..449731141 100644 --- a/internal/cmd/beta/sqlserverflex/database/delete/delete_test.go +++ b/internal/cmd/beta/sqlserverflex/database/delete/delete_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/beta/sqlserverflex/database/describe/describe.go b/internal/cmd/beta/sqlserverflex/database/describe/describe.go index 7ad000553..6fa5394fe 100644 --- a/internal/cmd/beta/sqlserverflex/database/describe/describe.go +++ b/internal/cmd/beta/sqlserverflex/database/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) const ( diff --git a/internal/cmd/beta/sqlserverflex/database/describe/describe_test.go b/internal/cmd/beta/sqlserverflex/database/describe/describe_test.go index 2a4bc2b01..176741037 100644 --- a/internal/cmd/beta/sqlserverflex/database/describe/describe_test.go +++ b/internal/cmd/beta/sqlserverflex/database/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/database/list/list.go b/internal/cmd/beta/sqlserverflex/database/list/list.go index 439b069df..3287a7981 100644 --- a/internal/cmd/beta/sqlserverflex/database/list/list.go +++ b/internal/cmd/beta/sqlserverflex/database/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) const ( diff --git a/internal/cmd/beta/sqlserverflex/database/list/list_test.go b/internal/cmd/beta/sqlserverflex/database/list/list_test.go index 236f64f78..bd9e6678b 100644 --- a/internal/cmd/beta/sqlserverflex/database/list/list_test.go +++ b/internal/cmd/beta/sqlserverflex/database/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/instance/create/create.go b/internal/cmd/beta/sqlserverflex/instance/create/create.go index 4cc0469e5..34499a3bc 100644 --- a/internal/cmd/beta/sqlserverflex/instance/create/create.go +++ b/internal/cmd/beta/sqlserverflex/instance/create/create.go @@ -122,13 +122,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SQLServer Flex instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, resp) diff --git a/internal/cmd/beta/sqlserverflex/instance/create/create_test.go b/internal/cmd/beta/sqlserverflex/instance/create/create_test.go index 8168cecc6..5916b6b34 100644 --- a/internal/cmd/beta/sqlserverflex/instance/create/create_test.go +++ b/internal/cmd/beta/sqlserverflex/instance/create/create_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/instance/delete/delete.go b/internal/cmd/beta/sqlserverflex/instance/delete/delete.go index 74840b4db..429dbb364 100644 --- a/internal/cmd/beta/sqlserverflex/instance/delete/delete.go +++ b/internal/cmd/beta/sqlserverflex/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SQLServer Flex instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/beta/sqlserverflex/instance/describe/describe_test.go b/internal/cmd/beta/sqlserverflex/instance/describe/describe_test.go index 4fb05fb0a..c653217bf 100644 --- a/internal/cmd/beta/sqlserverflex/instance/describe/describe_test.go +++ b/internal/cmd/beta/sqlserverflex/instance/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/instance/list/list.go b/internal/cmd/beta/sqlserverflex/instance/list/list.go index 7fe756750..379622006 100644 --- a/internal/cmd/beta/sqlserverflex/instance/list/list.go +++ b/internal/cmd/beta/sqlserverflex/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) const ( diff --git a/internal/cmd/beta/sqlserverflex/instance/list/list_test.go b/internal/cmd/beta/sqlserverflex/instance/list/list_test.go index d730fa056..966d4f14f 100644 --- a/internal/cmd/beta/sqlserverflex/instance/list/list_test.go +++ b/internal/cmd/beta/sqlserverflex/instance/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/instance/update/update.go b/internal/cmd/beta/sqlserverflex/instance/update/update.go index 81a4b5008..3a5c497b8 100644 --- a/internal/cmd/beta/sqlserverflex/instance/update/update.go +++ b/internal/cmd/beta/sqlserverflex/instance/update/update.go @@ -113,13 +113,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SQLServer Flex instance update: %w", err) } - s.Stop() } return outputResult(params.Printer, model, instanceLabel, resp) diff --git a/internal/cmd/beta/sqlserverflex/instance/update/update_test.go b/internal/cmd/beta/sqlserverflex/instance/update/update_test.go index 894839d93..925b80494 100644 --- a/internal/cmd/beta/sqlserverflex/instance/update/update_test.go +++ b/internal/cmd/beta/sqlserverflex/instance/update/update_test.go @@ -10,10 +10,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/options/options_test.go b/internal/cmd/beta/sqlserverflex/options/options_test.go index 3a43e1668..716f9a5bd 100644 --- a/internal/cmd/beta/sqlserverflex/options/options_test.go +++ b/internal/cmd/beta/sqlserverflex/options/options_test.go @@ -8,11 +8,12 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/user/create/create.go b/internal/cmd/beta/sqlserverflex/user/create/create.go index 1f3887390..109a0cab6 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) const ( diff --git a/internal/cmd/beta/sqlserverflex/user/create/create_test.go b/internal/cmd/beta/sqlserverflex/user/create/create_test.go index d8e9a8836..fc32a2312 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create_test.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/user/describe/describe_test.go b/internal/cmd/beta/sqlserverflex/user/describe/describe_test.go index 77123e300..0145cd339 100644 --- a/internal/cmd/beta/sqlserverflex/user/describe/describe_test.go +++ b/internal/cmd/beta/sqlserverflex/user/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/user/list/list.go b/internal/cmd/beta/sqlserverflex/user/list/list.go index 24a216bbd..6218b5d15 100644 --- a/internal/cmd/beta/sqlserverflex/user/list/list.go +++ b/internal/cmd/beta/sqlserverflex/user/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) const ( diff --git a/internal/cmd/beta/sqlserverflex/user/list/list_test.go b/internal/cmd/beta/sqlserverflex/user/list/list_test.go index fd7e31df7..37fa59f32 100644 --- a/internal/cmd/beta/sqlserverflex/user/list/list_test.go +++ b/internal/cmd/beta/sqlserverflex/user/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go index 921e39758..7be0952a8 100644 --- a/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go +++ b/internal/cmd/beta/sqlserverflex/user/reset-password/reset_password_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" ) type testCtxKey struct{} diff --git a/internal/cmd/config/list/list.go b/internal/cmd/config/list/list.go index 71e115b29..99112637a 100644 --- a/internal/cmd/config/list/list.go +++ b/internal/cmd/config/list/list.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/goccy/go-yaml" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/config" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/config/profile/import/import.go b/internal/cmd/config/profile/import/import.go index aede97304..3761ec3fa 100644 --- a/internal/cmd/config/profile/import/import.go +++ b/internal/cmd/config/profile/import/import.go @@ -2,6 +2,7 @@ package importProfile import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/config" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" diff --git a/internal/cmd/config/unset/unset.go b/internal/cmd/config/unset/unset.go index bf63e4474..ad57f8113 100644 --- a/internal/cmd/config/unset/unset.go +++ b/internal/cmd/config/unset/unset.go @@ -22,6 +22,7 @@ const ( projectIdFlag = globalflags.ProjectIdFlag regionFlag = globalflags.RegionFlag verbosityFlag = globalflags.VerbosityFlag + assumeYesFlag = globalflags.AssumeYesFlag sessionTimeLimitFlag = "session-time-limit" identityProviderCustomWellKnownConfigurationFlag = "identity-provider-custom-well-known-configuration" @@ -65,6 +66,7 @@ type inputModel struct { ProjectId bool Region bool Verbosity bool + AssumeYes bool SessionTimeLimit bool IdentityProviderCustomEndpoint bool @@ -137,6 +139,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if model.Verbosity { viper.Set(config.VerbosityKey, globalflags.VerbosityDefault) } + if model.AssumeYes { + viper.Set(config.AssumeYesKey, config.AssumeYesDefault) + } if model.SessionTimeLimit { viper.Set(config.SessionTimeLimitKey, config.SessionTimeLimitDefault) @@ -256,6 +261,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(regionFlag, false, "Region") cmd.Flags().Bool(outputFormatFlag, false, "Output format") cmd.Flags().Bool(verbosityFlag, false, "Verbosity of the CLI") + cmd.Flags().Bool(assumeYesFlag, false, "If set, skips all confirmation prompts") cmd.Flags().Bool(sessionTimeLimitFlag, false, fmt.Sprintf("Maximum time before authentication is required again. If unset, defaults to %s", config.SessionTimeLimitDefault)) cmd.Flags().Bool(identityProviderCustomWellKnownConfigurationFlag, false, "Identity Provider well-known OpenID configuration URL. If unset, uses the default identity provider") @@ -300,6 +306,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) *inputModel { ProjectId: flags.FlagToBoolValue(p, cmd, projectIdFlag), Region: flags.FlagToBoolValue(p, cmd, regionFlag), Verbosity: flags.FlagToBoolValue(p, cmd, verbosityFlag), + AssumeYes: flags.FlagToBoolValue(p, cmd, assumeYesFlag), SessionTimeLimit: flags.FlagToBoolValue(p, cmd, sessionTimeLimitFlag), IdentityProviderCustomEndpoint: flags.FlagToBoolValue(p, cmd, identityProviderCustomWellKnownConfigurationFlag), diff --git a/internal/cmd/config/unset/unset_test.go b/internal/cmd/config/unset/unset_test.go index dda5dcaee..49b6160f8 100644 --- a/internal/cmd/config/unset/unset_test.go +++ b/internal/cmd/config/unset/unset_test.go @@ -17,6 +17,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]bool)) map[string]bool outputFormatFlag: true, projectIdFlag: true, verbosityFlag: true, + assumeYesFlag: true, sessionTimeLimitFlag: true, identityProviderCustomWellKnownConfigurationFlag: true, @@ -62,6 +63,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { OutputFormat: true, ProjectId: true, Verbosity: true, + AssumeYes: true, SessionTimeLimit: true, IdentityProviderCustomEndpoint: true, @@ -123,6 +125,7 @@ func TestParseInput(t *testing.T) { model.OutputFormat = false model.ProjectId = false model.Verbosity = false + model.AssumeYes = false model.SessionTimeLimit = false model.IdentityProviderCustomEndpoint = false diff --git a/internal/cmd/curl/curl_test.go b/internal/cmd/curl/curl_test.go index ce2cd3c37..279e52153 100644 --- a/internal/cmd/curl/curl_test.go +++ b/internal/cmd/curl/curl_test.go @@ -15,6 +15,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/spf13/viper" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" diff --git a/internal/cmd/dns/record-set/create/create.go b/internal/cmd/dns/record-set/create/create.go index 2687b34da..1cf8c448b 100644 --- a/internal/cmd/dns/record-set/create/create.go +++ b/internal/cmd/dns/record-set/create/create.go @@ -4,9 +4,13 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-sdk-go/services/dns/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,8 +21,6 @@ import ( dnsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/dns" - "github.com/stackitcloud/stackit-sdk-go/services/dns/wait" ) const ( @@ -89,13 +91,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating record set") - _, err = wait.CreateRecordSetWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId, recordSetId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating record set", func() error { + _, err = wait.CreateRecordSetWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId, recordSetId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for DNS record set creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, zoneLabel, resp) diff --git a/internal/cmd/dns/record-set/create/create_test.go b/internal/cmd/dns/record-set/create/create_test.go index 633d7ec60..43186e1f5 100644 --- a/internal/cmd/dns/record-set/create/create_test.go +++ b/internal/cmd/dns/record-set/create/create_test.go @@ -11,11 +11,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) type testCtxKey struct{} diff --git a/internal/cmd/dns/record-set/delete/delete.go b/internal/cmd/dns/record-set/delete/delete.go index 09337b89e..ff1478ccc 100644 --- a/internal/cmd/dns/record-set/delete/delete.go +++ b/internal/cmd/dns/record-set/delete/delete.go @@ -88,13 +88,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting record set") - _, err = wait.DeleteRecordSetWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId, model.RecordSetId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting record set", func() error { + _, err = wait.DeleteRecordSetWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId, model.RecordSetId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for DNS record set deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/dns/record-set/describe/describe_test.go b/internal/cmd/dns/record-set/describe/describe_test.go index 8f7214918..41d38ff69 100644 --- a/internal/cmd/dns/record-set/describe/describe_test.go +++ b/internal/cmd/dns/record-set/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) type testCtxKey struct{} diff --git a/internal/cmd/dns/record-set/list/list.go b/internal/cmd/dns/record-set/list/list.go index 88a7f5324..dfd02612c 100644 --- a/internal/cmd/dns/record-set/list/list.go +++ b/internal/cmd/dns/record-set/list/list.go @@ -9,6 +9,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( dnsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) const ( diff --git a/internal/cmd/dns/record-set/list/list_test.go b/internal/cmd/dns/record-set/list/list_test.go index af2dc852b..a4751a5b8 100644 --- a/internal/cmd/dns/record-set/list/list_test.go +++ b/internal/cmd/dns/record-set/list/list_test.go @@ -13,12 +13,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) type testCtxKey struct{} diff --git a/internal/cmd/dns/record-set/update/update.go b/internal/cmd/dns/record-set/update/update.go index b9fb0e942..6246ff58f 100644 --- a/internal/cmd/dns/record-set/update/update.go +++ b/internal/cmd/dns/record-set/update/update.go @@ -108,13 +108,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating record set") - _, err = wait.PartialUpdateRecordSetWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId, model.RecordSetId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating record set", func() error { + _, err = wait.PartialUpdateRecordSetWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId, model.RecordSetId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for DNS record set update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/dns/zone/clone/clone.go b/internal/cmd/dns/zone/clone/clone.go index 6bc02546d..de171a2be 100644 --- a/internal/cmd/dns/zone/clone/clone.go +++ b/internal/cmd/dns/zone/clone/clone.go @@ -91,13 +91,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Cloning zone") - _, err = wait.CreateZoneWaitHandler(ctx, apiClient, model.ProjectId, zoneId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Cloning zone", func() error { + _, err = wait.CreateZoneWaitHandler(ctx, apiClient, model.ProjectId, zoneId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for DNS zone cloning: %w", err) } - s.Stop() } return outputResult(params.Printer, model, zoneLabel, resp) diff --git a/internal/cmd/dns/zone/clone/clone_test.go b/internal/cmd/dns/zone/clone/clone_test.go index 4479eea6e..84154b80f 100644 --- a/internal/cmd/dns/zone/clone/clone_test.go +++ b/internal/cmd/dns/zone/clone/clone_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) type testCtxKey struct{} diff --git a/internal/cmd/dns/zone/create/create.go b/internal/cmd/dns/zone/create/create.go index 75ef0a3e5..5b68f97b8 100644 --- a/internal/cmd/dns/zone/create/create.go +++ b/internal/cmd/dns/zone/create/create.go @@ -104,13 +104,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating zone") - _, err = wait.CreateZoneWaitHandler(ctx, apiClient, model.ProjectId, zoneId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating zone", func() error { + _, err = wait.CreateZoneWaitHandler(ctx, apiClient, model.ProjectId, zoneId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for DNS zone creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, resp) diff --git a/internal/cmd/dns/zone/create/create_test.go b/internal/cmd/dns/zone/create/create_test.go index bf26e688a..f4fd21ec6 100644 --- a/internal/cmd/dns/zone/create/create_test.go +++ b/internal/cmd/dns/zone/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) type testCtxKey struct{} diff --git a/internal/cmd/dns/zone/delete/delete.go b/internal/cmd/dns/zone/delete/delete.go index a204a5dbe..9945054ee 100644 --- a/internal/cmd/dns/zone/delete/delete.go +++ b/internal/cmd/dns/zone/delete/delete.go @@ -77,13 +77,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting zone") - _, err = wait.DeleteZoneWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting zone", func() error { + _, err = wait.DeleteZoneWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for DNS zone deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/dns/zone/describe/describe_test.go b/internal/cmd/dns/zone/describe/describe_test.go index a5704bfed..4a3340b47 100644 --- a/internal/cmd/dns/zone/describe/describe_test.go +++ b/internal/cmd/dns/zone/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) type testCtxKey struct{} diff --git a/internal/cmd/dns/zone/list/list_test.go b/internal/cmd/dns/zone/list/list_test.go index e270d69f7..a29d86714 100644 --- a/internal/cmd/dns/zone/list/list_test.go +++ b/internal/cmd/dns/zone/list/list_test.go @@ -13,12 +13,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/dns" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/dns" ) type testCtxKey struct{} diff --git a/internal/cmd/dns/zone/update/update.go b/internal/cmd/dns/zone/update/update.go index a3c31b097..c44ba724b 100644 --- a/internal/cmd/dns/zone/update/update.go +++ b/internal/cmd/dns/zone/update/update.go @@ -100,13 +100,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating zone") - _, err = wait.PartialUpdateZoneWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating zone", func() error { + _, err = wait.PartialUpdateZoneWaitHandler(ctx, apiClient, model.ProjectId, model.ZoneId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for DNS zone update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/git/flavor/list/list.go b/internal/cmd/git/flavor/list/list.go index d28448b6c..29f88573f 100644 --- a/internal/cmd/git/flavor/list/list.go +++ b/internal/cmd/git/flavor/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" ) type inputModel struct { diff --git a/internal/cmd/git/flavor/list/list_test.go b/internal/cmd/git/flavor/list/list_test.go index 66c4ad264..81b4d7553 100644 --- a/internal/cmd/git/flavor/list/list_test.go +++ b/internal/cmd/git/flavor/list/list_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" ) type testCtxKey struct{} diff --git a/internal/cmd/git/instance/create/create.go b/internal/cmd/git/instance/create/create.go index b11bb16a6..94adf0b52 100644 --- a/internal/cmd/git/instance/create/create.go +++ b/internal/cmd/git/instance/create/create.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-sdk-go/services/git/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,9 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" - "github.com/stackitcloud/stackit-sdk-go/services/git/wait" ) const ( @@ -28,7 +28,6 @@ const ( type inputModel struct { *globalflags.GlobalFlagModel - Id *string Name string Flavor string Acl []string @@ -80,20 +79,19 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if err != nil { return fmt.Errorf("create stackit git instance: %w", err) } - model.Id = result.Id // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating stackit git instance") - _, err = wait.CreateGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, *model.Id).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating STACKIT git instance", func() error { + _, err = wait.CreateGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, *result.Id).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for stackit git Instance creation: %w", err) } - s.Stop() } - return outputResult(params.Printer, model, result) + return outputResult(params.Printer, model.OutputFormat, model.Async, model.Name, result) }, } @@ -143,17 +141,20 @@ func createPayload(model *inputModel) git.CreateInstancePayload { } } -func outputResult(p *print.Printer, model *inputModel, resp *git.Instance) error { - if model == nil { - return fmt.Errorf("input model is nil") +func outputResult(p *print.Printer, outputFormat string, async bool, instanceName string, resp *git.Instance) error { + if resp == nil { + return fmt.Errorf("API resp is nil") } - var outputFormat string - if model.GlobalFlagModel != nil { - outputFormat = model.OutputFormat + if resp.Id == nil { + return fmt.Errorf("API resp is missing instance id") } return p.OutputResult(outputFormat, resp, func() error { - p.Outputf("Created instance %q with id %s\n", model.Name, utils.PtrString(model.Id)) + operationState := "Created" + if async { + operationState = "Triggered creation of" + } + p.Outputf("%s instance %q with id %s\n", operationState, instanceName, *resp.Id) return nil }) } diff --git a/internal/cmd/git/instance/create/create_test.go b/internal/cmd/git/instance/create/create_test.go index bde352a95..b1056d786 100644 --- a/internal/cmd/git/instance/create/create_test.go +++ b/internal/cmd/git/instance/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" ) type testCtxKey struct{} @@ -175,8 +176,10 @@ func TestBuildRequest(t *testing.T) { func TestOutputResult(t *testing.T) { type args struct { - model *inputModel - resp *git.Instance + outputFormat string + async bool + instanceName string + resp *git.Instance } tests := []struct { name string @@ -184,30 +187,32 @@ func TestOutputResult(t *testing.T) { wantErr bool }{ { - name: "nil", + name: "nil response", args: args{ - model: nil, - resp: nil, + outputFormat: "", + async: false, + instanceName: "", + resp: nil, }, wantErr: true, }, { name: "empty input", args: args{ - model: &inputModel{}, - resp: &git.Instance{}, + outputFormat: "", + async: false, + instanceName: "", + resp: &git.Instance{Id: utils.Ptr(uuid.NewString())}, }, wantErr: false, }, { name: "output json", args: args{ - model: &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{ - OutputFormat: print.JSONOutputFormat, - }, - }, - resp: nil, + outputFormat: print.JSONOutputFormat, + async: true, + instanceName: testName, + resp: &git.Instance{Id: utils.Ptr(uuid.NewString())}, }, wantErr: false, }, @@ -216,7 +221,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.model, tt.args.resp); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.instanceName, tt.args.resp); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/git/instance/delete/delete.go b/internal/cmd/git/instance/delete/delete.go index 7c056829a..b3d18146c 100644 --- a/internal/cmd/git/instance/delete/delete.go +++ b/internal/cmd/git/instance/delete/delete.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-sdk-go/services/git/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,8 +20,6 @@ import ( gitUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" - "github.com/stackitcloud/stackit-sdk-go/services/git/wait" ) type inputModel struct { @@ -81,13 +82,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting stackit git instance") - _, err = wait.DeleteGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting STACKIT git instance", func() error { + _, err = wait.DeleteGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for stackit git instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/git/instance/describe/describe.go b/internal/cmd/git/instance/describe/describe.go index e90dd0905..1ee2af24d 100644 --- a/internal/cmd/git/instance/describe/describe.go +++ b/internal/cmd/git/instance/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" ) type inputModel struct { diff --git a/internal/cmd/git/instance/describe/describe_test.go b/internal/cmd/git/instance/describe/describe_test.go index 6a0260f92..9b21d375a 100644 --- a/internal/cmd/git/instance/describe/describe_test.go +++ b/internal/cmd/git/instance/describe/describe_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/git" ) type testCtxKey struct{} diff --git a/internal/cmd/git/instance/list/list.go b/internal/cmd/git/instance/list/list.go index 0f7095199..4e8ec548b 100644 --- a/internal/cmd/git/instance/list/list.go +++ b/internal/cmd/git/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" ) type inputModel struct { diff --git a/internal/cmd/git/instance/list/list_test.go b/internal/cmd/git/instance/list/list_test.go index 459165b87..c065bb8da 100644 --- a/internal/cmd/git/instance/list/list_test.go +++ b/internal/cmd/git/instance/list/list_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/git" ) type testCtxKey struct{} diff --git a/internal/cmd/image/create/create.go b/internal/cmd/image/create/create.go index e463bf41e..fd1a20975 100644 --- a/internal/cmd/image/create/create.go +++ b/internal/cmd/image/create/create.go @@ -13,6 +13,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -21,7 +23,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/image/create/create_test.go b/internal/cmd/image/create/create_test.go index aeefdcdaa..bb437946a 100644 --- a/internal/cmd/image/create/create_test.go +++ b/internal/cmd/image/create/create_test.go @@ -11,11 +11,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/image/delete/delete.go b/internal/cmd/image/delete/delete.go index c41746f58..b41431710 100644 --- a/internal/cmd/image/delete/delete.go +++ b/internal/cmd/image/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/image/describe/describe.go b/internal/cmd/image/describe/describe.go index 09f9f86dc..9bcdd4ca5 100644 --- a/internal/cmd/image/describe/describe.go +++ b/internal/cmd/image/describe/describe.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { @@ -96,13 +97,16 @@ func outputResult(p *print.Printer, outputFormat string, resp *iaas.Image) error table := tables.NewTable() if id := resp.Id; id != nil { table.AddRow("ID", *id) + table.AddSeparator() } - table.AddSeparator() - if name := resp.Name; name != nil { table.AddRow("NAME", *name) table.AddSeparator() } + if status := resp.Status; status != nil { + table.AddRow("STATUS", *status) + table.AddSeparator() + } if format := resp.DiskFormat; format != nil { table.AddRow("FORMAT", *format) table.AddSeparator() diff --git a/internal/cmd/image/describe/describe_test.go b/internal/cmd/image/describe/describe_test.go index 256ef1c2a..2d9bd7fb8 100644 --- a/internal/cmd/image/describe/describe_test.go +++ b/internal/cmd/image/describe/describe_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -225,6 +226,33 @@ func TestOutputResult(t *testing.T) { args: args{}, wantErr: true, }, + { + name: "valid value", + args: args{ + resp: &iaas.Image{ + Id: utils.Ptr(uuid.NewString()), + Name: utils.Ptr("Image"), + Status: utils.Ptr("STATUS"), + DiskFormat: utils.Ptr("format"), + MinDiskSize: utils.Ptr(int64(0)), + MinRam: utils.Ptr(int64(0)), + Config: &iaas.ImageConfig{ + Architecture: utils.Ptr("architecture"), + OperatingSystem: utils.Ptr("os"), + OperatingSystemDistro: iaas.NewNullableString(utils.Ptr("os distro")), + OperatingSystemVersion: iaas.NewNullableString(utils.Ptr("0.00.0")), + Uefi: utils.Ptr(true), + }, + Labels: utils.Ptr(map[string]any{ + "label1": true, + "label2": false, + "label3": 42, + "foo": "bar", + }), + }, + }, + wantErr: false, + }, } p := print.NewPrinter() p.Cmd = NewCmd(&types.CmdParams{Printer: p}) diff --git a/internal/cmd/image/list/list.go b/internal/cmd/image/list/list.go index f30ed8a85..ba21fbe84 100644 --- a/internal/cmd/image/list/list.go +++ b/internal/cmd/image/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,18 +19,19 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { *globalflags.GlobalFlagModel LabelSelector *string Limit *int64 + All *bool } const ( labelSelectorFlag = "label-selector" limitFlag = "limit" + allFlag = "all" ) func NewCmd(params *types.CmdParams) *cobra.Command { @@ -39,7 +42,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `List all images`, + `List images in your project`, `$ stackit image list`, ), examples.NewExample( @@ -50,6 +53,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `List the first 10 images`, `$ stackit image list --limit=10`, ), + examples.NewExample( + `List all images`, + `$ stackit image list --all`, + ), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -102,6 +109,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().String(labelSelectorFlag, "", "Filter by label") cmd.Flags().Int64(limitFlag, 0, "Limit the output to the first n elements") + cmd.Flags().Bool(allFlag, false, "List all images available") } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { @@ -122,6 +130,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, GlobalFlagModel: globalFlags, LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag), Limit: limit, + All: flags.FlagToBoolPointer(p, cmd, allFlag), } p.DebugInputModel(model) @@ -133,13 +142,17 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli if model.LabelSelector != nil { request = request.LabelSelector(*model.LabelSelector) } + if model.All != nil { + request = request.All(*model.All) + } return request } + func outputResult(p *print.Printer, outputFormat string, items []iaas.Image) error { return p.OutputResult(outputFormat, items, func() error { table := tables.NewTable() - table.SetHeader("ID", "NAME", "OS", "ARCHITECTURE", "DISTRIBUTION", "VERSION", "LABELS") + table.SetHeader("ID", "NAME", "OS", "ARCHITECTURE", "DISTRIBUTION", "VERSION", "SCOPE", "OWNER", "LABELS") for i := range items { item := items[i] var ( @@ -147,6 +160,8 @@ func outputResult(p *print.Printer, outputFormat string, items []iaas.Image) err os = "n/a" distro = "n/a" version = "n/a" + owner = "n/a" + scope = "n/a" ) if cfg := item.Config; cfg != nil { if v := cfg.Architecture; v != nil { @@ -162,12 +177,21 @@ func outputResult(p *print.Printer, outputFormat string, items []iaas.Image) err version = *v.Get() } } + if v := item.GetOwner(); v != "" { + owner = v + } + if v := item.GetScope(); v != "" { + scope = v + } + table.AddRow(utils.PtrString(item.Id), utils.PtrString(item.Name), os, architecture, distro, version, + scope, + owner, utils.JoinStringKeysPtr(*item.Labels, ",")) } err := table.Display(p) diff --git a/internal/cmd/image/update/update.go b/internal/cmd/image/update/update.go index 26d8ca088..f039a1fac 100644 --- a/internal/cmd/image/update/update.go +++ b/internal/cmd/image/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type imageConfig struct { diff --git a/internal/cmd/key-pair/delete/delete_test.go b/internal/cmd/key-pair/delete/delete_test.go index bb45798c9..47f75577a 100644 --- a/internal/cmd/key-pair/delete/delete_test.go +++ b/internal/cmd/key-pair/delete/delete_test.go @@ -6,9 +6,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/key-pair/key-pair.go b/internal/cmd/key-pair/key-pair.go index e435a27df..0ac0c7015 100644 --- a/internal/cmd/key-pair/key-pair.go +++ b/internal/cmd/key-pair/key-pair.go @@ -2,6 +2,7 @@ package keypair import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/cmd/key-pair/create" diff --git a/internal/cmd/beta/kms/key/create/create.go b/internal/cmd/kms/key/create/create.go similarity index 77% rename from internal/cmd/beta/kms/key/create/create.go rename to internal/cmd/kms/key/create/create.go index de64af6a7..740deffa9 100644 --- a/internal/cmd/beta/kms/key/create/create.go +++ b/internal/cmd/kms/key/create/create.go @@ -2,25 +2,25 @@ package create import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" - "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" ) const ( @@ -55,22 +55,22 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Create a symmetric AES key (AES-256) with the name "symm-aes-gcm" under the key ring "my-keyring-id"`, - `$ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "aes_256_gcm" --name "symm-aes-gcm" --purpose "symmetric_encrypt_decrypt" --protection "software"`), + `$ stackit kms key create --keyring-id "my-keyring-id" --algorithm "aes_256_gcm" --name "symm-aes-gcm" --purpose "symmetric_encrypt_decrypt" --protection "software"`), examples.NewExample( `Create an asymmetric RSA encryption key (RSA-2048)`, - `$ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "prod-orders-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software"`), + `$ stackit kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "prod-orders-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software"`), examples.NewExample( `Create a message authentication key (HMAC-SHA512)`, - `$ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "hmac_sha512" --name "api-mac-key" --purpose "message_authentication_code" --protection "software"`), + `$ stackit kms key create --keyring-id "my-keyring-id" --algorithm "hmac_sha512" --name "api-mac-key" --purpose "message_authentication_code" --protection "software"`), examples.NewExample( `Create an ECDSA P-256 key for signing & verification`, - `$ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "ecdsa_p256_sha256" --name "signing-ecdsa-p256" --purpose "asymmetric_sign_verify" --protection "software"`), + `$ stackit kms key create --keyring-id "my-keyring-id" --algorithm "ecdsa_p256_sha256" --name "signing-ecdsa-p256" --purpose "asymmetric_sign_verify" --protection "software"`), examples.NewExample( `Create an import-only key (versions must be imported)`, - `$ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "ext-managed-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --import-only`), + `$ stackit kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "ext-managed-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --import-only`), examples.NewExample( `Create a key and print the result as YAML`, - `$ stackit beta kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "yaml-output-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --output yaml`), + `$ stackit kms key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256" --name "yaml-output-rsa" --purpose "asymmetric_encrypt_decrypt" --protection "software" --output yaml`), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -99,13 +99,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating key") - _, err = wait.CreateOrUpdateKeyWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, *resp.Id).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating key", func() error { + _, err = wait.CreateOrUpdateKeyWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, *resp.Id).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for KMS key creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, resp) @@ -159,29 +159,14 @@ func outputResult(p *print.Printer, model *inputModel, resp *kms.Key) error { return fmt.Errorf("response is nil") } - switch model.OutputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(model.OutputFormat, resp, func() error { operationState := "Created" if model.Async { operationState = "Triggered creation of" } p.Outputf("%s the KMS key %q. Key ID: %s\n", operationState, utils.PtrString(resp.DisplayName), utils.PtrString(resp.Id)) - } - return nil + return nil + }) } func configureFlags(cmd *cobra.Command) { diff --git a/internal/cmd/beta/kms/key/create/create_test.go b/internal/cmd/kms/key/create/create_test.go similarity index 99% rename from internal/cmd/beta/kms/key/create/create_test.go rename to internal/cmd/kms/key/create/create_test.go index 76c04dc6b..ce125a298 100644 --- a/internal/cmd/beta/kms/key/create/create_test.go +++ b/internal/cmd/kms/key/create/create_test.go @@ -10,10 +10,11 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/key/delete/delete.go b/internal/cmd/kms/key/delete/delete.go similarity index 86% rename from internal/cmd/beta/kms/key/delete/delete.go rename to internal/cmd/kms/key/delete/delete.go index d7a6e02f6..63bc78763 100644 --- a/internal/cmd/beta/kms/key/delete/delete.go +++ b/internal/cmd/kms/key/delete/delete.go @@ -2,13 +2,12 @@ package delete import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,8 +17,9 @@ import ( kmsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-sdk-go/services/kms" + + "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" ) const ( @@ -43,7 +43,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Delete a KMS key "MY_KEY_ID" inside the key ring "my-keyring-id"`, - `$ stackit beta kms key delete "MY_KEY_ID" --keyring-id "my-keyring-id"`), + `$ stackit kms key delete "MY_KEY_ID" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -127,22 +127,8 @@ func outputResult(p *print.Printer, outputFormat string, resp *kms.Key) error { return fmt.Errorf("response is nil") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal output to JSON: %w", err) - } - p.Outputln(string(details)) - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal output to YAML: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, resp, func() error { p.Outputf("Deletion of KMS key %s scheduled successfully for the deletion date: %s\n", utils.PtrString(resp.DisplayName), utils.PtrString(resp.DeletionDate)) - } - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/key/delete/delete_test.go b/internal/cmd/kms/key/delete/delete_test.go similarity index 99% rename from internal/cmd/beta/kms/key/delete/delete_test.go rename to internal/cmd/kms/key/delete/delete_test.go index 78a2fee98..8f387a048 100644 --- a/internal/cmd/beta/kms/key/delete/delete_test.go +++ b/internal/cmd/kms/key/delete/delete_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/key/describe/describe.go b/internal/cmd/kms/key/describe/describe.go similarity index 98% rename from internal/cmd/beta/kms/key/describe/describe.go rename to internal/cmd/kms/key/describe/describe.go index 4f036c374..1ddec4c7a 100644 --- a/internal/cmd/beta/kms/key/describe/describe.go +++ b/internal/cmd/kms/key/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -39,7 +40,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Describe a KMS key with ID xxx of keyring yyy`, - `$ stackit beta kms key describe xxx --keyring-id yyy`, + `$ stackit kms key describe xxx --keyring-id yyy`, ), ), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/cmd/beta/kms/key/describe/describe_test.go b/internal/cmd/kms/key/describe/describe_test.go similarity index 99% rename from internal/cmd/beta/kms/key/describe/describe_test.go rename to internal/cmd/kms/key/describe/describe_test.go index 6a34e5c74..62d8b45ab 100644 --- a/internal/cmd/beta/kms/key/describe/describe_test.go +++ b/internal/cmd/kms/key/describe/describe_test.go @@ -12,11 +12,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/kms/key/importKey/importKey.go b/internal/cmd/kms/key/importKey/importKey.go similarity index 86% rename from internal/cmd/beta/kms/key/importKey/importKey.go rename to internal/cmd/kms/key/importKey/importKey.go index 38010860e..8e83b9681 100644 --- a/internal/cmd/beta/kms/key/importKey/importKey.go +++ b/internal/cmd/kms/key/importKey/importKey.go @@ -3,24 +3,24 @@ package importKey import ( "context" "encoding/base64" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" kmsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -48,10 +48,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Import a new version for the given KMS key "MY_KEY_ID" from literal value`, - `$ stackit beta kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "BASE64_VALUE" --wrapping-key-id "MY_WRAPPING_KEY_ID"`), + `$ stackit kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "BASE64_VALUE" --wrapping-key-id "MY_WRAPPING_KEY_ID"`), examples.NewExample( `Import from a file`, - `$ stackit beta kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "@path/to/wrapped.key.b64" --wrapping-key-id "MY_WRAPPING_KEY_ID"`, + `$ stackit kms key import "MY_KEY_ID" --keyring-id "my-keyring-id" --wrapped-key "@path/to/wrapped.key.b64" --wrapping-key-id "MY_WRAPPING_KEY_ID"`, ), ), @@ -148,26 +148,10 @@ func outputResult(p *print.Printer, outputFormat, keyRingName, keyName string, r return fmt.Errorf("response is nil") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, resp, func() error { p.Outputf("Imported a new version for the key %q inside the key ring %q\n", keyName, keyRingName) - } - - return nil + return nil + }) } func configureFlags(cmd *cobra.Command) { diff --git a/internal/cmd/beta/kms/key/importKey/importKey_test.go b/internal/cmd/kms/key/importKey/importKey_test.go similarity index 99% rename from internal/cmd/beta/kms/key/importKey/importKey_test.go rename to internal/cmd/kms/key/importKey/importKey_test.go index 378f34ea0..6e19027c3 100644 --- a/internal/cmd/beta/kms/key/importKey/importKey_test.go +++ b/internal/cmd/kms/key/importKey/importKey_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/key/key.go b/internal/cmd/kms/key/key.go similarity index 62% rename from internal/cmd/beta/kms/key/key.go rename to internal/cmd/kms/key/key.go index d1ae57511..4ebf30501 100644 --- a/internal/cmd/beta/kms/key/key.go +++ b/internal/cmd/kms/key/key.go @@ -1,13 +1,13 @@ package key import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key/create" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key/importKey" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key/restore" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key/rotate" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key/importKey" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key/restore" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key/rotate" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" diff --git a/internal/cmd/beta/kms/key/list/list.go b/internal/cmd/kms/key/list/list.go similarity index 83% rename from internal/cmd/beta/kms/key/list/list.go rename to internal/cmd/kms/key/list/list.go index 576463689..244fb40a0 100644 --- a/internal/cmd/beta/kms/key/list/list.go +++ b/internal/cmd/kms/key/list/list.go @@ -2,13 +2,13 @@ package list import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -39,10 +38,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `List all KMS keys for the key ring "my-keyring-id"`, - `$ stackit beta kms key list --keyring-id "my-keyring-id"`), + `$ stackit kms key list --keyring-id "my-keyring-id"`), examples.NewExample( `List all KMS keys in JSON format`, - `$ stackit beta kms key list --keyring-id "my-keyring-id" --output-format json`), + `$ stackit kms key list --keyring-id "my-keyring-id" --output-format json`), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -106,22 +105,7 @@ func outputResult(p *print.Printer, outputFormat, projectId, keyRingId string, r keys := *resp.Keys - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(keys, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS Keys list: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(keys, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS Keys list: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, keys, func() error { if len(keys) == 0 { p.Outputf("No keys found for project %q under the key ring %q\n", projectId, keyRingId) return nil @@ -144,6 +128,6 @@ func outputResult(p *print.Printer, outputFormat, projectId, keyRingId string, r if err != nil { return fmt.Errorf("render table: %w", err) } - } - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/key/list/list_test.go b/internal/cmd/kms/key/list/list_test.go similarity index 99% rename from internal/cmd/beta/kms/key/list/list_test.go rename to internal/cmd/kms/key/list/list_test.go index 74491ae07..9f7b83b18 100644 --- a/internal/cmd/beta/kms/key/list/list_test.go +++ b/internal/cmd/kms/key/list/list_test.go @@ -10,9 +10,10 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/key/restore/restore.go b/internal/cmd/kms/key/restore/restore.go similarity index 86% rename from internal/cmd/beta/kms/key/restore/restore.go rename to internal/cmd/kms/key/restore/restore.go index c4fc71173..e6825cb92 100644 --- a/internal/cmd/beta/kms/key/restore/restore.go +++ b/internal/cmd/kms/key/restore/restore.go @@ -2,13 +2,12 @@ package restore import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,8 +17,9 @@ import ( kmsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-sdk-go/services/kms" + + "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" ) const ( @@ -43,7 +43,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Restore a KMS key "MY_KEY_ID" inside the key ring "my-keyring-id" that was scheduled for deletion.`, - `$ stackit beta kms key restore "MY_KEY_ID" --keyring-id "my-keyring-id"`), + `$ stackit kms key restore "MY_KEY_ID" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -126,22 +126,8 @@ func outputResult(p *print.Printer, outputFormat string, resp *kms.Key) error { return fmt.Errorf("response is nil") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal output to JSON: %w", err) - } - p.Outputln(string(details)) - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal output to YAML: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, resp, func() error { p.Outputf("Successfully restored KMS key %q\n", utils.PtrString(resp.DisplayName)) - } - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/key/restore/restore_test.go b/internal/cmd/kms/key/restore/restore_test.go similarity index 99% rename from internal/cmd/beta/kms/key/restore/restore_test.go rename to internal/cmd/kms/key/restore/restore_test.go index 9c75b8ec0..d15db6938 100644 --- a/internal/cmd/beta/kms/key/restore/restore_test.go +++ b/internal/cmd/kms/key/restore/restore_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/key/rotate/rotate.go b/internal/cmd/kms/key/rotate/rotate.go similarity index 85% rename from internal/cmd/beta/kms/key/rotate/rotate.go rename to internal/cmd/kms/key/rotate/rotate.go index 972234e7e..1f2332572 100644 --- a/internal/cmd/beta/kms/key/rotate/rotate.go +++ b/internal/cmd/kms/key/rotate/rotate.go @@ -2,13 +2,12 @@ package rotate import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,9 +16,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" kmsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/utils" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -43,7 +43,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Rotate a KMS key "MY_KEY_ID" and increase its version inside the key ring "my-keyring-id".`, - `$ stackit beta kms key rotate "MY_KEY_ID" --keyring-id "my-keyring-id"`), + `$ stackit kms key rotate "MY_KEY_ID" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -120,24 +120,8 @@ func outputResult(p *print.Printer, outputFormat string, resp *kms.Version) erro return fmt.Errorf("response is nil") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key version: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key version: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, resp, func() error { p.Outputf("Rotated key %s\n", utils.PtrString(resp.KeyId)) - } - - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/key/rotate/rotate_test.go b/internal/cmd/kms/key/rotate/rotate_test.go similarity index 99% rename from internal/cmd/beta/kms/key/rotate/rotate_test.go rename to internal/cmd/kms/key/rotate/rotate_test.go index 18965764d..6211e8fa5 100644 --- a/internal/cmd/beta/kms/key/rotate/rotate_test.go +++ b/internal/cmd/kms/key/rotate/rotate_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/keyring/create/create.go b/internal/cmd/kms/keyring/create/create.go similarity index 82% rename from internal/cmd/beta/kms/keyring/create/create.go rename to internal/cmd/kms/keyring/create/create.go index 0e773b364..2d031e85e 100644 --- a/internal/cmd/beta/kms/keyring/create/create.go +++ b/internal/cmd/kms/keyring/create/create.go @@ -2,25 +2,25 @@ package create import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" - "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" ) const ( @@ -43,13 +43,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Create a KMS key ring with name "my-keyring"`, - "$ stackit beta kms keyring create --name my-keyring"), + "$ stackit kms keyring create --name my-keyring"), examples.NewExample( `Create a KMS key ring with a description`, - "$ stackit beta kms keyring create --name my-keyring --description my-description"), + "$ stackit kms keyring create --name my-keyring --description my-description"), examples.NewExample( `Create a KMS key ring and print the result as YAML`, - "$ stackit beta kms keyring create --name my-keyring -o yaml"), + "$ stackit kms keyring create --name my-keyring -o yaml"), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -86,13 +86,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating key ring") - _, err = wait.CreateKeyRingWaitHandler(ctx, apiClient, model.ProjectId, model.Region, keyRingId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating key ring", func() error { + _, err = wait.CreateKeyRingWaitHandler(ctx, apiClient, model.ProjectId, model.Region, keyRingId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for KMS key ring creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, keyRing) @@ -147,29 +147,14 @@ func outputResult(p *print.Printer, model *inputModel, resp *kms.KeyRing) error return fmt.Errorf("response is nil") } - switch model.OutputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key ring: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key ring: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(model.OutputFormat, resp, func() error { operationState := "Created" if model.Async { operationState = "Triggered creation of" } p.Outputf("%s key ring. KMS key ring ID: %s\n", operationState, utils.PtrString(resp.Id)) - } - return nil + return nil + }) } func configureFlags(cmd *cobra.Command) { diff --git a/internal/cmd/beta/kms/keyring/create/create_test.go b/internal/cmd/kms/keyring/create/create_test.go similarity index 99% rename from internal/cmd/beta/kms/keyring/create/create_test.go rename to internal/cmd/kms/keyring/create/create_test.go index c4b307859..ae7111505 100644 --- a/internal/cmd/beta/kms/keyring/create/create_test.go +++ b/internal/cmd/kms/keyring/create/create_test.go @@ -10,10 +10,11 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/keyring/delete/delete.go b/internal/cmd/kms/keyring/delete/delete.go similarity index 98% rename from internal/cmd/beta/kms/keyring/delete/delete.go rename to internal/cmd/kms/keyring/delete/delete.go index 6e028c75b..03393c270 100644 --- a/internal/cmd/beta/kms/keyring/delete/delete.go +++ b/internal/cmd/kms/keyring/delete/delete.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,9 +15,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" kmsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/utils" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -37,7 +39,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Delete a KMS key ring with ID "MY_KEYRING_ID"`, - `$ stackit beta kms keyring delete "MY_KEYRING_ID"`), + `$ stackit kms keyring delete "MY_KEYRING_ID"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() diff --git a/internal/cmd/beta/kms/keyring/delete/delete_test.go b/internal/cmd/kms/keyring/delete/delete_test.go similarity index 99% rename from internal/cmd/beta/kms/keyring/delete/delete_test.go rename to internal/cmd/kms/keyring/delete/delete_test.go index 4881e63e0..0d950b0d8 100644 --- a/internal/cmd/beta/kms/keyring/delete/delete_test.go +++ b/internal/cmd/kms/keyring/delete/delete_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/keyring/describe/describe.go b/internal/cmd/kms/keyring/describe/describe.go similarity index 98% rename from internal/cmd/beta/kms/keyring/describe/describe.go rename to internal/cmd/kms/keyring/describe/describe.go index ed90cee8d..c5c54cc7d 100644 --- a/internal/cmd/beta/kms/keyring/describe/describe.go +++ b/internal/cmd/kms/keyring/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -36,7 +37,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Describe a KMS key ring with ID xxx`, - `$ stackit beta kms keyring describe xxx`, + `$ stackit kms keyring describe xxx`, ), ), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/cmd/beta/kms/keyring/describe/describe_test.go b/internal/cmd/kms/keyring/describe/describe_test.go similarity index 99% rename from internal/cmd/beta/kms/keyring/describe/describe_test.go rename to internal/cmd/kms/keyring/describe/describe_test.go index 8c0a309f5..cc0a8abdb 100644 --- a/internal/cmd/beta/kms/keyring/describe/describe_test.go +++ b/internal/cmd/kms/keyring/describe/describe_test.go @@ -12,11 +12,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/kms/keyring/keyring.go b/internal/cmd/kms/keyring/keyring.go similarity index 71% rename from internal/cmd/beta/kms/keyring/keyring.go rename to internal/cmd/kms/keyring/keyring.go index 8683a6907..237991e5c 100644 --- a/internal/cmd/beta/kms/keyring/keyring.go +++ b/internal/cmd/kms/keyring/keyring.go @@ -1,10 +1,10 @@ package keyring import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/keyring/create" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/keyring/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/keyring/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/keyring/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/keyring/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/keyring/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/keyring/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/keyring/list" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" diff --git a/internal/cmd/beta/kms/keyring/list/list.go b/internal/cmd/kms/keyring/list/list.go similarity index 81% rename from internal/cmd/beta/kms/keyring/list/list.go rename to internal/cmd/kms/keyring/list/list.go index 240992c43..4b3b09d67 100644 --- a/internal/cmd/beta/kms/keyring/list/list.go +++ b/internal/cmd/kms/keyring/list/list.go @@ -2,13 +2,13 @@ package list import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) type inputModel struct { @@ -33,10 +32,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `List all KMS key rings`, - "$ stackit beta kms keyring list"), + "$ stackit kms keyring list"), examples.NewExample( `List all KMS key rings in JSON format`, - "$ stackit beta kms keyring list --output-format json"), + "$ stackit kms keyring list --output-format json"), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -91,22 +90,7 @@ func outputResult(p *print.Printer, outputFormat, projectId string, resp *kms.Ke keyRings := *resp.KeyRings - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(keyRings, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key rings list: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(keyRings, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key rings list: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, keyRings, func() error { if len(keyRings) == 0 { p.Outputf("No key rings found for project %q\n", projectId) return nil @@ -128,7 +112,6 @@ func outputResult(p *print.Printer, outputFormat, projectId string, resp *kms.Ke if err != nil { return fmt.Errorf("render table: %w", err) } - } - - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/keyring/list/list_test.go b/internal/cmd/kms/keyring/list/list_test.go similarity index 99% rename from internal/cmd/beta/kms/keyring/list/list_test.go rename to internal/cmd/kms/keyring/list/list_test.go index d4e74c414..7fc3122fc 100644 --- a/internal/cmd/beta/kms/keyring/list/list_test.go +++ b/internal/cmd/kms/keyring/list/list_test.go @@ -10,9 +10,10 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/kms.go b/internal/cmd/kms/kms.go similarity index 72% rename from internal/cmd/beta/kms/kms.go rename to internal/cmd/kms/kms.go index 1adfc3004..46dc38c0b 100644 --- a/internal/cmd/beta/kms/kms.go +++ b/internal/cmd/kms/kms.go @@ -1,10 +1,10 @@ package kms import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/key" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/keyring" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/version" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/wrappingkey" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/key" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/keyring" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/version" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/wrappingkey" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" diff --git a/internal/cmd/beta/kms/version/destroy/destroy.go b/internal/cmd/kms/version/destroy/destroy.go similarity index 86% rename from internal/cmd/beta/kms/version/destroy/destroy.go rename to internal/cmd/kms/version/destroy/destroy.go index b33d5d5b6..bc56351c0 100644 --- a/internal/cmd/beta/kms/version/destroy/destroy.go +++ b/internal/cmd/kms/version/destroy/destroy.go @@ -2,14 +2,14 @@ package destroy import ( "context" - "encoding/json" "fmt" "strconv" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -44,7 +43,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Destroy key version "42" for the key "my-key-id" inside the key ring "my-keyring-id"`, - `$ stackit beta kms version destroy 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), + `$ stackit kms version destroy 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -125,24 +124,8 @@ func outputResult(p *print.Printer, outputFormat string, resp *kms.Version) erro return fmt.Errorf("response is nil") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, resp, func() error { p.Outputf("Destroyed version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId)) - } - - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/version/destroy/destroy_test.go b/internal/cmd/kms/version/destroy/destroy_test.go similarity index 99% rename from internal/cmd/beta/kms/version/destroy/destroy_test.go rename to internal/cmd/kms/version/destroy/destroy_test.go index 2dde6cd9b..0b2816315 100644 --- a/internal/cmd/beta/kms/version/destroy/destroy_test.go +++ b/internal/cmd/kms/version/destroy/destroy_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/version/disable/disable.go b/internal/cmd/kms/version/disable/disable.go similarity index 77% rename from internal/cmd/beta/kms/version/disable/disable.go rename to internal/cmd/kms/version/disable/disable.go index 9260c8e6a..00d92b3f9 100644 --- a/internal/cmd/beta/kms/version/disable/disable.go +++ b/internal/cmd/kms/version/disable/disable.go @@ -2,14 +2,15 @@ package disable import ( "context" - "encoding/json" "fmt" "strconv" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,8 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" - "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" ) const ( @@ -46,7 +45,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Disable key version "42" for the key "my-key-id" inside the key ring "my-keyring-id"`, - `$ stackit beta kms version disable 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), + `$ stackit kms version disable 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -72,13 +71,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Disabling key version") - _, err = wait.DisableKeyVersionWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, model.KeyId, model.VersionNumber).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Disabling key version", func() error { + _, err = wait.DisableKeyVersionWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, model.KeyId, model.VersionNumber).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for key version to be disabled: %w", err) } - s.Stop() } // Get the key version in its state afterwards @@ -87,7 +86,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { params.Printer.Debug(print.ErrorLevel, "get key version: %v", err) } - return outputResult(params.Printer, model.OutputFormat, resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, resp) }, } @@ -133,29 +132,17 @@ func configureFlags(cmd *cobra.Command) { cobra.CheckErr(err) } -func outputResult(p *print.Printer, outputFormat string, resp *kms.Version) error { +func outputResult(p *print.Printer, outputFormat string, async bool, resp *kms.Version) error { if resp == nil { return fmt.Errorf("response is nil") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) + return p.OutputResult(outputFormat, resp, func() error { + operationState := "Disabled" + if async { + operationState = "Triggered disable of" } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - default: - p.Outputf("Disabled version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId)) - } - - return nil + p.Outputf("%s version %d of the key %q\n", operationState, utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId)) + return nil + }) } diff --git a/internal/cmd/beta/kms/version/disable/disable_test.go b/internal/cmd/kms/version/disable/disable_test.go similarity index 98% rename from internal/cmd/beta/kms/version/disable/disable_test.go rename to internal/cmd/kms/version/disable/disable_test.go index 8108ea4b9..ad81db570 100644 --- a/internal/cmd/beta/kms/version/disable/disable_test.go +++ b/internal/cmd/kms/version/disable/disable_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -282,6 +283,7 @@ func TestOutputResult(t *testing.T) { description string wantErr bool outputFormat string + async bool resp *kms.Version }{ { @@ -312,7 +314,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := outputResult(p, tt.outputFormat, tt.resp) + err := outputResult(p, tt.outputFormat, tt.async, tt.resp) if (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/internal/cmd/beta/kms/version/enable/enable.go b/internal/cmd/kms/version/enable/enable.go similarity index 77% rename from internal/cmd/beta/kms/version/enable/enable.go rename to internal/cmd/kms/version/enable/enable.go index 06d8a85ec..909cf3a32 100644 --- a/internal/cmd/beta/kms/version/enable/enable.go +++ b/internal/cmd/kms/version/enable/enable.go @@ -2,14 +2,15 @@ package enable import ( "context" - "encoding/json" "fmt" "strconv" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,8 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" - "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" ) const ( @@ -46,7 +45,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Enable key version "42" for the key "my-key-id" inside the key ring "my-keyring-id"`, - `$ stackit beta kms version enable 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), + `$ stackit kms version enable 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -72,13 +71,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Enabling key version") - _, err = wait.EnableKeyVersionWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, model.KeyId, model.VersionNumber).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Enabling key version", func() error { + _, err = wait.EnableKeyVersionWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.KeyRingId, model.KeyId, model.VersionNumber).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for key version to be enabled: %w", err) } - s.Stop() } // Get the key version in its state afterwards @@ -87,7 +86,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { params.Printer.Debug(print.ErrorLevel, "get key version: %v", err) } - return outputResult(params.Printer, model.OutputFormat, resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, resp) }, } @@ -133,29 +132,17 @@ func configureFlags(cmd *cobra.Command) { cobra.CheckErr(err) } -func outputResult(p *print.Printer, outputFormat string, resp *kms.Version) error { +func outputResult(p *print.Printer, outputFormat string, async bool, resp *kms.Version) error { if resp == nil { return fmt.Errorf("response is nil") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) + return p.OutputResult(outputFormat, resp, func() error { + operationState := "Enabled" + if async { + operationState = "Triggered enable of" } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS key: %w", err) - } - p.Outputln(string(details)) - - default: - p.Outputf("Enabled version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId)) - } - - return nil + p.Outputf("%s version %d of the key %q\n", operationState, utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId)) + return nil + }) } diff --git a/internal/cmd/beta/kms/version/enable/enable_test.go b/internal/cmd/kms/version/enable/enable_test.go similarity index 98% rename from internal/cmd/beta/kms/version/enable/enable_test.go rename to internal/cmd/kms/version/enable/enable_test.go index 0cc35d43f..3e176d217 100644 --- a/internal/cmd/beta/kms/version/enable/enable_test.go +++ b/internal/cmd/kms/version/enable/enable_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -282,6 +283,7 @@ func TestOutputResult(t *testing.T) { description string wantErr bool outputFormat string + async bool resp *kms.Version }{ { @@ -312,7 +314,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := outputResult(p, tt.outputFormat, tt.resp) + err := outputResult(p, tt.outputFormat, tt.async, tt.resp) if (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/internal/cmd/beta/kms/version/list/list.go b/internal/cmd/kms/version/list/list.go similarity index 83% rename from internal/cmd/beta/kms/version/list/list.go rename to internal/cmd/kms/version/list/list.go index f9f606ac2..bae8aa2e2 100644 --- a/internal/cmd/beta/kms/version/list/list.go +++ b/internal/cmd/kms/version/list/list.go @@ -2,13 +2,13 @@ package list import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -41,10 +40,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `List all key versions for the key "my-key-id" inside the key ring "my-keyring-id"`, - `$ stackit beta kms version list --key-id "my-key-id" --keyring-id "my-keyring-id"`), + `$ stackit kms version list --key-id "my-key-id" --keyring-id "my-keyring-id"`), examples.NewExample( `List all key versions in JSON format`, - `$ stackit beta kms version list --key-id "my-key-id" --keyring-id "my-keyring-id" -o json`), + `$ stackit kms version list --key-id "my-key-id" --keyring-id "my-keyring-id" -o json`), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -100,22 +99,7 @@ func outputResult(p *print.Printer, outputFormat, projectId, keyId string, resp } versions := *resp.Versions - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(versions, "", " ") - if err != nil { - return fmt.Errorf("marshal key versions list: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(versions, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal key versions list: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, versions, func() error { if len(versions) == 0 { p.Outputf("No key versions found for project %q for the key %q\n", projectId, keyId) return nil @@ -137,9 +121,8 @@ func outputResult(p *print.Printer, outputFormat, projectId, keyId string, resp if err != nil { return fmt.Errorf("render table: %w", err) } - } - - return nil + return nil + }) } func configureFlags(cmd *cobra.Command) { diff --git a/internal/cmd/beta/kms/version/list/list_test.go b/internal/cmd/kms/version/list/list_test.go similarity index 99% rename from internal/cmd/beta/kms/version/list/list_test.go rename to internal/cmd/kms/version/list/list_test.go index e8e97d40c..eaede273b 100644 --- a/internal/cmd/beta/kms/version/list/list_test.go +++ b/internal/cmd/kms/version/list/list_test.go @@ -10,9 +10,10 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/version/restore/restore.go b/internal/cmd/kms/version/restore/restore.go similarity index 86% rename from internal/cmd/beta/kms/version/restore/restore.go rename to internal/cmd/kms/version/restore/restore.go index 2f5f0882d..c8a850a62 100644 --- a/internal/cmd/beta/kms/version/restore/restore.go +++ b/internal/cmd/kms/version/restore/restore.go @@ -2,14 +2,14 @@ package restore import ( "context" - "encoding/json" "fmt" "strconv" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -44,7 +43,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Restore key version "42" for the key "my-key-id" inside the key ring "my-keyring-id"`, - `$ stackit beta kms version restore 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), + `$ stackit kms version restore 42 --key-id "my-key-id" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -125,22 +124,8 @@ func outputResult(p *print.Printer, outputFormat string, resp *kms.Version) erro return fmt.Errorf("response is nil / empty") } - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal output to JSON: %w", err) - } - p.Outputln(string(details)) - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal output to YAML: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, resp, func() error { p.Outputf("Restored version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId)) - } - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/version/restore/restore_test.go b/internal/cmd/kms/version/restore/restore_test.go similarity index 99% rename from internal/cmd/beta/kms/version/restore/restore_test.go rename to internal/cmd/kms/version/restore/restore_test.go index 7454fc5a5..e51c94316 100644 --- a/internal/cmd/beta/kms/version/restore/restore_test.go +++ b/internal/cmd/kms/version/restore/restore_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/version/version.go b/internal/cmd/kms/version/version.go similarity index 67% rename from internal/cmd/beta/kms/version/version.go rename to internal/cmd/kms/version/version.go index 60b642679..e1a7d56fe 100644 --- a/internal/cmd/beta/kms/version/version.go +++ b/internal/cmd/kms/version/version.go @@ -1,11 +1,11 @@ package version import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/version/destroy" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/version/disable" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/version/enable" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/version/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/version/restore" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/version/destroy" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/version/disable" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/version/enable" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/version/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/version/restore" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" diff --git a/internal/cmd/beta/kms/wrappingkey/create/create.go b/internal/cmd/kms/wrappingkey/create/create.go similarity index 84% rename from internal/cmd/beta/kms/wrappingkey/create/create.go rename to internal/cmd/kms/wrappingkey/create/create.go index 885c3b3e2..3397f112f 100644 --- a/internal/cmd/beta/kms/wrappingkey/create/create.go +++ b/internal/cmd/kms/wrappingkey/create/create.go @@ -2,25 +2,25 @@ package create import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" - "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" ) const ( @@ -53,10 +53,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Create a symmetric (RSA + AES) KMS wrapping key with name "my-wrapping-key-name" in key ring with ID "my-keyring-id"`, - `$ stackit beta kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256_aes_256_key_wrap" --name "my-wrapping-key-name" --purpose "wrap_symmetric_key" --protection "software"`), + `$ stackit kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_2048_oaep_sha256_aes_256_key_wrap" --name "my-wrapping-key-name" --purpose "wrap_symmetric_key" --protection "software"`), examples.NewExample( `Create an asymmetric (RSA) KMS wrapping key with name "my-wrapping-key-name" in key ring with ID "my-keyring-id"`, - `$ stackit beta kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_3072_oaep_sha256" --name "my-wrapping-key-name" --purpose "wrap_asymmetric_key" --protection "software"`), + `$ stackit kms wrapping-key create --keyring-id "my-keyring-id" --algorithm "rsa_3072_oaep_sha256" --name "my-wrapping-key-name" --purpose "wrap_asymmetric_key" --protection "software"`), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -85,13 +85,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating wrapping key") - _, err = wait.CreateWrappingKeyWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *wrappingKey.KeyRingId, *wrappingKey.Id).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating wrapping key", func() error { + _, err = wait.CreateWrappingKeyWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *wrappingKey.KeyRingId, *wrappingKey.Id).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for KMS wrapping key creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, wrappingKey) @@ -144,30 +144,14 @@ func outputResult(p *print.Printer, model *inputModel, resp *kms.WrappingKey) er return fmt.Errorf("response is nil") } - switch model.OutputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS wrapping key: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS wrapping key: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(model.OutputFormat, resp, func() error { operationState := "Created" if model.Async { operationState = "Triggered creation of" } p.Outputf("%s wrapping key. Wrapping key ID: %s\n", operationState, utils.PtrString(resp.Id)) - } - - return nil + return nil + }) } func configureFlags(cmd *cobra.Command) { diff --git a/internal/cmd/beta/kms/wrappingkey/create/create_test.go b/internal/cmd/kms/wrappingkey/create/create_test.go similarity index 99% rename from internal/cmd/beta/kms/wrappingkey/create/create_test.go rename to internal/cmd/kms/wrappingkey/create/create_test.go index 2b7d356de..737b88ef2 100644 --- a/internal/cmd/beta/kms/wrappingkey/create/create_test.go +++ b/internal/cmd/kms/wrappingkey/create/create_test.go @@ -10,10 +10,11 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/wrappingkey/delete/delete.go b/internal/cmd/kms/wrappingkey/delete/delete.go similarity index 97% rename from internal/cmd/beta/kms/wrappingkey/delete/delete.go rename to internal/cmd/kms/wrappingkey/delete/delete.go index 75ce76a05..cd1ce3f6a 100644 --- a/internal/cmd/beta/kms/wrappingkey/delete/delete.go +++ b/internal/cmd/kms/wrappingkey/delete/delete.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +17,9 @@ import ( kmsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-sdk-go/services/kms" + + "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" ) const ( @@ -41,7 +43,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Delete a KMS wrapping key "MY_WRAPPING_KEY_ID" inside the key ring "my-keyring-id"`, - `$ stackit beta kms wrapping-key delete "MY_WRAPPING_KEY_ID" --keyring-id "my-keyring-id"`), + `$ stackit kms wrapping-key delete "MY_WRAPPING_KEY_ID" --keyring-id "my-keyring-id"`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() diff --git a/internal/cmd/beta/kms/wrappingkey/delete/delete_test.go b/internal/cmd/kms/wrappingkey/delete/delete_test.go similarity index 99% rename from internal/cmd/beta/kms/wrappingkey/delete/delete_test.go rename to internal/cmd/kms/wrappingkey/delete/delete_test.go index c8d3a2ee2..8b6460695 100644 --- a/internal/cmd/beta/kms/wrappingkey/delete/delete_test.go +++ b/internal/cmd/kms/wrappingkey/delete/delete_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/wrappingkey/describe/describe.go b/internal/cmd/kms/wrappingkey/describe/describe.go similarity index 98% rename from internal/cmd/beta/kms/wrappingkey/describe/describe.go rename to internal/cmd/kms/wrappingkey/describe/describe.go index f8edb6921..e58e9fa81 100644 --- a/internal/cmd/beta/kms/wrappingkey/describe/describe.go +++ b/internal/cmd/kms/wrappingkey/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -39,7 +40,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Describe a KMS wrapping key with ID xxx of keyring yyy`, - `$ stackit beta kms wrappingkey describe xxx --keyring-id yyy`, + `$ stackit kms wrapping-key describe xxx --keyring-id yyy`, ), ), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/cmd/beta/kms/wrappingkey/describe/describe_test.go b/internal/cmd/kms/wrappingkey/describe/describe_test.go similarity index 99% rename from internal/cmd/beta/kms/wrappingkey/describe/describe_test.go rename to internal/cmd/kms/wrappingkey/describe/describe_test.go index 579f27f3d..6855c27bd 100644 --- a/internal/cmd/beta/kms/wrappingkey/describe/describe_test.go +++ b/internal/cmd/kms/wrappingkey/describe/describe_test.go @@ -12,11 +12,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) type testCtxKey struct{} diff --git a/internal/cmd/beta/kms/wrappingkey/list/list.go b/internal/cmd/kms/wrappingkey/list/list.go similarity index 83% rename from internal/cmd/beta/kms/wrappingkey/list/list.go rename to internal/cmd/kms/wrappingkey/list/list.go index dbb514812..6d64940ed 100644 --- a/internal/cmd/beta/kms/wrappingkey/list/list.go +++ b/internal/cmd/kms/wrappingkey/list/list.go @@ -2,13 +2,13 @@ package list import ( "context" - "encoding/json" "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/kms/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( @@ -39,10 +38,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `List all KMS wrapping keys for the key ring "my-keyring-id"`, - `$ stackit beta kms wrapping-key list --keyring-id "my-keyring-id"`), + `$ stackit kms wrapping-key list --keyring-id "my-keyring-id"`), examples.NewExample( `List all KMS wrapping keys in JSON format`, - `$ stackit beta kms wrapping-key list --keyring-id "my-keyring-id" --output-format json`), + `$ stackit kms wrapping-key list --keyring-id "my-keyring-id" --output-format json`), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -105,22 +104,7 @@ func outputResult(p *print.Printer, outputFormat, keyRingId string, resp *kms.Wr wrappingKeys := *resp.WrappingKeys - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(wrappingKeys, "", " ") - if err != nil { - return fmt.Errorf("marshal KMS wrapping keys list: %w", err) - } - p.Outputln(string(details)) - - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(wrappingKeys, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) - if err != nil { - return fmt.Errorf("marshal KMS wrapping keys list: %w", err) - } - p.Outputln(string(details)) - - default: + return p.OutputResult(outputFormat, wrappingKeys, func() error { if len(wrappingKeys) == 0 { p.Outputf("No wrapping keys found under the key ring %q\n", keyRingId) return nil @@ -144,7 +128,6 @@ func outputResult(p *print.Printer, outputFormat, keyRingId string, resp *kms.Wr if err != nil { return fmt.Errorf("render table: %w", err) } - } - - return nil + return nil + }) } diff --git a/internal/cmd/beta/kms/wrappingkey/list/list_test.go b/internal/cmd/kms/wrappingkey/list/list_test.go similarity index 99% rename from internal/cmd/beta/kms/wrappingkey/list/list_test.go rename to internal/cmd/kms/wrappingkey/list/list_test.go index 05c571ed3..36c6b647d 100644 --- a/internal/cmd/beta/kms/wrappingkey/list/list_test.go +++ b/internal/cmd/kms/wrappingkey/list/list_test.go @@ -10,9 +10,10 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/kms" ) const ( diff --git a/internal/cmd/beta/kms/wrappingkey/wrappingkey.go b/internal/cmd/kms/wrappingkey/wrappingkey.go similarity index 70% rename from internal/cmd/beta/kms/wrappingkey/wrappingkey.go rename to internal/cmd/kms/wrappingkey/wrappingkey.go index 2aaa14640..ab873566d 100644 --- a/internal/cmd/beta/kms/wrappingkey/wrappingkey.go +++ b/internal/cmd/kms/wrappingkey/wrappingkey.go @@ -1,10 +1,10 @@ package wrappingkey import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/wrappingkey/create" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/wrappingkey/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/wrappingkey/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/kms/wrappingkey/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/wrappingkey/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/wrappingkey/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/wrappingkey/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms/wrappingkey/list" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" diff --git a/internal/cmd/load-balancer/create/create.go b/internal/cmd/load-balancer/create/create.go index 31e74d9ba..47d479c8a 100644 --- a/internal/cmd/load-balancer/create/create.go +++ b/internal/cmd/load-balancer/create/create.go @@ -9,6 +9,9 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,8 +22,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/wait" ) const ( @@ -93,13 +94,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating load balancer") - _, err = wait.CreateLoadBalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *model.Payload.Name).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating load balancer", func() error { + _, err = wait.CreateLoadBalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *model.Payload.Name).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for load balancer creation: %w", err) } - s.Stop() } operationState := "Created" diff --git a/internal/cmd/load-balancer/delete/delete.go b/internal/cmd/load-balancer/delete/delete.go index e1c3a33af..c60100607 100644 --- a/internal/cmd/load-balancer/delete/delete.go +++ b/internal/cmd/load-balancer/delete/delete.go @@ -66,13 +66,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting load balancer") - _, err = wait.DeleteLoadBalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.LoadBalancerName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting load balancer", func() error { + _, err = wait.DeleteLoadBalancerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.LoadBalancerName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for load balancer deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/load-balancer/describe/describe_test.go b/internal/cmd/load-balancer/describe/describe_test.go index 5dcbfe446..6254d9e93 100644 --- a/internal/cmd/load-balancer/describe/describe_test.go +++ b/internal/cmd/load-balancer/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/generate-payload/generate_payload.go b/internal/cmd/load-balancer/generate-payload/generate_payload.go index 1149ae311..0170fcbe9 100644 --- a/internal/cmd/load-balancer/generate-payload/generate_payload.go +++ b/internal/cmd/load-balancer/generate-payload/generate_payload.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/spf13/cobra" ) diff --git a/internal/cmd/load-balancer/generate-payload/generate_payload_test.go b/internal/cmd/load-balancer/generate-payload/generate_payload_test.go index ca20bc950..1f0fefcdf 100644 --- a/internal/cmd/load-balancer/generate-payload/generate_payload_test.go +++ b/internal/cmd/load-balancer/generate-payload/generate_payload_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/list/list_test.go b/internal/cmd/load-balancer/list/list_test.go index 4d6decfa3..e400b0d6a 100644 --- a/internal/cmd/load-balancer/list/list_test.go +++ b/internal/cmd/load-balancer/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/observability-credentials/add/add.go b/internal/cmd/load-balancer/observability-credentials/add/add.go index edb3f447e..89435c3fc 100644 --- a/internal/cmd/load-balancer/observability-credentials/add/add.go +++ b/internal/cmd/load-balancer/observability-credentials/add/add.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/load-balancer/observability-credentials/add/add_test.go b/internal/cmd/load-balancer/observability-credentials/add/add_test.go index f36d7e00c..d5b1fe7c8 100644 --- a/internal/cmd/load-balancer/observability-credentials/add/add_test.go +++ b/internal/cmd/load-balancer/observability-credentials/add/add_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/observability-credentials/cleanup/cleanup_test.go b/internal/cmd/load-balancer/observability-credentials/cleanup/cleanup_test.go index 338dcaa08..a081af49c 100644 --- a/internal/cmd/load-balancer/observability-credentials/cleanup/cleanup_test.go +++ b/internal/cmd/load-balancer/observability-credentials/cleanup/cleanup_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go index c53114075..3a8675d10 100644 --- a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go +++ b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go index 1060baeab..e553e0e3a 100644 --- a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go +++ b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/observability-credentials/list/list.go b/internal/cmd/load-balancer/observability-credentials/list/list.go index 1b58291a6..740c94b98 100644 --- a/internal/cmd/load-balancer/observability-credentials/list/list.go +++ b/internal/cmd/load-balancer/observability-credentials/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +20,6 @@ import ( lbUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/observability-credentials/list/list_test.go b/internal/cmd/load-balancer/observability-credentials/list/list_test.go index 85b3650fa..c5ad1d770 100644 --- a/internal/cmd/load-balancer/observability-credentials/list/list_test.go +++ b/internal/cmd/load-balancer/observability-credentials/list/list_test.go @@ -9,12 +9,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" lbUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/quota/quota_test.go b/internal/cmd/load-balancer/quota/quota_test.go index 1a3ccae0a..bee2dc9dc 100644 --- a/internal/cmd/load-balancer/quota/quota_test.go +++ b/internal/cmd/load-balancer/quota/quota_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/target-pool/add-target/add_target.go b/internal/cmd/load-balancer/target-pool/add-target/add_target.go index 4cc41d4ef..716c273a3 100644 --- a/internal/cmd/load-balancer/target-pool/add-target/add_target.go +++ b/internal/cmd/load-balancer/target-pool/add-target/add_target.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/spf13/cobra" ) diff --git a/internal/cmd/load-balancer/target-pool/add-target/add_target_test.go b/internal/cmd/load-balancer/target-pool/add-target/add_target_test.go index 16ebc3096..c4fec49c0 100644 --- a/internal/cmd/load-balancer/target-pool/add-target/add_target_test.go +++ b/internal/cmd/load-balancer/target-pool/add-target/add_target_test.go @@ -7,10 +7,11 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/load-balancer/target-pool/describe/describe.go b/internal/cmd/load-balancer/target-pool/describe/describe.go index dadfab47d..99b267962 100644 --- a/internal/cmd/load-balancer/target-pool/describe/describe.go +++ b/internal/cmd/load-balancer/target-pool/describe/describe.go @@ -9,6 +9,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( lbUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) const ( diff --git a/internal/cmd/load-balancer/target-pool/describe/describe_test.go b/internal/cmd/load-balancer/target-pool/describe/describe_test.go index 7c1a7535a..f16e3deaf 100644 --- a/internal/cmd/load-balancer/target-pool/describe/describe_test.go +++ b/internal/cmd/load-balancer/target-pool/describe/describe_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" ) type testCtxKey struct{} diff --git a/internal/cmd/load-balancer/target-pool/remove-target/remove_target.go b/internal/cmd/load-balancer/target-pool/remove-target/remove_target.go index 4cb053eff..129ca8773 100644 --- a/internal/cmd/load-balancer/target-pool/remove-target/remove_target.go +++ b/internal/cmd/load-balancer/target-pool/remove-target/remove_target.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/spf13/cobra" ) diff --git a/internal/cmd/load-balancer/target-pool/remove-target/remove_target_test.go b/internal/cmd/load-balancer/target-pool/remove-target/remove_target_test.go index e09747421..57d47df6f 100644 --- a/internal/cmd/load-balancer/target-pool/remove-target/remove_target_test.go +++ b/internal/cmd/load-balancer/target-pool/remove-target/remove_target_test.go @@ -7,10 +7,11 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/logme/credentials/create/create.go b/internal/cmd/logme/credentials/create/create.go index 79d5577c4..aab7a20ae 100644 --- a/internal/cmd/logme/credentials/create/create.go +++ b/internal/cmd/logme/credentials/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/logme" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/logme/client" logmeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/logme/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logme" ) const ( diff --git a/internal/cmd/logme/instance/create/create.go b/internal/cmd/logme/instance/create/create.go index c0b1fcd48..367c9cc60 100644 --- a/internal/cmd/logme/instance/create/create.go +++ b/internal/cmd/logme/instance/create/create.go @@ -114,13 +114,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for LogMe instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) diff --git a/internal/cmd/logme/instance/delete/delete.go b/internal/cmd/logme/instance/delete/delete.go index 9cdfac9d0..0dc766e3b 100644 --- a/internal/cmd/logme/instance/delete/delete.go +++ b/internal/cmd/logme/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instacne", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for LogMe instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/logme/instance/list/list.go b/internal/cmd/logme/instance/list/list.go index 20efce498..b3b15ce1c 100644 --- a/internal/cmd/logme/instance/list/list.go +++ b/internal/cmd/logme/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/logme" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/logme/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logme" ) const ( diff --git a/internal/cmd/logme/instance/update/update.go b/internal/cmd/logme/instance/update/update.go index 4cefddf26..9a16545ad 100644 --- a/internal/cmd/logme/instance/update/update.go +++ b/internal/cmd/logme/instance/update/update.go @@ -113,13 +113,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for LogMe instance update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/logme/plans/plans.go b/internal/cmd/logme/plans/plans.go index 5b1597bf4..76d762a1d 100644 --- a/internal/cmd/logme/plans/plans.go +++ b/internal/cmd/logme/plans/plans.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/logme" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/logme/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logme" ) const ( diff --git a/internal/cmd/logs/access_token/create/create.go b/internal/cmd/logs/access_token/create/create.go index 452342b3d..852810a85 100644 --- a/internal/cmd/logs/access_token/create/create.go +++ b/internal/cmd/logs/access_token/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/client" logsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/spf13/cobra" ) diff --git a/internal/cmd/logs/access_token/delete/delete.go b/internal/cmd/logs/access_token/delete/delete.go index 77b4777dc..d55c8d3a2 100644 --- a/internal/cmd/logs/access_token/delete/delete.go +++ b/internal/cmd/logs/access_token/delete/delete.go @@ -4,9 +4,10 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" diff --git a/internal/cmd/logs/access_token/delete_all/delete_all.go b/internal/cmd/logs/access_token/delete_all/delete_all.go index 337010be8..e12e89804 100644 --- a/internal/cmd/logs/access_token/delete_all/delete_all.go +++ b/internal/cmd/logs/access_token/delete_all/delete_all.go @@ -4,11 +4,12 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/logs/access_token/delete_all_expired/delete_all_expired.go b/internal/cmd/logs/access_token/delete_all_expired/delete_all_expired.go index a4673a073..fbad9528a 100644 --- a/internal/cmd/logs/access_token/delete_all_expired/delete_all_expired.go +++ b/internal/cmd/logs/access_token/delete_all_expired/delete_all_expired.go @@ -4,11 +4,12 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/logs/access_token/describe/describe.go b/internal/cmd/logs/access_token/describe/describe.go index 21f734a51..d81a8b258 100644 --- a/internal/cmd/logs/access_token/describe/describe.go +++ b/internal/cmd/logs/access_token/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/spf13/cobra" ) diff --git a/internal/cmd/logs/access_token/list/list.go b/internal/cmd/logs/access_token/list/list.go index 309fd073f..27967bdc8 100644 --- a/internal/cmd/logs/access_token/list/list.go +++ b/internal/cmd/logs/access_token/list/list.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/spf13/cobra" ) diff --git a/internal/cmd/logs/access_token/update/update.go b/internal/cmd/logs/access_token/update/update.go index 85952136a..6fe999dba 100644 --- a/internal/cmd/logs/access_token/update/update.go +++ b/internal/cmd/logs/access_token/update/update.go @@ -4,9 +4,10 @@ import ( "context" "fmt" - "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/logs/instance/create/create.go b/internal/cmd/logs/instance/create/create.go index 30c45de7a..a7fca44c3 100644 --- a/internal/cmd/logs/instance/create/create.go +++ b/internal/cmd/logs/instance/create/create.go @@ -4,22 +4,25 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/client" + "github.com/stackitcloud/stackit-sdk-go/services/logs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs/wait" ) const ( @@ -102,13 +105,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateLogsInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateLogsInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for logs instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, resp) diff --git a/internal/cmd/logs/instance/create/create_test.go b/internal/cmd/logs/instance/create/create_test.go index a1835d54c..f533b16ec 100644 --- a/internal/cmd/logs/instance/create/create_test.go +++ b/internal/cmd/logs/instance/create/create_test.go @@ -5,13 +5,15 @@ import ( "strconv" "testing" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" diff --git a/internal/cmd/logs/instance/delete/delete.go b/internal/cmd/logs/instance/delete/delete.go index 937239fc2..d00f33fc8 100644 --- a/internal/cmd/logs/instance/delete/delete.go +++ b/internal/cmd/logs/instance/delete/delete.go @@ -4,12 +4,14 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,9 +19,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/logs/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/client" logsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs/wait" ) const ( @@ -85,13 +88,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteLogsInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.InstanceID).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteLogsInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.InstanceID).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Logs instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/logs/instance/delete/delete_test.go b/internal/cmd/logs/instance/delete/delete_test.go index 7b89cccb0..64501bf2e 100644 --- a/internal/cmd/logs/instance/delete/delete_test.go +++ b/internal/cmd/logs/instance/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/logs" ) const ( diff --git a/internal/cmd/logs/instance/describe/describe.go b/internal/cmd/logs/instance/describe/describe.go index dc084eaeb..b145e6e66 100644 --- a/internal/cmd/logs/instance/describe/describe.go +++ b/internal/cmd/logs/instance/describe/describe.go @@ -4,10 +4,12 @@ import ( "context" "fmt" - "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/logs/instance/describe/describe_test.go b/internal/cmd/logs/instance/describe/describe_test.go index 661ce4aa5..ed6afc9d4 100644 --- a/internal/cmd/logs/instance/describe/describe_test.go +++ b/internal/cmd/logs/instance/describe/describe_test.go @@ -7,11 +7,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/logs" ) type testCtxKey struct{} diff --git a/internal/cmd/logs/instance/list/list.go b/internal/cmd/logs/instance/list/list.go index 571cb98b2..153a4c2a9 100644 --- a/internal/cmd/logs/instance/list/list.go +++ b/internal/cmd/logs/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/logs/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/logs" ) type inputModel struct { diff --git a/internal/cmd/logs/instance/update/update_test.go b/internal/cmd/logs/instance/update/update_test.go index b885d5677..07e43ea28 100644 --- a/internal/cmd/logs/instance/update/update_test.go +++ b/internal/cmd/logs/instance/update/update_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" diff --git a/internal/cmd/mariadb/credentials/list/list.go b/internal/cmd/mariadb/credentials/list/list.go index aa79aadf7..339891e99 100644 --- a/internal/cmd/mariadb/credentials/list/list.go +++ b/internal/cmd/mariadb/credentials/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mariadb" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( mariadbUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/mariadb/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mariadb" ) const ( diff --git a/internal/cmd/mariadb/instance/create/create.go b/internal/cmd/mariadb/instance/create/create.go index 22a8a2b91..de4c22b70 100644 --- a/internal/cmd/mariadb/instance/create/create.go +++ b/internal/cmd/mariadb/instance/create/create.go @@ -114,13 +114,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MariaDB instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) diff --git a/internal/cmd/mariadb/instance/delete/delete.go b/internal/cmd/mariadb/instance/delete/delete.go index 1cd809204..db499df05 100644 --- a/internal/cmd/mariadb/instance/delete/delete.go +++ b/internal/cmd/mariadb/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MariaDB instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/mariadb/instance/list/list.go b/internal/cmd/mariadb/instance/list/list.go index 5c13db105..a009ed2ee 100644 --- a/internal/cmd/mariadb/instance/list/list.go +++ b/internal/cmd/mariadb/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mariadb" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/mariadb/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mariadb" ) const ( diff --git a/internal/cmd/mariadb/instance/update/update.go b/internal/cmd/mariadb/instance/update/update.go index ebb48e315..94a7a871d 100644 --- a/internal/cmd/mariadb/instance/update/update.go +++ b/internal/cmd/mariadb/instance/update/update.go @@ -111,13 +111,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MariaDB instance update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/mariadb/plans/plans.go b/internal/cmd/mariadb/plans/plans.go index 5a6d9f017..5c5819695 100644 --- a/internal/cmd/mariadb/plans/plans.go +++ b/internal/cmd/mariadb/plans/plans.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mariadb" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/mariadb/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mariadb" ) const ( diff --git a/internal/cmd/mongodbflex/backup/describe/describe.go b/internal/cmd/mongodbflex/backup/describe/describe.go index 17bbe9b40..1e34a88ee 100644 --- a/internal/cmd/mongodbflex/backup/describe/describe.go +++ b/internal/cmd/mongodbflex/backup/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( mongoUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) const ( diff --git a/internal/cmd/mongodbflex/backup/restore-jobs/restore_jobs.go b/internal/cmd/mongodbflex/backup/restore-jobs/restore_jobs.go index 1822b4583..ad3bf61de 100644 --- a/internal/cmd/mongodbflex/backup/restore-jobs/restore_jobs.go +++ b/internal/cmd/mongodbflex/backup/restore-jobs/restore_jobs.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( mongodbflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) const ( diff --git a/internal/cmd/mongodbflex/backup/restore/restore.go b/internal/cmd/mongodbflex/backup/restore/restore.go index a4b6e5ca9..25171f7cb 100644 --- a/internal/cmd/mongodbflex/backup/restore/restore.go +++ b/internal/cmd/mongodbflex/backup/restore/restore.go @@ -99,16 +99,20 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } if !model.Async { - s := spinner.New(params.Printer) - s.Start("Restoring instance") - _, err = wait.RestoreInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.BackupId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Restoring instance", func() error { + _, err = wait.RestoreInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.BackupId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MongoDB Flex instance restoration: %w", err) } - s.Stop() } - params.Printer.Outputf("Restored instance %q with backup %q\n", model.InstanceId, model.BackupId) + operationState := "Restored" + if model.Async { + operationState = "Triggered restore of" + } + params.Printer.Outputf("%s instance %q with backup %q\n", operationState, model.InstanceId, model.BackupId) return nil } @@ -120,16 +124,20 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } if !model.Async { - s := spinner.New(params.Printer) - s.Start("Cloning instance") - _, err = wait.CloneInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Cloning instance", func() error { + _, err = wait.CloneInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MongoDB Flex instance cloning: %w", err) } - s.Stop() } - params.Printer.Outputf("Cloned instance %q from backup with timestamp %q\n", model.InstanceId, model.Timestamp) + operationState := "Cloned" + if model.Async { + operationState = "Triggered clone of" + } + params.Printer.Outputf("%s instance %q from backup with timestamp %q\n", operationState, model.InstanceId, model.Timestamp) return nil }, } diff --git a/internal/cmd/mongodbflex/backup/schedule/schedule.go b/internal/cmd/mongodbflex/backup/schedule/schedule.go index 614b42082..32d22a094 100644 --- a/internal/cmd/mongodbflex/backup/schedule/schedule.go +++ b/internal/cmd/mongodbflex/backup/schedule/schedule.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) const ( diff --git a/internal/cmd/mongodbflex/backup/update-schedule/update_schedule.go b/internal/cmd/mongodbflex/backup/update-schedule/update_schedule.go index 6934fb862..9730a8621 100644 --- a/internal/cmd/mongodbflex/backup/update-schedule/update_schedule.go +++ b/internal/cmd/mongodbflex/backup/update-schedule/update_schedule.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/client" mongoDBflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) const ( diff --git a/internal/cmd/mongodbflex/instance/create/create.go b/internal/cmd/mongodbflex/instance/create/create.go index e0239d2f5..26e65e9a0 100644 --- a/internal/cmd/mongodbflex/instance/create/create.go +++ b/internal/cmd/mongodbflex/instance/create/create.go @@ -8,6 +8,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,8 +22,6 @@ import ( mongodbflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex/wait" ) const ( @@ -121,13 +122,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MongoDB Flex instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) diff --git a/internal/cmd/mongodbflex/instance/delete/delete.go b/internal/cmd/mongodbflex/instance/delete/delete.go index cb4a2be9c..8c34e1533 100644 --- a/internal/cmd/mongodbflex/instance/delete/delete.go +++ b/internal/cmd/mongodbflex/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MongoDB Flex instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/mongodbflex/instance/update/update.go b/internal/cmd/mongodbflex/instance/update/update.go index bcd4bb894..e340f95d2 100644 --- a/internal/cmd/mongodbflex/instance/update/update.go +++ b/internal/cmd/mongodbflex/instance/update/update.go @@ -107,13 +107,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for MongoDB Flex instance update: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, instanceLabel, resp) diff --git a/internal/cmd/mongodbflex/options/options.go b/internal/cmd/mongodbflex/options/options.go index 73febf6cf..a49d3bc84 100644 --- a/internal/cmd/mongodbflex/options/options.go +++ b/internal/cmd/mongodbflex/options/options.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) const ( diff --git a/internal/cmd/mongodbflex/user/create/create.go b/internal/cmd/mongodbflex/user/create/create.go index 9fc9def6d..c25309c64 100644 --- a/internal/cmd/mongodbflex/user/create/create.go +++ b/internal/cmd/mongodbflex/user/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/client" mongodbflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/mongodbflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) const ( diff --git a/internal/cmd/network-area/create/create.go b/internal/cmd/network-area/create/create.go index 078a6568c..b75fa6634 100644 --- a/internal/cmd/network-area/create/create.go +++ b/internal/cmd/network-area/create/create.go @@ -8,6 +8,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,8 +22,6 @@ import ( rmUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -146,13 +147,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return fmt.Errorf("create network area region: %w", err) } if !model.Async { - s := spinner.New(params.Printer) - s.Start("Create network area region") - _, err = wait.CreateNetworkAreaRegionWaitHandler(ctx, apiClient, model.OrganizationId, *resp.Id, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Create network area region", func() error { + _, err = wait.CreateNetworkAreaRegionWaitHandler(ctx, apiClient, model.OrganizationId, *resp.Id, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for creating network area region %w", err) } - s.Stop() } responses.RegionalArea = respNetworkArea } diff --git a/internal/cmd/network-area/delete/delete.go b/internal/cmd/network-area/delete/delete.go index e1dbca35e..f9c20e3a9 100644 --- a/internal/cmd/network-area/delete/delete.go +++ b/internal/cmd/network-area/delete/delete.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -15,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" ) const ( diff --git a/internal/cmd/network-area/describe/describe.go b/internal/cmd/network-area/describe/describe.go index f51fa40a5..0c467f2c7 100644 --- a/internal/cmd/network-area/describe/describe.go +++ b/internal/cmd/network-area/describe/describe.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -17,7 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/list/list.go b/internal/cmd/network-area/list/list.go index a7688648e..cf8d9975d 100644 --- a/internal/cmd/network-area/list/list.go +++ b/internal/cmd/network-area/list/list.go @@ -9,6 +9,8 @@ import ( rmClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( rmUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/network-range/create/create.go b/internal/cmd/network-area/network-range/create/create.go index 0502663d8..0767cc9ea 100644 --- a/internal/cmd/network-area/network-range/create/create.go +++ b/internal/cmd/network-area/network-range/create/create.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/network-range/delete/delete.go b/internal/cmd/network-area/network-range/delete/delete.go index 84ceb85c7..eb0daa88d 100644 --- a/internal/cmd/network-area/network-range/delete/delete.go +++ b/internal/cmd/network-area/network-range/delete/delete.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/network-range/describe/describe.go b/internal/cmd/network-area/network-range/describe/describe.go index 1abd718b9..6ba53cb83 100644 --- a/internal/cmd/network-area/network-range/describe/describe.go +++ b/internal/cmd/network-area/network-range/describe/describe.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/network-range/list/list.go b/internal/cmd/network-area/network-range/list/list.go index 42d57c669..4ad161d30 100644 --- a/internal/cmd/network-area/network-range/list/list.go +++ b/internal/cmd/network-area/network-range/list/list.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/network_area.go b/internal/cmd/network-area/network_area.go index ff09d229e..a47e9585d 100644 --- a/internal/cmd/network-area/network_area.go +++ b/internal/cmd/network-area/network_area.go @@ -8,6 +8,7 @@ import ( networkrange "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/network-range" "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/region" "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/route" + "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable" "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" @@ -34,6 +35,7 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(describe.NewCmd(params)) cmd.AddCommand(list.NewCmd(params)) cmd.AddCommand(networkrange.NewCmd(params)) + cmd.AddCommand(routingtable.NewCmd(params)) cmd.AddCommand(region.NewCmd(params)) cmd.AddCommand(route.NewCmd(params)) cmd.AddCommand(update.NewCmd(params)) diff --git a/internal/cmd/network-area/region/create/create.go b/internal/cmd/network-area/region/create/create.go index 63a8c6348..791c8fabe 100644 --- a/internal/cmd/network-area/region/create/create.go +++ b/internal/cmd/network-area/region/create/create.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,8 +20,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" ) const ( @@ -108,16 +109,16 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } if !model.Async { - s := spinner.New(params.Printer) - s.Start("Create network area region") - _, err = wait.CreateNetworkAreaRegionWaitHandler(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Create network area region", func() error { + _, err = wait.CreateNetworkAreaRegionWaitHandler(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for network area region creation: %w", err) } - s.Stop() } - return outputResult(params.Printer, model.OutputFormat, model.Region, networkAreaLabel, *resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, model.Region, networkAreaLabel, *resp) }, } configureFlags(cmd) @@ -186,9 +187,13 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli return req.CreateNetworkAreaRegionPayload(payload) } -func outputResult(p *print.Printer, outputFormat, region, networkAreaLabel string, regionalArea iaas.RegionalArea) error { +func outputResult(p *print.Printer, outputFormat string, async bool, region, networkAreaLabel string, regionalArea iaas.RegionalArea) error { return p.OutputResult(outputFormat, regionalArea, func() error { - p.Outputf("Create region configuration for SNA %q.\nRegion: %s\n", networkAreaLabel, region) + operationState := "Created" + if async { + operationState = "Triggered creation of" + } + p.Outputf("%s region configuration for SNA %q.\nRegion: %s\n", operationState, networkAreaLabel, region) return nil }) } diff --git a/internal/cmd/network-area/region/create/create_test.go b/internal/cmd/network-area/region/create/create_test.go index cf92c59c3..d04b8dc23 100644 --- a/internal/cmd/network-area/region/create/create_test.go +++ b/internal/cmd/network-area/region/create/create_test.go @@ -11,11 +11,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( @@ -267,6 +268,7 @@ func TestBuildRequest(t *testing.T) { func Test_outputResult(t *testing.T) { type args struct { outputFormat string + async bool region string networkAreaLabel string regionalArea iaas.RegionalArea @@ -300,7 +302,7 @@ func Test_outputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.outputFormat, tt.args.region, tt.args.networkAreaLabel, tt.args.regionalArea); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.region, tt.args.networkAreaLabel, tt.args.regionalArea); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/network-area/region/delete/delete.go b/internal/cmd/network-area/region/delete/delete.go index bbbd44262..d016aff73 100644 --- a/internal/cmd/network-area/region/delete/delete.go +++ b/internal/cmd/network-area/region/delete/delete.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" ) const ( @@ -82,13 +83,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } if !model.Async { - s := spinner.New(params.Printer) - s.Start("Delete network area region") - _, err = wait.DeleteNetworkAreaRegionWaitHandler(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Delete network area region", func() error { + _, err = wait.DeleteNetworkAreaRegionWaitHandler(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for network area region deletion: %w", err) } - s.Stop() } params.Printer.Outputf("Delete regional network area %q for %q\n", model.Region, networkAreaName) diff --git a/internal/cmd/network-area/region/delete/delete_test.go b/internal/cmd/network-area/region/delete/delete_test.go index 919e86cb8..5a47b2b49 100644 --- a/internal/cmd/network-area/region/delete/delete_test.go +++ b/internal/cmd/network-area/region/delete/delete_test.go @@ -7,9 +7,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/region/describe/describe.go b/internal/cmd/network-area/region/describe/describe.go index 131d0f031..4694d1db5 100644 --- a/internal/cmd/network-area/region/describe/describe.go +++ b/internal/cmd/network-area/region/describe/describe.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +20,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/region/describe/describe_test.go b/internal/cmd/network-area/region/describe/describe_test.go index ea4beee77..18a040739 100644 --- a/internal/cmd/network-area/region/describe/describe_test.go +++ b/internal/cmd/network-area/region/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/region/list/list.go b/internal/cmd/network-area/region/list/list.go index c74f79848..4988c87cc 100644 --- a/internal/cmd/network-area/region/list/list.go +++ b/internal/cmd/network-area/region/list/list.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -17,7 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/region/list/list_test.go b/internal/cmd/network-area/region/list/list_test.go index f3cbc6ec8..34cd219e0 100644 --- a/internal/cmd/network-area/region/list/list_test.go +++ b/internal/cmd/network-area/region/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type testCtxKey struct{} diff --git a/internal/cmd/network-area/region/update/update.go b/internal/cmd/network-area/region/update/update.go index 8b68195ff..151c83a50 100644 --- a/internal/cmd/network-area/region/update/update.go +++ b/internal/cmd/network-area/region/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/region/update/update_test.go b/internal/cmd/network-area/region/update/update_test.go index 90535d384..11494fbf0 100644 --- a/internal/cmd/network-area/region/update/update_test.go +++ b/internal/cmd/network-area/region/update/update_test.go @@ -11,11 +11,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/route/create/create.go b/internal/cmd/network-area/route/create/create.go index 72c9fe006..7728da988 100644 --- a/internal/cmd/network-area/route/create/create.go +++ b/internal/cmd/network-area/route/create/create.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/route/delete/delete.go b/internal/cmd/network-area/route/delete/delete.go index dbad67c03..55ec64472 100644 --- a/internal/cmd/network-area/route/delete/delete.go +++ b/internal/cmd/network-area/route/delete/delete.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/route/describe/describe.go b/internal/cmd/network-area/route/describe/describe.go index 2a7e7e4f1..070b47738 100644 --- a/internal/cmd/network-area/route/describe/describe.go +++ b/internal/cmd/network-area/route/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-area/route/list/list.go b/internal/cmd/network-area/route/list/list.go index d85ac49db..8cadbdef7 100644 --- a/internal/cmd/network-area/route/list/list.go +++ b/internal/cmd/network-area/route/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/route/update/update.go b/internal/cmd/network-area/route/update/update.go index c20c86601..2a422a1d4 100644 --- a/internal/cmd/network-area/route/update/update.go +++ b/internal/cmd/network-area/route/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/route/update/update_test.go b/internal/cmd/network-area/route/update/update_test.go index 855d36513..11a47b15c 100644 --- a/internal/cmd/network-area/route/update/update_test.go +++ b/internal/cmd/network-area/route/update/update_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-area/routingtable/create/create.go b/internal/cmd/network-area/routingtable/create/create.go new file mode 100644 index 000000000..aed268466 --- /dev/null +++ b/internal/cmd/network-area/routingtable/create/create.go @@ -0,0 +1,161 @@ +package create + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + descriptionFlag = "description" + labelFlag = "labels" + nameFlag = "name" + networkAreaIdFlag = "network-area-id" + dynamicRoutesFlag = "dynamic-routes" + systemRoutesFlag = "system-routes" + organizationIdFlag = "organization-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Description *string + Labels *map[string]string + Name string + NetworkAreaId string + SystemRoutes bool + DynamicRoutes bool + OrganizationId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates a routing-table", + Long: "Creates a routing-table.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create a routing-table with name "rt"`, + `$ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt"`, + ), + examples.NewExample( + `Create a routing-table with name "rt" and description "some description"`, + `$ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt" --description "some description"`, + ), + examples.NewExample( + `Create a routing-table with name "rt" with system routes disabled`, + `$ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt" --system-routes=false`, + ), + examples.NewExample( + `Create a routing-table with name "rt" with dynamic routes disabled`, + `$ stackit network-area routing-table create --organization-id xxx --network-area-id yyy --name "rt" --dynamic-routes=false`, + ), + ), + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, nil) + if err != nil { + return err + } + + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + prompt := fmt.Sprintf("Are you sure you want to create the routing-table %q?", model.Name) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + req, err := buildRequest(ctx, model, apiClient) + if err != nil { + return err + } + + routingTableResp, err := req.Execute() + if err != nil { + return fmt.Errorf("create routing-table request failed: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, routingTableResp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(descriptionFlag, "", "Description of the routing-table") + cmd.Flags().StringToString(labelFlag, nil, "Key=value labels") + cmd.Flags().String(nameFlag, "", "Name of the routing-table") + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Bool(dynamicRoutesFlag, true, "If set to false, prevents dynamic routes from propagating to the routing table.") + cmd.Flags().Bool(systemRoutesFlag, true, "If set to false, disables routes for project-to-project communication.") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, nameFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := &inputModel{ + GlobalFlagModel: globalFlags, + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + DynamicRoutes: flags.FlagToBoolValue(p, cmd, dynamicRoutesFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), + Name: flags.FlagToStringValue(p, cmd, nameFlag), + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + SystemRoutes: flags.FlagToBoolValue(p, cmd, systemRoutesFlag), + } + + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) (iaas.ApiAddRoutingTableToAreaRequest, error) { + payload := iaas.AddRoutingTableToAreaPayload{ + Description: model.Description, + Name: utils.Ptr(model.Name), + Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), + SystemRoutes: utils.Ptr(model.SystemRoutes), + DynamicRoutes: utils.Ptr(model.DynamicRoutes), + } + + return apiClient.AddRoutingTableToArea( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + ).AddRoutingTableToAreaPayload(payload), nil +} + +func outputResult(p *print.Printer, outputFormat string, routingTable *iaas.RoutingTable) error { + if routingTable == nil { + return fmt.Errorf("routing-table is nil") + } + + if routingTable.Id == nil { + return fmt.Errorf("create routing-table id is empty") + } + + return p.OutputResult(outputFormat, routingTable, func() error { + p.Outputf("Created Routing-Table with ID %q\n", utils.PtrString(routingTable.Id)) + return nil + }) +} diff --git a/internal/cmd/network-area/routingtable/create/create_test.go b/internal/cmd/network-area/routingtable/create/create_test.go new file mode 100644 index 000000000..2ff9fa52b --- /dev/null +++ b/internal/cmd/network-area/routingtable/create/create_test.go @@ -0,0 +1,349 @@ +package create + +import ( + "context" + "strconv" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &iaas.APIClient{} + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() + +const testRoutingTableName = "test" +const testRoutingTableDescription = "test" + +const testSystemRoutesFlag = true +const testDynamicRoutesFlag = true + +const testLabelSelectorFlag = "key1=value1,key2=value2" + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + descriptionFlag: testRoutingTableDescription, + nameFlag: testRoutingTableName, + systemRoutesFlag: strconv.FormatBool(testSystemRoutesFlag), + dynamicRoutesFlag: strconv.FormatBool(testDynamicRoutesFlag), + labelFlag: testLabelSelectorFlag, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + Name: testRoutingTableName, + Description: utils.Ptr(testRoutingTableDescription), + SystemRoutes: testSystemRoutesFlag, + DynamicRoutes: testDynamicRoutesFlag, + Labels: testLabels, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiAddRoutingTableToAreaRequest)) iaas.ApiAddRoutingTableToAreaRequest { + request := testClient.AddRoutingTableToArea(testCtx, testOrgId, testNetworkAreaId, testRegion) + request = request.AddRoutingTableToAreaPayload(fixturePayload()) + for _, mod := range mods { + mod(&request) + } + return request +} + +func fixturePayload(mods ...func(payload *iaas.AddRoutingTableToAreaPayload)) iaas.AddRoutingTableToAreaPayload { + payload := iaas.AddRoutingTableToAreaPayload{ + Description: utils.Ptr(testRoutingTableDescription), + Name: utils.Ptr(testRoutingTableName), + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + SystemRoutes: utils.Ptr(true), + DynamicRoutes: utils.Ptr(true), + } + + for _, mod := range mods { + mod(&payload) + } + return payload +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "valid input", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "dynamic routes disabled", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[dynamicRoutesFlag] = "false" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.DynamicRoutes = false + }), + }, + { + description: "system routes disabled", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[systemRoutesFlag] = "false" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.SystemRoutes = false + }), + }, + { + description: "missing organization ID", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "invalid organization ID - empty", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "" + }), + isValid: false, + }, + { + description: "invalid organization ID - format", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "missing network area ID", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "invalid network area ID - empty", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "" + }), + isValid: false, + }, + { + description: "invalid network area ID - format", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "missing name", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, nameFlag) + }), + isValid: false, + }, + { + description: "missing labels", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + }, + { + description: "missing description", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, descriptionFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Description = nil + }), + }, + { + description: "no flags provided", + flagValues: map[string]string{}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest iaas.ApiAddRoutingTableToAreaRequest + }{ + { + description: "valid input", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "labels missing", + model: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutingTableToAreaRequest) { + *request = (*request).AddRoutingTableToAreaPayload( + fixturePayload(func(payload *iaas.AddRoutingTableToAreaPayload) { + payload.Labels = nil + }), + ) + }), + }, + { + description: "system routes disabled", + model: fixtureInputModel(func(model *inputModel) { + model.SystemRoutes = false + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutingTableToAreaRequest) { + *request = (*request).AddRoutingTableToAreaPayload( + fixturePayload(func(payload *iaas.AddRoutingTableToAreaPayload) { + payload.SystemRoutes = utils.Ptr(false) + }), + ) + }), + }, + { + description: "dynamic routes disabled", + model: fixtureInputModel(func(model *inputModel) { + model.DynamicRoutes = false + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutingTableToAreaRequest) { + *request = (*request).AddRoutingTableToAreaPayload( + fixturePayload(func(payload *iaas.AddRoutingTableToAreaPayload) { + payload.DynamicRoutes = utils.Ptr(false) + }), + ) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request, err := buildRequest(testCtx, tt.model, testClient) + if err != nil { + t.Fatalf("buildRequest returned error: %v", err) + } + + if diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx)); diff != "" { + t.Errorf("buildRequest() mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRoutingTable := iaas.RoutingTable{ + Id: utils.Ptr("id-foo"), + Name: utils.Ptr("route-table-foo"), + Description: utils.Ptr("description-foo"), + SystemRoutes: utils.Ptr(true), + DynamicRoutes: utils.Ptr(true), + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + CreatedAt: utils.Ptr(time.Now()), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + routingTable *iaas.RoutingTable + wantErr bool + }{ + { + name: "nil routing-table should return error", + outputFormat: "", + routingTable: nil, + wantErr: true, + }, + { + name: "empty routing-table", + outputFormat: print.PrettyOutputFormat, + routingTable: &iaas.RoutingTable{}, + wantErr: true, + }, + { + name: "pretty output routing-table", + outputFormat: print.PrettyOutputFormat, + routingTable: &dummyRoutingTable, + wantErr: false, + }, + { + name: "json output routing-table", + outputFormat: print.JSONOutputFormat, + routingTable: &dummyRoutingTable, + wantErr: false, + }, + { + name: "yaml output routing-table", + outputFormat: print.YAMLOutputFormat, + routingTable: &dummyRoutingTable, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, tt.routingTable); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/network-area/routingtable/delete/delete.go b/internal/cmd/network-area/routingtable/delete/delete.go new file mode 100644 index 000000000..1ae94658c --- /dev/null +++ b/internal/cmd/network-area/routingtable/delete/delete.go @@ -0,0 +1,117 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + networkAreaIdFlag = "network-area-id" + organizationIdFlag = "organization-id" + routingTableIdArg = "ROUTING_TABLE_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + NetworkAreaId string + OrganizationId string + RoutingTableId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", routingTableIdArg), + Short: "Deletes a routing-table", + Long: "Deletes a routing-table", + Args: args.SingleArg(routingTableIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Delete a routing-table with ID "xxx"`, + `$ stackit network-area routing-table delete xxx --organization-id yyy --network-area-id zzz`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err) + routingTableLabel = model.RoutingTableId + } else if routingTableLabel == "" { + routingTableLabel = model.RoutingTableId + } + + prompt := fmt.Sprintf("Are you sure you want to delete the routing-table %q?", routingTableLabel) + + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := apiClient.DeleteRoutingTableFromArea( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + ) + err = req.Execute() + if err != nil { + return fmt.Errorf("delete routing-table: %w", err) + } + + params.Printer.Outputf("Routing-table %q deleted.", model.RoutingTableId) + return nil + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + routingTableId := inputArgs[0] + + model := inputModel{ + GlobalFlagModel: globalFlags, + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RoutingTableId: routingTableId, + } + + p.DebugInputModel(model) + return &model, nil +} diff --git a/internal/cmd/network-area/routingtable/delete/delete_test.go b/internal/cmd/network-area/routingtable/delete/delete_test.go new file mode 100644 index 000000000..0bf08b186 --- /dev/null +++ b/internal/cmd/network-area/routingtable/delete/delete_test.go @@ -0,0 +1,145 @@ +package delete + +import ( + "testing" + + "github.com/google/uuid" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +const testRegion = "eu01" + +var ( + testOrgId = uuid.NewString() + testNetworkAreaId = uuid.NewString() + testRoutingTableId = uuid.NewString() +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + RoutingTableId: testRoutingTableId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "valid input", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(func(m *inputModel) { + m.RoutingTableId = testRoutingTableId + }), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "missing organization ID", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "invalid organization ID - empty", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "" + }), + isValid: false, + }, + { + description: "invalid organization ID - format", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "missing network area ID", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "invalid network area ID - empty", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "" + }), + isValid: false, + }, + { + description: "invalid network area ID - format", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "missing routing-table ID", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "invalid routing-table ID - format", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "invalid routing-table ID - empty", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[routingTableIdArg] = "" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} diff --git a/internal/cmd/network-area/routingtable/describe/describe.go b/internal/cmd/network-area/routingtable/describe/describe.go new file mode 100644 index 000000000..8292e3f9b --- /dev/null +++ b/internal/cmd/network-area/routingtable/describe/describe.go @@ -0,0 +1,152 @@ +package describe + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + networkAreaIdFlag = "network-area-id" + organizationIdFlag = "organization-id" + routingTableIdArg = "ROUTING_TABLE_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + NetworkAreaId string + OrganizationId string + RoutingTableId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("describe %s", routingTableIdArg), + Short: "Describes a routing-table", + Long: "Describes a routing-table", + Args: args.SingleArg(routingTableIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Describe a routing-table`, + `$ stackit network-area routing-table describe xxx --organization-id xxx --network-area-id yyy`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + request := apiClient.GetRoutingTableOfArea( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + ) + + response, err := request.Execute() + if err != nil { + return fmt.Errorf("describe routing-tables: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, response) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + routingTableId := inputArgs[0] + + model := inputModel{ + GlobalFlagModel: globalFlags, + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RoutingTableId: routingTableId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func outputResult(p *print.Printer, outputFormat string, routingTable *iaas.RoutingTable) error { + if routingTable == nil { + return fmt.Errorf("describe routingtable response is empty") + } + + return p.OutputResult(outputFormat, routingTable, func() error { + table := tables.NewTable() + + table.AddRow("ID", utils.PtrString(routingTable.Id)) + table.AddSeparator() + + table.AddRow("NAME", utils.PtrString(routingTable.Name)) + table.AddSeparator() + + table.AddRow("DESCRIPTION", utils.PtrString(routingTable.Description)) + table.AddSeparator() + + table.AddRow("DEFAULT", utils.PtrString(routingTable.Default)) + table.AddSeparator() + + if routingTable.Labels != nil && len(*routingTable.Labels) > 0 { + var labels []string + for key, value := range *routingTable.Labels { + labels = append(labels, fmt.Sprintf("%s: %s", key, value)) + } + table.AddRow("LABELS", strings.Join(labels, "\n")) + table.AddSeparator() + } + + table.AddRow("SYSTEM ROUTES", utils.PtrString(routingTable.SystemRoutes)) + table.AddSeparator() + + table.AddRow("DYNAMIC ROUTES", utils.PtrString(routingTable.DynamicRoutes)) + table.AddSeparator() + + table.AddRow("CREATED AT", utils.ConvertTimePToDateTimeString(routingTable.CreatedAt)) + table.AddSeparator() + + table.AddRow("UPDATED AT", utils.ConvertTimePToDateTimeString(routingTable.UpdatedAt)) + table.AddSeparator() + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/network-area/routingtable/describe/describe_test.go b/internal/cmd/network-area/routingtable/describe/describe_test.go new file mode 100644 index 000000000..295b394a2 --- /dev/null +++ b/internal/cmd/network-area/routingtable/describe/describe_test.go @@ -0,0 +1,222 @@ +package describe + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() +var testRoutingTableId = uuid.NewString() + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + RoutingTableId: testRoutingTableId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testRoutingTableId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + argValues []string + isValid bool + expectedModel *inputModel + }{ + { + description: "valid input", + flagValues: fixtureFlagValues(), + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + argValues: []string{}, + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "missing organization ID", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "invalid organization ID - empty", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "" + }), + isValid: false, + }, + { + description: "invalid organization ID - format", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "missing network area ID", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "invalid network area ID - empty", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "" + }), + isValid: false, + }, + { + description: "invalid network area ID - format", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "missing routing-table ID", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "invalid routing-table ID - format", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRouteTable := iaas.RoutingTable{ + CreatedAt: utils.Ptr(time.Now()), + Default: nil, + Description: utils.Ptr("description"), + Id: utils.Ptr("route-foo"), + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + Name: utils.Ptr("route-foo"), + SystemRoutes: utils.Ptr(true), + DynamicRoutes: utils.Ptr(true), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + routingTable *iaas.RoutingTable + wantErr bool + }{ + { + name: "nil routing table", + outputFormat: print.PrettyOutputFormat, + routingTable: nil, + wantErr: true, + }, + { + name: "empty routing table", + outputFormat: print.PrettyOutputFormat, + routingTable: &iaas.RoutingTable{}, + wantErr: false, + }, + { + name: "json empty routing table", + outputFormat: print.JSONOutputFormat, + routingTable: &iaas.RoutingTable{}, + wantErr: false, + }, + { + name: "pretty output one route", + outputFormat: print.PrettyOutputFormat, + routingTable: &dummyRouteTable, + wantErr: false, + }, + { + name: "json output one route", + outputFormat: print.JSONOutputFormat, + routingTable: &dummyRouteTable, + wantErr: false, + }, + { + name: "yaml output one route", + outputFormat: print.YAMLOutputFormat, + routingTable: &dummyRouteTable, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, tt.routingTable); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/network-area/routingtable/list/list.go b/internal/cmd/network-area/routingtable/list/list.go new file mode 100644 index 000000000..82553f83c --- /dev/null +++ b/internal/cmd/network-area/routingtable/list/list.go @@ -0,0 +1,176 @@ +package list + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + labelSelectorFlag = "label-selector" + limitFlag = "limit" + networkAreaIdFlag = "network-area-id" + organizationIdFlag = "organization-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + LabelSelector *string + Limit *int64 + NetworkAreaId string + OrganizationId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all routing-tables", + Long: "Lists all routing-tables", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List all routing-tables`, + `$ stackit network-area routing-table list --organization-id xxx --network-area-id yyy`, + ), + examples.NewExample( + `List all routing-tables with labels`, + `$ stackit network-area routing-table list --label-selector env=dev,env=rc --organization-id xxx --network-area-id yyy`, + ), + examples.NewExample( + `List all routing-tables with labels and set limit to 10`, + `$ stackit network-area routing-table list --label-selector env=dev,env=rc --limit 10 --organization-id xxx --network-area-id yyy`, + ), + ), + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, nil) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + request := buildRequest(ctx, model, apiClient) + + response, err := request.Execute() + if err != nil { + return fmt.Errorf("list routing-tables: %w", err) + } + + routingTables := utils.GetSliceFromPointer(response.Items) + + // Truncate output + if model.Limit != nil && len(routingTables) > int(*model.Limit) { + routingTables = routingTables[:*model.Limit] + } + + return outputResult(params.Printer, model.OutputFormat, routingTables, model.OrganizationId) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + cmd.Flags().String(labelSelectorFlag, "", "Filter by label") + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag), + Limit: limit, + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListRoutingTablesOfAreaRequest { + request := apiClient.ListRoutingTablesOfArea(ctx, model.OrganizationId, model.NetworkAreaId, model.Region) + if model.LabelSelector != nil { + request = request.LabelSelector(*model.LabelSelector) + } + + return request +} + +func outputResult(p *print.Printer, outputFormat string, routingTables []iaas.RoutingTable, orgId string) error { + if routingTables == nil { + return fmt.Errorf("list routing-table items are nil") + } + + return p.OutputResult(outputFormat, routingTables, func() error { + if len(routingTables) == 0 { + p.Outputf("No routing-tables found for organization %q\n", orgId) + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "NAME", "DESCRIPTION", "DEFAULT", "LABELS", "SYSTEM ROUTES", "DYNAMIC ROUTES", "CREATED AT", "UPDATED AT") + for _, routingTable := range routingTables { + var labels []string + if routingTable.Labels != nil && len(*routingTable.Labels) > 0 { + for key, value := range *routingTable.Labels { + labels = append(labels, fmt.Sprintf("%s: %s", key, value)) + } + } + + table.AddRow( + utils.PtrString(routingTable.Id), + utils.PtrString(routingTable.Name), + utils.PtrString(routingTable.Description), + utils.PtrString(routingTable.Default), + strings.Join(labels, "\n"), + utils.PtrString(routingTable.SystemRoutes), + utils.PtrString(routingTable.DynamicRoutes), + utils.ConvertTimePToDateTimeString(routingTable.CreatedAt), + utils.ConvertTimePToDateTimeString(routingTable.UpdatedAt), + ) + } + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + }) +} diff --git a/internal/cmd/network-area/routingtable/list/list_test.go b/internal/cmd/network-area/routingtable/list/list_test.go new file mode 100644 index 000000000..3bccad11c --- /dev/null +++ b/internal/cmd/network-area/routingtable/list/list_test.go @@ -0,0 +1,281 @@ +package list + +import ( + "context" + "strconv" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() + +const testLabelSelectorFlag = "key1=value1,key2=value2" + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &iaas.APIClient{} +var testLimitFlag = int64(10) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + labelSelectorFlag: testLabelSelectorFlag, + limitFlag: strconv.Itoa(int(testLimitFlag)), + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureRequest(mods ...func(request *iaas.ApiListRoutingTablesOfAreaRequest)) iaas.ApiListRoutingTablesOfAreaRequest { + request := testClient.ListRoutingTablesOfArea(testCtx, testOrgId, testNetworkAreaId, testRegion) + request = request.LabelSelector(testLabelSelectorFlag) + + for _, mod := range mods { + mod(&request) + } + return request +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + LabelSelector: utils.Ptr(testLabelSelectorFlag), + Limit: utils.Ptr(testLimitFlag), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "valid input", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "missing network area ID", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "missing organization ID", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "missing labels", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelSelectorFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.LabelSelector = nil + }), + }, + { + description: "missing limit", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, limitFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Limit = nil + }), + }, + { + description: "invalid limit flag", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "negative limit flag", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "-10" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRouteTable := iaas.RoutingTable{ + CreatedAt: utils.Ptr(time.Now()), + Default: nil, + Description: utils.Ptr("description"), + Id: utils.Ptr("route-foo"), + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + Name: utils.Ptr("route-foo"), + SystemRoutes: utils.Ptr(true), + DynamicRoutes: utils.Ptr(true), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + routingTable []iaas.RoutingTable + wantErr bool + }{ + { + name: "nil routing table", + outputFormat: print.PrettyOutputFormat, + routingTable: nil, + wantErr: true, + }, + { + name: "pretty empty routing table", + outputFormat: print.PrettyOutputFormat, + routingTable: []iaas.RoutingTable{}, + wantErr: false, + }, + { + name: "json empty routing table", + outputFormat: print.JSONOutputFormat, + routingTable: []iaas.RoutingTable{}, + wantErr: false, + }, + { + name: "yaml empty routing table", + outputFormat: print.YAMLOutputFormat, + routingTable: []iaas.RoutingTable{}, + wantErr: false, + }, + { + name: "pretty empty routing table in slice", + outputFormat: print.PrettyOutputFormat, + routingTable: []iaas.RoutingTable{{}}, + wantErr: false, + }, + { + name: "yaml empty routing table in slice", + outputFormat: print.YAMLOutputFormat, + routingTable: []iaas.RoutingTable{{}}, + wantErr: false, + }, + { + name: "pretty output with one route", + outputFormat: print.PrettyOutputFormat, + routingTable: []iaas.RoutingTable{dummyRouteTable}, + wantErr: false, + }, + { + name: "pretty output with multiple routes", + outputFormat: print.PrettyOutputFormat, + routingTable: []iaas.RoutingTable{dummyRouteTable, dummyRouteTable, dummyRouteTable}, + wantErr: false, + }, + { + name: "json output with one route", + outputFormat: print.JSONOutputFormat, + routingTable: []iaas.RoutingTable{dummyRouteTable}, + wantErr: false, + }, + { + name: "yaml output with one route", + outputFormat: print.YAMLOutputFormat, + routingTable: []iaas.RoutingTable{dummyRouteTable}, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, tt.routingTable, "dummy-org-id"); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest iaas.ApiListRoutingTablesOfAreaRequest + }{ + { + description: "valid input with label selector", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "missing label selector", + model: fixtureInputModel(func(model *inputModel) { + model.LabelSelector = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiListRoutingTablesOfAreaRequest) { + *request = testClient.ListRoutingTablesOfArea(testCtx, testOrgId, testNetworkAreaId, testRegion) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + if diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx)); diff != "" { + t.Errorf("buildRequest() mismatch (-got +want):\n%s", diff) + } + }) + } +} diff --git a/internal/cmd/network-area/routingtable/route/create/create.go b/internal/cmd/network-area/routingtable/route/create/create.go new file mode 100644 index 000000000..d3455087d --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/create/create.go @@ -0,0 +1,269 @@ +package create + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + destinationTypeFlag = "destination-type" + destinationValueFlag = "destination-value" + labelFlag = "labels" + networkAreaIdFlag = "network-area-id" + nextHopTypeFlag = "nexthop-type" + nextHopValueFlag = "nexthop-value" + organizationIdFlag = "organization-id" + routingTableIdFlag = "routing-table-id" + + // Destination Type Constants + destTypeCIDRv4 = "cidrv4" + destTypeCIDRv6 = "cidrv6" + + // NextHop Type Constants + nextHopTypeIPv4 = "ipv4" + nextHopTypeIPv6 = "ipv6" + nextHopTypeInternet = "internet" + nextHopTypeBlackhole = "blackhole" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + DestinationType string + DestinationValue *string + Labels *map[string]string + NetworkAreaId string + NextHopType string + NextHopValue *string + OrganizationId string + RoutingTableId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates a route in a routing-table", + Long: "Creates a route in a routing-table.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample("Create a route with CIDRv4 destination and IPv4 nexthop", + `$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv4 --destination-value --nexthop-type ipv4 --nexthop-value `), + + examples.NewExample("Create a route with CIDRv6 destination and IPv6 nexthop", + `$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value --nexthop-type ipv6 --nexthop-value `), + + examples.NewExample("Create a route with CIDRv6 destination and Nexthop Internet", + `$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value --nexthop-type internet`), + ), + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, nil) + if err != nil { + return err + } + + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err) + routingTableLabel = model.RoutingTableId + } else if routingTableLabel == "" { + routingTableLabel = model.RoutingTableId + } + + prompt := fmt.Sprintf("Are you sure you want to create a route for routing-table %q?", routingTableLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + req, err := buildRequest(ctx, model, apiClient) + if err != nil { + return err + } + + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("create route request failed: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, resp.GetItems()) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.CIDRFlag(), destinationValueFlag, "Destination value") + cmd.Flags().String(nextHopValueFlag, "", "NextHop value") + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID") + + cmd.Flags().Var( + flags.EnumFlag(true, "", destTypeCIDRv4, destTypeCIDRv6), + destinationTypeFlag, + "Destination type") + + cmd.Flags().Var( + flags.EnumFlag(true, "", nextHopTypeIPv4, nextHopTypeIPv6, nextHopTypeInternet, nextHopTypeBlackhole), + nextHopTypeFlag, + "Next hop type") + + cmd.Flags().StringToString(labelFlag, nil, "Key=value labels") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag, destinationTypeFlag, destinationValueFlag, nextHopTypeFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := &inputModel{ + GlobalFlagModel: globalFlags, + DestinationType: flags.FlagToStringValue(p, cmd, destinationTypeFlag), + DestinationValue: flags.FlagToStringPointer(p, cmd, destinationValueFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + NextHopType: flags.FlagToStringValue(p, cmd, nextHopTypeFlag), + NextHopValue: flags.FlagToStringPointer(p, cmd, nextHopValueFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag), + } + + // Next Hop validation logic + switch strings.ToLower(model.NextHopType) { + case nextHopTypeInternet, nextHopTypeBlackhole: + if model.NextHopValue != nil && *model.NextHopValue != "" { + return nil, errors.New("--nexthop-value is not allowed when --nexthop-type is 'internet' or 'blackhole'") + } + case nextHopTypeIPv4, nextHopTypeIPv6: + if model.NextHopValue == nil || *model.NextHopValue == "" { + return nil, errors.New("--nexthop-value is required when --nexthop-type is 'ipv4' or 'ipv6'") + } + default: + return nil, fmt.Errorf("invalid nexthop-type: %q", model.NextHopType) + } + + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) (iaas.ApiAddRoutesToRoutingTableRequest, error) { + destination := buildDestination(model) + nextHop := buildNextHop(model) + + if destination != nil && nextHop != nil { + payload := iaas.AddRoutesToRoutingTablePayload{ + Items: &[]iaas.Route{ + { + Destination: destination, + Nexthop: nextHop, + Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), + }, + }, + } + + return apiClient.AddRoutesToRoutingTable( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + ).AddRoutesToRoutingTablePayload(payload), nil + } + + return nil, fmt.Errorf("invalid input") +} + +func buildDestination(model *inputModel) *iaas.RouteDestination { + if model.DestinationValue == nil { + return nil + } + + destinationType := strings.ToLower(model.DestinationType) + switch destinationType { + case destTypeCIDRv4: + return &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: &model.DestinationType, + Value: model.DestinationValue, + }, + } + case destTypeCIDRv6: + return &iaas.RouteDestination{ + DestinationCIDRv6: &iaas.DestinationCIDRv6{ + Type: &model.DestinationType, + Value: model.DestinationValue, + }, + } + default: + return nil + } +} + +func buildNextHop(model *inputModel) *iaas.RouteNexthop { + nextHopType := strings.ToLower(model.NextHopType) + switch nextHopType { + case nextHopTypeIPv4: + return &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: &model.NextHopType, + Value: model.NextHopValue, + }, + } + case nextHopTypeIPv6: + return &iaas.RouteNexthop{ + NexthopIPv6: &iaas.NexthopIPv6{ + Type: &model.NextHopType, + Value: model.NextHopValue, + }, + } + case nextHopTypeInternet: + return &iaas.RouteNexthop{ + NexthopInternet: &iaas.NexthopInternet{ + Type: &model.NextHopType, + }, + } + case nextHopTypeBlackhole: + return &iaas.RouteNexthop{ + NexthopBlackhole: &iaas.NexthopBlackhole{ + Type: &model.NextHopType, + }, + } + default: + return nil + } +} + +func outputResult(p *print.Printer, outputFormat string, routes []iaas.Route) error { + if len(routes) == 0 { + return fmt.Errorf("create routes response is empty") + } + + return p.OutputResult(outputFormat, routes, func() error { + for _, route := range routes { + p.Outputf("Created route with ID %q\n", utils.PtrString(route.Id)) + } + return nil + }) +} diff --git a/internal/cmd/network-area/routingtable/route/create/create_test.go b/internal/cmd/network-area/routingtable/route/create/create_test.go new file mode 100644 index 000000000..a5b99aff0 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/create/create_test.go @@ -0,0 +1,702 @@ +package create + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &iaas.APIClient{} + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() +var testRoutingTableId = uuid.NewString() + +const testDestinationTypeFlag = destTypeCIDRv4 +const testDestinationValueFlag = "1.1.1.0/24" +const testNextHopTypeFlag = nextHopTypeIPv4 +const testNextHopValueFlag = "1.1.1.1" +const testLabelSelectorFlag = "key1=value1,key2=value2" + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + labelFlag: testLabelSelectorFlag, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + routingTableIdFlag: testRoutingTableId, + destinationTypeFlag: testDestinationTypeFlag, + destinationValueFlag: testDestinationValueFlag, + nextHopTypeFlag: testNextHopTypeFlag, + nextHopValueFlag: testNextHopValueFlag, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + RoutingTableId: testRoutingTableId, + DestinationType: testDestinationTypeFlag, + DestinationValue: utils.Ptr(testDestinationValueFlag), + NextHopType: testNextHopTypeFlag, + NextHopValue: utils.Ptr(testNextHopValueFlag), + Labels: testLabels, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiAddRoutesToRoutingTableRequest)) iaas.ApiAddRoutesToRoutingTableRequest { + request := testClient.AddRoutesToRoutingTable(testCtx, testOrgId, testNetworkAreaId, testRegion, testRoutingTableId) + request = request.AddRoutesToRoutingTablePayload(fixturePayload()) + for _, mod := range mods { + mod(&request) + } + return request +} + +func fixturePayload(mods ...func(payload *iaas.AddRoutesToRoutingTablePayload)) iaas.AddRoutesToRoutingTablePayload { + payload := iaas.AddRoutesToRoutingTablePayload{ + Items: &[]iaas.Route{ + { + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(testDestinationTypeFlag), + Value: utils.Ptr(testDestinationValueFlag), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(testNextHopTypeFlag), + Value: utils.Ptr(testNextHopValueFlag), + }, + }, + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + }, + }, + } + for _, mod := range mods { + mod(&payload) + } + return payload +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "valid input", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "routing-table ID missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, routingTableIdFlag) + }), + isValid: false, + }, + { + description: "destination value missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, destinationValueFlag) + }), + isValid: false, + }, + { + description: "destination type missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, destinationTypeFlag) + }), + isValid: false, + }, + { + description: "next hop type missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, nextHopTypeFlag) + }), + isValid: false, + }, + { + description: "next hop value missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, nextHopValueFlag) + }), + isValid: false, + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "organization ID missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "organization ID invalid - empty", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "" + }), + isValid: false, + }, + { + description: "organization ID invalid - format", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "network area ID missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "network area ID invalid - empty", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "" + }), + isValid: false, + }, + { + description: "network area ID invalid - format", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "invalid destination type enum", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[destinationTypeFlag] = nextHopTypeIPv4 // Deliberately invalid for dest + }), + isValid: false, + }, + { + description: "destination value not IPv4 CIDR", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[destinationValueFlag] = "0.0.0.0" + }), + isValid: false, + }, + { + description: "destination value not IPv6 CIDR", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[destinationTypeFlag] = destTypeCIDRv6 + flagValues[destinationValueFlag] = "2001:db8::" + }), + isValid: false, + }, + { + description: "destination value is IPv6 CIDR", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[destinationTypeFlag] = destTypeCIDRv6 + flagValues[destinationValueFlag] = "2001:db8::/32" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.DestinationType = destTypeCIDRv6 + model.DestinationValue = utils.Ptr("2001:db8::/32") + }), + }, + { + description: "invalid next hop type enum", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = destTypeCIDRv4 // Deliberately invalid for hop + }), + isValid: false, + }, + { + description: "next hop type is internet and next hop value is provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = nextHopTypeInternet + flagValues[nextHopValueFlag] = "1.1.1.1" // should not be allowed + }), + isValid: false, + }, + { + description: "next hop type is blackhole and next hop value is provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = nextHopTypeBlackhole + flagValues[nextHopValueFlag] = "1.1.1.1" + }), + isValid: false, + }, + { + description: "next hop type is internet and next hop value is not provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = nextHopTypeInternet + delete(flagValues, nextHopValueFlag) + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.NextHopType = nextHopTypeInternet + model.NextHopValue = nil + }), + isValid: true, + }, + { + description: "next hop type is blackhole and next hop value is not provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = nextHopTypeBlackhole + delete(flagValues, nextHopValueFlag) + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.NextHopType = nextHopTypeBlackhole + model.NextHopValue = nil + }), + isValid: true, + }, + { + description: "next hop type is IPv4 and next hop value is missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = nextHopTypeIPv4 + delete(flagValues, nextHopValueFlag) + }), + isValid: false, + }, + { + description: "next hop type is IPv6 and next hop value is missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = nextHopTypeIPv6 + delete(flagValues, nextHopValueFlag) + }), + isValid: false, + }, + { + description: "invalid next hop type provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nextHopTypeFlag] = "invalid-type" + }), + isValid: false, + }, + { + description: "optional labels are provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[labelFlag] = "key=value" + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = utils.Ptr(map[string]string{"key": "value"}) + }), + isValid: true, + }, + { + description: "argument value is empty string", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "argument value wrong", + argValues: []string{"foo-bar"}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildNextHop(t *testing.T) { + tests := []struct { + description string + model *inputModel + expected *iaas.RouteNexthop + }{ + { + description: "IPv4 next hop", + model: fixtureInputModel(func(m *inputModel) { + m.NextHopType = nextHopTypeIPv4 + m.NextHopValue = utils.Ptr("1.1.1.1") + }), + expected: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(nextHopTypeIPv4), + Value: utils.Ptr("1.1.1.1"), + }, + }, + }, + { + description: "IPv6 next hop", + model: fixtureInputModel(func(m *inputModel) { + m.NextHopType = nextHopTypeIPv6 + m.NextHopValue = utils.Ptr("::1") + }), + expected: &iaas.RouteNexthop{ + NexthopIPv6: &iaas.NexthopIPv6{ + Type: utils.Ptr(nextHopTypeIPv6), + Value: utils.Ptr("::1"), + }, + }, + }, + { + description: "Internet next hop", + model: fixtureInputModel(func(m *inputModel) { + m.NextHopType = nextHopTypeInternet + m.NextHopValue = nil + }), + expected: &iaas.RouteNexthop{ + NexthopInternet: &iaas.NexthopInternet{ + Type: utils.Ptr(nextHopTypeInternet), + }, + }, + }, + { + description: "Blackhole next hop", + model: fixtureInputModel(func(m *inputModel) { + m.NextHopType = nextHopTypeBlackhole + m.NextHopValue = nil + }), + expected: &iaas.RouteNexthop{ + NexthopBlackhole: &iaas.NexthopBlackhole{ + Type: utils.Ptr(nextHopTypeBlackhole), + }, + }, + }, + { + description: "Unsupported next hop type", + model: fixtureInputModel(func(m *inputModel) { + m.NextHopType = "unsupported" + }), + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got := buildNextHop(tt.model) + if diff := cmp.Diff(tt.expected, got); diff != "" { + t.Errorf("buildNextHop() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestBuildDestination(t *testing.T) { + tests := []struct { + description string + model *inputModel + expected *iaas.RouteDestination + }{ + { + description: "CIDRv4 destination", + model: fixtureInputModel(func(m *inputModel) { + m.DestinationType = destTypeCIDRv4 + m.DestinationValue = utils.Ptr("192.168.1.0/24") + }), + expected: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(destTypeCIDRv4), + Value: utils.Ptr("192.168.1.0/24"), + }, + }, + }, + { + description: "CIDRv6 destination", + model: fixtureInputModel(func(m *inputModel) { + m.DestinationType = destTypeCIDRv6 + m.DestinationValue = utils.Ptr("2001:db8::/32") + }), + expected: &iaas.RouteDestination{ + DestinationCIDRv6: &iaas.DestinationCIDRv6{ + Type: utils.Ptr(destTypeCIDRv6), + Value: utils.Ptr("2001:db8::/32"), + }, + }, + }, + { + description: "unsupported destination type", + model: fixtureInputModel(func(m *inputModel) { + m.DestinationType = "other" + m.DestinationValue = utils.Ptr("1.1.1.1") + }), + expected: nil, + }, + { + description: "nil destination value", + model: fixtureInputModel(func(m *inputModel) { + m.DestinationValue = nil + }), + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got := buildDestination(tt.model) + if diff := cmp.Diff(tt.expected, got); diff != "" { + t.Errorf("buildDestination() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest iaas.ApiAddRoutesToRoutingTableRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "optional labels provided", + model: fixtureInputModel(func(model *inputModel) { + model.Labels = utils.Ptr(map[string]string{"key": "value"}) + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) { + *request = (*request).AddRoutesToRoutingTablePayload(fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) { + (*payload.Items)[0].Labels = utils.ConvertStringMapToInterfaceMap(utils.Ptr(map[string]string{"key": "value"})) + })) + }), + }, + { + description: "destination is cidrv6 and nexthop is ipv6", + model: fixtureInputModel(func(model *inputModel) { + model.DestinationType = destTypeCIDRv6 + model.DestinationValue = utils.Ptr("2001:db8::/32") + model.NextHopType = nextHopTypeIPv6 + model.NextHopValue = utils.Ptr("2001:db8::1") + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) { + *request = (*request).AddRoutesToRoutingTablePayload(iaas.AddRoutesToRoutingTablePayload{ + Items: &[]iaas.Route{ + { + Destination: &iaas.RouteDestination{ + DestinationCIDRv6: &iaas.DestinationCIDRv6{ + Type: utils.Ptr(destTypeCIDRv6), + Value: utils.Ptr("2001:db8::/32"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv6: &iaas.NexthopIPv6{ + Type: utils.Ptr(nextHopTypeIPv6), + Value: utils.Ptr("2001:db8::1"), + }, + }, + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + }, + }, + }) + }), + }, + { + description: "nexthop type is internet (no value)", + model: fixtureInputModel(func(model *inputModel) { + model.NextHopType = nextHopTypeInternet + model.NextHopValue = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) { + payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) { + (*payload.Items)[0].Nexthop = &iaas.RouteNexthop{ + NexthopInternet: &iaas.NexthopInternet{ + Type: utils.Ptr(nextHopTypeInternet), + }, + } + }) + *request = (*request).AddRoutesToRoutingTablePayload(payload) + }), + }, + { + description: "nexthop type is blackhole (no value)", + model: fixtureInputModel(func(model *inputModel) { + model.NextHopType = nextHopTypeBlackhole + model.NextHopValue = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) { + payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) { + (*payload.Items)[0].Nexthop = &iaas.RouteNexthop{ + NexthopBlackhole: &iaas.NexthopBlackhole{ + Type: utils.Ptr(nextHopTypeBlackhole), + }, + } + }) + *request = (*request).AddRoutesToRoutingTablePayload(payload) + }), + }, + { + description: "nexthop type is ipv4 with value", + model: fixtureInputModel(func(model *inputModel) { + model.NextHopType = nextHopTypeIPv4 + model.NextHopValue = utils.Ptr("1.2.3.4") + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) { + payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) { + (*payload.Items)[0].Nexthop = &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(nextHopTypeIPv4), + Value: utils.Ptr("1.2.3.4"), + }, + } + }) + *request = (*request).AddRoutesToRoutingTablePayload(payload) + }), + }, + { + description: "nexthop type is ipv6 with value", + model: fixtureInputModel(func(model *inputModel) { + model.NextHopType = nextHopTypeIPv6 + model.NextHopValue = utils.Ptr("2001:db8::1") + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) { + payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) { + (*payload.Items)[0].Nexthop = &iaas.RouteNexthop{ + NexthopIPv6: &iaas.NexthopIPv6{ + Type: utils.Ptr(nextHopTypeIPv6), + Value: utils.Ptr("2001:db8::1"), + }, + } + }) + *request = (*request).AddRoutesToRoutingTablePayload(payload) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request, err := buildRequest(testCtx, tt.model, testClient) + if err != nil { + t.Fatalf("buildRequest returned error: %v", err) + } + + if diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx)); diff != "" { + t.Errorf("buildRequest() mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRoute := iaas.Route{ + Id: utils.Ptr("route-foo"), + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(destTypeCIDRv4), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(nextHopTypeIPv4), + Value: utils.Ptr("10.0.0.1"), + }, + }, + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + CreatedAt: utils.Ptr(time.Now()), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + routes []iaas.Route + wantErr bool + }{ + { + name: "nil routes should return error", + outputFormat: print.PrettyOutputFormat, + routes: nil, + wantErr: true, + }, + { + name: "empty routes list", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{}, + wantErr: true, + }, + { + name: "route list with empty struct", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{{}}, + wantErr: false, + }, + { + name: "pretty output with one route", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{dummyRoute}, + wantErr: false, + }, + { + name: "pretty output with multiple routes", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{dummyRoute, dummyRoute, dummyRoute}, + wantErr: false, + }, + { + name: "json output with one route", + outputFormat: print.JSONOutputFormat, + routes: []iaas.Route{dummyRoute}, + wantErr: false, + }, + { + name: "yaml output with one route", + outputFormat: print.YAMLOutputFormat, + routes: []iaas.Route{dummyRoute}, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, tt.routes); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/network-area/routingtable/route/delete/delete.go b/internal/cmd/network-area/routingtable/route/delete/delete.go new file mode 100644 index 000000000..ba4ea28cb --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/delete/delete.go @@ -0,0 +1,121 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + networkAreaIdFlag = "network-area-id" + organizationIdFlag = "organization-id" + routeIdArg = "ROUTE_ID" + routingTableIdFlag = "routing-table-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + NetworkAreaId string + OrganizationId string + RouteID string + RoutingTableId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", routingTableIdFlag), + Short: "Deletes a route within a routing-table", + Long: "Deletes a route within a routing-table", + Args: args.SingleArg(routeIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Deletes a route within a routing-table`, + `$ stackit network-area routing-table route delete xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err) + routingTableLabel = model.RoutingTableId + } else if routingTableLabel == "" { + routingTableLabel = model.RoutingTableId + } + + prompt := fmt.Sprintf("Are you sure you want to delete the route %q in routing-table %q for network area id %q?", model.RouteID, routingTableLabel, model.NetworkAreaId) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := apiClient.DeleteRouteFromRoutingTable( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + model.RouteID, + ) + err = req.Execute() + if err != nil { + return fmt.Errorf("delete route from routing-table: %w", err) + } + + params.Printer.Outputf("Route %q from routing-table %q deleted.\n", model.RouteID, model.RoutingTableId) + return nil + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + routeId := inputArgs[0] + + model := inputModel{ + GlobalFlagModel: globalFlags, + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RouteID: routeId, + RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag), + } + + p.DebugInputModel(model) + return &model, nil +} diff --git a/internal/cmd/network-area/routingtable/route/delete/delete_test.go b/internal/cmd/network-area/routingtable/route/delete/delete_test.go new file mode 100644 index 000000000..eb956ef98 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/delete/delete_test.go @@ -0,0 +1,133 @@ +package delete + +import ( + "testing" + + "github.com/google/uuid" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var ( + testOrgId = uuid.NewString() + testNetworkAreaId = uuid.NewString() + testRoutingTableId = uuid.NewString() + testRouteId = uuid.NewString() +) + +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { + flagValues := map[string]string{ + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + routingTableIdFlag: testRoutingTableId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(*inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.InfoVerbosity, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + RoutingTableId: testRoutingTableId, + RouteID: testRouteId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "valid input", + argValues: []string{testRouteId}, + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(func(m *inputModel) { + m.RouteID = testRouteId + }), + }, + { + description: "missing route id arg", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "missing organization-id flag", + argValues: []string{testRouteId}, + flagValues: fixtureFlagValues(func(m map[string]string) { + delete(m, "organization-id") + }), + isValid: false, + }, + { + description: "missing network-area-id flag", + argValues: []string{testRouteId}, + flagValues: fixtureFlagValues(func(m map[string]string) { + delete(m, "network-area-id") + }), + isValid: false, + }, + { + description: "missing routing-table-id flag", + argValues: []string{testRouteId}, + flagValues: fixtureFlagValues(func(m map[string]string) { + delete(m, "routing-table-id") + }), + isValid: false, + }, + { + description: "arg value missing", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "arg value wrong", + argValues: []string{"foo-bar"}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "invalid organization-id flag", + argValues: []string{testRouteId}, + flagValues: map[string]string{"organization-id": "invalid-org"}, + isValid: false, + }, + { + description: "invalid network-area-id flag", + argValues: []string{testRouteId}, + flagValues: map[string]string{"network-area-id": "invalid-area"}, + isValid: false, + }, + { + description: "invalid routing-table-id flag", + argValues: []string{testRouteId}, + flagValues: map[string]string{"routing-table-id": "invalid-table"}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} diff --git a/internal/cmd/network-area/routingtable/route/describe/describe.go b/internal/cmd/network-area/routingtable/route/describe/describe.go new file mode 100644 index 000000000..128394538 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/describe/describe.go @@ -0,0 +1,158 @@ +package describe + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + routeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/network-area/routing-table/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + networkAreaIdFlag = "network-area-id" + organizationIdFlag = "organization-id" + routeIdArg = "ROUTE_ID" + routingTableIdFlag = "routing-table-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + NetworkAreaId string + OrganizationId string + RouteID string + RoutingTableId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("describe %s", routeIdArg), + Short: "Describes a route within a routing-table", + Long: "Describes a route within a routing-table", + Args: args.SingleArg(routeIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Describe a route within a routing-table`, + `$ stackit network-area routing-table route describe xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + request := apiClient.GetRouteOfRoutingTable( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + model.RouteID, + ) + + response, err := request.Execute() + if err != nil { + return fmt.Errorf("describe route: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, response) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + routeId := inputArgs[0] + + model := inputModel{ + GlobalFlagModel: globalFlags, + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RouteID: routeId, + RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func outputResult(p *print.Printer, outputFormat string, route *iaas.Route) error { + if route == nil { + return fmt.Errorf("describe route response is empty") + } + + return p.OutputResult(outputFormat, route, func() error { + routeDetails := routeUtils.ExtractRouteDetails(*route) + + table := tables.NewTable() + + table.AddRow("ID", utils.PtrString(route.Id)) + table.AddSeparator() + + table.AddRow("DESTINATION TYPE", routeDetails.DestType) + table.AddSeparator() + + table.AddRow("DESTINATION VALUE", routeDetails.DestValue) + table.AddSeparator() + + table.AddRow("NEXTHOP TYPE", routeDetails.HopType) + table.AddSeparator() + + table.AddRow("NEXTHOP VALUE", routeDetails.HopValue) + table.AddSeparator() + + if route.Labels != nil && len(*route.Labels) > 0 { + var labels []string + for key, value := range *route.Labels { + labels = append(labels, fmt.Sprintf("%s: %s", key, value)) + } + table.AddRow("LABELS", strings.Join(labels, "\n")) + table.AddSeparator() + } + + table.AddRow("CREATED AT", routeDetails.CreatedAt) + table.AddSeparator() + + table.AddRow("UPDATED AT", routeDetails.UpdatedAt) + table.AddSeparator() + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + }) +} diff --git a/internal/cmd/network-area/routingtable/route/describe/describe_test.go b/internal/cmd/network-area/routingtable/route/describe/describe_test.go new file mode 100644 index 000000000..4459a2fb8 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/describe/describe_test.go @@ -0,0 +1,234 @@ +package describe + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() +var testRoutingTableId = uuid.NewString() +var testRouteId = uuid.NewString() + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + routingTableIdFlag: testRoutingTableId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + RoutingTableId: testRoutingTableId, + RouteID: testRouteId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testRouteId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + argValues []string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + argValues: []string{}, + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "routing-table-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, routingTableIdFlag) + }), + isValid: false, + }, + { + description: "network-area-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "org-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "invalid routing-table-id", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[routingTableIdFlag] = "invalid-id" + }), + isValid: false, + }, + { + description: "arg value missing", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "arg value wrong", + argValues: []string{"foo-bar"}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "invalid organization-id", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[organizationIdFlag] = "invalid-org" + }), + isValid: false, + }, + { + description: "invalid network-area-id", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[networkAreaIdFlag] = "invalid-area" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRoute := iaas.Route{ + Id: utils.Ptr("route-foo"), + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr("cidrv4"), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr("ipv4"), + Value: utils.Ptr("10.0.0.1"), + }, + }, + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + CreatedAt: utils.Ptr(time.Now()), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + route *iaas.Route + wantErr bool + }{ + { + name: "nil route should return error", + outputFormat: print.PrettyOutputFormat, + route: nil, + wantErr: true, + }, + { + name: "empty route", + outputFormat: print.PrettyOutputFormat, + route: &iaas.Route{}, + wantErr: false, + }, + { + name: "json empty route", + outputFormat: print.JSONOutputFormat, + route: &iaas.Route{}, + wantErr: false, + }, + { + name: "pretty output with one route", + outputFormat: print.PrettyOutputFormat, + route: &dummyRoute, + wantErr: false, + }, + { + name: "json output with one route", + outputFormat: print.JSONOutputFormat, + route: &dummyRoute, + wantErr: false, + }, + { + name: "yaml output with one route", + outputFormat: print.YAMLOutputFormat, + route: &dummyRoute, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, tt.route); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/network-area/routingtable/route/list/list.go b/internal/cmd/network-area/routingtable/route/list/list.go new file mode 100644 index 000000000..4fe9f1122 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/list/list.go @@ -0,0 +1,173 @@ +package list + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + routeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/network-area/routing-table/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + labelSelectorFlag = "label-selector" + limitFlag = "limit" + networkAreaIdFlag = "network-area-id" + organizationIdFlag = "organization-id" + routingTableIdFlag = "routing-table-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + LabelSelector *string + Limit *int64 + NetworkAreaId string + OrganizationId string + RoutingTableId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all routes within a routing-table", + Long: "Lists all routes within a routing-table", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List all routes within a routing-table`, + `$ stackit network-area routing-table route list --routing-table-id xxx --organization-id yyy --network-area-id zzz`, + ), + examples.NewExample( + `List all routes within a routing-table with labels`, + `$ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc`, + ), + examples.NewExample( + `List all routes within a routing-tables with labels and limit to 10`, + `$ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc --limit 10`, + ), + ), + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, nil) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + request := apiClient.ListRoutesOfRoutingTable( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + ) + + if model.LabelSelector != nil { + request.LabelSelector(*model.LabelSelector) + } + + response, err := request.Execute() + if err != nil { + return fmt.Errorf("list routes: %w", err) + } + + routes := utils.GetSliceFromPointer(response.Items) + + // Truncate output + if model.Limit != nil && len(routes) > int(*model.Limit) { + routes = routes[:*model.Limit] + } + + return outputResult(params.Printer, model.OutputFormat, routes, model.OrganizationId, model.RoutingTableId) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + cmd.Flags().String(labelSelectorFlag, "", "Filter by label") + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag), + Limit: limit, + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func outputResult(p *print.Printer, outputFormat string, routes []iaas.Route, orgId, routeTableId string) error { + if routes == nil { + return fmt.Errorf("list routes routes are nil") + } + + return p.OutputResult(outputFormat, routes, func() error { + if len(routes) == 0 { + p.Outputf("No routes found for routing-table %q in organization %q\n", routeTableId, orgId) + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "DESTINATION TYPE", "DESTINATION VALUE", "NEXTHOP TYPE", "NEXTHOP VALUE", "LABELS", "CREATED AT", "UPDATED AT") + for _, route := range routes { + routeDetails := routeUtils.ExtractRouteDetails(route) + table.AddRow( + utils.PtrString(route.Id), + routeDetails.DestType, + routeDetails.DestValue, + routeDetails.HopType, + routeDetails.HopValue, + routeDetails.Labels, + routeDetails.CreatedAt, + routeDetails.UpdatedAt, + ) + } + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/network-area/routingtable/route/list/list_test.go b/internal/cmd/network-area/routingtable/route/list/list_test.go new file mode 100644 index 000000000..18930a4a2 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/list/list_test.go @@ -0,0 +1,247 @@ +package list + +import ( + "strconv" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() +var testRoutingTableId = uuid.NewString() + +const testLabelSelectorFlag = "key1=value1,key2=value2" + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +var testLimitFlag = int64(10) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + routingTableIdFlag: testRoutingTableId, + labelSelectorFlag: testLabelSelectorFlag, + limitFlag: strconv.Itoa(int(testLimitFlag)), + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + RoutingTableId: testRoutingTableId, + LabelSelector: utils.Ptr(testLabelSelectorFlag), + Limit: utils.Ptr(testLimitFlag), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + argValues []string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "routing-table-id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, routingTableIdFlag) + }), + isValid: false, + }, + { + description: "network-area-id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "org-id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "labels missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelSelectorFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.LabelSelector = nil + }), + }, + { + description: "limit missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, limitFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Limit = nil + }), + }, + { + description: "invalid limit flag", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "negative limit flag", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "-10" + }), + isValid: false, + }, + { + description: "limit zero flag", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRoute := iaas.Route{ + Id: utils.Ptr("route-foo"), + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr("cidrv4"), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr("ipv4"), + Value: utils.Ptr("10.0.0.1"), + }, + }, + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + CreatedAt: utils.Ptr(time.Now()), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + routes []iaas.Route + wantErr bool + }{ + { + name: "nil routes should return error", + outputFormat: print.PrettyOutputFormat, + routes: nil, + wantErr: true, + }, + { + name: "empty routes list", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{}, + wantErr: false, + }, + { + name: "empty routes list json output", + outputFormat: print.JSONOutputFormat, + routes: []iaas.Route{}, + wantErr: false, + }, + { + name: "empty routes list json output", + outputFormat: print.YAMLOutputFormat, + routes: []iaas.Route{}, + wantErr: false, + }, + { + name: "route list with empty struct", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{{}}, + wantErr: false, + }, + { + name: "pretty output with one route", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{dummyRoute}, + wantErr: false, + }, + { + name: "pretty output with multiple routes", + outputFormat: print.PrettyOutputFormat, + routes: []iaas.Route{dummyRoute, dummyRoute, dummyRoute}, + wantErr: false, + }, + { + name: "json output with one route", + outputFormat: print.JSONOutputFormat, + routes: []iaas.Route{dummyRoute}, + wantErr: false, + }, + { + name: "yaml output with one route", + outputFormat: print.YAMLOutputFormat, + routes: []iaas.Route{dummyRoute}, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, tt.routes, "dummy-org", "dummy-route-table-id"); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/network-area/routingtable/route/route.go b/internal/cmd/network-area/routingtable/route/route.go new file mode 100644 index 000000000..7b2d05559 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/route.go @@ -0,0 +1,34 @@ +package route + +import ( + "github.com/spf13/cobra" + + "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/update" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "route", + Short: "Manages routes of a routing-table", + Long: "Manages routes of a routing-table", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(describe.NewCmd(params)) + cmd.AddCommand(list.NewCmd(params)) + cmd.AddCommand(delete.NewCmd(params)) + cmd.AddCommand(update.NewCmd(params)) + cmd.AddCommand(create.NewCmd(params)) +} diff --git a/internal/cmd/network-area/routingtable/route/update/update.go b/internal/cmd/network-area/routingtable/route/update/update.go new file mode 100644 index 000000000..719043be2 --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/update/update.go @@ -0,0 +1,155 @@ +package update + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + labelFlag = "labels" + networkAreaIdFlag = "network-area-id" + organizationIdFlag = "organization-id" + routeIdArg = "ROUTE_ID" + routingTableIdFlag = "routing-table-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Labels *map[string]string + NetworkAreaId string + OrganizationId string + RouteId string + RoutingTableId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("update %s", routeIdArg), + Short: "Updates a route in a routing-table", + Long: "Updates a route in a routing-table.", + Args: args.SingleArg(routeIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Updates the label(s) of a route with ID "xxx" in a routing-table ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"`, + "$ stackit network-area routing-table route update xxx --labels key=value,foo=bar --routing-table-id xxx --organization-id yyy --network-area-id zzz", + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err) + routingTableLabel = model.RoutingTableId + } else if routingTableLabel == "" { + routingTableLabel = model.RoutingTableId + } + + prompt := fmt.Sprintf("Are you sure you want to update route %q for routing-table %q?", model.RouteId, routingTableLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("update route %q of routing-table %q : %w", model.RouteId, model.RoutingTableId, err) + } + + return outputResult(params.Printer, model.OutputFormat, model.RoutingTableId, model.NetworkAreaId, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a route. A label can be provided with the format key=value and the flag can be used multiple times to provide a list of labels") + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID") + + err := flags.MarkFlagsRequired(cmd, labelFlag, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + routeId := inputArgs[0] + + labels := flags.FlagToStringToStringPointer(p, cmd, labelFlag) + + if labels == nil { + return nil, &cliErr.EmptyUpdateError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Labels: labels, + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RouteId: routeId, + RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func outputResult(p *print.Printer, outputFormat, routingTableId, networkAreaId string, route *iaas.Route) error { + return p.OutputResult(outputFormat, route, func() error { + if route == nil { + return fmt.Errorf("update route response is empty") + } + + if route.Id == nil || *route.Id == "" { + return fmt.Errorf("update route response has empty id") + } + + p.Outputf("Updated route %q for routing-table %q in network-area %q.\n", *route.Id, routingTableId, networkAreaId) + return nil + }) +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiUpdateRouteOfRoutingTableRequest { + req := apiClient.UpdateRouteOfRoutingTable( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + model.RouteId, + ) + + payload := iaas.UpdateRouteOfRoutingTablePayload{ + Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), + } + + return req.UpdateRouteOfRoutingTablePayload(payload) +} diff --git a/internal/cmd/network-area/routingtable/route/update/update_test.go b/internal/cmd/network-area/routingtable/route/update/update_test.go new file mode 100644 index 000000000..1e1e0a19b --- /dev/null +++ b/internal/cmd/network-area/routingtable/route/update/update_test.go @@ -0,0 +1,302 @@ +package update + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &iaas.APIClient{} + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() +var testRoutingTableId = uuid.NewString() +var testRouteId = uuid.NewString() + +const testLabelSelectorFlag = "key1=value1,key2=value2" + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + routingTableIdFlag: testRoutingTableId, + labelFlag: testLabelSelectorFlag, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + RoutingTableId: testRoutingTableId, + RouteId: testRouteId, + Labels: testLabels, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testRouteId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureRequest(mods ...func(req *iaas.ApiUpdateRouteOfRoutingTableRequest)) iaas.ApiUpdateRouteOfRoutingTableRequest { + req := testClient.UpdateRouteOfRoutingTable( + testCtx, + testOrgId, + testNetworkAreaId, + testRegion, + testRoutingTableId, + testRouteId, + ) + + payload := iaas.UpdateRouteOfRoutingTablePayload{ + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + } + + req = req.UpdateRouteOfRoutingTablePayload(payload) + + for _, mod := range mods { + mod(&req) + } + + return req +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + argValues []string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + argValues: []string{}, + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "routing-table-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, routingTableIdFlag) + }), + isValid: false, + }, + { + description: "network-area-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "org-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "routing-table-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, routingTableIdFlag) + }), + isValid: false, + }, + { + description: "arg value missing", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "arg value wrong", + argValues: []string{"foo-bar"}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "labels are missing", + argValues: []string{}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelFlag) + }), + isValid: false, + }, + { + description: "invalid label format", + argValues: []string{}, + flagValues: map[string]string{labelFlag: "invalid-label"}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest iaas.ApiUpdateRouteOfRoutingTableRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "labels nil", + model: fixtureInputModel(func(m *inputModel) { + m.Labels = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateRouteOfRoutingTableRequest) { + *request = (*request).UpdateRouteOfRoutingTablePayload(iaas.UpdateRouteOfRoutingTablePayload{ + Labels: nil, + }) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + gotReq := buildRequest(testCtx, tt.model, testClient) + + if diff := cmp.Diff( + tt.expectedRequest, + gotReq, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ); diff != "" { + t.Errorf("buildRequest() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRoute := iaas.Route{ + Id: utils.Ptr("route-foo"), + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr("cidrv4"), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr("ipv4"), + Value: utils.Ptr("10.0.0.1"), + }, + }, + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + CreatedAt: utils.Ptr(time.Now()), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + route *iaas.Route + wantErr bool + }{ + { + name: "nil route should return error", + outputFormat: print.PrettyOutputFormat, + route: nil, + wantErr: true, + }, + { + name: "empty route", + outputFormat: print.PrettyOutputFormat, + route: &iaas.Route{}, + // should fail on pretty format + wantErr: true, + }, + { + name: "pretty output with one route", + outputFormat: print.PrettyOutputFormat, + route: &dummyRoute, + wantErr: false, + }, + { + name: "json output with one route", + outputFormat: print.JSONOutputFormat, + route: &dummyRoute, + wantErr: false, + }, + { + name: "yaml output with one route", + outputFormat: print.YAMLOutputFormat, + route: &dummyRoute, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, "", "", tt.route); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/network-area/routingtable/routingtable.go b/internal/cmd/network-area/routingtable/routingtable.go new file mode 100644 index 000000000..514d8146a --- /dev/null +++ b/internal/cmd/network-area/routingtable/routingtable.go @@ -0,0 +1,41 @@ +package routingtable + +import ( + "github.com/spf13/cobra" + + rtCreate "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/create" + rtDelete "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/delete" + rtDescribe "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/describe" + rtList "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/list" + rtRoute "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route" + rtUpdate "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/update" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "routing-table", + Short: "Manage routing-tables and its according routes", + Long: `Manage routing-tables and their associated routes. + +This API is currently available only to selected customers. +To request access, please contact your account manager or submit a support ticket.`, + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand( + rtCreate.NewCmd(params), + rtUpdate.NewCmd(params), + rtList.NewCmd(params), + rtDescribe.NewCmd(params), + rtDelete.NewCmd(params), + rtRoute.NewCmd(params), + ) +} diff --git a/internal/cmd/network-area/routingtable/update/update.go b/internal/cmd/network-area/routingtable/update/update.go new file mode 100644 index 000000000..0d01f5e81 --- /dev/null +++ b/internal/cmd/network-area/routingtable/update/update.go @@ -0,0 +1,180 @@ +package update + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + descriptionFlag = "description" + labelFlag = "labels" + nameFlag = "name" + networkAreaIdFlag = "network-area-id" + dynamicRoutesFlag = "dynamic-routes" + systemRoutesFlag = "system-routes" + organizationIdFlag = "organization-id" + routingTableIdArg = "ROUTING_TABLE_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + OrganizationId string + NetworkAreaId string + DynamicRoutes *bool + SystemRoutes *bool + RoutingTableId string + Description *string + Labels *map[string]string + Name *string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("update %s", routingTableIdArg), + Short: "Updates a routing-table", + Long: "Updates a routing-table.", + Args: args.SingleArg(routingTableIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Updates the label(s) of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"`, + "$ stackit network-area routing-table update xxx --labels key=value,foo=bar --organization-id yyy --network-area-id zzz", + ), + examples.NewExample( + `Updates the name of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"`, + "$ stackit network-area routing-table update xxx --name foo --organization-id yyy --network-area-id zzz", + ), + examples.NewExample( + `Updates the description of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"`, + "$ stackit network-area routing-table update xxx --description foo --organization-id yyy --network-area-id zzz", + ), + examples.NewExample( + `Disables the dynamic routes of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"`, + "$ stackit network-area routing-table update xxx --organization-id yyy --network-area-id zzz --dynamic-routes=false", + ), + examples.NewExample( + `Disables the system routes of a routing-table with ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"`, + "$ stackit network-area routing-table update xxx --organization-id yyy --network-area-id zzz --system-routes=false", + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err) + routingTableLabel = model.RoutingTableId + } else if routingTableLabel == "" { + routingTableLabel = model.RoutingTableId + } + + prompt := fmt.Sprintf("Are you sure you want to update the routing-table %q?", routingTableLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("update routing-table %q : %w", model.RoutingTableId, err) + } + + return outputResult(params.Printer, model.OutputFormat, model.NetworkAreaId, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(descriptionFlag, "", "Description of the routing-table") + cmd.Flags().String(nameFlag, "", "Name of the routing-table") + cmd.Flags().StringToString(labelFlag, nil, "Key=value labels") + cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID") + cmd.Flags().Bool(dynamicRoutesFlag, false, "If set to false, prevents dynamic routes from propagating to the routing table.") + cmd.Flags().Bool(systemRoutesFlag, false, "If set to false, disables routes for project-to-project communication.") + cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + + err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) + cmd.MarkFlagsOneRequired(dynamicRoutesFlag, systemRoutesFlag, nameFlag, descriptionFlag, labelFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + routeTableId := inputArgs[0] + + model := inputModel{ + GlobalFlagModel: globalFlags, + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), + Name: flags.FlagToStringPointer(p, cmd, nameFlag), + NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag), + SystemRoutes: flags.FlagToBoolPointer(p, cmd, systemRoutesFlag), + DynamicRoutes: flags.FlagToBoolPointer(p, cmd, dynamicRoutesFlag), + OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag), + RoutingTableId: routeTableId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func outputResult(p *print.Printer, outputFormat, networkAreaId string, routingTable *iaas.RoutingTable) error { + return p.OutputResult(outputFormat, routingTable, func() error { + if routingTable == nil { + return fmt.Errorf("update routing-table response is empty") + } + + if routingTable.Id == nil { + return fmt.Errorf("update routing-table id is empty") + } + + p.Outputf("Updated routing-table %q in network-area %q.", *routingTable.Id, networkAreaId) + return nil + }) +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiUpdateRoutingTableOfAreaRequest { + req := apiClient.UpdateRoutingTableOfArea( + ctx, + model.OrganizationId, + model.NetworkAreaId, + model.Region, + model.RoutingTableId, + ) + + payload := iaas.UpdateRoutingTableOfAreaPayload{ + Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), + Name: model.Name, + Description: model.Description, + DynamicRoutes: model.DynamicRoutes, + SystemRoutes: model.SystemRoutes, + } + + return req.UpdateRoutingTableOfAreaPayload(payload) +} diff --git a/internal/cmd/network-area/routingtable/update/update_test.go b/internal/cmd/network-area/routingtable/update/update_test.go new file mode 100644 index 000000000..790782124 --- /dev/null +++ b/internal/cmd/network-area/routingtable/update/update_test.go @@ -0,0 +1,415 @@ +package update + +import ( + "context" + "strconv" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &iaas.APIClient{} + +const testRegion = "eu01" + +var testOrgId = uuid.NewString() +var testNetworkAreaId = uuid.NewString() +var testRoutingTableId = uuid.NewString() + +const testRoutingTableName = "test" +const testRoutingTableDescription = "test" +const testLabelSelectorFlag = "key1=value1,key2=value2" + +const testSystemRoutesFlag = true +const testDynamicRoutesFlag = true + +var testLabels = &map[string]string{ + "key1": "value1", + "key2": "value2", +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + organizationIdFlag: testOrgId, + networkAreaIdFlag: testNetworkAreaId, + descriptionFlag: testRoutingTableDescription, + nameFlag: testRoutingTableName, + systemRoutesFlag: strconv.FormatBool(testSystemRoutesFlag), + dynamicRoutesFlag: strconv.FormatBool(testDynamicRoutesFlag), + labelFlag: testLabelSelectorFlag, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + OrganizationId: testOrgId, + NetworkAreaId: testNetworkAreaId, + Name: utils.Ptr(testRoutingTableName), + Description: utils.Ptr(testRoutingTableDescription), + SystemRoutes: utils.Ptr(testSystemRoutesFlag), + DynamicRoutes: utils.Ptr(testDynamicRoutesFlag), + Labels: testLabels, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testRoutingTableId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureRequest(mods ...func(request *iaas.ApiUpdateRoutingTableOfAreaRequest)) iaas.ApiUpdateRoutingTableOfAreaRequest { + req := testClient.UpdateRoutingTableOfArea( + testCtx, + testOrgId, + testNetworkAreaId, + testRegion, + testRoutingTableId, + ) + + payload := iaas.UpdateRoutingTableOfAreaPayload{ + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + Name: utils.Ptr(testRoutingTableName), + Description: utils.Ptr(testRoutingTableDescription), + DynamicRoutes: utils.Ptr(true), + SystemRoutes: utils.Ptr(true), + } + + req = req.UpdateRoutingTableOfAreaPayload(payload) + + for _, mod := range mods { + mod(&req) + } + return req +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + argValues []string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + }), + }, + { + description: "dynamic routes disabled", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[dynamicRoutesFlag] = "false" + }), + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.DynamicRoutes = utils.Ptr(false) + model.RoutingTableId = testRoutingTableId + }), + }, + { + description: "system routes disabled", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[systemRoutesFlag] = "false" + }), + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.SystemRoutes = utils.Ptr(false) + model.RoutingTableId = testRoutingTableId + }), + }, + { + description: "no values", + argValues: []string{}, + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "network-area-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, networkAreaIdFlag) + }), + isValid: false, + }, + { + description: "org-id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, organizationIdFlag) + }), + isValid: false, + }, + { + description: "all required flags missing", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, dynamicRoutesFlag) + delete(flagValues, systemRoutesFlag) + delete(flagValues, nameFlag) + delete(flagValues, labelFlag) + delete(flagValues, descriptionFlag) + }), + isValid: false, + }, + { + description: "all except one required flag missing (description flag)", + argValues: []string{testRoutingTableId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, dynamicRoutesFlag) + delete(flagValues, systemRoutesFlag) + delete(flagValues, nameFlag) + delete(flagValues, labelFlag) + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + model.DynamicRoutes = nil + model.SystemRoutes = nil + model.Labels = nil + model.Name = nil + model.Description = utils.Ptr(testRoutingTableDescription) + }), + isValid: true, + }, + { + description: "arg value missing", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "arg value wrong", + argValues: []string{"foo-bar"}, + flagValues: fixtureFlagValues(), + isValid: false, + expectedModel: fixtureInputModel(), + }, + { + description: "labels are missing", + argValues: []string{}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelFlag) + }), + isValid: false, + }, + { + description: "invalid label format", + argValues: []string{}, + flagValues: map[string]string{labelFlag: "invalid-label"}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest iaas.ApiUpdateRoutingTableOfAreaRequest + }{ + { + description: "base", + model: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + }), + expectedRequest: fixtureRequest(), + }, + { + description: "labels missing", + model: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + model.Labels = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateRoutingTableOfAreaRequest) { + *request = (*request).UpdateRoutingTableOfAreaPayload(iaas.UpdateRoutingTableOfAreaPayload{ + Labels: nil, + Name: utils.Ptr(testRoutingTableName), + Description: utils.Ptr(testRoutingTableDescription), + DynamicRoutes: utils.Ptr(true), + SystemRoutes: utils.Ptr(true), + }) + }), + }, + { + description: "name missing", + model: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + model.Name = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateRoutingTableOfAreaRequest) { + *request = (*request).UpdateRoutingTableOfAreaPayload(iaas.UpdateRoutingTableOfAreaPayload{ + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + Name: nil, + Description: utils.Ptr(testRoutingTableDescription), + DynamicRoutes: utils.Ptr(true), + SystemRoutes: utils.Ptr(true), + }) + }), + }, + { + description: "description missing", + model: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + model.Description = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateRoutingTableOfAreaRequest) { + *request = (*request).UpdateRoutingTableOfAreaPayload(iaas.UpdateRoutingTableOfAreaPayload{ + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + Name: utils.Ptr(testRoutingTableName), + Description: nil, + DynamicRoutes: utils.Ptr(true), + SystemRoutes: utils.Ptr(true), + }) + }), + }, + { + description: "dynamic routes disabled", + model: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + model.DynamicRoutes = utils.Ptr(false) + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateRoutingTableOfAreaRequest) { + *request = (*request).UpdateRoutingTableOfAreaPayload(iaas.UpdateRoutingTableOfAreaPayload{ + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + Name: utils.Ptr(testRoutingTableName), + Description: utils.Ptr(testRoutingTableDescription), + DynamicRoutes: utils.Ptr(false), + SystemRoutes: utils.Ptr(true), + }) + }), + }, + { + description: "system routes disabled", + model: fixtureInputModel(func(model *inputModel) { + model.RoutingTableId = testRoutingTableId + model.DynamicRoutes = utils.Ptr(false) + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateRoutingTableOfAreaRequest) { + *request = (*request).UpdateRoutingTableOfAreaPayload(iaas.UpdateRoutingTableOfAreaPayload{ + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + Name: utils.Ptr(testRoutingTableName), + Description: utils.Ptr(testRoutingTableDescription), + SystemRoutes: utils.Ptr(true), + DynamicRoutes: utils.Ptr(false), + }) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + req := buildRequest(testCtx, tt.model, testClient) + + if diff := cmp.Diff(req, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ); diff != "" { + t.Errorf("buildRequest() mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + dummyRoutingTable := iaas.RoutingTable{ + Id: utils.Ptr("id-foo"), + Name: utils.Ptr("route-table-foo"), + Description: utils.Ptr("description-foo"), + SystemRoutes: utils.Ptr(true), + DynamicRoutes: utils.Ptr(true), + Labels: utils.ConvertStringMapToInterfaceMap(testLabels), + CreatedAt: utils.Ptr(time.Now()), + UpdatedAt: utils.Ptr(time.Now()), + } + + tests := []struct { + name string + outputFormat string + routingTable *iaas.RoutingTable + wantErr bool + }{ + { + name: "nil routing-table should return error", + outputFormat: print.PrettyOutputFormat, + routingTable: nil, + wantErr: true, + }, + { + name: "empty routing-table", + outputFormat: print.PrettyOutputFormat, + routingTable: &iaas.RoutingTable{}, + wantErr: true, + }, + { + name: "pretty output routing-table", + outputFormat: print.PrettyOutputFormat, + routingTable: &dummyRoutingTable, + wantErr: false, + }, + { + name: "json output routing-table", + outputFormat: print.JSONOutputFormat, + routingTable: &dummyRoutingTable, + wantErr: false, + }, + { + name: "yaml output routing-table", + outputFormat: print.YAMLOutputFormat, + routingTable: &dummyRoutingTable, + wantErr: false, + }, + } + + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.outputFormat, "network-area-id", tt.routingTable); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/network-area/update/update.go b/internal/cmd/network-area/update/update.go index 98f778bc3..08d1f825c 100644 --- a/internal/cmd/network-area/update/update.go +++ b/internal/cmd/network-area/update/update.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -17,7 +19,6 @@ import ( rmClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" rmUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/network-interface/create/create.go b/internal/cmd/network-interface/create/create.go index 5939d712d..5a1f5b8c3 100644 --- a/internal/cmd/network-interface/create/create.go +++ b/internal/cmd/network-interface/create/create.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-interface/create/create_test.go b/internal/cmd/network-interface/create/create_test.go index 5ebe70d2b..50dd643d0 100644 --- a/internal/cmd/network-interface/create/create_test.go +++ b/internal/cmd/network-interface/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-interface/delete/delete.go b/internal/cmd/network-interface/delete/delete.go index de6d972cb..af8a49eee 100644 --- a/internal/cmd/network-interface/delete/delete.go +++ b/internal/cmd/network-interface/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-interface/delete/delete_test.go b/internal/cmd/network-interface/delete/delete_test.go index e541c34a7..2f50c9511 100644 --- a/internal/cmd/network-interface/delete/delete_test.go +++ b/internal/cmd/network-interface/delete/delete_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-interface/describe/describe.go b/internal/cmd/network-interface/describe/describe.go index 52a47532a..49e93d690 100644 --- a/internal/cmd/network-interface/describe/describe.go +++ b/internal/cmd/network-interface/describe/describe.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-interface/describe/describe_test.go b/internal/cmd/network-interface/describe/describe_test.go index be6d7f317..6403f68bf 100644 --- a/internal/cmd/network-interface/describe/describe_test.go +++ b/internal/cmd/network-interface/describe/describe_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-interface/list/list.go b/internal/cmd/network-interface/list/list.go index b3ac79c9d..33941d1de 100644 --- a/internal/cmd/network-interface/list/list.go +++ b/internal/cmd/network-interface/list/list.go @@ -10,6 +10,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -20,7 +22,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network-interface/network-interface.go b/internal/cmd/network-interface/network-interface.go index d9cb6214d..3b9c2fb48 100644 --- a/internal/cmd/network-interface/network-interface.go +++ b/internal/cmd/network-interface/network-interface.go @@ -2,6 +2,7 @@ package networkinterface import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/network-interface/create" "github.com/stackitcloud/stackit-cli/internal/cmd/network-interface/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/network-interface/describe" diff --git a/internal/cmd/network-interface/update/update.go b/internal/cmd/network-interface/update/update.go index eeae44774..e1b9b6e1e 100644 --- a/internal/cmd/network-interface/update/update.go +++ b/internal/cmd/network-interface/update/update.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/network/create/create.go b/internal/cmd/network/create/create.go index fee9123ce..46e9e0e57 100644 --- a/internal/cmd/network/create/create.go +++ b/internal/cmd/network/create/create.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -35,6 +36,7 @@ const ( nonRoutedFlag = "non-routed" noIpv4GatewayFlag = "no-ipv4-gateway" noIpv6GatewayFlag = "no-ipv6-gateway" + routingTableIdFlag = "routing-table-id" labelFlag = "labels" ) @@ -52,6 +54,7 @@ type inputModel struct { NonRouted bool NoIPv4Gateway bool NoIPv6Gateway bool + RoutingTableID *string Labels *map[string]string } @@ -86,6 +89,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `Create an IPv6 network with name "network-1" with DNS name servers, a prefix and a gateway`, `$ stackit network create --name network-1 --ipv6-dns-name-servers "2001:4860:4860::8888,2001:4860:4860::8844" --ipv6-prefix "2001:4860:4860::8888" --ipv6-gateway "2001:4860:4860::8888"`, ), + examples.NewExample( + `Create a network with name "network-1" and attach routing-table "xxx"`, + `$ stackit network create --name network-1 --routing-table-id xxx`, + ), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -128,15 +135,14 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating network") - _, err = wait.CreateNetworkWaitHandler(ctx, apiClient, model.ProjectId, model.Region, networkId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating network", func() error { + _, err = wait.CreateNetworkWaitHandler(ctx, apiClient, model.ProjectId, model.Region, networkId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for network creation: %w", err) } - s.Stop() } - return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) }, } @@ -157,6 +163,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(nonRoutedFlag, false, "If set to true, the network is not routed and therefore not accessible from other networks") cmd.Flags().Bool(noIpv4GatewayFlag, false, "If set to true, the network doesn't have an IPv4 gateway") cmd.Flags().Bool(noIpv6GatewayFlag, false, "If set to true, the network doesn't have an IPv6 gateway") + cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "The ID of the routing-table for the network") cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a network. E.g. '--labels key1=value1,key2=value2,...'") // IPv4 checks @@ -195,6 +202,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, NonRouted: flags.FlagToBoolValue(p, cmd, nonRoutedFlag), NoIPv4Gateway: flags.FlagToBoolValue(p, cmd, noIpv4GatewayFlag), NoIPv6Gateway: flags.FlagToBoolValue(p, cmd, noIpv6GatewayFlag), + RoutingTableID: flags.FlagToStringPointer(p, cmd, routingTableIdFlag), Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), } @@ -288,11 +296,12 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli } payload := iaas.CreateNetworkPayload{ - Name: model.Name, - Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), - Routed: utils.Ptr(!model.NonRouted), - Ipv4: ipv4Network, - Ipv6: ipv6Network, + Name: model.Name, + Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), + Routed: utils.Ptr(!model.NonRouted), + Ipv4: ipv4Network, + Ipv6: ipv6Network, + RoutingTableId: model.RoutingTableID, } return req.CreateNetworkPayload(payload) diff --git a/internal/cmd/network/create/create_test.go b/internal/cmd/network/create/create_test.go index dbb2ea6d3..c933f5a7b 100644 --- a/internal/cmd/network/create/create_test.go +++ b/internal/cmd/network/create/create_test.go @@ -40,9 +40,10 @@ var ( type testCtxKey struct{} var ( - testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") - testClient = &iaas.APIClient{} - testProjectId = uuid.NewString() + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{} + testProjectId = uuid.NewString() + testRoutingTableId = uuid.NewString() ) func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { @@ -50,9 +51,10 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st globalflags.ProjectIdFlag: testProjectId, globalflags.RegionFlag: testRegion, - nameFlag: testNetworkName, - nonRoutedFlag: strconv.FormatBool(testNonRouted), - labelFlag: "key=value", + nameFlag: testNetworkName, + nonRoutedFlag: strconv.FormatBool(testNonRouted), + labelFlag: "key=value", + routingTableIdFlag: testRoutingTableId, } for _, mod := range mods { mod(flagValues) @@ -102,6 +104,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { Labels: utils.Ptr(map[string]string{ "key": "value", }), + RoutingTableID: utils.Ptr(testRoutingTableId), } for _, mod := range mods { mod(model) @@ -169,6 +172,7 @@ func fixturePayload(mods ...func(payload *iaas.CreateNetworkPayload)) iaas.Creat Labels: utils.Ptr(map[string]interface{}{ "key": "value", }), + RoutingTableId: utils.Ptr(testRoutingTableId), } for _, mod := range mods { mod(&payload) @@ -469,6 +473,24 @@ func TestParseInput(t *testing.T) { }), isValid: true, }, + { + description: "routing-table id invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[routingTableIdFlag] = "invalid-uuid" + }), + expectedModel: nil, + isValid: false, + }, + { + description: "routing-table id not set", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, routingTableIdFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.RoutingTableID = nil + }), + }, } for _, tt := range tests { @@ -531,6 +553,23 @@ func TestBuildRequest(t *testing.T) { Routed: utils.Ptr(false), }), }, + { + description: "network with routing-table id attached", + model: &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + Region: testRegion, + }, + Name: utils.Ptr(testNetworkName), + RoutingTableID: utils.Ptr(testRoutingTableId), + }, + expectedRequest: testClient.CreateNetwork(testCtx, testProjectId, testRegion).CreateNetworkPayload(iaas.CreateNetworkPayload{ + Name: utils.Ptr(testNetworkName), + RoutingTableId: utils.Ptr(testRoutingTableId), + Routed: utils.Ptr(true), + }), + }, { description: "use ipv4 dns servers and prefix length", model: &inputModel{ diff --git a/internal/cmd/network/delete/delete.go b/internal/cmd/network/delete/delete.go index 1f3b00b95..3b3cc2177 100644 --- a/internal/cmd/network/delete/delete.go +++ b/internal/cmd/network/delete/delete.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -81,13 +82,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting network") - _, err = wait.DeleteNetworkWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.NetworkId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting network", func() error { + _, err = wait.DeleteNetworkWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.NetworkId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for network deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/network/describe/describe.go b/internal/cmd/network/describe/describe.go index ab81a8c48..4f2a20319 100644 --- a/internal/cmd/network/describe/describe.go +++ b/internal/cmd/network/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) @@ -151,6 +152,11 @@ func outputResult(p *print.Printer, outputFormat string, network *iaas.Network) table.AddRow("ROUTED", routed) table.AddSeparator() + if network.RoutingTableId != nil { + table.AddRow("ROUTING TABLE ID", utils.PtrString(network.RoutingTableId)) + table.AddSeparator() + } + if ipv4Gateway != nil { table.AddRow("IPv4 GATEWAY", *ipv4Gateway) table.AddSeparator() diff --git a/internal/cmd/network/list/list.go b/internal/cmd/network/list/list.go index 3b1fabea5..6bc0a8b67 100644 --- a/internal/cmd/network/list/list.go +++ b/internal/cmd/network/list/list.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) @@ -141,7 +142,7 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli func outputResult(p *print.Printer, outputFormat string, networks []iaas.Network) error { return p.OutputResult(outputFormat, networks, func() error { table := tables.NewTable() - table.SetHeader("ID", "NAME", "STATUS", "PUBLIC IP", "PREFIXES", "ROUTED") + table.SetHeader("ID", "NAME", "STATUS", "PUBLIC IP", "PREFIXES", "ROUTED", "ROUTING TABLE ID") for _, network := range networks { var publicIp, prefixes string @@ -162,6 +163,7 @@ func outputResult(p *print.Printer, outputFormat string, networks []iaas.Network publicIp, prefixes, routed, + utils.PtrString(network.RoutingTableId), ) table.AddSeparator() } diff --git a/internal/cmd/network/update/update.go b/internal/cmd/network/update/update.go index 13e7e5acc..add37c6fa 100644 --- a/internal/cmd/network/update/update.go +++ b/internal/cmd/network/update/update.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -32,6 +33,7 @@ const ( ipv6GatewayFlag = "ipv6-gateway" noIpv4GatewayFlag = "no-ipv4-gateway" noIpv6GatewayFlag = "no-ipv6-gateway" + routingTableIdFlag = "routing-table-id" labelFlag = "labels" ) @@ -45,6 +47,7 @@ type inputModel struct { IPv6Gateway *string NoIPv4Gateway bool NoIPv6Gateway bool + RoutingTableId *string Labels *map[string]string } @@ -71,6 +74,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `Update IPv6 network with ID "xxx" with new name "network-1-new", new gateway and new DNS name servers`, `$ stackit network update xxx --name network-1-new --ipv6-dns-name-servers "2001:4860:4860::8888" --ipv6-gateway "2001:4860:4860::8888"`, ), + examples.NewExample( + `Update network with ID "xxx" with new routing-table id "xxx"`, + `$ stackit network update xxx --routing-table-id xxx`, + ), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -109,15 +116,14 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating network") - _, err = wait.UpdateNetworkWaitHandler(ctx, apiClient, model.ProjectId, model.Region, networkId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating network", func() error { + _, err = wait.UpdateNetworkWaitHandler(ctx, apiClient, model.ProjectId, model.Region, networkId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for network update: %w", err) } - s.Stop() } - operationState := "Updated" if model.Async { operationState = "Triggered update of" @@ -138,6 +144,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(ipv6GatewayFlag, "", "The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway") cmd.Flags().Bool(noIpv4GatewayFlag, false, "If set to true, the network doesn't have an IPv4 gateway") cmd.Flags().Bool(noIpv6GatewayFlag, false, "If set to true, the network doesn't have an IPv6 gateway") + cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "The ID of the routing-table for the network") cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a network. E.g. '--labels key1=value1,key2=value2,...'") } @@ -159,6 +166,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu IPv6Gateway: flags.FlagToStringPointer(p, cmd, ipv6GatewayFlag), NoIPv4Gateway: flags.FlagToBoolValue(p, cmd, noIpv4GatewayFlag), NoIPv6Gateway: flags.FlagToBoolValue(p, cmd, noIpv6GatewayFlag), + RoutingTableId: flags.FlagToStringPointer(p, cmd, routingTableIdFlag), Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), } @@ -196,10 +204,11 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli } payload := iaas.PartialUpdateNetworkPayload{ - Name: model.Name, - Ipv4: payloadIPv4, - Ipv6: payloadIPv6, - Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), + Name: model.Name, + Ipv4: payloadIPv4, + Ipv6: payloadIPv6, + Labels: utils.ConvertStringMapToInterfaceMap(model.Labels), + RoutingTableId: model.RoutingTableId, } return req.PartialUpdateNetworkPayload(payload) diff --git a/internal/cmd/network/update/update_test.go b/internal/cmd/network/update/update_test.go index 87b533c0a..628b93190 100644 --- a/internal/cmd/network/update/update_test.go +++ b/internal/cmd/network/update/update_test.go @@ -27,6 +27,7 @@ var testClient = &iaas.APIClient{} var testProjectId = uuid.NewString() var testNetworkId = uuid.NewString() +var testRoutingTableId = uuid.NewString() func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ @@ -49,6 +50,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st ipv6DnsNameServersFlag: "2001:4860:4860::8888,2001:4860:4860::8844", ipv6GatewayFlag: "2001:4860:4860::8888", labelFlag: "key=value", + routingTableIdFlag: testRoutingTableId, } for _, mod := range mods { mod(flagValues) @@ -72,6 +74,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { Labels: utils.Ptr(map[string]string{ "key": "value", }), + RoutingTableId: utils.Ptr(testRoutingTableId), } for _, mod := range mods { mod(model) @@ -102,6 +105,7 @@ func fixturePayload(mods ...func(payload *iaas.PartialUpdateNetworkPayload)) iaa Nameservers: utils.Ptr([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), Gateway: iaas.NewNullableString(utils.Ptr("2001:4860:4860::8888")), }, + RoutingTableId: utils.Ptr(testRoutingTableId), } for _, mod := range mods { mod(&payload) @@ -241,6 +245,15 @@ func TestParseInput(t *testing.T) { }), isValid: true, }, + { + description: "route-table id wrong format", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[routingTableIdFlag] = "wrong-format" + }), + expectedModel: nil, + isValid: false, + }, } for _, tt := range tests { diff --git a/internal/cmd/object-storage/bucket/create/create.go b/internal/cmd/object-storage/bucket/create/create.go index 9b8aae9a3..577d244d0 100644 --- a/internal/cmd/object-storage/bucket/create/create.go +++ b/internal/cmd/object-storage/bucket/create/create.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -16,17 +17,19 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/wait" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api/wait" ) const ( - bucketNameArg = "BUCKET_NAME" + bucketNameArg = "BUCKET_NAME" + objectLockEnabledFlag = "object-lock-enabled" ) type inputModel struct { *globalflags.GlobalFlagModel - BucketName string + BucketName string + ObjectLockEnabled bool } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -39,6 +42,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { examples.NewExample( `Create an Object Storage bucket with name "my-bucket"`, "$ stackit object-storage bucket create my-bucket"), + examples.NewExample( + `Create an Object Storage bucket with enabled object-lock`, + `$ stackit object-storage bucket create my-bucket --object-lock-enabled`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -60,7 +66,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } // Check if the project is enabled before trying to create - enabled, err := utils.ProjectEnabled(ctx, apiClient, model.ProjectId, model.Region) + enabled, err := utils.ProjectEnabled(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region) if err != nil { return fmt.Errorf("check if Object Storage is enabled: %w", err) } @@ -79,21 +85,26 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating bucket") - _, err = wait.CreateBucketWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating bucket", func() error { + _, err = wait.CreateBucketWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Object Storage bucket creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, model.BucketName, resp) }, } + configureFlags(cmd) return cmd } +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Bool(objectLockEnabledFlag, false, "is the object-lock enabled for the bucket") +} + func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { bucketName := inputArgs[0] @@ -103,8 +114,9 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } model := inputModel{ - GlobalFlagModel: globalFlags, - BucketName: bucketName, + GlobalFlagModel: globalFlags, + BucketName: bucketName, + ObjectLockEnabled: flags.FlagToBoolValue(p, cmd, objectLockEnabledFlag), } p.DebugInputModel(model) @@ -112,7 +124,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateBucketRequest { - req := apiClient.CreateBucket(ctx, model.ProjectId, model.Region, model.BucketName) + req := apiClient.DefaultAPI.CreateBucket(ctx, model.ProjectId, model.Region, model.BucketName).ObjectLockEnabled(model.ObjectLockEnabled) return req } diff --git a/internal/cmd/object-storage/bucket/create/create_test.go b/internal/cmd/object-storage/bucket/create/create_test.go index da4fcb9cf..2460b63c7 100644 --- a/internal/cmd/object-storage/bucket/create/create_test.go +++ b/internal/cmd/object-storage/bucket/create/create_test.go @@ -13,13 +13,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() const ( @@ -55,7 +55,8 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { Verbosity: globalflags.VerbosityDefault, Region: testRegion, }, - BucketName: testBucketName, + BucketName: testBucketName, + ObjectLockEnabled: false, } for _, mod := range mods { mod(model) @@ -63,10 +64,10 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { return model } -func fixtureRequest(mods ...func(request *objectstorage.ApiCreateBucketRequest)) objectstorage.ApiCreateBucketRequest { - request := testClient.CreateBucket(testCtx, testProjectId, testRegion, testBucketName) +func fixtureRequest(mods ...func(request objectstorage.ApiCreateBucketRequest) objectstorage.ApiCreateBucketRequest) objectstorage.ApiCreateBucketRequest { + request := testClient.DefaultAPI.CreateBucket(testCtx, testProjectId, testRegion, testBucketName).ObjectLockEnabled(false) for _, mod := range mods { - mod(&request) + request = mod(request) } return request } @@ -134,6 +135,17 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(), isValid: false, }, + { + description: "enable object-lock", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[objectLockEnabledFlag] = "true" + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ObjectLockEnabled = true + }), + isValid: true, + }, } for _, tt := range tests { @@ -154,6 +166,15 @@ func TestBuildRequest(t *testing.T) { model: fixtureInputModel(), expectedRequest: fixtureRequest(), }, + { + description: "object-lock enabled", + model: fixtureInputModel(func(model *inputModel) { + model.ObjectLockEnabled = true + }), + expectedRequest: fixtureRequest(func(request objectstorage.ApiCreateBucketRequest) objectstorage.ApiCreateBucketRequest { + return request.ObjectLockEnabled(true) + }), + }, } for _, tt := range tests { @@ -161,7 +182,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/bucket/delete/delete.go b/internal/cmd/object-storage/bucket/delete/delete.go index c06e88718..c6cc396f8 100644 --- a/internal/cmd/object-storage/bucket/delete/delete.go +++ b/internal/cmd/object-storage/bucket/delete/delete.go @@ -15,8 +15,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/wait" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api/wait" ) const ( @@ -67,13 +67,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting bucket") - _, err = wait.DeleteBucketWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting bucket", func() error { + _, err = wait.DeleteBucketWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Object Storage bucket deletion: %w", err) } - s.Stop() } operationState := "Deleted" @@ -105,6 +105,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteBucketRequest { - req := apiClient.DeleteBucket(ctx, model.ProjectId, model.Region, model.BucketName) + req := apiClient.DefaultAPI.DeleteBucket(ctx, model.ProjectId, model.Region, model.BucketName) return req } diff --git a/internal/cmd/object-storage/bucket/delete/delete_test.go b/internal/cmd/object-storage/bucket/delete/delete_test.go index 829c374cf..2bc6f06be 100644 --- a/internal/cmd/object-storage/bucket/delete/delete_test.go +++ b/internal/cmd/object-storage/bucket/delete/delete_test.go @@ -10,13 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() const ( @@ -61,7 +61,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteBucketRequest)) objectstorage.ApiDeleteBucketRequest { - request := testClient.DeleteBucket(testCtx, testProjectId, testRegion, testBucketName) + request := testClient.DefaultAPI.DeleteBucket(testCtx, testProjectId, testRegion, testBucketName) for _, mod := range mods { mod(&request) } @@ -158,7 +158,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/bucket/describe/describe.go b/internal/cmd/object-storage/bucket/describe/describe.go index a2a3ceedb..1375ff05a 100644 --- a/internal/cmd/object-storage/bucket/describe/describe.go +++ b/internal/cmd/object-storage/bucket/describe/describe.go @@ -13,10 +13,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) const ( @@ -61,7 +60,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return fmt.Errorf("read Object Storage bucket: %w", err) } - return outputResult(params.Printer, model.OutputFormat, resp.Bucket) + return outputResult(params.Printer, model.OutputFormat, resp) }, } return cmd @@ -85,24 +84,26 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiGetBucketRequest { - req := apiClient.GetBucket(ctx, model.ProjectId, model.Region, model.BucketName) + req := apiClient.DefaultAPI.GetBucket(ctx, model.ProjectId, model.Region, model.BucketName) return req } -func outputResult(p *print.Printer, outputFormat string, bucket *objectstorage.Bucket) error { - if bucket == nil { - return fmt.Errorf("bucket is empty") +func outputResult(p *print.Printer, outputFormat string, resp *objectstorage.GetBucketResponse) error { + if resp == nil { + return fmt.Errorf("response is nil") } - return p.OutputResult(outputFormat, bucket, func() error { + return p.OutputResult(outputFormat, resp.Bucket, func() error { table := tables.NewTable() - table.AddRow("Name", utils.PtrString(bucket.Name)) + table.AddRow("Name", resp.Bucket.Name) table.AddSeparator() - table.AddRow("Region", utils.PtrString(bucket.Region)) + table.AddRow("Region", resp.Bucket.Region) table.AddSeparator() - table.AddRow("URL (Path Style)", utils.PtrString(bucket.UrlPathStyle)) + table.AddRow("URL (Path Style)", resp.Bucket.UrlPathStyle) table.AddSeparator() - table.AddRow("URL (Virtual Hosted Style)", utils.PtrString(bucket.UrlVirtualHostedStyle)) + table.AddRow("URL (Virtual Hosted Style)", resp.Bucket.UrlVirtualHostedStyle) + table.AddSeparator() + table.AddRow("Object Lock Enabled", resp.Bucket.ObjectLockEnabled) table.AddSeparator() err := table.Display(p) if err != nil { diff --git a/internal/cmd/object-storage/bucket/describe/describe_test.go b/internal/cmd/object-storage/bucket/describe/describe_test.go index 106844ed2..86deeef89 100644 --- a/internal/cmd/object-storage/bucket/describe/describe_test.go +++ b/internal/cmd/object-storage/bucket/describe/describe_test.go @@ -13,13 +13,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() const ( @@ -64,7 +64,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiGetBucketRequest)) objectstorage.ApiGetBucketRequest { - request := testClient.GetBucket(testCtx, testProjectId, testRegion, testBucketName) + request := testClient.DefaultAPI.GetBucket(testCtx, testProjectId, testRegion, testBucketName) for _, mod := range mods { mod(&request) } @@ -161,7 +161,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { @@ -174,7 +174,7 @@ func TestBuildRequest(t *testing.T) { func TestOutputResult(t *testing.T) { type args struct { outputFormat string - bucket *objectstorage.Bucket + resp *objectstorage.GetBucketResponse } tests := []struct { name string @@ -187,9 +187,9 @@ func TestOutputResult(t *testing.T) { wantErr: true, }, { - name: "set empty bucket", + name: "set empty response", args: args{ - bucket: &objectstorage.Bucket{}, + resp: &objectstorage.GetBucketResponse{}, }, wantErr: false, }, @@ -198,7 +198,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.outputFormat, tt.args.bucket); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.resp); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/object-storage/bucket/list/list.go b/internal/cmd/object-storage/bucket/list/list.go index ff01c60d6..352841fa6 100644 --- a/internal/cmd/object-storage/bucket/list/list.go +++ b/internal/cmd/object-storage/bucket/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" ) const ( @@ -114,7 +114,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListBucketsRequest { - req := apiClient.ListBuckets(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.ListBuckets(ctx, model.ProjectId, model.Region) return req } @@ -130,14 +130,15 @@ func outputResult(p *print.Printer, outputFormat, projectLabel string, buckets [ } table := tables.NewTable() - table.SetHeader("NAME", "REGION", "URL (PATH STYLE)", "URL (VIRTUAL HOSTED STYLE)") + table.SetHeader("NAME", "REGION", "URL (PATH STYLE)", "URL (VIRTUAL HOSTED STYLE)", "OBJECT LOCK ENABLED") for i := range buckets { bucket := buckets[i] table.AddRow( - utils.PtrString(bucket.Name), - utils.PtrString(bucket.Region), - utils.PtrString(bucket.UrlPathStyle), - utils.PtrString(bucket.UrlVirtualHostedStyle), + bucket.Name, + bucket.Region, + bucket.UrlPathStyle, + bucket.UrlVirtualHostedStyle, + bucket.ObjectLockEnabled, ) } err := table.Display(p) diff --git a/internal/cmd/object-storage/bucket/list/list_test.go b/internal/cmd/object-storage/bucket/list/list_test.go index 47be7605e..c2916bfb3 100644 --- a/internal/cmd/object-storage/bucket/list/list_test.go +++ b/internal/cmd/object-storage/bucket/list/list_test.go @@ -14,13 +14,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() var testRegion = "eu01" @@ -52,7 +52,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiListBucketsRequest)) objectstorage.ApiListBucketsRequest { - request := testClient.ListBuckets(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.ListBuckets(testCtx, testProjectId, testRegion) for _, mod := range mods { mod(&request) } @@ -140,7 +140,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/compliance-lock/compliance-lock.go b/internal/cmd/object-storage/compliance-lock/compliance-lock.go new file mode 100644 index 000000000..49df4e178 --- /dev/null +++ b/internal/cmd/object-storage/compliance-lock/compliance-lock.go @@ -0,0 +1,30 @@ +package compliancelock + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/compliance-lock/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/compliance-lock/lock" + "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/compliance-lock/unlock" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "compliance-lock", + Short: "Provides functionality to manage Object Storage compliance lock", + Long: "Provides functionality to manage Object Storage compliance lock.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(lock.NewCmd(params)) + cmd.AddCommand(unlock.NewCmd(params)) + cmd.AddCommand(describe.NewCmd(params)) +} diff --git a/internal/cmd/object-storage/compliance-lock/describe/describe.go b/internal/cmd/object-storage/compliance-lock/describe/describe.go new file mode 100644 index 000000000..2aa3ea34a --- /dev/null +++ b/internal/cmd/object-storage/compliance-lock/describe/describe.go @@ -0,0 +1,111 @@ +package describe + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" + objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "describe", + Short: "Describe object storage compliance lock", + Long: "Describe object storage compliance lock.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Describe object storage compliance lock`, + "$ stackit object-storage compliance-lock describe"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Check if the project is enabled before trying to describe + enabled, err := objectStorageUtils.ProjectEnabled(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region) + if err != nil { + return fmt.Errorf("check if Object Storage is enabled: %w", err) + } + if !enabled { + return &errors.ServiceDisabledError{ + Service: "object-storage", + } + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get object storage compliance lock: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiGetComplianceLockRequest { + req := apiClient.DefaultAPI.GetComplianceLock(ctx, model.ProjectId, model.Region) + return req +} + +func outputResult(p *print.Printer, outputFormat string, resp *objectstorage.ComplianceLockResponse) error { + return p.OutputResult(outputFormat, resp, func() error { + if resp == nil { + return fmt.Errorf("response is empty") + } + + table := tables.NewTable() + table.AddRow("PROJECT ID", resp.Project) + table.AddSeparator() + table.AddRow("MAX RETENTION DAYS", resp.MaxRetentionDays) + table.AddSeparator() + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + }) +} diff --git a/internal/cmd/object-storage/compliance-lock/describe/describe_test.go b/internal/cmd/object-storage/compliance-lock/describe/describe_test.go new file mode 100644 index 000000000..1c92d7cdf --- /dev/null +++ b/internal/cmd/object-storage/compliance-lock/describe/describe_test.go @@ -0,0 +1,179 @@ +package describe + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} +var testProjectId = uuid.NewString() + +const ( + testRegion = "eu01" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *objectstorage.ApiGetComplianceLockRequest)) objectstorage.ApiGetComplianceLockRequest { + request := testClient.DefaultAPI.GetComplianceLock(testCtx, testProjectId, testRegion) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, nil, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest objectstorage.ApiGetComplianceLockRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + complianceLock *objectstorage.ComplianceLockResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{ + outputFormat: print.PrettyOutputFormat, + }, + wantErr: true, + }, + { + name: "set empty compliance lock", + args: args{ + outputFormat: print.PrettyOutputFormat, + complianceLock: &objectstorage.ComplianceLockResponse{}, + }, + wantErr: false, + }, + { + name: "set filled lock", + args: args{ + outputFormat: print.PrettyOutputFormat, + complianceLock: &objectstorage.ComplianceLockResponse{ + Project: uuid.New().String(), + MaxRetentionDays: int32(42), + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.complianceLock); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/object-storage/compliance-lock/lock/lock.go b/internal/cmd/object-storage/compliance-lock/lock/lock.go new file mode 100644 index 000000000..e179f7c26 --- /dev/null +++ b/internal/cmd/object-storage/compliance-lock/lock/lock.go @@ -0,0 +1,116 @@ +package lock + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" + + "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "lock", + Short: "Create object storage compliance lock", + Long: "Create object storage compliance lock.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create object storage compliance lock`, + "$ stackit object-storage compliance-lock lock"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } else if projectLabel == "" { + projectLabel = model.ProjectId + } + + prompt := fmt.Sprintf("Are you sure you want to create object storage compliance-lock for project %s?", projectLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Check if the project is enabled before trying to create + enabled, err := utils.ProjectEnabled(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region) + if err != nil { + return fmt.Errorf("check if Object Storage is enabled: %w", err) + } + if !enabled { + return &errors.ServiceDisabledError{ + Service: "object-storage", + } + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("create object storage compliance lock: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, projectLabel, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateComplianceLockRequest { + req := apiClient.DefaultAPI.CreateComplianceLock(ctx, model.ProjectId, model.Region) + return req +} + +func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *objectstorage.ComplianceLockResponse) error { + return p.OutputResult(outputFormat, resp, func() error { + if resp == nil { + return fmt.Errorf("create compliance lock response is empty") + } + + p.Outputf("Created object storage compliance lock for project \"%s\" with maximum retention period of %d days.\n", projectLabel, resp.MaxRetentionDays) + return nil + }) +} diff --git a/internal/cmd/object-storage/compliance-lock/lock/lock_test.go b/internal/cmd/object-storage/compliance-lock/lock/lock_test.go new file mode 100644 index 000000000..48e39cb90 --- /dev/null +++ b/internal/cmd/object-storage/compliance-lock/lock/lock_test.go @@ -0,0 +1,180 @@ +package lock + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} +var testProjectId = uuid.NewString() + +const ( + testRegion = "eu01" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *objectstorage.ApiCreateComplianceLockRequest)) objectstorage.ApiCreateComplianceLockRequest { + request := testClient.DefaultAPI.CreateComplianceLock(testCtx, testProjectId, testRegion) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, nil, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest objectstorage.ApiCreateComplianceLockRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + projectLabel string + complianceLock *objectstorage.ComplianceLockResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{ + outputFormat: print.PrettyOutputFormat, + }, + wantErr: true, + }, + { + name: "set empty compliance lock", + args: args{ + outputFormat: print.PrettyOutputFormat, + complianceLock: &objectstorage.ComplianceLockResponse{}, + }, + wantErr: false, + }, + { + name: "set filled lock", + args: args{ + outputFormat: print.PrettyOutputFormat, + complianceLock: &objectstorage.ComplianceLockResponse{ + Project: uuid.New().String(), + MaxRetentionDays: int32(42), + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.projectLabel, tt.args.complianceLock); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/object-storage/compliance-lock/unlock/unlock.go b/internal/cmd/object-storage/compliance-lock/unlock/unlock.go new file mode 100644 index 000000000..ad9320c2a --- /dev/null +++ b/internal/cmd/object-storage/compliance-lock/unlock/unlock.go @@ -0,0 +1,106 @@ +package unlock + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" + + "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "unlock", + Short: "Delete object storage compliance lock", + Long: "Delete object storage compliance lock.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Delete object storage compliance lock`, + "$ stackit object-storage compliance-lock unlock"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } else if projectLabel == "" { + projectLabel = model.ProjectId + } + + prompt := fmt.Sprintf("Are you sure you want to delete object storage compliance-lock for project %s?", projectLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Check if the project is enabled before trying to create + enabled, err := utils.ProjectEnabled(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region) + if err != nil { + return fmt.Errorf("check if Object Storage is enabled: %w", err) + } + if !enabled { + return &errors.ServiceDisabledError{ + Service: "object-storage", + } + } + + // Call API + _, err = buildRequest(ctx, model, apiClient).Execute() + if err != nil { + return fmt.Errorf("delete object storage compliance lock: %w", err) + } + + params.Printer.Outputf("Deleted object storage compliance lock for project \"%s\".\n", projectLabel) + + return nil + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteComplianceLockRequest { + req := apiClient.DefaultAPI.DeleteComplianceLock(ctx, model.ProjectId, model.Region) + return req +} diff --git a/internal/cmd/object-storage/compliance-lock/unlock/unlock_test.go b/internal/cmd/object-storage/compliance-lock/unlock/unlock_test.go new file mode 100644 index 000000000..1df9eb3a9 --- /dev/null +++ b/internal/cmd/object-storage/compliance-lock/unlock/unlock_test.go @@ -0,0 +1,128 @@ +package unlock + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} +var testProjectId = uuid.NewString() + +const ( + testRegion = "eu01" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteComplianceLockRequest)) objectstorage.ApiDeleteComplianceLockRequest { + request := testClient.DefaultAPI.DeleteComplianceLock(testCtx, testProjectId, testRegion) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, nil, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest objectstorage.ApiDeleteComplianceLockRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/object-storage/credentials-group/create/create.go b/internal/cmd/object-storage/credentials-group/create/create.go index 9150b98fe..07623da46 100644 --- a/internal/cmd/object-storage/credentials-group/create/create.go +++ b/internal/cmd/object-storage/credentials-group/create/create.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -13,10 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - - "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" ) const ( @@ -95,24 +94,24 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateCredentialsGroupRequest { - req := apiClient.CreateCredentialsGroup(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.CreateCredentialsGroup(ctx, model.ProjectId, model.Region) req = req.CreateCredentialsGroupPayload(objectstorage.CreateCredentialsGroupPayload{ - DisplayName: utils.Ptr(model.CredentialsGroupName), + DisplayName: model.CredentialsGroupName, }) return req } func outputResult(p *print.Printer, outputFormat string, resp *objectstorage.CreateCredentialsGroupResponse) error { - if resp == nil || resp.CredentialsGroup == nil { - return fmt.Errorf("create createndials group response is empty") - } - return p.OutputResult(outputFormat, resp, func() error { + if resp == nil { + return fmt.Errorf("create credentials group response is empty") + } + p.Outputf("Created credentials group %q. Credentials group ID: %s\n\n", - utils.PtrString(resp.CredentialsGroup.DisplayName), - utils.PtrString(resp.CredentialsGroup.CredentialsGroupId), + resp.CredentialsGroup.DisplayName, + resp.CredentialsGroup.CredentialsGroupId, ) - p.Outputf("URN: %s\n", utils.PtrString(resp.CredentialsGroup.Urn)) + p.Outputf("URN: %s\n", resp.CredentialsGroup.Urn) return nil }) } diff --git a/internal/cmd/object-storage/credentials-group/create/create_test.go b/internal/cmd/object-storage/credentials-group/create/create_test.go index 2823fc5da..6068e5fc0 100644 --- a/internal/cmd/object-storage/credentials-group/create/create_test.go +++ b/internal/cmd/object-storage/credentials-group/create/create_test.go @@ -6,21 +6,20 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() const ( @@ -57,7 +56,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixturePayload(mods ...func(payload *objectstorage.CreateCredentialsGroupPayload)) objectstorage.CreateCredentialsGroupPayload { payload := objectstorage.CreateCredentialsGroupPayload{ - DisplayName: utils.Ptr(testCredentialsGroupName), + DisplayName: testCredentialsGroupName, } for _, mod := range mods { mod(&payload) @@ -66,7 +65,7 @@ func fixturePayload(mods ...func(payload *objectstorage.CreateCredentialsGroupPa } func fixtureRequest(mods ...func(request *objectstorage.ApiCreateCredentialsGroupRequest)) objectstorage.ApiCreateCredentialsGroupRequest { - request := testClient.CreateCredentialsGroup(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.CreateCredentialsGroup(testCtx, testProjectId, testRegion) request = request.CreateCredentialsGroupPayload(fixturePayload()) for _, mod := range mods { mod(&request) @@ -148,7 +147,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { @@ -178,13 +177,13 @@ func TestOutputResult(t *testing.T) { args: args{ createCredentialsGroupResponse: &objectstorage.CreateCredentialsGroupResponse{}, }, - wantErr: true, + wantErr: false, }, { name: "set create credentials group response", args: args{ createCredentialsGroupResponse: &objectstorage.CreateCredentialsGroupResponse{ - CredentialsGroup: &objectstorage.CredentialsGroup{}, + CredentialsGroup: objectstorage.CredentialsGroup{}, }, }, wantErr: false, diff --git a/internal/cmd/object-storage/credentials-group/delete/delete.go b/internal/cmd/object-storage/credentials-group/delete/delete.go index 79daa26b3..ac14221e1 100644 --- a/internal/cmd/object-storage/credentials-group/delete/delete.go +++ b/internal/cmd/object-storage/credentials-group/delete/delete.go @@ -16,7 +16,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) const ( @@ -52,7 +52,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region) + credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region) if err != nil { params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err) credentialsGroupLabel = model.CredentialsGroupId @@ -96,6 +96,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteCredentialsGroupRequest { - req := apiClient.DeleteCredentialsGroup(ctx, model.ProjectId, model.Region, model.CredentialsGroupId) + req := apiClient.DefaultAPI.DeleteCredentialsGroup(ctx, model.ProjectId, model.Region, model.CredentialsGroupId) return req } diff --git a/internal/cmd/object-storage/credentials-group/delete/delete_test.go b/internal/cmd/object-storage/credentials-group/delete/delete_test.go index 9be471b32..e2c2e33d1 100644 --- a/internal/cmd/object-storage/credentials-group/delete/delete_test.go +++ b/internal/cmd/object-storage/credentials-group/delete/delete_test.go @@ -10,13 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() var testCredentialsGroupId = uuid.NewString() @@ -59,7 +59,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteCredentialsGroupRequest)) objectstorage.ApiDeleteCredentialsGroupRequest { - request := testClient.DeleteCredentialsGroup(testCtx, testProjectId, testRegion, testCredentialsGroupId) + request := testClient.DefaultAPI.DeleteCredentialsGroup(testCtx, testProjectId, testRegion, testCredentialsGroupId) for _, mod := range mods { mod(&request) } @@ -162,7 +162,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/credentials-group/list/list.go b/internal/cmd/object-storage/credentials-group/list/list.go index f422ad6d3..6c777f0ca 100644 --- a/internal/cmd/object-storage/credentials-group/list/list.go +++ b/internal/cmd/object-storage/credentials-group/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" ) const ( @@ -105,7 +105,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListCredentialsGroupsRequest { - req := apiClient.ListCredentialsGroups(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.ListCredentialsGroups(ctx, model.ProjectId, model.Region) return req } @@ -121,9 +121,9 @@ func outputResult(p *print.Printer, outputFormat string, credentialsGroups []obj for i := range credentialsGroups { c := credentialsGroups[i] table.AddRow( - utils.PtrString(c.CredentialsGroupId), - utils.PtrString(c.DisplayName), - utils.PtrString(c.Urn), + c.CredentialsGroupId, + c.DisplayName, + c.Urn, ) } err := table.Display(p) diff --git a/internal/cmd/object-storage/credentials-group/list/list_test.go b/internal/cmd/object-storage/credentials-group/list/list_test.go index cbbbb1a89..520a0e011 100644 --- a/internal/cmd/object-storage/credentials-group/list/list_test.go +++ b/internal/cmd/object-storage/credentials-group/list/list_test.go @@ -14,13 +14,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() const testRegion = "eu01" @@ -53,7 +53,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiListCredentialsGroupsRequest)) objectstorage.ApiListCredentialsGroupsRequest { - request := testClient.ListCredentialsGroups(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.ListCredentialsGroups(testCtx, testProjectId, testRegion) for _, mod := range mods { mod(&request) } @@ -141,7 +141,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/credentials/create/create.go b/internal/cmd/object-storage/credentials/create/create.go index c970dcb21..a9ce8fe47 100644 --- a/internal/cmd/object-storage/credentials/create/create.go +++ b/internal/cmd/object-storage/credentials/create/create.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" ) const ( @@ -60,7 +60,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region) + credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region) if err != nil { params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err) credentialsGroupLabel = model.CredentialsGroupId @@ -119,7 +119,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateAccessKeyRequest { - req := apiClient.CreateAccessKey(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.CreateAccessKey(ctx, model.ProjectId, model.Region) req = req.CredentialsGroup(model.CredentialsGroupId) req = req.CreateAccessKeyPayload(objectstorage.CreateAccessKeyPayload{ Expires: model.ExpireDate, @@ -134,13 +134,13 @@ func outputResult(p *print.Printer, outputFormat, credentialsGroupLabel string, return p.OutputResult(outputFormat, resp, func() error { expireDate := "Never" - if resp.Expires != nil && resp.Expires.IsSet() && *resp.Expires.Get() != "" { + if resp.Expires.IsSet() && *resp.Expires.Get() != "" { expireDate = *resp.Expires.Get() } - p.Outputf("Created credentials in group %q. Credentials ID: %s\n\n", credentialsGroupLabel, utils.PtrString(resp.KeyId)) - p.Outputf("Access Key ID: %s\n", utils.PtrString(resp.AccessKey)) - p.Outputf("Secret Access Key: %s\n", utils.PtrString(resp.SecretAccessKey)) + p.Outputf("Created credentials in group %q. Credentials ID: %s\n\n", credentialsGroupLabel, resp.KeyId) + p.Outputf("Access Key ID: %s\n", resp.AccessKey) + p.Outputf("Secret Access Key: %s\n", resp.SecretAccessKey) p.Outputf("Expire Date: %s\n", expireDate) return nil diff --git a/internal/cmd/object-storage/credentials/create/create_test.go b/internal/cmd/object-storage/credentials/create/create_test.go index 46f0e2e18..c960ddd49 100644 --- a/internal/cmd/object-storage/credentials/create/create_test.go +++ b/internal/cmd/object-storage/credentials/create/create_test.go @@ -15,13 +15,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() var testCredentialsGroupId = uuid.NewString() @@ -79,7 +79,7 @@ func fixturePayload(mods ...func(payload *objectstorage.CreateAccessKeyPayload)) } func fixtureRequest(mods ...func(request *objectstorage.ApiCreateAccessKeyRequest)) objectstorage.ApiCreateAccessKeyRequest { - request := testClient.CreateAccessKey(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.CreateAccessKey(testCtx, testProjectId, testRegion) request = request.CreateAccessKeyPayload(fixturePayload()) request = request.CredentialsGroup(testCredentialsGroupId) for _, mod := range mods { @@ -207,7 +207,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/credentials/delete/delete.go b/internal/cmd/object-storage/credentials/delete/delete.go index 84a3b363b..bc707923a 100644 --- a/internal/cmd/object-storage/credentials/delete/delete.go +++ b/internal/cmd/object-storage/credentials/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" ) const ( @@ -53,13 +54,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region) + credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region) if err != nil { params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err) credentialsGroupLabel = model.CredentialsGroupId } - credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.CredentialsId, model.Region) + credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.CredentialsId, model.Region) if err != nil { params.Printer.Debug(print.ErrorLevel, "get credentials name: %v", err) credentialsLabel = model.CredentialsId @@ -112,7 +113,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteAccessKeyRequest { - req := apiClient.DeleteAccessKey(ctx, model.ProjectId, model.Region, model.CredentialsId) + req := apiClient.DefaultAPI.DeleteAccessKey(ctx, model.ProjectId, model.Region, model.CredentialsId) req = req.CredentialsGroup(model.CredentialsGroupId) return req } diff --git a/internal/cmd/object-storage/credentials/delete/delete_test.go b/internal/cmd/object-storage/credentials/delete/delete_test.go index 699d36bcf..12be8797a 100644 --- a/internal/cmd/object-storage/credentials/delete/delete_test.go +++ b/internal/cmd/object-storage/credentials/delete/delete_test.go @@ -10,13 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() var testCredentialsGroupId = uuid.NewString() @@ -64,7 +64,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteAccessKeyRequest)) objectstorage.ApiDeleteAccessKeyRequest { - request := testClient.DeleteAccessKey(testCtx, testProjectId, testRegion, testCredentialsId) + request := testClient.DefaultAPI.DeleteAccessKey(testCtx, testProjectId, testRegion, testCredentialsId) request = request.CredentialsGroup(testCredentialsGroupId) for _, mod := range mods { mod(&request) @@ -183,7 +183,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/credentials/list/list.go b/internal/cmd/object-storage/credentials/list/list.go index f1ef8c155..119de7c76 100644 --- a/internal/cmd/object-storage/credentials/list/list.go +++ b/internal/cmd/object-storage/credentials/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" ) const ( @@ -69,7 +69,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } credentials := resp.GetAccessKeys() - credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region) + credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region) if err != nil { params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err) credentialsGroupLabel = model.CredentialsGroupId @@ -119,7 +119,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListAccessKeysRequest { - req := apiClient.ListAccessKeys(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.ListAccessKeys(ctx, model.ProjectId, model.Region) req = req.CredentialsGroup(model.CredentialsGroupId) return req } @@ -136,8 +136,11 @@ func outputResult(p *print.Printer, outputFormat, credentialsGroupLabel string, for i := range credentials { c := credentials[i] - expiresAt := utils.PtrStringDefault(c.Expires, "Never") - table.AddRow(utils.PtrString(c.KeyId), utils.PtrString(c.DisplayName), expiresAt) + expiresAt := "Never" + if c.Expires != "" { + expiresAt = c.Expires + } + table.AddRow(c.KeyId, c.DisplayName, expiresAt) } err := table.Display(p) if err != nil { diff --git a/internal/cmd/object-storage/credentials/list/list_test.go b/internal/cmd/object-storage/credentials/list/list_test.go index ba6967c84..b950ac36a 100644 --- a/internal/cmd/object-storage/credentials/list/list_test.go +++ b/internal/cmd/object-storage/credentials/list/list_test.go @@ -14,13 +14,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() var testCredentialsGroupId = uuid.NewString() var testRegion = "eu01" @@ -55,7 +55,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiListAccessKeysRequest)) objectstorage.ApiListAccessKeysRequest { - request := testClient.ListAccessKeys(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.ListAccessKeys(testCtx, testProjectId, testRegion) request = request.CredentialsGroup(testCredentialsGroupId) for _, mod := range mods { mod(&request) @@ -165,7 +165,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/disable/disable.go b/internal/cmd/object-storage/disable/disable.go index e7b0f501b..22eba8a93 100644 --- a/internal/cmd/object-storage/disable/disable.go +++ b/internal/cmd/object-storage/disable/disable.go @@ -15,7 +15,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type inputModel struct { @@ -91,6 +91,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDisableServiceRequest { - req := apiClient.DisableService(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.DisableService(ctx, model.ProjectId, model.Region) return req } diff --git a/internal/cmd/object-storage/disable/disable_test.go b/internal/cmd/object-storage/disable/disable_test.go index 8820b8cc3..9f6ce3322 100644 --- a/internal/cmd/object-storage/disable/disable_test.go +++ b/internal/cmd/object-storage/disable/disable_test.go @@ -10,13 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() const testRegion = "eu01" @@ -47,7 +47,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiDisableServiceRequest)) objectstorage.ApiDisableServiceRequest { - request := testClient.DisableService(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.DisableService(testCtx, testProjectId, testRegion) for _, mod := range mods { mod(&request) } @@ -121,7 +121,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/enable/enable.go b/internal/cmd/object-storage/enable/enable.go index bd5304493..0018d56d9 100644 --- a/internal/cmd/object-storage/enable/enable.go +++ b/internal/cmd/object-storage/enable/enable.go @@ -15,7 +15,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type inputModel struct { @@ -91,6 +91,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiEnableServiceRequest { - req := apiClient.EnableService(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.EnableService(ctx, model.ProjectId, model.Region) return req } diff --git a/internal/cmd/object-storage/enable/enable_test.go b/internal/cmd/object-storage/enable/enable_test.go index db8c35d3f..936b2d0f9 100644 --- a/internal/cmd/object-storage/enable/enable_test.go +++ b/internal/cmd/object-storage/enable/enable_test.go @@ -10,13 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &objectstorage.APIClient{} +var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}} var testProjectId = uuid.NewString() const testRegion = "eu01" @@ -47,7 +47,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *objectstorage.ApiEnableServiceRequest)) objectstorage.ApiEnableServiceRequest { - request := testClient.EnableService(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.EnableService(testCtx, testProjectId, testRegion) for _, mod := range mods { mod(&request) } @@ -121,7 +121,7 @@ func TestBuildRequest(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), + cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}), cmpopts.EquateComparable(testCtx), ) if diff != "" { diff --git a/internal/cmd/object-storage/object_storage.go b/internal/cmd/object-storage/object_storage.go index 88358e0d8..1f4c11799 100644 --- a/internal/cmd/object-storage/object_storage.go +++ b/internal/cmd/object-storage/object_storage.go @@ -2,6 +2,7 @@ package objectstorage import ( "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/bucket" + complianceLock "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/compliance-lock" "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials" credentialsGroup "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/credentials-group" "github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/disable" @@ -31,4 +32,5 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(enable.NewCmd(params)) cmd.AddCommand(credentialsGroup.NewCmd(params)) cmd.AddCommand(credentials.NewCmd(params)) + cmd.AddCommand(complianceLock.NewCmd(params)) } diff --git a/internal/cmd/observability/credentials/create/create.go b/internal/cmd/observability/credentials/create/create.go index 12ff53649..6c2f8289f 100644 --- a/internal/cmd/observability/credentials/create/create.go +++ b/internal/cmd/observability/credentials/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/observability" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/client" observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/observability" ) const ( diff --git a/internal/cmd/observability/credentials/create/create_test.go b/internal/cmd/observability/credentials/create/create_test.go index c00d81989..0bd9c6ea6 100644 --- a/internal/cmd/observability/credentials/create/create_test.go +++ b/internal/cmd/observability/credentials/create/create_test.go @@ -6,10 +6,11 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/observability" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/observability" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/observability/grafana/public-read-access/disable/disable.go b/internal/cmd/observability/grafana/public-read-access/disable/disable.go index 70a3ff85b..0b6b3fcc5 100644 --- a/internal/cmd/observability/grafana/public-read-access/disable/disable.go +++ b/internal/cmd/observability/grafana/public-read-access/disable/disable.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/observability" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/client" observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/observability" "github.com/spf13/cobra" ) diff --git a/internal/cmd/observability/grafana/public-read-access/disable/disable_test.go b/internal/cmd/observability/grafana/public-read-access/disable/disable_test.go index 58fdfd098..89cbf05e9 100644 --- a/internal/cmd/observability/grafana/public-read-access/disable/disable_test.go +++ b/internal/cmd/observability/grafana/public-read-access/disable/disable_test.go @@ -12,8 +12,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-sdk-go/services/observability" + + observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/observability/grafana/public-read-access/enable/enable.go b/internal/cmd/observability/grafana/public-read-access/enable/enable.go index 04f9eabf2..88f58227d 100644 --- a/internal/cmd/observability/grafana/public-read-access/enable/enable.go +++ b/internal/cmd/observability/grafana/public-read-access/enable/enable.go @@ -15,8 +15,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" - observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-sdk-go/services/observability" + + observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" ) const ( diff --git a/internal/cmd/observability/grafana/public-read-access/enable/enable_test.go b/internal/cmd/observability/grafana/public-read-access/enable/enable_test.go index 1f3d0fb89..e81ed3bc4 100644 --- a/internal/cmd/observability/grafana/public-read-access/enable/enable_test.go +++ b/internal/cmd/observability/grafana/public-read-access/enable/enable_test.go @@ -12,8 +12,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-sdk-go/services/observability" + + observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/observability/grafana/single-sign-on/disable/disable.go b/internal/cmd/observability/grafana/single-sign-on/disable/disable.go index 57d7d7b8b..a2023c789 100644 --- a/internal/cmd/observability/grafana/single-sign-on/disable/disable.go +++ b/internal/cmd/observability/grafana/single-sign-on/disable/disable.go @@ -15,8 +15,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" - observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-sdk-go/services/observability" + + observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" ) const ( diff --git a/internal/cmd/observability/grafana/single-sign-on/disable/disable_test.go b/internal/cmd/observability/grafana/single-sign-on/disable/disable_test.go index 224c73cca..704b7a199 100644 --- a/internal/cmd/observability/grafana/single-sign-on/disable/disable_test.go +++ b/internal/cmd/observability/grafana/single-sign-on/disable/disable_test.go @@ -12,8 +12,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-sdk-go/services/observability" + + observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/observability/instance/create/create.go b/internal/cmd/observability/instance/create/create.go index c492902e5..72d53ebfe 100644 --- a/internal/cmd/observability/instance/create/create.go +++ b/internal/cmd/observability/instance/create/create.go @@ -19,9 +19,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" - observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" "github.com/stackitcloud/stackit-sdk-go/services/observability" "github.com/stackitcloud/stackit-sdk-go/services/observability/wait" + + observabilityUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/utils" ) const ( @@ -94,13 +95,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, instanceId, model.ProjectId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, instanceId, model.ProjectId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Observability instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) diff --git a/internal/cmd/observability/instance/delete/delete.go b/internal/cmd/observability/instance/delete/delete.go index 0aec201da..d9fc972ab 100644 --- a/internal/cmd/observability/instance/delete/delete.go +++ b/internal/cmd/observability/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.InstanceId, model.ProjectId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.InstanceId, model.ProjectId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Observability instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/observability/instance/list/list.go b/internal/cmd/observability/instance/list/list.go index 2d31e348c..8712e2fcd 100644 --- a/internal/cmd/observability/instance/list/list.go +++ b/internal/cmd/observability/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/observability" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/observability" ) const ( diff --git a/internal/cmd/observability/instance/update/update.go b/internal/cmd/observability/instance/update/update.go index 51d0b9f56..38b2fe693 100644 --- a/internal/cmd/observability/instance/update/update.go +++ b/internal/cmd/observability/instance/update/update.go @@ -100,13 +100,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.UpdateInstanceWaitHandler(ctx, apiClient, instanceId, model.ProjectId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.UpdateInstanceWaitHandler(ctx, apiClient, instanceId, model.ProjectId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Observability instance update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/observability/scrape-config/create/create.go b/internal/cmd/observability/scrape-config/create/create.go index f3e9a0515..87d206cfe 100644 --- a/internal/cmd/observability/scrape-config/create/create.go +++ b/internal/cmd/observability/scrape-config/create/create.go @@ -106,13 +106,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating scrape config") - _, err = wait.CreateScrapeConfigWaitHandler(ctx, apiClient, model.InstanceId, *jobName, model.ProjectId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating scrape config", func() error { + _, err = wait.CreateScrapeConfigWaitHandler(ctx, apiClient, model.InstanceId, *jobName, model.ProjectId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for scrape configuration creation: %w", err) } - s.Stop() } operationState := "Created" diff --git a/internal/cmd/observability/scrape-config/create/create_test.go b/internal/cmd/observability/scrape-config/create/create_test.go index 9d4ab9ba9..8da9b00a2 100644 --- a/internal/cmd/observability/scrape-config/create/create_test.go +++ b/internal/cmd/observability/scrape-config/create/create_test.go @@ -26,7 +26,7 @@ var testProjectId = uuid.NewString() var testInstanceId = uuid.NewString() var testPayload = &observability.CreateScrapeConfigPayload{ - BasicAuth: &observability.CreateScrapeConfigPayloadBasicAuth{ + BasicAuth: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{ Username: utils.Ptr("username"), Password: utils.Ptr("password"), }, @@ -35,9 +35,9 @@ var testPayload = &observability.CreateScrapeConfigPayload{ HonorTimeStamps: utils.Ptr(true), MetricsPath: utils.Ptr("/metrics"), JobName: utils.Ptr("default-name"), - MetricsRelabelConfigs: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + MetricsRelabelConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{ { - Action: observability.CREATESCRAPECONFIGPAYLOADMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(), + Action: observability.PARTIALUPDATESCRAPECONFIGSREQUESTINNERMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(), Modulus: utils.Ptr(1.0), Regex: utils.Ptr("regex"), Replacement: utils.Ptr("replacement"), @@ -54,7 +54,7 @@ var testPayload = &observability.CreateScrapeConfigPayload{ Scheme: observability.CREATESCRAPECONFIGPAYLOADSCHEME_HTTPS.Ptr(), ScrapeInterval: utils.Ptr("interval"), ScrapeTimeout: utils.Ptr("timeout"), - StaticConfigs: &[]observability.CreateScrapeConfigPayloadStaticConfigsInner{ + StaticConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerStaticConfigsInner{ { Labels: &map[string]interface{}{ "label": "value", @@ -63,7 +63,7 @@ var testPayload = &observability.CreateScrapeConfigPayload{ Targets: &[]string{"target"}, }, }, - TlsConfig: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{ + TlsConfig: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{ InsecureSkipVerify: utils.Ptr(true), }, } diff --git a/internal/cmd/observability/scrape-config/delete/delete.go b/internal/cmd/observability/scrape-config/delete/delete.go index 4cd15229b..7ec1e8ccf 100644 --- a/internal/cmd/observability/scrape-config/delete/delete.go +++ b/internal/cmd/observability/scrape-config/delete/delete.go @@ -78,13 +78,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting scrape config") - _, err = wait.DeleteScrapeConfigWaitHandler(ctx, apiClient, model.InstanceId, model.JobName, model.ProjectId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting scrape config", func() error { + _, err = wait.DeleteScrapeConfigWaitHandler(ctx, apiClient, model.InstanceId, model.JobName, model.ProjectId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for scrape config deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/observability/scrape-config/describe/describe.go b/internal/cmd/observability/scrape-config/describe/describe.go index 91dc0d064..5dc11a619 100644 --- a/internal/cmd/observability/scrape-config/describe/describe.go +++ b/internal/cmd/observability/scrape-config/describe/describe.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/observability" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/observability/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/observability" ) const ( diff --git a/internal/cmd/observability/scrape-config/update/update_test.go b/internal/cmd/observability/scrape-config/update/update_test.go index 595b4f09e..2f7ce1b13 100644 --- a/internal/cmd/observability/scrape-config/update/update_test.go +++ b/internal/cmd/observability/scrape-config/update/update_test.go @@ -24,7 +24,7 @@ var testInstanceId = uuid.NewString() var testJobName = "my-config" var testPayload = observability.UpdateScrapeConfigPayload{ - BasicAuth: &observability.CreateScrapeConfigPayloadBasicAuth{ + BasicAuth: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{ Username: utils.Ptr("username"), Password: utils.Ptr("password"), }, @@ -32,9 +32,9 @@ var testPayload = observability.UpdateScrapeConfigPayload{ HonorLabels: utils.Ptr(true), HonorTimeStamps: utils.Ptr(true), MetricsPath: utils.Ptr("/metrics"), - MetricsRelabelConfigs: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + MetricsRelabelConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{ { - Action: observability.CREATESCRAPECONFIGPAYLOADMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(), + Action: observability.PARTIALUPDATESCRAPECONFIGSREQUESTINNERMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(), Modulus: utils.Ptr(1.0), Regex: utils.Ptr("regex"), Replacement: utils.Ptr("replacement"), diff --git a/internal/cmd/opensearch/credentials/create/create.go b/internal/cmd/opensearch/credentials/create/create.go index 06c5b87dd..cfc9bd5f3 100644 --- a/internal/cmd/opensearch/credentials/create/create.go +++ b/internal/cmd/opensearch/credentials/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/opensearch" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/opensearch/client" opensearchUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/opensearch/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/opensearch" ) const ( diff --git a/internal/cmd/opensearch/credentials/create/create_test.go b/internal/cmd/opensearch/credentials/create/create_test.go index 0c768de74..b70f26ed2 100644 --- a/internal/cmd/opensearch/credentials/create/create_test.go +++ b/internal/cmd/opensearch/credentials/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/opensearch" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/opensearch" ) type testCtxKey struct{} diff --git a/internal/cmd/opensearch/credentials/describe/describe.go b/internal/cmd/opensearch/credentials/describe/describe.go index 17ddd78f9..50a495940 100644 --- a/internal/cmd/opensearch/credentials/describe/describe.go +++ b/internal/cmd/opensearch/credentials/describe/describe.go @@ -3,6 +3,7 @@ package describe import ( "context" "fmt" + "strings" "github.com/stackitcloud/stackit-cli/internal/pkg/types" @@ -121,6 +122,13 @@ func outputResult(p *print.Printer, outputFormat string, credentials *opensearch table.AddRow("PASSWORD", utils.PtrString(credentials.Raw.Credentials.Password)) table.AddSeparator() table.AddRow("URI", utils.PtrString(credentials.Raw.Credentials.Uri)) + table.AddSeparator() + table.AddRow("HOST", utils.PtrString(credentials.Raw.Credentials.Host)) + hosts := credentials.Raw.Credentials.Hosts + if hosts != nil && len(*hosts) > 0 { + table.AddSeparator() + table.AddRow("HOSTS", strings.Join(*hosts, "\n")) + } } err := table.Display(p) if err != nil { diff --git a/internal/cmd/opensearch/credentials/describe/describe_test.go b/internal/cmd/opensearch/credentials/describe/describe_test.go index 254912869..2d8ec1a32 100644 --- a/internal/cmd/opensearch/credentials/describe/describe_test.go +++ b/internal/cmd/opensearch/credentials/describe/describe_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -220,6 +221,35 @@ func TestOutputResult(t *testing.T) { }, wantErr: false, }, + { + name: "host and hosts", + args: args{ + credentials: &opensearch.CredentialsResponse{ + Raw: &opensearch.RawCredentials{ + Credentials: &opensearch.Credentials{ + Host: utils.Ptr("host"), + Hosts: utils.Ptr([]string{ + "hosts-a", + "hosts-b", + }), + }, + }, + }, + }, + }, + { + name: "raw credentials nil host & hosts", + args: args{ + credentials: &opensearch.CredentialsResponse{ + Raw: &opensearch.RawCredentials{ + Credentials: &opensearch.Credentials{ + Host: nil, + Hosts: nil, + }, + }, + }, + }, + }, } p := print.NewPrinter() p.Cmd = NewCmd(&types.CmdParams{Printer: p}) diff --git a/internal/cmd/opensearch/instance/create/create.go b/internal/cmd/opensearch/instance/create/create.go index bc03d82dd..776e6b86f 100644 --- a/internal/cmd/opensearch/instance/create/create.go +++ b/internal/cmd/opensearch/instance/create/create.go @@ -116,13 +116,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for OpenSearch instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, instanceId, resp) diff --git a/internal/cmd/opensearch/instance/delete/delete.go b/internal/cmd/opensearch/instance/delete/delete.go index e1cc328fb..58f4f59d1 100644 --- a/internal/cmd/opensearch/instance/delete/delete.go +++ b/internal/cmd/opensearch/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for OpenSearch instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/opensearch/instance/list/list.go b/internal/cmd/opensearch/instance/list/list.go index 0dacbcf6d..48f7e986b 100644 --- a/internal/cmd/opensearch/instance/list/list.go +++ b/internal/cmd/opensearch/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/opensearch" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/opensearch/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/opensearch" ) const ( diff --git a/internal/cmd/opensearch/instance/update/update.go b/internal/cmd/opensearch/instance/update/update.go index aa9b90eb5..557fb1712 100644 --- a/internal/cmd/opensearch/instance/update/update.go +++ b/internal/cmd/opensearch/instance/update/update.go @@ -114,13 +114,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for OpenSearch instance update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/opensearch/plans/plans.go b/internal/cmd/opensearch/plans/plans.go index 3f644c000..f5705879e 100644 --- a/internal/cmd/opensearch/plans/plans.go +++ b/internal/cmd/opensearch/plans/plans.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/opensearch" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/opensearch/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/opensearch" ) const ( diff --git a/internal/cmd/organization/describe/describe.go b/internal/cmd/organization/describe/describe.go new file mode 100644 index 000000000..74ad1a2dc --- /dev/null +++ b/internal/cmd/organization/describe/describe.go @@ -0,0 +1,120 @@ +package describe + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + organizationIdArg = "ORGANIZATION_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + OrganizationId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "describe", + Short: "Show an organization", + Long: "Show an organization.", + // the arg can be the organization uuid or the container id, which is not a uuid, so no validation needed + Args: args.SingleArg(organizationIdArg, nil), + Example: examples.Build( + examples.NewExample( + `Describe the organization with the organization uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`, + "$ stackit organization describe xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + ), + examples.NewExample( + `Describe the organization with the container id "foo-bar-organization"`, + "$ stackit organization describe foo-bar-organization", + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return err + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + organizationId := inputArgs[0] + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: globalFlags, + OrganizationId: organizationId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *resourcemanager.APIClient) resourcemanager.ApiGetOrganizationRequest { + req := apiClient.GetOrganization(ctx, model.OrganizationId) + return req +} + +func outputResult(p *print.Printer, outputFormat string, organization *resourcemanager.OrganizationResponse) error { + return p.OutputResult(outputFormat, organization, func() error { + if organization == nil { + p.Outputln("show organization: empty response") + return nil + } + + table := tables.NewTable() + + table.AddRow("ORGANIZATION ID", utils.PtrString(organization.OrganizationId)) + table.AddSeparator() + table.AddRow("NAME", utils.PtrString(organization.Name)) + table.AddSeparator() + table.AddRow("CONTAINER ID", utils.PtrString(organization.ContainerId)) + table.AddSeparator() + table.AddRow("STATUS", utils.PtrString(organization.LifecycleState)) + table.AddSeparator() + table.AddRow("CREATION TIME", utils.PtrString(organization.CreationTime)) + table.AddSeparator() + table.AddRow("UPDATE TIME", utils.PtrString(organization.UpdateTime)) + table.AddSeparator() + table.AddRow("LABELS", utils.JoinStringMap(utils.PtrValue(organization.Labels), ": ", ", ")) + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/organization/describe/describe_test.go b/internal/cmd/organization/describe/describe_test.go new file mode 100644 index 000000000..00d1d2598 --- /dev/null +++ b/internal/cmd/organization/describe/describe_test.go @@ -0,0 +1,190 @@ +package describe + +import ( + "context" + "testing" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &resourcemanager.APIClient{} + +var ( + testOrganizationId = uuid.NewString() +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testOrganizationId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + }, + OrganizationId: testOrganizationId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *resourcemanager.ApiGetOrganizationRequest)) resourcemanager.ApiGetOrganizationRequest { + request := testClient.GetOrganization(testCtx, testOrganizationId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "uuid as example for an organization id", + argValues: []string{"12345678-90ab-cdef-1234-1234567890ab"}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.OrganizationId = "12345678-90ab-cdef-1234-1234567890ab" + }), + }, + { + description: "non uuid string as example for a container id", + argValues: []string{"foo-bar-organization"}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.OrganizationId = "foo-bar-organization" + }), + }, + { + description: "no args", + argValues: []string{}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest resourcemanager.ApiGetOrganizationRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + organization *resourcemanager.OrganizationResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "nil pointer as organization", + args: args{ + organization: nil, + }, + wantErr: false, + }, + { + name: "empty organization", + args: args{ + organization: utils.Ptr(resourcemanager.OrganizationResponse{}), + }, + wantErr: false, + }, + { + name: "full response", + args: args{ + organization: utils.Ptr(resourcemanager.OrganizationResponse{ + OrganizationId: utils.Ptr(uuid.NewString()), + Name: utils.Ptr("foo bar"), + LifecycleState: utils.Ptr(resourcemanager.LIFECYCLESTATE_ACTIVE), + ContainerId: utils.Ptr("foo-bar-organization"), + CreationTime: utils.Ptr(time.Now()), + UpdateTime: utils.Ptr(time.Now()), + Labels: utils.Ptr(map[string]string{ + "foo": "true", + "bar": "false", + }), + }), + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.organization); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/organization/list/list.go b/internal/cmd/organization/list/list.go new file mode 100644 index 000000000..050d265f0 --- /dev/null +++ b/internal/cmd/organization/list/list.go @@ -0,0 +1,146 @@ +package list + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + limitFlag = "limit" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Limit *int64 + Member string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all organizations", + Long: "Lists all organizations.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Lists organizations for your user`, + "$ stackit organization list", + ), + examples.NewExample( + `Lists the first 10 organizations`, + "$ stackit organization list --limit 10", + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + activeProfile, err := config.GetProfile() + if err != nil { + return fmt.Errorf("get profile: %w", err) + } + model.Member = auth.GetProfileEmail(activeProfile) + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return err + } + + if resp == nil { + return fmt.Errorf("list organizations: empty response") + } + + return outputResult(params.Printer, model.OutputFormat, utils.PtrValue(resp.Items)) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list (default 50)") +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && (*limit < 1 || *limit > 100) { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be between 0 and 100", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Limit: limit, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *resourcemanager.APIClient) resourcemanager.ApiListOrganizationsRequest { + req := apiClient.ListOrganizations(ctx) + req = req.Member(model.Member) + if model.Limit != nil { + req = req.Limit(float32(*model.Limit)) + } + return req +} + +func outputResult(p *print.Printer, outputFormat string, organizations []resourcemanager.ListOrganizationsResponseItemsInner) error { + return p.OutputResult(outputFormat, organizations, func() error { + if len(organizations) == 0 { + p.Outputln("No organizations found") + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "NAME", "CONTAINER ID") + + for _, organization := range organizations { + table.AddRow( + utils.PtrString(organization.OrganizationId), + utils.PtrString(organization.Name), + utils.PtrString(organization.ContainerId), + ) + table.AddSeparator() + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/organization/list/list_test.go b/internal/cmd/organization/list/list_test.go new file mode 100644 index 000000000..ec54c69ea --- /dev/null +++ b/internal/cmd/organization/list/list_test.go @@ -0,0 +1,191 @@ +package list + +import ( + "context" + "strconv" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &resourcemanager.APIClient{} + +const ( + testEmail = "foo@bar" + testLimit = 10 +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + limitFlag: strconv.Itoa(int(testLimit)), + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + }, + Limit: utils.Ptr(int64(testLimit)), + Member: testEmail, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *resourcemanager.ApiListOrganizationsRequest)) resourcemanager.ApiListOrganizationsRequest { + request := testClient.ListOrganizations(testCtx) + request = request.Limit(testLimit) + request = request.Member(testEmail) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + // model.Member is set by the Run function afterwards + model.Member = "" + }), + }, + { + description: "no limit", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, limitFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + // model.Member is set by the Run function afterwards + model.Member = "" + model.Limit = nil + }), + }, + { + description: "limit invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "limit invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest resourcemanager.ApiListOrganizationsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "empty input model", + model: fixtureInputModel(func(model *inputModel) { + model.Member = "" + model.Limit = nil + }), + expectedRequest: testClient.ListOrganizations(testCtx).Member(""), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + organizations []resourcemanager.ListOrganizationsResponseItemsInner + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "empty organizations slice", + args: args{ + organizations: []resourcemanager.ListOrganizationsResponseItemsInner{}, + }, + wantErr: false, + }, + { + name: "empty organization in organizations slice", + args: args{ + organizations: []resourcemanager.ListOrganizationsResponseItemsInner{{}}, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.organizations); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/organization/member/add/add.go b/internal/cmd/organization/member/add/add.go index db8754c5f..325669ae3 100644 --- a/internal/cmd/organization/member/add/add.go +++ b/internal/cmd/organization/member/add/add.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/authorization" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/authorization/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/authorization" ) const ( diff --git a/internal/cmd/organization/member/list/list.go b/internal/cmd/organization/member/list/list.go index 7e17796c6..fef787e39 100644 --- a/internal/cmd/organization/member/list/list.go +++ b/internal/cmd/organization/member/list/list.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/authorization" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/authorization/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/authorization" ) const ( diff --git a/internal/cmd/organization/organization.go b/internal/cmd/organization/organization.go index 7a68177b7..adc9fa8e8 100644 --- a/internal/cmd/organization/organization.go +++ b/internal/cmd/organization/organization.go @@ -5,6 +5,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/cmd/organization/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/organization/list" "github.com/stackitcloud/stackit-cli/internal/cmd/organization/member" "github.com/stackitcloud/stackit-cli/internal/cmd/organization/role" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -31,4 +33,6 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(member.NewCmd(params)) cmd.AddCommand(role.NewCmd(params)) + cmd.AddCommand(list.NewCmd(params)) + cmd.AddCommand(describe.NewCmd(params)) } diff --git a/internal/cmd/organization/role/list/list.go b/internal/cmd/organization/role/list/list.go index bb59b28c5..f9e7e4bf7 100644 --- a/internal/cmd/organization/role/list/list.go +++ b/internal/cmd/organization/role/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/authorization" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/authorization/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/authorization" ) const ( diff --git a/internal/cmd/postgresflex/backup/describe/describe.go b/internal/cmd/postgresflex/backup/describe/describe.go index 891b78888..ee3e3753e 100644 --- a/internal/cmd/postgresflex/backup/describe/describe.go +++ b/internal/cmd/postgresflex/backup/describe/describe.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) const ( diff --git a/internal/cmd/postgresflex/backup/describe/describe_test.go b/internal/cmd/postgresflex/backup/describe/describe_test.go index cd44d829e..4d9016ca5 100644 --- a/internal/cmd/postgresflex/backup/describe/describe_test.go +++ b/internal/cmd/postgresflex/backup/describe/describe_test.go @@ -10,10 +10,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/backup/list/list_test.go b/internal/cmd/postgresflex/backup/list/list_test.go index 39c9d8f53..3f6384ae8 100644 --- a/internal/cmd/postgresflex/backup/list/list_test.go +++ b/internal/cmd/postgresflex/backup/list/list_test.go @@ -12,10 +12,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/backup/update-schedule/update_schedule.go b/internal/cmd/postgresflex/backup/update-schedule/update_schedule.go index 16bb967fe..447fe0864 100644 --- a/internal/cmd/postgresflex/backup/update-schedule/update_schedule.go +++ b/internal/cmd/postgresflex/backup/update-schedule/update_schedule.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" postgresflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) const ( diff --git a/internal/cmd/postgresflex/instance/clone/clone.go b/internal/cmd/postgresflex/instance/clone/clone.go index d9a784d2a..facfdf105 100644 --- a/internal/cmd/postgresflex/instance/clone/clone.go +++ b/internal/cmd/postgresflex/instance/clone/clone.go @@ -97,13 +97,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Cloning instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Cloning instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for PostgreSQL Flex instance cloning: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, instanceLabel, instanceId, resp) diff --git a/internal/cmd/postgresflex/instance/clone/clone_test.go b/internal/cmd/postgresflex/instance/clone/clone_test.go index 657670aa2..ebe60a424 100644 --- a/internal/cmd/postgresflex/instance/clone/clone_test.go +++ b/internal/cmd/postgresflex/instance/clone/clone_test.go @@ -11,11 +11,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/instance/create/create.go b/internal/cmd/postgresflex/instance/create/create.go index 8359ad2f7..7cad848d6 100644 --- a/internal/cmd/postgresflex/instance/create/create.go +++ b/internal/cmd/postgresflex/instance/create/create.go @@ -122,13 +122,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for PostgreSQL Flex instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, instanceId, resp) diff --git a/internal/cmd/postgresflex/instance/create/create_test.go b/internal/cmd/postgresflex/instance/create/create_test.go index f3fa498c2..5e7f58cd8 100644 --- a/internal/cmd/postgresflex/instance/create/create_test.go +++ b/internal/cmd/postgresflex/instance/create/create_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/instance/delete/delete.go b/internal/cmd/postgresflex/instance/delete/delete.go index 2108d31c3..3a8aa4cfe 100644 --- a/internal/cmd/postgresflex/instance/delete/delete.go +++ b/internal/cmd/postgresflex/instance/delete/delete.go @@ -92,13 +92,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for PostgreSQL Flex instance deletion: %w", err) } - s.Stop() } } @@ -112,13 +112,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Forcing deletion of instance") - _, err = wait.ForceDeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Forcing deletion of instance", func() error { + _, err = wait.ForceDeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for PostgreSQL Flex instance force deletion: %w", err) } - s.Stop() } } diff --git a/internal/cmd/postgresflex/instance/describe/describe.go b/internal/cmd/postgresflex/instance/describe/describe.go index a50f3f167..c97762a76 100644 --- a/internal/cmd/postgresflex/instance/describe/describe.go +++ b/internal/cmd/postgresflex/instance/describe/describe.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( postgresflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "golang.org/x/text/cases" - "golang.org/x/text/language" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" diff --git a/internal/cmd/postgresflex/instance/describe/describe_test.go b/internal/cmd/postgresflex/instance/describe/describe_test.go index 80ce6c262..fef025e6e 100644 --- a/internal/cmd/postgresflex/instance/describe/describe_test.go +++ b/internal/cmd/postgresflex/instance/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/instance/list/list.go b/internal/cmd/postgresflex/instance/list/list.go index 2b20dc3e2..9f1b0f690 100644 --- a/internal/cmd/postgresflex/instance/list/list.go +++ b/internal/cmd/postgresflex/instance/list/list.go @@ -7,6 +7,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,9 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) const ( diff --git a/internal/cmd/postgresflex/instance/list/list_test.go b/internal/cmd/postgresflex/instance/list/list_test.go index dfde2729a..0af825a2a 100644 --- a/internal/cmd/postgresflex/instance/list/list_test.go +++ b/internal/cmd/postgresflex/instance/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/instance/update/update.go b/internal/cmd/postgresflex/instance/update/update.go index 8623a78f4..74a3a402d 100644 --- a/internal/cmd/postgresflex/instance/update/update.go +++ b/internal/cmd/postgresflex/instance/update/update.go @@ -107,13 +107,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for PostgreSQL Flex instance update: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, instanceLabel, resp) diff --git a/internal/cmd/postgresflex/instance/update/update_test.go b/internal/cmd/postgresflex/instance/update/update_test.go index 17cfb46c8..8a3a0e61e 100644 --- a/internal/cmd/postgresflex/instance/update/update_test.go +++ b/internal/cmd/postgresflex/instance/update/update_test.go @@ -10,10 +10,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/options/options.go b/internal/cmd/postgresflex/options/options.go index ad57495d3..e569b331e 100644 --- a/internal/cmd/postgresflex/options/options.go +++ b/internal/cmd/postgresflex/options/options.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) const ( diff --git a/internal/cmd/postgresflex/options/options_test.go b/internal/cmd/postgresflex/options/options_test.go index bd47b14ba..9d56b28ea 100644 --- a/internal/cmd/postgresflex/options/options_test.go +++ b/internal/cmd/postgresflex/options/options_test.go @@ -8,11 +8,12 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/user/create/create.go b/internal/cmd/postgresflex/user/create/create.go index fc53b1f07..97dc0c520 100644 --- a/internal/cmd/postgresflex/user/create/create.go +++ b/internal/cmd/postgresflex/user/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" postgresflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) const ( diff --git a/internal/cmd/postgresflex/user/create/create_test.go b/internal/cmd/postgresflex/user/create/create_test.go index 2ab611d42..cc630472c 100644 --- a/internal/cmd/postgresflex/user/create/create_test.go +++ b/internal/cmd/postgresflex/user/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/user/describe/describe.go b/internal/cmd/postgresflex/user/describe/describe.go index 01ab5fce1..4bf92198a 100644 --- a/internal/cmd/postgresflex/user/describe/describe.go +++ b/internal/cmd/postgresflex/user/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) const ( diff --git a/internal/cmd/postgresflex/user/describe/describe_test.go b/internal/cmd/postgresflex/user/describe/describe_test.go index 92b83cc99..214df4ff3 100644 --- a/internal/cmd/postgresflex/user/describe/describe_test.go +++ b/internal/cmd/postgresflex/user/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/user/list/list.go b/internal/cmd/postgresflex/user/list/list.go index a8dc23773..d0c9dbd36 100644 --- a/internal/cmd/postgresflex/user/list/list.go +++ b/internal/cmd/postgresflex/user/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( postgresflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) const ( diff --git a/internal/cmd/postgresflex/user/list/list_test.go b/internal/cmd/postgresflex/user/list/list_test.go index 2695296c7..d84fd6706 100644 --- a/internal/cmd/postgresflex/user/list/list_test.go +++ b/internal/cmd/postgresflex/user/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/postgresflex/user/reset-password/reset_password.go b/internal/cmd/postgresflex/user/reset-password/reset_password.go index c899b5659..19e34dd2e 100644 --- a/internal/cmd/postgresflex/user/reset-password/reset_password.go +++ b/internal/cmd/postgresflex/user/reset-password/reset_password.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" postgresflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) const ( diff --git a/internal/cmd/postgresflex/user/reset-password/reset_password_test.go b/internal/cmd/postgresflex/user/reset-password/reset_password_test.go index 920c78b93..9b13c8b42 100644 --- a/internal/cmd/postgresflex/user/reset-password/reset_password_test.go +++ b/internal/cmd/postgresflex/user/reset-password/reset_password_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" ) type testCtxKey struct{} diff --git a/internal/cmd/project/create/create_test.go b/internal/cmd/project/create/create_test.go index 095469298..d96d3b475 100644 --- a/internal/cmd/project/create/create_test.go +++ b/internal/cmd/project/create/create_test.go @@ -9,13 +9,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" + "github.com/zalando/go-keyring" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" - "github.com/zalando/go-keyring" ) type testCtxKey struct{} diff --git a/internal/cmd/project/describe/describe_test.go b/internal/cmd/project/describe/describe_test.go index b5f3ddcff..218f36671 100644 --- a/internal/cmd/project/describe/describe_test.go +++ b/internal/cmd/project/describe/describe_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/project/list/list.go b/internal/cmd/project/list/list.go index d4ef99b63..497aefb2e 100644 --- a/internal/cmd/project/list/list.go +++ b/internal/cmd/project/list/list.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" @@ -18,7 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" ) const ( diff --git a/internal/cmd/project/list/list_test.go b/internal/cmd/project/list/list_test.go index 8050a0f27..7e26d6f8c 100644 --- a/internal/cmd/project/list/list_test.go +++ b/internal/cmd/project/list/list_test.go @@ -16,13 +16,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" + "github.com/zalando/go-keyring" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" - "github.com/zalando/go-keyring" ) type testCtxKey struct{} diff --git a/internal/cmd/project/member/list/list.go b/internal/cmd/project/member/list/list.go index 66df41524..97b3003ef 100644 --- a/internal/cmd/project/member/list/list.go +++ b/internal/cmd/project/member/list/list.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/authorization" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/authorization/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/authorization" ) const ( diff --git a/internal/cmd/project/member/list/list_test.go b/internal/cmd/project/member/list/list_test.go index af050ba78..942a3096b 100644 --- a/internal/cmd/project/member/list/list_test.go +++ b/internal/cmd/project/member/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/authorization" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/authorization" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/project/role/list/list.go b/internal/cmd/project/role/list/list.go index 7be67dc6b..292cad0d2 100644 --- a/internal/cmd/project/role/list/list.go +++ b/internal/cmd/project/role/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/authorization" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/authorization/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/authorization" ) const ( diff --git a/internal/cmd/project/role/list/list_test.go b/internal/cmd/project/role/list/list_test.go index 3bc59db9a..0c70eed79 100644 --- a/internal/cmd/project/role/list/list_test.go +++ b/internal/cmd/project/role/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/authorization" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/authorization" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/public-ip/associate/associate.go b/internal/cmd/public-ip/associate/associate.go index 357a6c87f..9b86d2f61 100644 --- a/internal/cmd/public-ip/associate/associate.go +++ b/internal/cmd/public-ip/associate/associate.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/public-ip/associate/associate_test.go b/internal/cmd/public-ip/associate/associate_test.go index 8d40c1a6a..113ddc50c 100644 --- a/internal/cmd/public-ip/associate/associate_test.go +++ b/internal/cmd/public-ip/associate/associate_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/public-ip/create/create.go b/internal/cmd/public-ip/create/create.go index a13574190..9da469d1e 100644 --- a/internal/cmd/public-ip/create/create.go +++ b/internal/cmd/public-ip/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/public-ip/delete/delete.go b/internal/cmd/public-ip/delete/delete.go index 2bf9c234f..8663ab334 100644 --- a/internal/cmd/public-ip/delete/delete.go +++ b/internal/cmd/public-ip/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/public-ip/describe/describe.go b/internal/cmd/public-ip/describe/describe.go index fbcc2b15b..a4334b421 100644 --- a/internal/cmd/public-ip/describe/describe.go +++ b/internal/cmd/public-ip/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/public-ip/disassociate/disassociate.go b/internal/cmd/public-ip/disassociate/disassociate.go index 7df01b637..fae9afa0e 100644 --- a/internal/cmd/public-ip/disassociate/disassociate.go +++ b/internal/cmd/public-ip/disassociate/disassociate.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/public-ip/disassociate/disassociate_test.go b/internal/cmd/public-ip/disassociate/disassociate_test.go index 41c8d26e8..42bb505d6 100644 --- a/internal/cmd/public-ip/disassociate/disassociate_test.go +++ b/internal/cmd/public-ip/disassociate/disassociate_test.go @@ -11,8 +11,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" ) const ( diff --git a/internal/cmd/public-ip/list/list.go b/internal/cmd/public-ip/list/list.go index b2acfff92..1888e2d1d 100644 --- a/internal/cmd/public-ip/list/list.go +++ b/internal/cmd/public-ip/list/list.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/public-ip/ranges/list/list.go b/internal/cmd/public-ip/ranges/list/list.go index 918cb2041..cf01450cf 100644 --- a/internal/cmd/public-ip/ranges/list/list.go +++ b/internal/cmd/public-ip/ranges/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/public-ip/ranges/list/list_test.go b/internal/cmd/public-ip/ranges/list/list_test.go index 9a50dfeb1..1e0379a91 100644 --- a/internal/cmd/public-ip/ranges/list/list_test.go +++ b/internal/cmd/public-ip/ranges/list/list_test.go @@ -8,10 +8,11 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) func TestParseInput(t *testing.T) { diff --git a/internal/cmd/public-ip/update/update.go b/internal/cmd/public-ip/update/update.go index 8fb1d8a77..bdecb7dfc 100644 --- a/internal/cmd/public-ip/update/update.go +++ b/internal/cmd/public-ip/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/quota/list/list.go b/internal/cmd/quota/list/list.go index d16da37d0..ebb1ca353 100644 --- a/internal/cmd/quota/list/list.go +++ b/internal/cmd/quota/list/list.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/rabbitmq/credentials/create/create.go b/internal/cmd/rabbitmq/credentials/create/create.go index 3aeea6efe..8fcf54748 100644 --- a/internal/cmd/rabbitmq/credentials/create/create.go +++ b/internal/cmd/rabbitmq/credentials/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/rabbitmq/client" rabbitmqUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/rabbitmq/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) const ( diff --git a/internal/cmd/rabbitmq/credentials/create/create_test.go b/internal/cmd/rabbitmq/credentials/create/create_test.go index 3286d4931..186556aa4 100644 --- a/internal/cmd/rabbitmq/credentials/create/create_test.go +++ b/internal/cmd/rabbitmq/credentials/create/create_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) type testCtxKey struct{} diff --git a/internal/cmd/rabbitmq/credentials/describe/describe_test.go b/internal/cmd/rabbitmq/credentials/describe/describe_test.go index b92353fb9..5372405e2 100644 --- a/internal/cmd/rabbitmq/credentials/describe/describe_test.go +++ b/internal/cmd/rabbitmq/credentials/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) type testCtxKey struct{} diff --git a/internal/cmd/rabbitmq/credentials/list/list.go b/internal/cmd/rabbitmq/credentials/list/list.go index 218b4a97f..3c8b44a3c 100644 --- a/internal/cmd/rabbitmq/credentials/list/list.go +++ b/internal/cmd/rabbitmq/credentials/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( rabbitmqUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/rabbitmq/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) const ( diff --git a/internal/cmd/rabbitmq/credentials/list/list_test.go b/internal/cmd/rabbitmq/credentials/list/list_test.go index d2593b39b..8dda0a05d 100644 --- a/internal/cmd/rabbitmq/credentials/list/list_test.go +++ b/internal/cmd/rabbitmq/credentials/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) type testCtxKey struct{} diff --git a/internal/cmd/rabbitmq/instance/create/create.go b/internal/cmd/rabbitmq/instance/create/create.go index 1ac8e7cc2..cb2bf470a 100644 --- a/internal/cmd/rabbitmq/instance/create/create.go +++ b/internal/cmd/rabbitmq/instance/create/create.go @@ -116,13 +116,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for RabbitMQ instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, instanceId, resp) diff --git a/internal/cmd/rabbitmq/instance/create/create_test.go b/internal/cmd/rabbitmq/instance/create/create_test.go index 8a267ca2a..726bcacb3 100644 --- a/internal/cmd/rabbitmq/instance/create/create_test.go +++ b/internal/cmd/rabbitmq/instance/create/create_test.go @@ -12,10 +12,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) type testCtxKey struct{} diff --git a/internal/cmd/rabbitmq/instance/delete/delete.go b/internal/cmd/rabbitmq/instance/delete/delete.go index 766ced827..ec5c35104 100644 --- a/internal/cmd/rabbitmq/instance/delete/delete.go +++ b/internal/cmd/rabbitmq/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for RabbitMQ instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/rabbitmq/instance/describe/describe_test.go b/internal/cmd/rabbitmq/instance/describe/describe_test.go index c92834466..d48cb0a36 100644 --- a/internal/cmd/rabbitmq/instance/describe/describe_test.go +++ b/internal/cmd/rabbitmq/instance/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) type testCtxKey struct{} diff --git a/internal/cmd/rabbitmq/instance/list/list.go b/internal/cmd/rabbitmq/instance/list/list.go index 715f6fa45..70e47500e 100644 --- a/internal/cmd/rabbitmq/instance/list/list.go +++ b/internal/cmd/rabbitmq/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/rabbitmq/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) const ( diff --git a/internal/cmd/rabbitmq/instance/list/list_test.go b/internal/cmd/rabbitmq/instance/list/list_test.go index 18bfa1817..10e461d71 100644 --- a/internal/cmd/rabbitmq/instance/list/list_test.go +++ b/internal/cmd/rabbitmq/instance/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) type testCtxKey struct{} diff --git a/internal/cmd/rabbitmq/instance/update/update.go b/internal/cmd/rabbitmq/instance/update/update.go index d3cf53603..b58590a6e 100644 --- a/internal/cmd/rabbitmq/instance/update/update.go +++ b/internal/cmd/rabbitmq/instance/update/update.go @@ -114,13 +114,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for RabbitMQ instance update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/rabbitmq/plans/plans_test.go b/internal/cmd/rabbitmq/plans/plans_test.go index f98191e88..e83a33072 100644 --- a/internal/cmd/rabbitmq/plans/plans_test.go +++ b/internal/cmd/rabbitmq/plans/plans_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" ) type testCtxKey struct{} diff --git a/internal/cmd/redis/credentials/create/create_test.go b/internal/cmd/redis/credentials/create/create_test.go index 79d571121..ddb64fac1 100644 --- a/internal/cmd/redis/credentials/create/create_test.go +++ b/internal/cmd/redis/credentials/create/create_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/redis/credentials/describe/describe_test.go b/internal/cmd/redis/credentials/describe/describe_test.go index 7ee94e50c..0697f37c8 100644 --- a/internal/cmd/redis/credentials/describe/describe_test.go +++ b/internal/cmd/redis/credentials/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/redis/credentials/list/list.go b/internal/cmd/redis/credentials/list/list.go index 332ef35e0..536ef13c7 100644 --- a/internal/cmd/redis/credentials/list/list.go +++ b/internal/cmd/redis/credentials/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( redisUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/redis/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) const ( diff --git a/internal/cmd/redis/credentials/list/list_test.go b/internal/cmd/redis/credentials/list/list_test.go index fdf207e55..ee8b74679 100644 --- a/internal/cmd/redis/credentials/list/list_test.go +++ b/internal/cmd/redis/credentials/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/redis/instance/create/create.go b/internal/cmd/redis/instance/create/create.go index 8993b98ca..817a769ce 100644 --- a/internal/cmd/redis/instance/create/create.go +++ b/internal/cmd/redis/instance/create/create.go @@ -114,13 +114,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating instance") - _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Redis instance creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, instanceId, resp) diff --git a/internal/cmd/redis/instance/create/create_test.go b/internal/cmd/redis/instance/create/create_test.go index cc6baaeed..c133ec83e 100644 --- a/internal/cmd/redis/instance/create/create_test.go +++ b/internal/cmd/redis/instance/create/create_test.go @@ -10,11 +10,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/redis/instance/delete/delete.go b/internal/cmd/redis/instance/delete/delete.go index 0c8124906..f54412008 100644 --- a/internal/cmd/redis/instance/delete/delete.go +++ b/internal/cmd/redis/instance/delete/delete.go @@ -75,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Redis instance deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/redis/instance/describe/describe_test.go b/internal/cmd/redis/instance/describe/describe_test.go index 16a99ab65..2a1cb3acc 100644 --- a/internal/cmd/redis/instance/describe/describe_test.go +++ b/internal/cmd/redis/instance/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/redis/instance/list/list.go b/internal/cmd/redis/instance/list/list.go index 2051f471e..b0d207907 100644 --- a/internal/cmd/redis/instance/list/list.go +++ b/internal/cmd/redis/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/redis/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) const ( diff --git a/internal/cmd/redis/instance/list/list_test.go b/internal/cmd/redis/instance/list/list_test.go index 81053ecdd..15250c774 100644 --- a/internal/cmd/redis/instance/list/list_test.go +++ b/internal/cmd/redis/instance/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/redis/instance/update/update.go b/internal/cmd/redis/instance/update/update.go index 3fcc2e7b4..ca078eb82 100644 --- a/internal/cmd/redis/instance/update/update.go +++ b/internal/cmd/redis/instance/update/update.go @@ -111,13 +111,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating instance") - _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating instance", func() error { + _, err = wait.PartialUpdateInstanceWaitHandler(ctx, apiClient, model.ProjectId, instanceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for Redis instance update: %w", err) } - s.Stop() } operationState := "Updated" diff --git a/internal/cmd/redis/plans/plans.go b/internal/cmd/redis/plans/plans.go index f7ecfe5cf..923006376 100644 --- a/internal/cmd/redis/plans/plans.go +++ b/internal/cmd/redis/plans/plans.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/redis/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) const ( diff --git a/internal/cmd/redis/plans/plans_test.go b/internal/cmd/redis/plans/plans_test.go index a18048cd6..a85d010dc 100644 --- a/internal/cmd/redis/plans/plans_test.go +++ b/internal/cmd/redis/plans/plans_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/redis" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/redis" ) var projectIdFlag = globalflags.ProjectIdFlag diff --git a/internal/cmd/root.go b/internal/cmd/root.go index a264bddef..c120ccc76 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -17,6 +17,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/git" "github.com/stackitcloud/stackit-cli/internal/cmd/image" keypair "github.com/stackitcloud/stackit-cli/internal/cmd/key-pair" + "github.com/stackitcloud/stackit-cli/internal/cmd/kms" loadbalancer "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer" "github.com/stackitcloud/stackit-cli/internal/cmd/logme" "github.com/stackitcloud/stackit-cli/internal/cmd/logs" @@ -197,6 +198,7 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(quota.NewCmd(params)) cmd.AddCommand(affinityGroups.NewCmd(params)) cmd.AddCommand(git.NewCmd(params)) + cmd.AddCommand(kms.NewCmd(params)) } // traverseCommands calls f for c and all of its children. diff --git a/internal/cmd/root_test.go b/internal/cmd/root_test.go index 71861a46f..1becca94e 100644 --- a/internal/cmd/root_test.go +++ b/internal/cmd/root_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/spf13/cobra" + pkgErrors "github.com/stackitcloud/stackit-cli/internal/pkg/errors" ) diff --git a/internal/cmd/secrets-manager/instance/create/create.go b/internal/cmd/secrets-manager/instance/create/create.go index 0c6d8420b..83662cf44 100644 --- a/internal/cmd/secrets-manager/instance/create/create.go +++ b/internal/cmd/secrets-manager/instance/create/create.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" "github.com/spf13/cobra" ) @@ -23,6 +24,11 @@ import ( const ( instanceNameFlag = "name" aclFlag = "acl" + + kmsKeyIdFlag = "kms-key-id" + kmsKeyringIdFlag = "kms-keyring-id" + kmsKeyVersionFlag = "kms-key-version" + kmsServiceAccountEmailFlag = "kms-service-account-email" ) type inputModel struct { @@ -30,6 +36,11 @@ type inputModel struct { InstanceName *string Acls *[]string + + KmsKeyId *string + KmsKeyringId *string + KmsKeyVersion *int64 + KmsServiceAccountEmail *string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -45,6 +56,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { examples.NewExample( `Create a Secrets Manager instance with name "my-instance" and specify IP range which is allowed to access it`, `$ stackit secrets-manager instance create --name my-instance --acl 1.2.3.0/24`), + examples.NewExample( + `Create a Secrets Manager instance with name "my-instance" and configure KMS key options`, + `$ stackit secrets-manager instance create --name my-instance --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -103,8 +117,15 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name") cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "List of IP networks in CIDR notation which are allowed to access this instance") + cmd.Flags().String(kmsKeyIdFlag, "", "ID of the KMS key to use for encryption") + cmd.Flags().String(kmsKeyringIdFlag, "", "ID of the KMS key ring") + cmd.Flags().Int64(kmsKeyVersionFlag, 0, "Version of the KMS key") + cmd.Flags().String(kmsServiceAccountEmailFlag, "", "Service account email for KMS access") + err := flags.MarkFlagsRequired(cmd, instanceNameFlag) cobra.CheckErr(err) + + cmd.MarkFlagsRequiredTogether(kmsKeyIdFlag, kmsKeyringIdFlag, kmsKeyVersionFlag, kmsServiceAccountEmailFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { @@ -114,9 +135,13 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } model := inputModel{ - GlobalFlagModel: globalFlags, - InstanceName: flags.FlagToStringPointer(p, cmd, instanceNameFlag), - Acls: flags.FlagToStringSlicePointer(p, cmd, aclFlag), + GlobalFlagModel: globalFlags, + InstanceName: flags.FlagToStringPointer(p, cmd, instanceNameFlag), + Acls: flags.FlagToStringSlicePointer(p, cmd, aclFlag), + KmsKeyId: flags.FlagToStringPointer(p, cmd, kmsKeyIdFlag), + KmsKeyringId: flags.FlagToStringPointer(p, cmd, kmsKeyringIdFlag), + KmsKeyVersion: flags.FlagToInt64Pointer(p, cmd, kmsKeyVersionFlag), + KmsServiceAccountEmail: flags.FlagToStringPointer(p, cmd, kmsServiceAccountEmailFlag), } p.DebugInputModel(model) @@ -126,9 +151,20 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateInstanceRequest { req := apiClient.CreateInstance(ctx, model.ProjectId) - req = req.CreateInstancePayload(secretsmanager.CreateInstancePayload{ + payload := secretsmanager.CreateInstancePayload{ Name: model.InstanceName, - }) + } + + if model.KmsKeyId != nil { + payload.KmsKey = &secretsmanager.KmsKeyPayload{ + KeyId: model.KmsKeyId, + KeyRingId: model.KmsKeyringId, + KeyVersion: model.KmsKeyVersion, + ServiceAccountEmail: model.KmsServiceAccountEmail, + } + } + + req = req.CreateInstancePayload(payload) return req } diff --git a/internal/cmd/secrets-manager/instance/create/create_test.go b/internal/cmd/secrets-manager/instance/create/create_test.go index 4cef0d887..437da9492 100644 --- a/internal/cmd/secrets-manager/instance/create/create_test.go +++ b/internal/cmd/secrets-manager/instance/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -25,6 +26,13 @@ var testClient = &secretsmanager.APIClient{} var testProjectId = uuid.NewString() var testInstanceId = uuid.NewString() +const ( + testKmsKeyId = "key-id" + testKmsKeyringId = "keyring-id" + testKmsKeyVersion = int64(1) + testKmsServiceAccountEmail = "my-service-account-1234567@sa.stackit.cloud" +) + func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ projectIdFlag: testProjectId, @@ -162,6 +170,24 @@ func TestParseInput(t *testing.T) { *model.Acls = append(*model.Acls, "1.2.3.4/32") }), }, + { + description: "kms flags", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, aclFlag) + flagValues[kmsKeyIdFlag] = testKmsKeyId + flagValues[kmsKeyringIdFlag] = testKmsKeyringId + flagValues[kmsKeyVersionFlag] = "1" + flagValues[kmsServiceAccountEmailFlag] = testKmsServiceAccountEmail + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { @@ -205,6 +231,28 @@ func TestBuildCreateInstanceRequest(t *testing.T) { model: fixtureInputModel(), expectedRequest: fixtureRequest(), }, + { + description: "with kms", + model: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + expectedRequest: fixtureRequest(func(request *secretsmanager.ApiCreateInstanceRequest) { + payload := secretsmanager.CreateInstancePayload{ + Name: utils.Ptr("example"), + KmsKey: &secretsmanager.KmsKeyPayload{ + KeyId: utils.Ptr(testKmsKeyId), + KeyRingId: utils.Ptr(testKmsKeyringId), + KeyVersion: utils.Ptr(testKmsKeyVersion), + ServiceAccountEmail: utils.Ptr(testKmsServiceAccountEmail), + }, + } + *request = (*request).CreateInstancePayload(payload) + }), + }, } for _, tt := range tests { diff --git a/internal/cmd/secrets-manager/instance/delete/delete.go b/internal/cmd/secrets-manager/instance/delete/delete.go index 94ed3385a..c4c2d1602 100644 --- a/internal/cmd/secrets-manager/instance/delete/delete.go +++ b/internal/cmd/secrets-manager/instance/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/client" secretsmanagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" ) const ( diff --git a/internal/cmd/secrets-manager/instance/describe/describe.go b/internal/cmd/secrets-manager/instance/describe/describe.go index bbd162bff..75c8cbd7c 100644 --- a/internal/cmd/secrets-manager/instance/describe/describe.go +++ b/internal/cmd/secrets-manager/instance/describe/describe.go @@ -128,6 +128,17 @@ func outputResult(p *print.Printer, outputFormat string, instance *secretsmanage table.AddSeparator() table.AddRow("CREATION DATE", utils.PtrString(instance.CreationStartDate)) table.AddSeparator() + kmsKey := instance.KmsKey + showKms := kmsKey != nil && (kmsKey.KeyId != nil || kmsKey.KeyRingId != nil || kmsKey.KeyVersion != nil || kmsKey.ServiceAccountEmail != nil) + if showKms { + table.AddRow("KMS KEY ID", utils.PtrString(kmsKey.KeyId)) + table.AddSeparator() + table.AddRow("KMS KEYRING ID", utils.PtrString(kmsKey.KeyRingId)) + table.AddSeparator() + table.AddRow("KMS KEY VERSION", utils.PtrString(kmsKey.KeyVersion)) + table.AddSeparator() + table.AddRow("KMS SERVICE ACCOUNT EMAIL", utils.PtrString(kmsKey.ServiceAccountEmail)) + } // Only show ACL if it's present and not empty if aclList.Acls != nil && len(*aclList.Acls) > 0 { var cidrs []string @@ -136,6 +147,9 @@ func outputResult(p *print.Printer, outputFormat string, instance *secretsmanage cidrs = append(cidrs, *acl.Cidr) } + if showKms { + table.AddSeparator() + } table.AddRow("ACL", strings.Join(cidrs, ",")) } err := table.Display(p) diff --git a/internal/cmd/secrets-manager/instance/describe/describe_test.go b/internal/cmd/secrets-manager/instance/describe/describe_test.go index c1e3e0bb7..fb36f10bb 100644 --- a/internal/cmd/secrets-manager/instance/describe/describe_test.go +++ b/internal/cmd/secrets-manager/instance/describe/describe_test.go @@ -9,6 +9,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -247,6 +248,21 @@ func TestOutputResult(t *testing.T) { }, wantErr: false, }, + { + name: "instance with kms key", + args: args{ + instance: &secretsmanager.Instance{ + KmsKey: &secretsmanager.KmsKeyPayload{ + KeyId: utils.Ptr("key-id"), + KeyRingId: utils.Ptr("keyring-id"), + KeyVersion: utils.Ptr(int64(1)), + ServiceAccountEmail: utils.Ptr("my-service-account-1234567@sa.stackit.cloud"), + }, + }, + aclList: &secretsmanager.ListACLsResponse{}, + }, + wantErr: false, + }, } p := print.NewPrinter() p.Cmd = NewCmd(&types.CmdParams{Printer: p}) diff --git a/internal/cmd/secrets-manager/instance/list/list.go b/internal/cmd/secrets-manager/instance/list/list.go index e32f99ed0..a8f7b9391 100644 --- a/internal/cmd/secrets-manager/instance/list/list.go +++ b/internal/cmd/secrets-manager/instance/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" ) const ( diff --git a/internal/cmd/secrets-manager/instance/update/update.go b/internal/cmd/secrets-manager/instance/update/update.go index 58786ffd6..0a066ec01 100644 --- a/internal/cmd/secrets-manager/instance/update/update.go +++ b/internal/cmd/secrets-manager/instance/update/update.go @@ -24,14 +24,26 @@ import ( const ( instanceIdArg = "INSTANCE_ID" - aclFlag = "acl" + instanceNameFlag = "name" + aclFlag = "acl" + + kmsKeyIdFlag = "kms-key-id" + kmsKeyringIdFlag = "kms-keyring-id" + kmsKeyVersionFlag = "kms-key-version" + kmsServiceAccountEmailFlag = "kms-service-account-email" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - Acls *[]string + InstanceName *string + Acls *[]string + + KmsKeyId *string + KmsKeyringId *string + KmsKeyVersion *int64 + KmsServiceAccountEmail *string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -41,9 +53,18 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Long: "Updates a Secrets Manager instance.", Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( + examples.NewExample( + `Update the name of a Secrets Manager instance with ID "xxx"`, + "$ stackit secrets-manager instance update xxx --name my-new-name"), examples.NewExample( `Update the range of IPs allowed to access a Secrets Manager instance with ID "xxx"`, "$ stackit secrets-manager instance update xxx --acl 1.2.3.0/24"), + examples.NewExample( + `Update the name and ACLs of a Secrets Manager instance with ID "xxx"`, + "$ stackit secrets-manager instance update xxx --name my-new-name --acl 1.2.3.0/24"), + examples.NewExample( + `Update the KMS key settings of a Secrets Manager instance with ID "xxx"`, + "$ stackit secrets-manager instance update xxx --name my-instance --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -58,26 +79,42 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - instanceLabel, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) + existingInstanceName, err := secretsManagerUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) if err != nil { params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err) - instanceLabel = model.InstanceId + existingInstanceName = model.InstanceId } - prompt := fmt.Sprintf("Are you sure you want to update instance %q?", instanceLabel) + prompt := fmt.Sprintf("Are you sure you want to update instance %q?", existingInstanceName) err = params.Printer.PromptForConfirmation(prompt) if err != nil { return err } - // Call API - req := buildRequest(ctx, model, apiClient) - err = req.Execute() - if err != nil { - return fmt.Errorf("update Secrets Manager instance: %w", err) + // Call API - execute UpdateInstance and/or UpdateACLs based on flags + if model.InstanceName != nil { + req := buildUpdateInstanceRequest(ctx, model, apiClient) + err = req.Execute() + if err != nil { + return fmt.Errorf("update Secrets Manager instance: %w", err) + } } - params.Printer.Info("Updated instance %q\n", instanceLabel) + if model.Acls != nil { + req := buildUpdateACLsRequest(ctx, model, apiClient) + err = req.Execute() + if err != nil { + if model.InstanceName != nil { + return fmt.Errorf(`the Secrets Manager instance was successfully updated, but the configuration of the ACLs failed. + +If you want to retry configuring the ACLs, you can do it via: + $ stackit secrets-manager instance update %s --acl %s`, model.InstanceId, *model.Acls) + } + return fmt.Errorf("update Secrets Manager instance ACLs: %w", err) + } + } + + params.Printer.Info("Updated instance %q\n", existingInstanceName) return nil }, } @@ -86,7 +123,16 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } func configureFlags(cmd *cobra.Command) { + cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name") cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "List of IP networks in CIDR notation which are allowed to access this instance") + + cmd.Flags().String(kmsKeyIdFlag, "", "ID of the KMS key to use for encryption") + cmd.Flags().String(kmsKeyringIdFlag, "", "ID of the KMS key ring") + cmd.Flags().Int64(kmsKeyVersionFlag, 0, "Version of the KMS key") + cmd.Flags().String(kmsServiceAccountEmailFlag, "", "Service account email for KMS access") + + cmd.MarkFlagsRequiredTogether(kmsKeyIdFlag, kmsKeyringIdFlag, kmsKeyVersionFlag, kmsServiceAccountEmailFlag) + cmd.MarkFlagsOneRequired(aclFlag, instanceNameFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -97,23 +143,47 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return nil, &cliErr.ProjectIdError{} } - acls := flags.FlagToStringSlicePointer(p, cmd, aclFlag) - - if acls == nil { - return nil, &cliErr.EmptyUpdateError{} + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: instanceId, + InstanceName: flags.FlagToStringPointer(p, cmd, instanceNameFlag), + Acls: flags.FlagToStringSlicePointer(p, cmd, aclFlag), + KmsKeyId: flags.FlagToStringPointer(p, cmd, kmsKeyIdFlag), + KmsKeyringId: flags.FlagToStringPointer(p, cmd, kmsKeyringIdFlag), + KmsKeyVersion: flags.FlagToInt64Pointer(p, cmd, kmsKeyVersionFlag), + KmsServiceAccountEmail: flags.FlagToStringPointer(p, cmd, kmsServiceAccountEmailFlag), } - model := inputModel{ - GlobalFlagModel: globalFlags, - InstanceId: instanceId, - Acls: acls, + if model.KmsKeyId != nil && model.InstanceName == nil { + return nil, fmt.Errorf("--name is required when using KMS flags") } p.DebugInputModel(model) return &model, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateACLsRequest { +func buildUpdateInstanceRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateInstanceRequest { + req := apiClient.UpdateInstance(ctx, model.ProjectId, model.InstanceId) + + payload := secretsmanager.UpdateInstancePayload{ + Name: model.InstanceName, + } + + if model.KmsKeyId != nil { + payload.KmsKey = &secretsmanager.KmsKeyPayload{ + KeyId: model.KmsKeyId, + KeyRingId: model.KmsKeyringId, + KeyVersion: model.KmsKeyVersion, + ServiceAccountEmail: model.KmsServiceAccountEmail, + } + } + + req = req.UpdateInstancePayload(payload) + + return req +} + +func buildUpdateACLsRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateACLsRequest { req := apiClient.UpdateACLs(ctx, model.ProjectId, model.InstanceId) cidrs := []secretsmanager.UpdateACLPayload{} diff --git a/internal/cmd/secrets-manager/instance/update/update_test.go b/internal/cmd/secrets-manager/instance/update/update_test.go index 24e14d1fb..02a2641b3 100644 --- a/internal/cmd/secrets-manager/instance/update/update_test.go +++ b/internal/cmd/secrets-manager/instance/update/update_test.go @@ -4,10 +4,8 @@ import ( "context" "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" @@ -33,6 +31,14 @@ var ( testInstanceId = uuid.NewString() ) +const ( + testInstanceName = "test-instance" + testKmsKeyId = "key-id" + testKmsKeyringId = "keyring-id" + testKmsKeyVersion = int64(1) + testKmsServiceAccountEmail = "my-service-account-1234567@sa.stackit.cloud" +) + func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ testInstanceId, @@ -82,6 +88,24 @@ func fixtureRequest(mods ...func(request *secretsmanager.ApiUpdateACLsRequest)) return request } +func fixtureUpdateInstanceRequest(mods ...func(request *secretsmanager.ApiUpdateInstanceRequest)) secretsmanager.ApiUpdateInstanceRequest { + request := testClient.UpdateInstance(testCtx, testProjectId, testInstanceId) + request = request.UpdateInstancePayload(secretsmanager.UpdateInstancePayload{ + Name: utils.Ptr(testInstanceName), + KmsKey: &secretsmanager.KmsKeyPayload{ + KeyId: utils.Ptr(testKmsKeyId), + KeyRingId: utils.Ptr(testKmsKeyringId), + KeyVersion: utils.Ptr(testKmsKeyVersion), + ServiceAccountEmail: utils.Ptr(testKmsServiceAccountEmail), + }, + }) + + for _, mod := range mods { + mod(&request) + } + return request +} + func TestParseInput(t *testing.T) { tests := []struct { description string @@ -111,13 +135,7 @@ func TestParseInput(t *testing.T) { isValid: false, }, { - description: "no flag values", - argValues: fixtureArgValues(), - flagValues: map[string]string{}, - isValid: false, - }, - { - description: "required flags only (no values to update)", + description: "no update flags", argValues: fixtureArgValues(), flagValues: map[string]string{ projectIdFlag: testProjectId, @@ -172,6 +190,27 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(), isValid: false, }, + { + description: "kms key id without other required kms flags", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + kmsKeyIdFlag: "key-id", + }, + isValid: false, + }, + { + description: "kms flags without name flag", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + kmsKeyIdFlag: "key-id", + kmsKeyringIdFlag: "keyring-id", + kmsKeyVersionFlag: "1", + kmsServiceAccountEmailFlag: "my-service-account-1234567@sa.stackit.cloud", + }, + isValid: false, + }, { description: "repeated acl flags", argValues: fixtureArgValues(), @@ -195,72 +234,85 @@ func TestParseInput(t *testing.T) { ) }), }, + { + description: "name flag only", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + instanceNameFlag: "updated-name", + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.InstanceName = utils.Ptr("updated-name") + }), + }, + { + description: "name and acl flags", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + instanceNameFlag: testInstanceName, + aclFlag: testACL1, + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.InstanceName = utils.Ptr(testInstanceName) + }), + }, + { + description: "kms flags with name", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + instanceNameFlag: testInstanceName, + kmsKeyIdFlag: testKmsKeyId, + kmsKeyringIdFlag: testKmsKeyringId, + kmsKeyVersionFlag: "1", + kmsServiceAccountEmailFlag: testKmsServiceAccountEmail, + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.InstanceName = utils.Ptr(testInstanceName) + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + }, + { + description: "name, acl and kms flags together", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + instanceNameFlag: testInstanceName, + aclFlag: testACL1, + kmsKeyIdFlag: testKmsKeyId, + kmsKeyringIdFlag: testKmsKeyringId, + kmsKeyVersionFlag: "1", + kmsServiceAccountEmailFlag: testKmsServiceAccountEmail, + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.InstanceName = utils.Ptr(testInstanceName) + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - p := print.NewPrinter() - cmd := NewCmd(&types.CmdParams{Printer: p}) - err := globalflags.Configure(cmd.Flags()) - if err != nil { - t.Fatalf("configure global flags: %v", err) - } - - for flag, value := range tt.flagValues { - err := cmd.Flags().Set(flag, value) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("setting flag --%s=%s: %v", flag, value, err) - } - } - - for _, value := range tt.aclValues { - err := cmd.Flags().Set(aclFlag, value) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("setting flag --%s=%s: %v", aclFlag, value, err) - } - } - - err = cmd.ValidateArgs(tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating args: %v", err) - } - - err = cmd.ValidateRequiredFlags() - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating flags: %v", err) - } - - model, err := parseInput(p, cmd, tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error parsing flags: %v", err) - } - - if !tt.isValid { - t.Fatalf("did not fail on invalid input") - } - diff := cmp.Diff(model, tt.expectedModel) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) - } + testutils.TestParseInputWithAdditionalFlags(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, map[string][]string{ + aclFlag: tt.aclValues, + }, tt.isValid) }) } } -func TestBuildRequest(t *testing.T) { +func TestBuildUpdateACLsRequest(t *testing.T) { tests := []struct { description string model *inputModel @@ -286,7 +338,53 @@ func TestBuildRequest(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - request := buildRequest(testCtx, tt.model, testClient) + request := buildUpdateACLsRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildUpdateInstanceRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest secretsmanager.ApiUpdateInstanceRequest + }{ + { + description: "with name only", + model: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.InstanceName = utils.Ptr(testInstanceName) + }), + expectedRequest: testClient.UpdateInstance(testCtx, testProjectId, testInstanceId). + UpdateInstancePayload(secretsmanager.UpdateInstancePayload{ + Name: utils.Ptr(testInstanceName), + }), + }, + { + description: "with KMS settings", + model: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.InstanceName = utils.Ptr(testInstanceName) + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + expectedRequest: fixtureUpdateInstanceRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildUpdateInstanceRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), diff --git a/internal/cmd/secrets-manager/user/list/list.go b/internal/cmd/secrets-manager/user/list/list.go index 9b601d47a..163c523c9 100644 --- a/internal/cmd/secrets-manager/user/list/list.go +++ b/internal/cmd/secrets-manager/user/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( secretsManagerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" ) const ( diff --git a/internal/cmd/secrets-manager/user/user.go b/internal/cmd/secrets-manager/user/user.go index 738426858..6f14e07dd 100644 --- a/internal/cmd/secrets-manager/user/user.go +++ b/internal/cmd/secrets-manager/user/user.go @@ -6,6 +6,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/create" "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/secrets-manager/user/describe" diff --git a/internal/cmd/security-group/create/create.go b/internal/cmd/security-group/create/create.go index 5c52d133a..dfbbc258c 100644 --- a/internal/cmd/security-group/create/create.go +++ b/internal/cmd/security-group/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/create/create_test.go b/internal/cmd/security-group/create/create_test.go index b025e7419..0a082ee15 100644 --- a/internal/cmd/security-group/create/create_test.go +++ b/internal/cmd/security-group/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/delete/delete.go b/internal/cmd/security-group/delete/delete.go index 1950d712b..da840337c 100644 --- a/internal/cmd/security-group/delete/delete.go +++ b/internal/cmd/security-group/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/security-group/describe/describe_test.go b/internal/cmd/security-group/describe/describe_test.go index 9342e946e..84c928b0e 100644 --- a/internal/cmd/security-group/describe/describe_test.go +++ b/internal/cmd/security-group/describe/describe_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/list/list.go b/internal/cmd/security-group/list/list.go index a368152a6..d3788ee9c 100644 --- a/internal/cmd/security-group/list/list.go +++ b/internal/cmd/security-group/list/list.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/security-group/list/list_test.go b/internal/cmd/security-group/list/list_test.go index 22c588604..18cfa967e 100644 --- a/internal/cmd/security-group/list/list_test.go +++ b/internal/cmd/security-group/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/rule/create/create.go b/internal/cmd/security-group/rule/create/create.go index b2f443ace..9b836dd81 100644 --- a/internal/cmd/security-group/rule/create/create.go +++ b/internal/cmd/security-group/rule/create/create.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/security-group/rule/create/create_test.go b/internal/cmd/security-group/rule/create/create_test.go index 5099c7ca3..02a29fe95 100644 --- a/internal/cmd/security-group/rule/create/create_test.go +++ b/internal/cmd/security-group/rule/create/create_test.go @@ -11,10 +11,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/rule/delete/delete.go b/internal/cmd/security-group/rule/delete/delete.go index 388e3b0b7..837a94b9c 100644 --- a/internal/cmd/security-group/rule/delete/delete.go +++ b/internal/cmd/security-group/rule/delete/delete.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/rule/delete/delete_test.go b/internal/cmd/security-group/rule/delete/delete_test.go index 0d6a9b4cb..73ae4e466 100644 --- a/internal/cmd/security-group/rule/delete/delete_test.go +++ b/internal/cmd/security-group/rule/delete/delete_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/rule/describe/describe.go b/internal/cmd/security-group/rule/describe/describe.go index 82486d989..c4e421fa6 100644 --- a/internal/cmd/security-group/rule/describe/describe.go +++ b/internal/cmd/security-group/rule/describe/describe.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/security-group/rule/describe/describe_test.go b/internal/cmd/security-group/rule/describe/describe_test.go index f1af54485..924b0dced 100644 --- a/internal/cmd/security-group/rule/describe/describe_test.go +++ b/internal/cmd/security-group/rule/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/rule/list/list_test.go b/internal/cmd/security-group/rule/list/list_test.go index 166b58e58..b617a1b80 100644 --- a/internal/cmd/security-group/rule/list/list_test.go +++ b/internal/cmd/security-group/rule/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/security-group/update/update.go b/internal/cmd/security-group/update/update.go index 6f612f744..d73590631 100644 --- a/internal/cmd/security-group/update/update.go +++ b/internal/cmd/security-group/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { diff --git a/internal/cmd/server/backup/list/list.go b/internal/cmd/server/backup/list/list.go index 7f60f6a70..86e2ac155 100644 --- a/internal/cmd/server/backup/list/list.go +++ b/internal/cmd/server/backup/list/list.go @@ -9,6 +9,8 @@ import ( iaasClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/serverbackup/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" ) const ( diff --git a/internal/cmd/server/backup/schedule/describe/describe_test.go b/internal/cmd/server/backup/schedule/describe/describe_test.go index e5808e545..7ce57f910 100644 --- a/internal/cmd/server/backup/schedule/describe/describe_test.go +++ b/internal/cmd/server/backup/schedule/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" ) type testCtxKey struct{} diff --git a/internal/cmd/server/backup/schedule/list/list.go b/internal/cmd/server/backup/schedule/list/list.go index 58f8f7ac9..7c573fcc9 100644 --- a/internal/cmd/server/backup/schedule/list/list.go +++ b/internal/cmd/server/backup/schedule/list/list.go @@ -9,6 +9,8 @@ import ( iaasClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/serverbackup/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" ) const ( diff --git a/internal/cmd/server/command/create/create.go b/internal/cmd/server/command/create/create.go index 1561ab3b5..9259e3046 100644 --- a/internal/cmd/server/command/create/create.go +++ b/internal/cmd/server/command/create/create.go @@ -9,6 +9,8 @@ import ( iaasClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/runcommand" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" runcommandUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/runcommand" ) const ( @@ -119,14 +120,15 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, CommandTemplateName: flags.FlagToStringValue(p, cmd, commandTemplateNameFlag), Params: flags.FlagToStringToStringPointer(p, cmd, paramsFlag), } - parsedParams, err := runcommandUtils.ParseScriptParams(*model.Params) + + var err error + model.Params, err = runcommandUtils.ParseScriptParams(model.Params) if err != nil { return nil, &cliErr.FlagValidationError{ Flag: paramsFlag, Details: err.Error(), } } - model.Params = &parsedParams p.DebugInputModel(model) return &model, nil diff --git a/internal/cmd/server/command/create/create_test.go b/internal/cmd/server/command/create/create_test.go index 5b19bf508..8029d65e5 100644 --- a/internal/cmd/server/command/create/create_test.go +++ b/internal/cmd/server/command/create/create_test.go @@ -105,6 +105,16 @@ func TestParseInput(t *testing.T) { flagValues: map[string]string{}, isValid: false, }, + { + description: "params flag missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, paramsFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Params = nil + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { diff --git a/internal/cmd/server/command/describe/describe.go b/internal/cmd/server/command/describe/describe.go index 8ca9b6aa3..520ead530 100644 --- a/internal/cmd/server/command/describe/describe.go +++ b/internal/cmd/server/command/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/runcommand" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/runcommand" ) const ( diff --git a/internal/cmd/server/command/list/list.go b/internal/cmd/server/command/list/list.go index e5607abd0..21a96bae1 100644 --- a/internal/cmd/server/command/list/list.go +++ b/internal/cmd/server/command/list/list.go @@ -9,6 +9,8 @@ import ( iaasClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/runcommand" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/runcommand" ) const ( diff --git a/internal/cmd/server/command/template/describe/describe.go b/internal/cmd/server/command/template/describe/describe.go index 99c6f4941..486e5c6ac 100644 --- a/internal/cmd/server/command/template/describe/describe.go +++ b/internal/cmd/server/command/template/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/runcommand" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/runcommand" ) const ( diff --git a/internal/cmd/server/command/template/list/list.go b/internal/cmd/server/command/template/list/list.go index 723772b45..90ce846ca 100644 --- a/internal/cmd/server/command/template/list/list.go +++ b/internal/cmd/server/command/template/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/runcommand" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/runcommand" ) const ( diff --git a/internal/cmd/server/console/console.go b/internal/cmd/server/console/console.go index 23e7f5c10..48e061615 100644 --- a/internal/cmd/server/console/console.go +++ b/internal/cmd/server/console/console.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/server/create/create.go b/internal/cmd/server/create/create.go index a93efcbf7..1efaac4f4 100644 --- a/internal/cmd/server/create/create.go +++ b/internal/cmd/server/create/create.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -144,16 +145,16 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating server") - _, err = wait.CreateServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, serverId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating server", func() error { + _, err = wait.CreateServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, serverId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server creation: %w", err) } - s.Stop() } - return outputResult(params.Printer, model.OutputFormat, projectLabel, resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) }, } configureFlags(cmd) @@ -322,12 +323,16 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli return req.CreateServerPayload(payload) } -func outputResult(p *print.Printer, outputFormat, projectLabel string, server *iaas.Server) error { +func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, server *iaas.Server) error { if server == nil { return fmt.Errorf("server response is empty") } return p.OutputResult(outputFormat, server, func() error { - p.Outputf("Created server for project %q.\nServer ID: %s\n", projectLabel, utils.PtrString(server.Id)) + operationState := "Created" + if async { + operationState = "Triggered creation of" + } + p.Outputf("%s server for project %q.\nServer ID: %s\n", operationState, projectLabel, utils.PtrString(server.Id)) return nil }) } diff --git a/internal/cmd/server/create/create_test.go b/internal/cmd/server/create/create_test.go index 521b80922..7ab029013 100644 --- a/internal/cmd/server/create/create_test.go +++ b/internal/cmd/server/create/create_test.go @@ -382,6 +382,7 @@ func TestBuildRequest(t *testing.T) { func TestOutputResult(t *testing.T) { type args struct { outputFormat string + async bool projectLabel string server *iaas.Server } @@ -407,7 +408,7 @@ func TestOutputResult(t *testing.T) { p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.outputFormat, tt.args.projectLabel, tt.args.server); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.server); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/server/deallocate/deallocate.go b/internal/cmd/server/deallocate/deallocate.go index bed42bf7a..41886042d 100644 --- a/internal/cmd/server/deallocate/deallocate.go +++ b/internal/cmd/server/deallocate/deallocate.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -78,13 +79,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deallocating server") - _, err = wait.DeallocateServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deallocating server", func() error { + _, err = wait.DeallocateServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server deallocating: %w", err) } - s.Stop() } operationState := "Deallocated" diff --git a/internal/cmd/server/delete/delete.go b/internal/cmd/server/delete/delete.go index 412c099dc..cbedd5a29 100644 --- a/internal/cmd/server/delete/delete.go +++ b/internal/cmd/server/delete/delete.go @@ -4,8 +4,13 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,10 +20,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" - - "github.com/spf13/cobra" ) const ( @@ -81,13 +82,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting server") - _, err = wait.DeleteServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting server", func() error { + _, err = wait.DeleteServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/server/log/log.go b/internal/cmd/server/log/log.go index a211a44c1..086e228b4 100644 --- a/internal/cmd/server/log/log.go +++ b/internal/cmd/server/log/log.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/server/machine-type/describe/describe.go b/internal/cmd/server/machine-type/describe/describe.go index d496d5a83..4bd07771e 100644 --- a/internal/cmd/server/machine-type/describe/describe.go +++ b/internal/cmd/server/machine-type/describe/describe.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,7 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/server/machine-type/list/list.go b/internal/cmd/server/machine-type/list/list.go index 9dc3aad50..c134edd55 100644 --- a/internal/cmd/server/machine-type/list/list.go +++ b/internal/cmd/server/machine-type/list/list.go @@ -23,11 +23,13 @@ import ( type inputModel struct { *globalflags.GlobalFlagModel - Limit *int64 + Limit *int64 + Filter *string } const ( - limitFlag = "limit" + limitFlag = "limit" + filterFlag = "filter" ) func NewCmd(params *types.CmdParams) *cobra.Command { @@ -49,6 +51,14 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `List the first 10 machine types`, `$ stackit server machine-type list --limit=10`, ), + examples.NewExample( + `List machine types with exactly 2 vCPUs`, + `$ stackit server machine-type list --filter="vcpus==2"`, + ), + examples.NewExample( + `List machine types with at least 2 vCPUs and 2048 MB RAM`, + `$ stackit server machine-type list --filter="vcpus >= 2 && ram >= 2048"`, + ), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -95,6 +105,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(limitFlag, 0, "Limit the output to the first n elements") + cmd.Flags().String(filterFlag, "", "Filter resources by fields. A subset of expr-lang is supported. See https://expr-lang.org/docs/language-definition for usage details") } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { @@ -113,7 +124,8 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, model := inputModel{ GlobalFlagModel: globalFlags, - Limit: flags.FlagToInt64Pointer(p, cmd, limitFlag), + Limit: limit, + Filter: flags.FlagToStringPointer(p, cmd, filterFlag), } p.DebugInputModel(model) @@ -121,18 +133,39 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListMachineTypesRequest { - return apiClient.ListMachineTypes(ctx, model.ProjectId, model.Region) + req := apiClient.ListMachineTypes(ctx, model.ProjectId, model.Region) + if model.Filter != nil { + req = req.Filter(*model.Filter) + } + return req } func outputResult(p *print.Printer, outputFormat string, machineTypes iaas.MachineTypeListResponse) error { return p.OutputResult(outputFormat, machineTypes, func() error { table := tables.NewTable() table.SetTitle("Machine-Types") - - table.SetHeader("NAME", "DESCRIPTION") + table.SetHeader("NAME", "VCPUS", "RAM (GB)", "DESCRIPTION", "EXTRA SPECS") if items := machineTypes.GetItems(); len(items) > 0 { for _, machineType := range items { - table.AddRow(*machineType.Name, utils.PtrString(machineType.Description)) + extraSpecMap := make(map[string]string) + if machineType.ExtraSpecs != nil && len(*machineType.ExtraSpecs) > 0 { + for key, value := range *machineType.ExtraSpecs { + extraSpecMap[key] = fmt.Sprintf("%v", value) + } + } + ramGB := int64(0) + if machineType.Ram != nil { + ramGB = *machineType.Ram / 1024 + } + + table.AddRow( + utils.PtrString(machineType.Name), + utils.PtrValue(machineType.Vcpus), + ramGB, + utils.PtrString(machineType.Description), + utils.JoinStringMap(extraSpecMap, ": ", "\n"), + ) + table.AddSeparator() } } diff --git a/internal/cmd/server/machine-type/list/list_test.go b/internal/cmd/server/machine-type/list/list_test.go index 8b07ca64c..19afe28b8 100644 --- a/internal/cmd/server/machine-type/list/list_test.go +++ b/internal/cmd/server/machine-type/list/list_test.go @@ -77,6 +77,16 @@ func TestParseInput(t *testing.T) { isValid: true, expectedModel: fixtureInputModel(), }, + { + description: "with filter", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[filterFlag] = "vcpus >= 2 && ram >= 2048" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Filter = utils.Ptr("vcpus >= 2 && ram >= 2048") + }), + }, { description: "no values", flagValues: map[string]string{}, @@ -142,6 +152,15 @@ func TestBuildRequest(t *testing.T) { model: fixtureInputModel(), expectedRequest: fixtureRequest(), }, + { + description: "with filter", + model: fixtureInputModel(func(model *inputModel) { + model.Filter = utils.Ptr("vcpus==2") + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiListMachineTypesRequest) { + *request = (*request).Filter("vcpus==2") + }), + }, } for _, tt := range tests { @@ -174,6 +193,33 @@ func TestOutputResult(t *testing.T) { args: args{}, wantErr: false, }, + { + name: "with populated data", + args: args{ + outputFormat: "table", + machineTypes: iaas.MachineTypeListResponse{ + Items: &[]iaas.MachineType{ + { + Name: utils.Ptr("c1.2"), + Vcpus: utils.Ptr(int64(2)), + Ram: utils.Ptr(int64(2048)), // Should display as 2 GB + Description: utils.Ptr("Compute optimized 2 vCPU"), + ExtraSpecs: &map[string]interface{}{ + "cpu": "intel-icelake-generic", + }, + }, + { + Name: utils.Ptr("m1.2"), + Vcpus: utils.Ptr(int64(2)), + Ram: utils.Ptr(int64(8192)), // Should display as 8 GB + Description: utils.Ptr("Memory optimized 2 vCPU"), + // No ExtraSpecs provided to test nil safety + }, + }, + }, + }, + wantErr: false, + }, } p := print.NewPrinter() p.Cmd = NewCmd(&types.CmdParams{Printer: p}) diff --git a/internal/cmd/server/network-interface/attach/attach.go b/internal/cmd/server/network-interface/attach/attach.go index 5bcf7ed6d..9d1da9752 100644 --- a/internal/cmd/server/network-interface/attach/attach.go +++ b/internal/cmd/server/network-interface/attach/attach.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/network-interface/detach/detach.go b/internal/cmd/server/network-interface/detach/detach.go index ed891e912..41e9cf194 100644 --- a/internal/cmd/server/network-interface/detach/detach.go +++ b/internal/cmd/server/network-interface/detach/detach.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/network-interface/list/list.go b/internal/cmd/server/network-interface/list/list.go index 607798a89..c154f89b3 100644 --- a/internal/cmd/server/network-interface/list/list.go +++ b/internal/cmd/server/network-interface/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/os-update/describe/describe.go b/internal/cmd/server/os-update/describe/describe.go index 026cec137..8d349791b 100644 --- a/internal/cmd/server/os-update/describe/describe.go +++ b/internal/cmd/server/os-update/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/serverosupdate/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" ) const ( diff --git a/internal/cmd/server/os-update/schedule/describe/describe.go b/internal/cmd/server/os-update/schedule/describe/describe.go index 4e68b04bd..0e810343d 100644 --- a/internal/cmd/server/os-update/schedule/describe/describe.go +++ b/internal/cmd/server/os-update/schedule/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/serverosupdate/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" ) const ( diff --git a/internal/cmd/server/os-update/schedule/list/list.go b/internal/cmd/server/os-update/schedule/list/list.go index 0e300e547..c2d6153f6 100644 --- a/internal/cmd/server/os-update/schedule/list/list.go +++ b/internal/cmd/server/os-update/schedule/list/list.go @@ -9,6 +9,8 @@ import ( iaasClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +21,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/serverosupdate/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" ) const ( diff --git a/internal/cmd/server/os-update/schedule/update/update.go b/internal/cmd/server/os-update/schedule/update/update.go index 72c3f92f4..f14429e2c 100644 --- a/internal/cmd/server/os-update/schedule/update/update.go +++ b/internal/cmd/server/os-update/schedule/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/serverosupdate/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serverupdate" ) const ( diff --git a/internal/cmd/server/public-ip/attach/attach.go b/internal/cmd/server/public-ip/attach/attach.go index 7532eac13..af300bb19 100644 --- a/internal/cmd/server/public-ip/attach/attach.go +++ b/internal/cmd/server/public-ip/attach/attach.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/public-ip/attach/attach_test.go b/internal/cmd/server/public-ip/attach/attach_test.go index 4aef71f1f..11e894f33 100644 --- a/internal/cmd/server/public-ip/attach/attach_test.go +++ b/internal/cmd/server/public-ip/attach/attach_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/public-ip/detach/detach.go b/internal/cmd/server/public-ip/detach/detach.go index 8c85195e9..4e53bdf0c 100644 --- a/internal/cmd/server/public-ip/detach/detach.go +++ b/internal/cmd/server/public-ip/detach/detach.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/reboot/reboot.go b/internal/cmd/server/reboot/reboot.go index bf689cfcc..fcc57bc08 100644 --- a/internal/cmd/server/reboot/reboot.go +++ b/internal/cmd/server/reboot/reboot.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/server/rescue/rescue.go b/internal/cmd/server/rescue/rescue.go index dd77e4625..9dbd1aa73 100644 --- a/internal/cmd/server/rescue/rescue.go +++ b/internal/cmd/server/rescue/rescue.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -82,13 +83,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Rescuing server") - _, err = wait.RescueServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Rescuing server", func() error { + _, err = wait.RescueServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server rescuing: %w", err) } - s.Stop() } operationState := "Rescued" diff --git a/internal/cmd/server/resize/resize.go b/internal/cmd/server/resize/resize.go index 804dbce1a..582341781 100644 --- a/internal/cmd/server/resize/resize.go +++ b/internal/cmd/server/resize/resize.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -82,13 +83,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Resizing server") - _, err = wait.ResizeServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Resizing server", func() error { + _, err = wait.ResizeServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server resizing: %w", err) } - s.Stop() } operationState := "Resized" diff --git a/internal/cmd/server/security-group/attach/attach.go b/internal/cmd/server/security-group/attach/attach.go new file mode 100644 index 000000000..f6324237c --- /dev/null +++ b/internal/cmd/server/security-group/attach/attach.go @@ -0,0 +1,120 @@ +package attach + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" +) + +const ( + serverIdFlag = "server-id" + securityGroupIdFlag = "security-group-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + ServerId string + SecurityGroupId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "attach", + Short: "Attaches a security group to a server", + Long: "Attaches a security group to a server.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Attach a security group with ID "xxx" to a server with ID "yyy"`, + `$ stackit server security-group attach --server-id yyy --security-group-id xxx`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + serverLabel, err := iaasUtils.GetServerName(ctx, apiClient, model.ProjectId, model.Region, model.ServerId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get server name: %v", err) + serverLabel = model.ServerId + } else if serverLabel == "" { + serverLabel = model.ServerId + } + + securityGroupLabel, err := iaasUtils.GetSecurityGroupName(ctx, apiClient, model.ProjectId, model.Region, model.SecurityGroupId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get security group name: %v", err) + securityGroupLabel = model.SecurityGroupId + } + + prompt := fmt.Sprintf("Are you sure you want to attach security group %q to server %q?", securityGroupLabel, serverLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + if err := req.Execute(); err != nil { + return fmt.Errorf("attach security group to server: %w", err) + } + + params.Printer.Outputf("Attached security group %q to server %q\n", securityGroupLabel, serverLabel) + + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), serverIdFlag, "Server ID") + cmd.Flags().Var(flags.UUIDFlag(), securityGroupIdFlag, "Security Group ID") + + err := flags.MarkFlagsRequired(cmd, serverIdFlag, securityGroupIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), + SecurityGroupId: flags.FlagToStringValue(p, cmd, securityGroupIdFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiAddSecurityGroupToServerRequest { + req := apiClient.AddSecurityGroupToServer(ctx, model.ProjectId, model.Region, model.ServerId, model.SecurityGroupId) + return req +} diff --git a/internal/cmd/server/security-group/attach/attach_test.go b/internal/cmd/server/security-group/attach/attach_test.go new file mode 100644 index 000000000..9056e3f8e --- /dev/null +++ b/internal/cmd/server/security-group/attach/attach_test.go @@ -0,0 +1,182 @@ +package attach + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +const ( + testRegion = "eu01" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &iaas.APIClient{} +var testProjectId = uuid.NewString() +var testServerId = uuid.NewString() +var testSecurityGroupId = uuid.NewString() + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + + serverIdFlag: testServerId, + securityGroupIdFlag: testSecurityGroupId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + ProjectId: testProjectId, + Region: testRegion, + }, + ServerId: testServerId, + SecurityGroupId: testSecurityGroupId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiAddSecurityGroupToServerRequest)) iaas.ApiAddSecurityGroupToServerRequest { + request := testClient.AddSecurityGroupToServer(testCtx, testProjectId, testRegion, testServerId, testSecurityGroupId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "security group id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, securityGroupIdFlag) + }), + isValid: false, + }, + { + description: "security group id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[securityGroupIdFlag] = "" + }), + isValid: false, + }, + { + description: "security group id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[securityGroupIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "server id flag missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, serverIdFlag) + }), + isValid: false, + }, + { + description: "server id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[serverIdFlag] = "" + }), + isValid: false, + }, + { + description: "server id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[serverIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, []string{}, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest iaas.ApiAddSecurityGroupToServerRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/server/security-group/detach/detach.go b/internal/cmd/server/security-group/detach/detach.go new file mode 100644 index 000000000..81fe5b30a --- /dev/null +++ b/internal/cmd/server/security-group/detach/detach.go @@ -0,0 +1,120 @@ +package detach + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" +) + +const ( + serverIdFlag = "server-id" + securityGroupIdFlag = "security-group-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + ServerId string + SecurityGroupId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "detach", + Short: "Detaches a security group from a server", + Long: "Detaches a security group from a server.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Detach a security group with ID "xxx" from a server with ID "yyy"`, + `$ stackit server security-group detach --server-id yyy --security-group-id xxx`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + serverLabel, err := iaasUtils.GetServerName(ctx, apiClient, model.ProjectId, model.Region, model.ServerId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get server name: %v", err) + serverLabel = model.ServerId + } else if serverLabel == "" { + serverLabel = model.ServerId + } + + securityGroupLabel, err := iaasUtils.GetSecurityGroupName(ctx, apiClient, model.ProjectId, model.Region, model.SecurityGroupId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get security group name: %v", err) + securityGroupLabel = model.SecurityGroupId + } + + prompt := fmt.Sprintf("Are you sure you want to detach security group %q from server %q?", securityGroupLabel, serverLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + if err := req.Execute(); err != nil { + return fmt.Errorf("detach security group from server: %w", err) + } + + params.Printer.Outputf("Detached security group %q from server %q\n", securityGroupLabel, serverLabel) + + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), serverIdFlag, "Server ID") + cmd.Flags().Var(flags.UUIDFlag(), securityGroupIdFlag, "Security Group ID") + + err := flags.MarkFlagsRequired(cmd, serverIdFlag, securityGroupIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), + SecurityGroupId: flags.FlagToStringValue(p, cmd, securityGroupIdFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiRemoveSecurityGroupFromServerRequest { + req := apiClient.RemoveSecurityGroupFromServer(ctx, model.ProjectId, model.Region, model.ServerId, model.SecurityGroupId) + return req +} diff --git a/internal/cmd/server/security-group/detach/detach_test.go b/internal/cmd/server/security-group/detach/detach_test.go new file mode 100644 index 000000000..dbf4cc8f3 --- /dev/null +++ b/internal/cmd/server/security-group/detach/detach_test.go @@ -0,0 +1,182 @@ +package detach + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +const ( + testRegion = "eu01" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &iaas.APIClient{} +var testProjectId = uuid.NewString() +var testServerId = uuid.NewString() +var testSecurityGroupId = uuid.NewString() + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + + serverIdFlag: testServerId, + securityGroupIdFlag: testSecurityGroupId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + ProjectId: testProjectId, + Region: testRegion, + }, + ServerId: testServerId, + SecurityGroupId: testSecurityGroupId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiRemoveSecurityGroupFromServerRequest)) iaas.ApiRemoveSecurityGroupFromServerRequest { + request := testClient.RemoveSecurityGroupFromServer(testCtx, testProjectId, testRegion, testServerId, testSecurityGroupId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "security group id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, securityGroupIdFlag) + }), + isValid: false, + }, + { + description: "security group id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[securityGroupIdFlag] = "" + }), + isValid: false, + }, + { + description: "security group id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[securityGroupIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "server id flag missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, serverIdFlag) + }), + isValid: false, + }, + { + description: "server id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[serverIdFlag] = "" + }), + isValid: false, + }, + { + description: "server id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[serverIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, []string{}, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest iaas.ApiRemoveSecurityGroupFromServerRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/server/security-group/security-group.go b/internal/cmd/server/security-group/security-group.go new file mode 100644 index 000000000..aed9bfa4a --- /dev/null +++ b/internal/cmd/server/security-group/security-group.go @@ -0,0 +1,28 @@ +package securitygroup + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/server/security-group/attach" + "github.com/stackitcloud/stackit-cli/internal/cmd/server/security-group/detach" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "security-group", + Short: "Allows attaching/detaching security groups to servers", + Long: "Allows attaching/detaching security groups to servers.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(attach.NewCmd(params)) + cmd.AddCommand(detach.NewCmd(params)) +} diff --git a/internal/cmd/server/server.go b/internal/cmd/server/server.go index e671fda2b..3cb8a5ffc 100644 --- a/internal/cmd/server/server.go +++ b/internal/cmd/server/server.go @@ -17,6 +17,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/server/reboot" "github.com/stackitcloud/stackit-cli/internal/cmd/server/rescue" "github.com/stackitcloud/stackit-cli/internal/cmd/server/resize" + securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/server/security-group" serviceaccount "github.com/stackitcloud/stackit-cli/internal/cmd/server/service-account" "github.com/stackitcloud/stackit-cli/internal/cmd/server/start" "github.com/stackitcloud/stackit-cli/internal/cmd/server/stop" @@ -51,6 +52,7 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(describe.NewCmd(params)) cmd.AddCommand(list.NewCmd(params)) cmd.AddCommand(publicip.NewCmd(params)) + cmd.AddCommand(securitygroup.NewCmd(params)) cmd.AddCommand(serviceaccount.NewCmd(params)) cmd.AddCommand(update.NewCmd(params)) cmd.AddCommand(volume.NewCmd(params)) diff --git a/internal/cmd/server/service-account/attach/attach_test.go b/internal/cmd/server/service-account/attach/attach_test.go index b34109960..c4bcdaeaa 100644 --- a/internal/cmd/server/service-account/attach/attach_test.go +++ b/internal/cmd/server/service-account/attach/attach_test.go @@ -6,9 +6,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/server/service-account/detach/detach.go b/internal/cmd/server/service-account/detach/detach.go index 00830e7d6..07b34db82 100644 --- a/internal/cmd/server/service-account/detach/detach.go +++ b/internal/cmd/server/service-account/detach/detach.go @@ -63,7 +63,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { serverLabel = model.ServerId } - prompt := fmt.Sprintf("Are your sure you want to detach service account %q from a server %q?", model.ServiceAccMail, serverLabel) + prompt := fmt.Sprintf("Are you sure you want to detach service account %q from a server %q?", model.ServiceAccMail, serverLabel) err = params.Printer.PromptForConfirmation(prompt) if err != nil { return err diff --git a/internal/cmd/server/service-account/detach/detach_test.go b/internal/cmd/server/service-account/detach/detach_test.go index 0867408f1..f421c504d 100644 --- a/internal/cmd/server/service-account/detach/detach_test.go +++ b/internal/cmd/server/service-account/detach/detach_test.go @@ -6,9 +6,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/server/service-account/service-account.go b/internal/cmd/server/service-account/service-account.go index 6bb4576ba..1f61348a4 100644 --- a/internal/cmd/server/service-account/service-account.go +++ b/internal/cmd/server/service-account/service-account.go @@ -2,6 +2,7 @@ package serviceaccount import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/cmd/server/service-account/attach" diff --git a/internal/cmd/server/start/start.go b/internal/cmd/server/start/start.go index 3dc23ff31..69ea27c96 100644 --- a/internal/cmd/server/start/start.go +++ b/internal/cmd/server/start/start.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -72,13 +73,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Starting server") - _, err = wait.StartServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Starting server", func() error { + _, err = wait.StartServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server starting: %w", err) } - s.Stop() } operationState := "Started" diff --git a/internal/cmd/server/stop/stop.go b/internal/cmd/server/stop/stop.go index a7a9b4604..3b74699c5 100644 --- a/internal/cmd/server/stop/stop.go +++ b/internal/cmd/server/stop/stop.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -78,13 +79,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Stopping server") - _, err = wait.StopServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Stopping server", func() error { + _, err = wait.StopServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server stopping: %w", err) } - s.Stop() } operationState := "Stopped" diff --git a/internal/cmd/server/unrescue/unrescue.go b/internal/cmd/server/unrescue/unrescue.go index f47ef6794..0dbc71319 100644 --- a/internal/cmd/server/unrescue/unrescue.go +++ b/internal/cmd/server/unrescue/unrescue.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -78,13 +79,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Unrescuing server") - _, err = wait.UnrescueServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Unrescuing server", func() error { + _, err = wait.UnrescueServerWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ServerId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for server unrescuing: %w", err) } - s.Stop() } operationState := "Unrescued" diff --git a/internal/cmd/server/update/update.go b/internal/cmd/server/update/update.go index 7349d1c28..570c45019 100644 --- a/internal/cmd/server/update/update.go +++ b/internal/cmd/server/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/attach/attach.go b/internal/cmd/server/volume/attach/attach.go index 27c734f8d..a1b1695a7 100644 --- a/internal/cmd/server/volume/attach/attach.go +++ b/internal/cmd/server/volume/attach/attach.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/describe/describe.go b/internal/cmd/server/volume/describe/describe.go index 385b585bf..bd1328de8 100644 --- a/internal/cmd/server/volume/describe/describe.go +++ b/internal/cmd/server/volume/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/describe/describe_test.go b/internal/cmd/server/volume/describe/describe_test.go index 2e88ae1c4..3a0a631d9 100644 --- a/internal/cmd/server/volume/describe/describe_test.go +++ b/internal/cmd/server/volume/describe/describe_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/detach/detach.go b/internal/cmd/server/volume/detach/detach.go index 0c8081a5d..4e46187ee 100644 --- a/internal/cmd/server/volume/detach/detach.go +++ b/internal/cmd/server/volume/detach/detach.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/detach/detach_test.go b/internal/cmd/server/volume/detach/detach_test.go index a9b5843b1..59d93a547 100644 --- a/internal/cmd/server/volume/detach/detach_test.go +++ b/internal/cmd/server/volume/detach/detach_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/list/list.go b/internal/cmd/server/volume/list/list.go index 10df56261..995303bd7 100644 --- a/internal/cmd/server/volume/list/list.go +++ b/internal/cmd/server/volume/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/list/list_test.go b/internal/cmd/server/volume/list/list_test.go index ea65dc2cb..c8c7339ab 100644 --- a/internal/cmd/server/volume/list/list_test.go +++ b/internal/cmd/server/volume/list/list_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/server/volume/update/update.go b/internal/cmd/server/volume/update/update.go index 4ba1a9342..389ad26a6 100644 --- a/internal/cmd/server/volume/update/update.go +++ b/internal/cmd/server/volume/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/service-account/key/list/list.go b/internal/cmd/service-account/key/list/list.go index e450ef021..2cb1d3288 100644 --- a/internal/cmd/service-account/key/list/list.go +++ b/internal/cmd/service-account/key/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serviceaccount" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-account/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serviceaccount" ) const ( diff --git a/internal/cmd/service-account/list/list.go b/internal/cmd/service-account/list/list.go index f444e83b3..692ea90c8 100644 --- a/internal/cmd/service-account/list/list.go +++ b/internal/cmd/service-account/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serviceaccount" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-account/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serviceaccount" ) const ( diff --git a/internal/cmd/service-account/token/create/create.go b/internal/cmd/service-account/token/create/create.go index a3286219b..c5e8f9a18 100644 --- a/internal/cmd/service-account/token/create/create.go +++ b/internal/cmd/service-account/token/create/create.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serviceaccount" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-account/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serviceaccount" ) const ( diff --git a/internal/cmd/ske/cluster/create/create.go b/internal/cmd/ske/cluster/create/create.go index 1602e3680..eafcdc349 100644 --- a/internal/cmd/ske/cluster/create/create.go +++ b/internal/cmd/ske/cluster/create/create.go @@ -8,6 +8,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -21,8 +24,6 @@ import ( skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" ) const ( @@ -106,7 +107,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } // Check if cluster exists - exists, err := skeUtils.ClusterExists(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName) + exists, err := skeUtils.ClusterExists(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName) if err != nil { return err } @@ -116,7 +117,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Fill in default payload, if needed if model.Payload == nil { - defaultPayload, err := skeUtils.GetDefaultPayload(ctx, apiClient, model.Region) + defaultPayload, err := skeUtils.GetDefaultPayload(ctx, apiClient.DefaultAPI, model.Region) if err != nil { return fmt.Errorf("get default payload: %w", err) } @@ -133,13 +134,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating cluster") - _, err = wait.CreateOrUpdateClusterWaitHandler(ctx, apiClient, model.ProjectId, model.Region, name).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating cluster", func() error { + _, err = wait.CreateOrUpdateClusterWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, name).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE cluster creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) @@ -182,7 +183,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiCreateOrUpdateClusterRequest { - req := apiClient.CreateOrUpdateCluster(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.CreateOrUpdateCluster(ctx, model.ProjectId, model.Region, model.ClusterName) req = req.CreateOrUpdateClusterPayload(*model.Payload) return req diff --git a/internal/cmd/ske/cluster/create/create_test.go b/internal/cmd/ske/cluster/create/create_test.go index 99392a63d..896e9421a 100644 --- a/internal/cmd/ske/cluster/create/create_test.go +++ b/internal/cmd/ske/cluster/create/create_test.go @@ -2,7 +2,6 @@ package create import ( "context" - "fmt" "testing" "time" @@ -17,7 +16,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -25,53 +24,67 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" const testRegion = "eu01" var testPayload = &ske.CreateOrUpdateClusterPayload{ - Kubernetes: &ske.Kubernetes{ - Version: utils.Ptr("1.25.15"), + Kubernetes: ske.Kubernetes{ + Version: "1.25.15", + AdditionalProperties: map[string]any{}, }, - Nodepools: &[]ske.Nodepool{ + Nodepools: []ske.Nodepool{ { - Name: utils.Ptr("np-name"), - Machine: &ske.Machine{ - Image: &ske.Image{ - Name: utils.Ptr("flatcar"), - Version: utils.Ptr("3760.2.1"), + Name: "np-name", + Machine: ske.Machine{ + Image: ske.Image{ + Name: "flatcar", + Version: "3760.2.1", + AdditionalProperties: map[string]any{}, }, - Type: utils.Ptr("b1.2"), + Type: "b1.2", + AdditionalProperties: map[string]any{}, }, - Minimum: utils.Ptr(int64(1)), - Maximum: utils.Ptr(int64(2)), - MaxSurge: utils.Ptr(int64(1)), - Volume: &ske.Volume{ - Type: utils.Ptr("storage_premium_perf0"), - Size: utils.Ptr(int64(40)), + Minimum: int32(1), + Maximum: int32(2), + MaxSurge: utils.Ptr(int32(1)), + Volume: ske.Volume{ + Type: utils.Ptr("storage_premium_perf0"), + Size: int32(40), + AdditionalProperties: map[string]any{}, }, - AvailabilityZones: &[]string{"eu01-3"}, - Cri: &ske.CRI{Name: ske.CRINAME_DOCKER.Ptr()}, + AvailabilityZones: []string{"eu01-3"}, + Cri: &ske.CRI{ + Name: utils.Ptr("containerd"), + AdditionalProperties: map[string]any{}, + }, + AdditionalProperties: map[string]any{}, }, }, Extensions: &ske.Extension{ Acl: &ske.ACL{ - Enabled: utils.Ptr(true), - AllowedCidrs: &[]string{"0.0.0.0/0"}, + Enabled: true, + AllowedCidrs: []string{"0.0.0.0/0"}, + AdditionalProperties: map[string]any{}, }, + AdditionalProperties: map[string]any{}, }, Maintenance: &ske.Maintenance{ - AutoUpdate: &ske.MaintenanceAutoUpdate{ - KubernetesVersion: utils.Ptr(true), - MachineImageVersion: utils.Ptr(true), + AutoUpdate: ske.MaintenanceAutoUpdate{ + KubernetesVersion: utils.Ptr(true), + MachineImageVersion: utils.Ptr(true), + AdditionalProperties: map[string]any{}, }, - TimeWindow: &ske.TimeWindow{ - End: utils.Ptr(time.Date(0, 1, 1, 5, 0, 0, 0, time.FixedZone("test-zone", 2*60*60))), - Start: utils.Ptr(time.Date(0, 1, 1, 3, 0, 0, 0, time.FixedZone("test-zone", 2*60*60))), + TimeWindow: ske.TimeWindow{ + End: time.Date(0, 1, 1, 5, 0, 0, 0, time.FixedZone("test-zone", 2*60*60)), + Start: time.Date(0, 1, 1, 3, 0, 0, 0, time.FixedZone("test-zone", 2*60*60)), + AdditionalProperties: map[string]any{}, }, + AdditionalProperties: map[string]any{}, }, + AdditionalProperties: map[string]any{}, } func fixtureArgValues(mods ...func(argValues []string)) []string { @@ -88,8 +101,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st flagValues := map[string]string{ globalflags.ProjectIdFlag: testProjectId, globalflags.RegionFlag: testRegion, - payloadFlag: fmt.Sprintf(`{ - "name": "cli-jp", + payloadFlag: `{ "kubernetes": { "version": "1.25.15" }, @@ -107,7 +119,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st "maximum": 2, "maxSurge": 1, "volume": { "type": "storage_premium_perf0", "size": 40 }, - "cri": { "name": "%s" }, + "cri": { "name": "containerd" }, "availabilityZones": ["eu01-3"] } ], @@ -122,7 +134,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st "start": "0000-01-01T03:00:00+02:00" } } - }`, ske.CRINAME_DOCKER), + }`, } for _, mod := range mods { mod(flagValues) @@ -147,7 +159,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiCreateOrUpdateClusterRequest)) ske.ApiCreateOrUpdateClusterRequest { - request := testClient.CreateOrUpdateCluster(testCtx, testProjectId, testRegion, fixtureInputModel().ClusterName) + request := testClient.DefaultAPI.CreateOrUpdateCluster(testCtx, testProjectId, testRegion, fixtureInputModel().ClusterName) request = request.CreateOrUpdateClusterPayload(*testPayload) for _, mod := range mods { mod(&request) @@ -262,6 +274,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/ske/cluster/delete/delete.go b/internal/cmd/ske/cluster/delete/delete.go index 34de6f2c7..f8833c202 100644 --- a/internal/cmd/ske/cluster/delete/delete.go +++ b/internal/cmd/ske/cluster/delete/delete.go @@ -15,8 +15,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" ) const ( @@ -67,13 +67,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting cluster") - _, err = wait.DeleteClusterWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting cluster", func() error { + _, err = wait.DeleteClusterWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE cluster deletion: %w", err) } - s.Stop() } operationState := "Deleted" @@ -105,6 +105,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiDeleteClusterRequest { - req := apiClient.DeleteCluster(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.DeleteCluster(ctx, model.ProjectId, model.Region, model.ClusterName) return req } diff --git a/internal/cmd/ske/cluster/delete/delete_test.go b/internal/cmd/ske/cluster/delete/delete_test.go index 86cef5d06..182af1949 100644 --- a/internal/cmd/ske/cluster/delete/delete_test.go +++ b/internal/cmd/ske/cluster/delete/delete_test.go @@ -10,7 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -18,7 +18,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" @@ -61,7 +61,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiDeleteClusterRequest)) ske.ApiDeleteClusterRequest { - request := testClient.DeleteCluster(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.DeleteCluster(testCtx, testProjectId, testRegion, testClusterName) for _, mod := range mods { mod(&request) } @@ -156,6 +156,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/ske/cluster/describe/describe.go b/internal/cmd/ske/cluster/describe/describe.go index 3e94fa9d9..bde8763f7 100644 --- a/internal/cmd/ske/cluster/describe/describe.go +++ b/internal/cmd/ske/cluster/describe/describe.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) const ( @@ -85,7 +86,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiGetClusterRequest { - req := apiClient.GetCluster(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.GetCluster(ctx, model.ProjectId, model.Region, model.ClusterName) return req } @@ -97,7 +98,7 @@ func outputResult(p *print.Printer, outputFormat string, cluster *ske.Cluster) e return p.OutputResult(outputFormat, cluster, func() error { acl := []string{} if cluster.Extensions != nil && cluster.Extensions.Acl != nil { - acl = *cluster.Extensions.Acl.AllowedCidrs + acl = cluster.Extensions.Acl.AllowedCidrs } table := tables.NewTable() @@ -110,8 +111,8 @@ func outputResult(p *print.Printer, outputFormat string, cluster *ske.Cluster) e handleClusterErrors(clusterErrs, &table) } } - if cluster.Kubernetes != nil { - table.AddRow("VERSION", utils.PtrString(cluster.Kubernetes.Version)) + if cluster.Kubernetes.Version != "" { + table.AddRow("VERSION", cluster.Kubernetes.Version) table.AddSeparator() } @@ -131,7 +132,7 @@ func handleClusterErrors(clusterErrs []ske.ClusterError, table *tables.Table) { b := new(strings.Builder) fmt.Fprint(b, e.GetCode()) if msg, ok := e.GetMessageOk(); ok { - fmt.Fprintf(b, ": %s", msg) + fmt.Fprintf(b, ": %s", *msg) } errs = append(errs, b.String()) } diff --git a/internal/cmd/ske/cluster/describe/describe_test.go b/internal/cmd/ske/cluster/describe/describe_test.go index 3049998fe..026a1405e 100644 --- a/internal/cmd/ske/cluster/describe/describe_test.go +++ b/internal/cmd/ske/cluster/describe/describe_test.go @@ -14,7 +14,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -22,7 +22,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" @@ -65,7 +65,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiGetClusterRequest)) ske.ApiGetClusterRequest { - request := testClient.GetCluster(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.GetCluster(testCtx, testProjectId, testRegion, testClusterName) for _, mod := range mods { mod(&request) } @@ -160,6 +160,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) @@ -197,7 +198,7 @@ func TestOutputResult(t *testing.T) { cluster: &ske.Cluster{ Name: utils.Ptr("test-cluster"), Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{ + Errors: []ske.ClusterError{ { Code: utils.Ptr("SKE_INFRA_SNA_NETWORK_NOT_FOUND"), Message: utils.Ptr("Network configuration not found"), @@ -215,7 +216,7 @@ func TestOutputResult(t *testing.T) { cluster: &ske.Cluster{ Name: utils.Ptr("test-cluster"), Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{ + Errors: []ske.ClusterError{ { Code: utils.Ptr("SKE_INFRA_SNA_NETWORK_NOT_FOUND"), Message: utils.Ptr("Network configuration not found"), @@ -241,7 +242,7 @@ func TestOutputResult(t *testing.T) { cluster: &ske.Cluster{ Name: utils.Ptr("test-cluster"), Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{ + Errors: []ske.ClusterError{ { Code: utils.Ptr("SKE_FETCHING_ERRORS_NOT_POSSIBLE"), }, @@ -271,7 +272,7 @@ func TestOutputResult(t *testing.T) { cluster: &ske.Cluster{ Name: utils.Ptr("test-cluster"), Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{}, + Errors: []ske.ClusterError{}, }, }, }, @@ -294,7 +295,7 @@ func TestOutputResult(t *testing.T) { cluster: &ske.Cluster{ Name: utils.Ptr("test-cluster"), Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{ + Errors: []ske.ClusterError{ { Code: utils.Ptr("SKE_INFRA_SNA_NETWORK_NOT_FOUND"), Message: utils.Ptr("Network configuration not found"), @@ -312,7 +313,7 @@ func TestOutputResult(t *testing.T) { cluster: &ske.Cluster{ Name: utils.Ptr("test-cluster"), Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{ + Errors: []ske.ClusterError{ { Code: utils.Ptr("SKE_INFRA_SNA_NETWORK_NOT_FOUND"), Message: utils.Ptr("Network configuration not found"), @@ -329,11 +330,11 @@ func TestOutputResult(t *testing.T) { outputFormat: "", cluster: &ske.Cluster{ Name: utils.Ptr("test-cluster"), - Kubernetes: &ske.Kubernetes{ - Version: utils.Ptr("1.28.0"), + Kubernetes: ske.Kubernetes{ + Version: "1.28.0", }, Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{ + Errors: []ske.ClusterError{ { Code: utils.Ptr("SKE_INFRA_SNA_NETWORK_NOT_FOUND"), Message: utils.Ptr("Network configuration not found"), @@ -352,12 +353,12 @@ func TestOutputResult(t *testing.T) { Name: utils.Ptr("test-cluster"), Extensions: &ske.Extension{ Acl: &ske.ACL{ - AllowedCidrs: &[]string{"10.0.0.0/8"}, - Enabled: utils.Ptr(true), + AllowedCidrs: []string{"10.0.0.0/8"}, + Enabled: true, }, }, Status: &ske.ClusterStatus{ - Errors: &[]ske.ClusterError{ + Errors: []ske.ClusterError{ { Code: utils.Ptr("SKE_INFRA_SNA_NETWORK_NOT_FOUND"), Message: utils.Ptr("Network configuration not found"), diff --git a/internal/cmd/ske/cluster/generate-payload/generate_payload.go b/internal/cmd/ske/cluster/generate-payload/generate_payload.go index 5afa14cf8..7ff0a87d3 100644 --- a/internal/cmd/ske/cluster/generate-payload/generate_payload.go +++ b/internal/cmd/ske/cluster/generate-payload/generate_payload.go @@ -18,7 +18,7 @@ import ( skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) const ( @@ -71,7 +71,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { var payload *ske.CreateOrUpdateClusterPayload if model.ClusterName == nil { - payload, err = skeUtils.GetDefaultPayload(ctx, apiClient, model.Region) + payload, err = skeUtils.GetDefaultPayload(ctx, apiClient.DefaultAPI, model.Region) if err != nil { return err } @@ -82,10 +82,12 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return fmt.Errorf("read SKE cluster: %w", err) } payload = &ske.CreateOrUpdateClusterPayload{ + Access: resp.Access, Extensions: resp.Extensions, Hibernation: resp.Hibernation, Kubernetes: resp.Kubernetes, Maintenance: resp.Maintenance, + Network: resp.Network, Nodepools: resp.Nodepools, Status: resp.Status, } @@ -123,7 +125,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiGetClusterRequest { - req := apiClient.GetCluster(ctx, model.ProjectId, model.Region, *model.ClusterName) + req := apiClient.DefaultAPI.GetCluster(ctx, model.ProjectId, model.Region, *model.ClusterName) return req } diff --git a/internal/cmd/ske/cluster/generate-payload/generate_payload_test.go b/internal/cmd/ske/cluster/generate-payload/generate_payload_test.go index 97f0aa013..16c1ad392 100644 --- a/internal/cmd/ske/cluster/generate-payload/generate_payload_test.go +++ b/internal/cmd/ske/cluster/generate-payload/generate_payload_test.go @@ -14,7 +14,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -22,7 +22,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() const ( @@ -62,7 +62,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiGetClusterRequest)) ske.ApiGetClusterRequest { - request := testClient.GetCluster(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.GetCluster(testCtx, testProjectId, testRegion, testClusterName) for _, mod := range mods { mod(&request) } @@ -162,6 +162,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/ske/cluster/hibernate/hibernate.go b/internal/cmd/ske/cluster/hibernate/hibernate.go index 5e679e38b..b2a345175 100644 --- a/internal/cmd/ske/cluster/hibernate/hibernate.go +++ b/internal/cmd/ske/cluster/hibernate/hibernate.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" ) const ( @@ -72,13 +73,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Hibernating cluster") - _, err = wait.TriggerClusterHibernationWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Hibernating cluster", func() error { + _, err = wait.TriggerClusterHibernationWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE cluster hibernation: %w", err) } - s.Stop() } operationState := "Hibernated" @@ -110,6 +111,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiTriggerHibernateRequest { - req := apiClient.TriggerHibernate(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.TriggerHibernate(ctx, model.ProjectId, model.Region, model.ClusterName) return req } diff --git a/internal/cmd/ske/cluster/hibernate/hibernate_test.go b/internal/cmd/ske/cluster/hibernate/hibernate_test.go index d9d531ef1..0e532170f 100644 --- a/internal/cmd/ske/cluster/hibernate/hibernate_test.go +++ b/internal/cmd/ske/cluster/hibernate/hibernate_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) type testCtxKey struct{} @@ -22,7 +23,7 @@ const ( ) var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() func fixtureArgValues(mods ...func(argValues []string)) []string { @@ -62,7 +63,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiTriggerHibernateRequest)) ske.ApiTriggerHibernateRequest { - request := testClient.TriggerHibernate(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.TriggerHibernate(testCtx, testProjectId, testRegion, testClusterName) for _, mod := range mods { mod(&request) } @@ -177,6 +178,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmpopts.EquateComparable(testCtx), cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("request mismatch:\n%s", diff) diff --git a/internal/cmd/ske/cluster/list/list.go b/internal/cmd/ske/cluster/list/list.go index b1fa9f024..054c4f508 100644 --- a/internal/cmd/ske/cluster/list/list.go +++ b/internal/cmd/ske/cluster/list/list.go @@ -20,7 +20,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) const ( @@ -83,7 +83,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if err != nil { return fmt.Errorf("get SKE clusters: %w", err) } - clusters := *resp.Items + clusters := resp.Items // Truncate output if model.Limit != nil && len(clusters) > int(*model.Limit) { @@ -134,7 +134,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiListClustersRequest { - req := apiClient.ListClusters(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.ListClusters(ctx, model.ProjectId, model.Region) return req } @@ -150,19 +150,17 @@ func outputResult(p *print.Printer, outputFormat, projectLabel string, clusters for i := range clusters { c := clusters[i] monitoring := "Disabled" - if c.Extensions != nil && c.Extensions.Observability != nil && *c.Extensions.Observability.Enabled { + if c.Extensions != nil && c.Extensions.Observability != nil && c.Extensions.Observability.Enabled { monitoring = "Enabled" } - statusAggregated, kubernetesVersion := "", "" + statusAggregated := "" if c.HasStatus() { statusAggregated = utils.PtrString(c.Status.Aggregated) } - if c.Kubernetes != nil { - kubernetesVersion = utils.PtrString(c.Kubernetes.Version) - } + kubernetesVersion := c.Kubernetes.Version countNodepools := 0 if c.Nodepools != nil { - countNodepools = len(*c.Nodepools) + countNodepools = len(c.Nodepools) } table.AddRow( utils.PtrString(c.Name), diff --git a/internal/cmd/ske/cluster/list/list_test.go b/internal/cmd/ske/cluster/list/list_test.go index 2b123dba0..a40ad6984 100644 --- a/internal/cmd/ske/cluster/list/list_test.go +++ b/internal/cmd/ske/cluster/list/list_test.go @@ -14,7 +14,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -22,7 +22,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() const testRegion = "eu01" @@ -55,7 +55,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiListClustersRequest)) ske.ApiListClustersRequest { - request := testClient.ListClusters(testCtx, testProjectId, testRegion) + request := testClient.DefaultAPI.ListClusters(testCtx, testProjectId, testRegion) for _, mod := range mods { mod(&request) } @@ -145,6 +145,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/ske/cluster/maintenance/maintenance.go b/internal/cmd/ske/cluster/maintenance/maintenance.go index 0b5f406ca..fbf0f16f4 100644 --- a/internal/cmd/ske/cluster/maintenance/maintenance.go +++ b/internal/cmd/ske/cluster/maintenance/maintenance.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" ) const ( @@ -72,13 +73,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Performing cluster maintenance") - _, err = wait.TriggerClusterMaintenanceWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Performing cluster maintenance", func() error { + _, err = wait.TriggerClusterMaintenanceWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE cluster maintenance to complete: %w", err) } - s.Stop() } operationState := "Performed maintenance for" @@ -110,6 +111,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiTriggerMaintenanceRequest { - req := apiClient.TriggerMaintenance(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.TriggerMaintenance(ctx, model.ProjectId, model.Region, model.ClusterName) return req } diff --git a/internal/cmd/ske/cluster/maintenance/maintenance_test.go b/internal/cmd/ske/cluster/maintenance/maintenance_test.go index fe0ab07cb..5d374d75d 100644 --- a/internal/cmd/ske/cluster/maintenance/maintenance_test.go +++ b/internal/cmd/ske/cluster/maintenance/maintenance_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) type testCtxKey struct{} @@ -22,7 +23,7 @@ const ( ) var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() func fixtureArgValues(mods ...func([]string)) []string { @@ -62,7 +63,7 @@ func fixtureInputModel(mods ...func(*inputModel)) *inputModel { } func fixtureRequest(mods ...func(*ske.ApiTriggerMaintenanceRequest)) ske.ApiTriggerMaintenanceRequest { - request := testClient.TriggerMaintenance(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.TriggerMaintenance(testCtx, testProjectId, testRegion, testClusterName) for _, m := range mods { m(&request) } @@ -178,6 +179,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(got, want, cmpopts.EquateComparable(testCtx), cmp.AllowUnexported(want), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("request mismatch:\n%s", diff) diff --git a/internal/cmd/ske/cluster/reconcile/reconcile.go b/internal/cmd/ske/cluster/reconcile/reconcile.go index 0108ae568..8e499c418 100644 --- a/internal/cmd/ske/cluster/reconcile/reconcile.go +++ b/internal/cmd/ske/cluster/reconcile/reconcile.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,8 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" ) const ( @@ -60,13 +61,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Performing cluster reconciliation") - _, err = wait.TriggerClusterReconciliationWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Performing cluster reconciliation", func() error { + _, err = wait.TriggerClusterReconciliationWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE cluster reconciliation: %w", err) } - s.Stop() } operationState := "Performed reconciliation for" @@ -98,6 +99,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiTriggerReconcileRequest { - req := apiClient.TriggerReconcile(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.TriggerReconcile(ctx, model.ProjectId, model.Region, model.ClusterName) return req } diff --git a/internal/cmd/ske/cluster/reconcile/reconcile_test.go b/internal/cmd/ske/cluster/reconcile/reconcile_test.go index 5c96f295b..ce5e15bc9 100644 --- a/internal/cmd/ske/cluster/reconcile/reconcile_test.go +++ b/internal/cmd/ske/cluster/reconcile/reconcile_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) type testCtxKey struct{} @@ -22,7 +23,7 @@ const ( ) var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() func fixtureArgValues(mods ...func([]string)) []string { @@ -61,8 +62,8 @@ func fixtureInputModel(mods ...func(*inputModel)) *inputModel { return model } -func fixtureRequest(mods ...func(request *ske.ApiTriggerReconcileRequest)) ske.ApiTriggerHibernateRequest { - request := testClient.TriggerReconcile(testCtx, testProjectId, testRegion, testClusterName) +func fixtureRequest(mods ...func(request *ske.ApiTriggerReconcileRequest)) ske.ApiTriggerReconcileRequest { + request := testClient.DefaultAPI.TriggerReconcile(testCtx, testProjectId, testRegion, testClusterName) for _, m := range mods { m(&request) } @@ -161,7 +162,7 @@ func TestBuildRequest(t *testing.T) { tests := []struct { description string model *inputModel - expectedRequest ske.ApiTriggerHibernateRequest + expectedRequest ske.ApiTriggerReconcileRequest }{ { description: "base", @@ -178,6 +179,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(got, want, cmpopts.EquateComparable(testCtx), cmp.AllowUnexported(want), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("request mismatch:\n%s", diff) diff --git a/internal/cmd/ske/cluster/update/update.go b/internal/cmd/ske/cluster/update/update.go index 6150a9bda..bd1facd7a 100644 --- a/internal/cmd/ske/cluster/update/update.go +++ b/internal/cmd/ske/cluster/update/update.go @@ -18,8 +18,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" ) const ( @@ -77,7 +77,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } // Check if cluster exists - exists, err := skeUtils.ClusterExists(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName) + exists, err := skeUtils.ClusterExists(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName) if err != nil { return err } @@ -95,13 +95,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Updating cluster") - _, err = wait.CreateOrUpdateClusterWaitHandler(ctx, apiClient, model.ProjectId, model.Region, name).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Updating cluster", func() error { + _, err = wait.CreateOrUpdateClusterWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, name).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE cluster update: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, model.ClusterName, resp) @@ -144,7 +144,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiCreateOrUpdateClusterRequest { - req := apiClient.CreateOrUpdateCluster(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.CreateOrUpdateCluster(ctx, model.ProjectId, model.Region, model.ClusterName) req = req.CreateOrUpdateClusterPayload(model.Payload) return req diff --git a/internal/cmd/ske/cluster/update/update_test.go b/internal/cmd/ske/cluster/update/update_test.go index e4a28fb91..e4cad4e35 100644 --- a/internal/cmd/ske/cluster/update/update_test.go +++ b/internal/cmd/ske/cluster/update/update_test.go @@ -2,7 +2,6 @@ package update import ( "context" - "fmt" "testing" "time" @@ -16,7 +15,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -24,53 +23,67 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" const testRegion = "eu01" var testPayload = ske.CreateOrUpdateClusterPayload{ - Kubernetes: &ske.Kubernetes{ - Version: utils.Ptr("1.25.15"), + Kubernetes: ske.Kubernetes{ + Version: "1.25.15", + AdditionalProperties: map[string]any{}, }, - Nodepools: &[]ske.Nodepool{ + Nodepools: []ske.Nodepool{ { - Name: utils.Ptr("np-name"), - Machine: &ske.Machine{ - Image: &ske.Image{ - Name: utils.Ptr("flatcar"), - Version: utils.Ptr("3760.2.1"), + Name: "np-name", + Machine: ske.Machine{ + Image: ske.Image{ + Name: "flatcar", + Version: "3760.2.1", + AdditionalProperties: map[string]any{}, }, - Type: utils.Ptr("b1.2"), + Type: "b1.2", + AdditionalProperties: map[string]any{}, }, - Minimum: utils.Ptr(int64(1)), - Maximum: utils.Ptr(int64(2)), - MaxSurge: utils.Ptr(int64(1)), - Volume: &ske.Volume{ - Type: utils.Ptr("storage_premium_perf0"), - Size: utils.Ptr(int64(40)), + Minimum: int32(1), + Maximum: int32(2), + MaxSurge: utils.Ptr(int32(1)), + Volume: ske.Volume{ + Type: utils.Ptr("storage_premium_perf0"), + Size: int32(40), + AdditionalProperties: map[string]any{}, }, - AvailabilityZones: &[]string{"eu01-3"}, - Cri: &ske.CRI{Name: ske.CRINAME_DOCKER.Ptr()}, + AvailabilityZones: []string{"eu01-3"}, + Cri: &ske.CRI{ + Name: utils.Ptr("containerd"), + AdditionalProperties: map[string]any{}, + }, + AdditionalProperties: map[string]any{}, }, }, Extensions: &ske.Extension{ Acl: &ske.ACL{ - Enabled: utils.Ptr(true), - AllowedCidrs: &[]string{"0.0.0.0/0"}, + Enabled: true, + AllowedCidrs: []string{"0.0.0.0/0"}, + AdditionalProperties: map[string]any{}, }, + AdditionalProperties: map[string]any{}, }, Maintenance: &ske.Maintenance{ - AutoUpdate: &ske.MaintenanceAutoUpdate{ - KubernetesVersion: utils.Ptr(true), - MachineImageVersion: utils.Ptr(true), + AutoUpdate: ske.MaintenanceAutoUpdate{ + KubernetesVersion: utils.Ptr(true), + MachineImageVersion: utils.Ptr(true), + AdditionalProperties: map[string]any{}, }, - TimeWindow: &ske.TimeWindow{ - End: utils.Ptr(time.Date(0, 1, 1, 5, 0, 0, 0, time.FixedZone("test-zone", 2*60*60))), - Start: utils.Ptr(time.Date(0, 1, 1, 3, 0, 0, 0, time.FixedZone("test-zone", 2*60*60))), + TimeWindow: ske.TimeWindow{ + End: time.Date(0, 1, 1, 5, 0, 0, 0, time.FixedZone("test-zone", 2*60*60)), + Start: time.Date(0, 1, 1, 3, 0, 0, 0, time.FixedZone("test-zone", 2*60*60)), + AdditionalProperties: map[string]any{}, }, + AdditionalProperties: map[string]any{}, }, + AdditionalProperties: map[string]any{}, } func fixtureArgValues(mods ...func(argValues []string)) []string { @@ -87,8 +100,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st flagValues := map[string]string{ globalflags.ProjectIdFlag: testProjectId, globalflags.RegionFlag: testRegion, - payloadFlag: fmt.Sprintf(`{ - "name": "cli-jp", + payloadFlag: `{ "kubernetes": { "version": "1.25.15" }, @@ -106,7 +118,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st "maximum": 2, "maxSurge": 1, "volume": { "type": "storage_premium_perf0", "size": 40 }, - "cri": { "name": "%s" }, + "cri": { "name": "containerd" }, "availabilityZones": ["eu01-3"] } ], @@ -121,7 +133,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st "start": "0000-01-01T03:00:00+02:00" } } - }`, ske.CRINAME_DOCKER), + }`, } for _, mod := range mods { mod(flagValues) @@ -146,7 +158,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiCreateOrUpdateClusterRequest)) ske.ApiCreateOrUpdateClusterRequest { - request := testClient.CreateOrUpdateCluster(testCtx, testProjectId, testRegion, fixtureInputModel().ClusterName) + request := testClient.DefaultAPI.CreateOrUpdateCluster(testCtx, testProjectId, testRegion, fixtureInputModel().ClusterName) request = request.CreateOrUpdateClusterPayload(testPayload) for _, mod := range mods { mod(&request) @@ -249,6 +261,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/ske/cluster/wakeup/wakeup.go b/internal/cmd/ske/cluster/wakeup/wakeup.go index 64b0e5ccf..2ed99d8ad 100644 --- a/internal/cmd/ske/cluster/wakeup/wakeup.go +++ b/internal/cmd/ske/cluster/wakeup/wakeup.go @@ -7,6 +7,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,8 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" ) const ( @@ -60,13 +61,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Performing cluster wakeup") - _, err = wait.TriggerClusterWakeupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Performing cluster wakeup", func() error { + _, err = wait.TriggerClusterWakeupWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE cluster wakeup: %w", err) } - s.Stop() } operationState := "Performed wakeup of" @@ -98,6 +99,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiTriggerWakeupRequest { - req := apiClient.TriggerWakeup(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.TriggerWakeup(ctx, model.ProjectId, model.Region, model.ClusterName) return req } diff --git a/internal/cmd/ske/cluster/wakeup/wakeup_test.go b/internal/cmd/ske/cluster/wakeup/wakeup_test.go index dd93881c1..a729af751 100644 --- a/internal/cmd/ske/cluster/wakeup/wakeup_test.go +++ b/internal/cmd/ske/cluster/wakeup/wakeup_test.go @@ -9,9 +9,10 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) type testCtxKey struct{} @@ -22,7 +23,7 @@ const ( ) var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() func fixtureArgValues(mods ...func([]string)) []string { @@ -60,7 +61,7 @@ func fixtureInputModel(mods ...func(*inputModel)) *inputModel { } func fixtureRequest(mods ...func(*ske.ApiTriggerWakeupRequest)) ske.ApiTriggerWakeupRequest { - req := testClient.TriggerWakeup(testCtx, testProjectId, testRegion, testClusterName) + req := testClient.DefaultAPI.TriggerWakeup(testCtx, testProjectId, testRegion, testClusterName) for _, m := range mods { m(&req) } @@ -159,7 +160,7 @@ func TestBuildRequest(t *testing.T) { tests := []struct { description string model *inputModel - expectedRequest ske.ApiTriggerHibernateRequest + expectedRequest ske.ApiTriggerWakeupRequest }{ { description: "base", @@ -176,6 +177,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(got, want, cmpopts.EquateComparable(testCtx), cmp.AllowUnexported(want), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("request mismatch:\n%s", diff) diff --git a/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go b/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go index 25f2601aa..734a89735 100644 --- a/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go +++ b/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go @@ -15,8 +15,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" ) const ( @@ -84,13 +84,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Completing credentials rotation") - _, err = wait.CompleteCredentialsRotationWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Completing credentials rotation", func() error { + _, err = wait.CompleteCredentialsRotationWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for completing SKE credentials rotation %w", err) } - s.Stop() } operationState := "Rotation of credentials is completed" @@ -123,6 +123,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiCompleteCredentialsRotationRequest { - req := apiClient.CompleteCredentialsRotation(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.CompleteCredentialsRotation(ctx, model.ProjectId, model.Region, model.ClusterName) return req } diff --git a/internal/cmd/ske/credentials/complete-rotation/complete_rotation_test.go b/internal/cmd/ske/credentials/complete-rotation/complete_rotation_test.go index ee40fc120..9ef9dc2f8 100644 --- a/internal/cmd/ske/credentials/complete-rotation/complete_rotation_test.go +++ b/internal/cmd/ske/credentials/complete-rotation/complete_rotation_test.go @@ -10,7 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -18,7 +18,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" @@ -61,7 +61,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiCompleteCredentialsRotationRequest)) ske.ApiCompleteCredentialsRotationRequest { - request := testClient.CompleteCredentialsRotation(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.CompleteCredentialsRotation(testCtx, testProjectId, testRegion, testClusterName) for _, mod := range mods { mod(&request) } @@ -154,6 +154,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/ske/credentials/start-rotation/start_rotation.go b/internal/cmd/ske/credentials/start-rotation/start_rotation.go index 3cb3cee55..ea2933fab 100644 --- a/internal/cmd/ske/credentials/start-rotation/start_rotation.go +++ b/internal/cmd/ske/credentials/start-rotation/start_rotation.go @@ -15,8 +15,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + wait "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api/wait" ) const ( @@ -87,13 +87,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Starting credentials rotation") - _, err = wait.StartCredentialsRotationWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Starting credentials rotation", func() error { + _, err = wait.StartCredentialsRotationWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.ClusterName).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for start SKE credentials rotation %w", err) } - s.Stop() } operationState := "Rotation of credentials is ready to be completed" @@ -126,6 +126,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiStartCredentialsRotationRequest { - req := apiClient.StartCredentialsRotation(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.StartCredentialsRotation(ctx, model.ProjectId, model.Region, model.ClusterName) return req } diff --git a/internal/cmd/ske/credentials/start-rotation/start_rotation_test.go b/internal/cmd/ske/credentials/start-rotation/start_rotation_test.go index 063269174..e3fae33d2 100644 --- a/internal/cmd/ske/credentials/start-rotation/start_rotation_test.go +++ b/internal/cmd/ske/credentials/start-rotation/start_rotation_test.go @@ -10,7 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -18,7 +18,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" @@ -61,7 +61,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiStartCredentialsRotationRequest)) ske.ApiStartCredentialsRotationRequest { - request := testClient.StartCredentialsRotation(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.StartCredentialsRotation(testCtx, testProjectId, testRegion, testClusterName) for _, mod := range mods { mod(&request) } @@ -154,6 +154,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/ske/describe/describe.go b/internal/cmd/ske/describe/describe.go index 414525335..07fedda34 100644 --- a/internal/cmd/ske/describe/describe.go +++ b/internal/cmd/ske/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" ) type inputModel struct { diff --git a/internal/cmd/ske/describe/describe_test.go b/internal/cmd/ske/describe/describe_test.go index 53dd3afc8..0af19c957 100644 --- a/internal/cmd/ske/describe/describe_test.go +++ b/internal/cmd/ske/describe/describe_test.go @@ -6,11 +6,12 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" serviceEnablementUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/cmd/ske/disable/disable.go b/internal/cmd/ske/disable/disable.go index 075711b1a..43c103e7b 100644 --- a/internal/cmd/ske/disable/disable.go +++ b/internal/cmd/ske/disable/disable.go @@ -70,13 +70,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Disabling SKE") - _, err = wait.DisableServiceWaitHandler(ctx, apiClient, model.Region, model.ProjectId, utils.SKEServiceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Disabling SKE", func() error { + _, err = wait.DisableServiceWaitHandler(ctx, apiClient, model.Region, model.ProjectId, utils.SKEServiceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE disabling: %w", err) } - s.Stop() } operationState := "Disabled" diff --git a/internal/cmd/ske/enable/enable.go b/internal/cmd/ske/enable/enable.go index 366f8d5f2..ea83a337b 100644 --- a/internal/cmd/ske/enable/enable.go +++ b/internal/cmd/ske/enable/enable.go @@ -70,13 +70,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Enabling SKE") - _, err = wait.EnableServiceWaitHandler(ctx, apiClient, model.Region, model.ProjectId, utils.SKEServiceId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Enabling SKE", func() error { + _, err = wait.EnableServiceWaitHandler(ctx, apiClient, model.Region, model.ProjectId, utils.SKEServiceId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for SKE enabling: %w", err) } - s.Stop() } operationState := "Enabled" diff --git a/internal/cmd/ske/kubeconfig/create/create.go b/internal/cmd/ske/kubeconfig/create/create.go index 355364098..96093f901 100644 --- a/internal/cmd/ske/kubeconfig/create/create.go +++ b/internal/cmd/ske/kubeconfig/create/create.go @@ -8,6 +8,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/goccy/go-yaml" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -19,7 +20,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) const ( @@ -29,6 +30,7 @@ const ( expirationFlag = "expiration" filepathFlag = "filepath" loginFlag = "login" + idpFlag = "idp" overwriteFlag = "overwrite" ) @@ -39,6 +41,7 @@ type inputModel struct { ExpirationTime *string Filepath *string Login bool + IDP bool Overwrite bool } @@ -47,34 +50,38 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Use: fmt.Sprintf("create %s", clusterNameArg), Short: "Creates or update a kubeconfig for a SKE cluster", Long: fmt.Sprintf("%s\n\n%s\n%s\n%s\n%s", - "Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exists in the kubeconfig file the information will be updated.", - "By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created.", + "Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster. By default an admin kubeconfig is created. Use the `--idp` option to create an IDP kubeconfig that authenticates via the STACKIT IDP.", + "If the config exists in the kubeconfig file the information will be updated. By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created.", "You can override this behavior by specifying a custom filepath using the --filepath flag or by setting the KUBECONFIG env variable (fallback).\n", "An expiration time can be set for the kubeconfig. The expiration time is set in seconds(s), minutes(m), hours(h), days(d) or months(M). Default is 1h.\n", "Note that the format is , e.g. 30d for 30 days and you can't combine units."), Args: args.SingleArg(clusterNameArg, nil), Example: examples.Build( examples.NewExample( - `Create or update a kubeconfig for the SKE cluster with name "my-cluster. If the config exits in the kubeconfig file the information will be updated."`, - "$ stackit ske kubeconfig create my-cluster"), - examples.NewExample( - `Get a login kubeconfig for the SKE cluster with name "my-cluster". `+ - "This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command.", + `Get a short-lived admin kubeconfig for the SKE cluster with name "my-cluster". `+ + "This kubeconfig does not contain any credentials and instead obtains valid admin credentials via the `stackit ske kubeconfig login` command.", "$ stackit ske kubeconfig create my-cluster --login"), examples.NewExample( - `Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days. If the config exits in the kubeconfig file the information will be updated.`, + `Get an IDP kubeconfig for the SKE cluster with name "my-cluster". `+ + "This kubeconfig does not grant permissions in the cluster by default and obtains credentials on-demand via the `stackit ske kubeconfig login` command.", + "$ stackit ske kubeconfig create my-cluster --idp"), + examples.NewExample( + `Create or update a short-lived admin kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath. If the config exits in the kubeconfig file the information will be updated.`, + "$ stackit ske kubeconfig create my-cluster --login --filepath /path/to/config"), + examples.NewExample( + `Create or update an admin kubeconfig for the SKE cluster with name "my-cluster". If the config exits in the kubeconfig file the information will be updated."`, + "$ stackit ske kubeconfig create my-cluster"), + examples.NewExample( + `Create an admin kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days. If the config exits in the kubeconfig file the information will be updated.`, "$ stackit ske kubeconfig create my-cluster --expiration 30d"), examples.NewExample( - `Create or update a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months. If the config exits in the kubeconfig file the information will be updated.`, + `Create or update an admin kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months. If the config exits in the kubeconfig file the information will be updated.`, "$ stackit ske kubeconfig create my-cluster --expiration 2M"), examples.NewExample( - `Create or update a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath. If the config exits in the kubeconfig file the information will be updated.`, - "$ stackit ske kubeconfig create my-cluster --filepath /path/to/config"), - examples.NewExample( - `Get a kubeconfig for the SKE cluster with name "my-cluster" without writing it to a file and format the output as json`, + `Get an admin kubeconfig for the SKE cluster with name "my-cluster" without writing it to a file and format the output as json`, "$ stackit ske kubeconfig create my-cluster --disable-writing --output-format json"), examples.NewExample( - `Create a kubeconfig for the SKE cluster with name "my-cluster. It will OVERWRITE your current kubeconfig file."`, + `Create an admin kubeconfig for the SKE cluster with name "my-cluster". It will OVERWRITE your current kubeconfig file.`, "$ stackit ske kubeconfig create my-cluster --overwrite true"), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -95,7 +102,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if model.Overwrite { prompt = fmt.Sprintf("Are you sure you want to create a kubeconfig for SKE cluster %q? This will OVERWRITE your current kubeconfig file, if it exists.", model.ClusterName) } else { - prompt = fmt.Sprintf("Are you sure you want to update your kubeconfig for SKE cluster %q? This will update your kubeconfig file. \nIf it the kubeconfig file doesn't exists, it will create a new one.", model.ClusterName) + prompt = fmt.Sprintf("Are you sure you want to update your kubeconfig for SKE cluster %q? This will update your kubeconfig file. \nIf the kubeconfig file does not exists, it will create a new one.", model.ClusterName) } err = params.Printer.PromptForConfirmation(prompt) if err != nil { @@ -108,22 +115,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { kubeconfig string respKubeconfig *ske.Kubeconfig respLogin *ske.LoginKubeconfig + respIDP *ske.IDPKubeconfig ) - if !model.Login { - req, err := buildRequestCreate(ctx, model, apiClient) - if err != nil { - return fmt.Errorf("build kubeconfig create request: %w", err) - } - respKubeconfig, err = req.Execute() - if err != nil { - return fmt.Errorf("create kubeconfig for SKE cluster: %w", err) - } - if respKubeconfig.Kubeconfig == nil { - return fmt.Errorf("no kubeconfig returned from the API") - } - kubeconfig = *respKubeconfig.Kubeconfig - } else { + if model.Login { req, err := buildRequestLogin(ctx, model, apiClient) if err != nil { return fmt.Errorf("build login kubeconfig create request: %w", err) @@ -136,6 +131,32 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return fmt.Errorf("no login kubeconfig returned from the API") } kubeconfig = *respLogin.Kubeconfig + } else if model.IDP { + req, err := buildRequestIDP(ctx, model, apiClient) + if err != nil { + return fmt.Errorf("build idp kubeconfig create request: %w", err) + } + respIDP, err = req.Execute() + if err != nil { + return fmt.Errorf("create idp kubeconfig for SKE cluster: %w", err) + } + if respIDP.Kubeconfig == nil { + return fmt.Errorf("no idp kubeconfig returned from the API") + } + kubeconfig = *respIDP.Kubeconfig + } else { + req, err := buildRequestCreate(ctx, model, apiClient) + if err != nil { + return fmt.Errorf("build kubeconfig create request: %w", err) + } + respKubeconfig, err = req.Execute() + if err != nil { + return fmt.Errorf("create kubeconfig for SKE cluster: %w", err) + } + if respKubeconfig.Kubeconfig == nil { + return fmt.Errorf("no kubeconfig returned from the API") + } + kubeconfig = *respKubeconfig.Kubeconfig } // Create the config file @@ -161,7 +182,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { params.Printer.Outputf("\nSet kubectl context to %s with: kubectl config use-context %s\n", model.ClusterName, model.ClusterName) } - return outputResult(params.Printer, model.OutputFormat, model.ClusterName, kubeconfigPath, respKubeconfig, respLogin) + return outputResult(params.Printer, model.OutputFormat, model.ClusterName, kubeconfigPath, respKubeconfig, respLogin, respIDP) }, } configureFlags(cmd) @@ -170,11 +191,12 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(disableWritingFlag, false, fmt.Sprintf("Disable the writing of kubeconfig. Set the output format to json or yaml using the --%s flag to display the kubeconfig.", globalflags.OutputFormatFlag)) - cmd.Flags().BoolP(loginFlag, "l", false, "Create a login kubeconfig that obtains valid credentials via the STACKIT CLI. This flag is mutually exclusive with the expiration flag.") + cmd.Flags().BoolP(loginFlag, "l", false, "Create a short-lived admin kubeconfig that obtains valid credentials via the STACKIT CLI. This flag is mutually exclusive with the expiration flag.") + cmd.Flags().Bool(idpFlag, false, "Create a non-admin kubeconfig that uses the STACKIT IDP to obtain credentials.") cmd.Flags().String(filepathFlag, "", "Path to create the kubeconfig file. Will fall back to KUBECONFIG env variable if not set. In case both aren't set, the kubeconfig is created as file named 'config' in the .kube folder in the user's home directory.") cmd.Flags().StringP(expirationFlag, "e", "", "Expiration time for the kubeconfig in seconds(s), minutes(m), hours(h), days(d) or months(M). Example: 30d. By default, expiration time is 1h") cmd.Flags().Bool(overwriteFlag, false, "Overwrite the kubeconfig file.") - cmd.MarkFlagsMutuallyExclusive(loginFlag, expirationFlag) + cmd.MarkFlagsMutuallyExclusive(loginFlag, expirationFlag, idpFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -213,6 +235,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu Filepath: flags.FlagToStringPointer(p, cmd, filepathFlag), GlobalFlagModel: globalFlags, Login: flags.FlagToBoolValue(p, cmd, loginFlag), + IDP: flags.FlagToBoolValue(p, cmd, idpFlag), Overwrite: flags.FlagToBoolValue(p, cmd, overwriteFlag), } @@ -221,7 +244,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequestCreate(ctx context.Context, model *inputModel, apiClient *ske.APIClient) (ske.ApiCreateKubeconfigRequest, error) { - req := apiClient.CreateKubeconfig(ctx, model.ProjectId, model.Region, model.ClusterName) + req := apiClient.DefaultAPI.CreateKubeconfig(ctx, model.ProjectId, model.Region, model.ClusterName) payload := ske.CreateKubeconfigPayload{} @@ -232,11 +255,15 @@ func buildRequestCreate(ctx context.Context, model *inputModel, apiClient *ske.A return req.CreateKubeconfigPayload(payload), nil } +func buildRequestIDP(ctx context.Context, model *inputModel, apiClient *ske.APIClient) (ske.ApiGetIDPKubeconfigRequest, error) { + return apiClient.DefaultAPI.GetIDPKubeconfig(ctx, model.ProjectId, model.Region, model.ClusterName), nil +} + func buildRequestLogin(ctx context.Context, model *inputModel, apiClient *ske.APIClient) (ske.ApiGetLoginKubeconfigRequest, error) { - return apiClient.GetLoginKubeconfig(ctx, model.ProjectId, model.Region, model.ClusterName), nil + return apiClient.DefaultAPI.GetLoginKubeconfig(ctx, model.ProjectId, model.Region, model.ClusterName), nil } -func outputResult(p *print.Printer, outputFormat, clusterName, kubeconfigPath string, respKubeconfig *ske.Kubeconfig, respLogin *ske.LoginKubeconfig) error { +func outputResult(p *print.Printer, outputFormat, clusterName, kubeconfigPath string, respKubeconfig *ske.Kubeconfig, respLogin *ske.LoginKubeconfig, respIDP *ske.IDPKubeconfig) error { switch outputFormat { case print.JSONOutputFormat: var err error @@ -245,6 +272,8 @@ func outputResult(p *print.Printer, outputFormat, clusterName, kubeconfigPath st details, err = json.MarshalIndent(respKubeconfig, "", " ") } else if respLogin != nil { details, err = json.MarshalIndent(respLogin, "", " ") + } else if respIDP != nil { + details, err = json.MarshalIndent(respIDP, "", " ") } if err != nil { return fmt.Errorf("marshal SKE Kubeconfig: %w", err) @@ -259,6 +288,8 @@ func outputResult(p *print.Printer, outputFormat, clusterName, kubeconfigPath st details, err = yaml.MarshalWithOptions(respKubeconfig, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) } else if respLogin != nil { details, err = yaml.MarshalWithOptions(respLogin, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) + } else if respIDP != nil { + details, err = yaml.MarshalWithOptions(respIDP, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) } if err != nil { return fmt.Errorf("marshal SKE Kubeconfig: %w", err) diff --git a/internal/cmd/ske/kubeconfig/create/create_test.go b/internal/cmd/ske/kubeconfig/create/create_test.go index f8e826064..a7772f6c8 100644 --- a/internal/cmd/ske/kubeconfig/create/create_test.go +++ b/internal/cmd/ske/kubeconfig/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -21,7 +22,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" @@ -64,7 +65,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *ske.ApiCreateKubeconfigRequest)) ske.ApiCreateKubeconfigRequest { - request := testClient.CreateKubeconfig(testCtx, testProjectId, testRegion, testClusterName) + request := testClient.DefaultAPI.CreateKubeconfig(testCtx, testProjectId, testRegion, testClusterName) request = request.CreateKubeconfigPayload(ske.CreateKubeconfigPayload{}) for _, mod := range mods { mod(&request) @@ -72,6 +73,14 @@ func fixtureRequest(mods ...func(request *ske.ApiCreateKubeconfigRequest)) ske.A return request } +func fixtureRequestLogin() ske.ApiGetLoginKubeconfigRequest { + return testClient.DefaultAPI.GetLoginKubeconfig(testCtx, testProjectId, testRegion, testClusterName) +} + +func fixtureRequestIDP() ske.ApiGetIDPKubeconfigRequest { + return testClient.DefaultAPI.GetIDPKubeconfig(testCtx, testProjectId, testRegion, testClusterName) +} + func TestParseInput(t *testing.T) { tests := []struct { description string @@ -109,6 +118,17 @@ func TestParseInput(t *testing.T) { model.Login = true }), }, + { + description: "idp", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["idp"] = "true" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.IDP = true + }), + }, { description: "custom filepath", argValues: fixtureArgValues(), @@ -238,18 +258,37 @@ func TestBuildRequestCreate(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { request, _ := buildRequestCreate(testCtx, tt.model, testClient) - - diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), - cmpopts.EquateComparable(testCtx), - ) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) - } + assertNoDiff(t, request, tt.expectedRequest) }) } } +func assertNoDiff(t *testing.T, actual, expected any) { + t.Helper() + diff := cmp.Diff(actual, expected, + cmp.AllowUnexported(expected), + cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } +} + +func TestBuildRequestLogin(t *testing.T) { + model := fixtureInputModel() + expectedRequest := fixtureRequestLogin() + request, _ := buildRequestLogin(testCtx, model, testClient) + assertNoDiff(t, request, expectedRequest) +} + +func TestBuildRequestIDP(t *testing.T) { + model := fixtureInputModel() + expectedRequest := fixtureRequestIDP() + request, _ := buildRequestIDP(testCtx, model, testClient) + assertNoDiff(t, request, expectedRequest) +} + func Test_outputResult(t *testing.T) { type args struct { outputFormat string @@ -257,6 +296,7 @@ func Test_outputResult(t *testing.T) { kubeconfigPath string respKubeconfig *ske.Kubeconfig respLogin *ske.LoginKubeconfig + respIDP *ske.IDPKubeconfig } tests := []struct { name string @@ -282,12 +322,19 @@ func Test_outputResult(t *testing.T) { }, wantErr: false, }, + { + name: "missing idp", + args: args{ + respIDP: &ske.IDPKubeconfig{}, + }, + wantErr: false, + }, } p := print.NewPrinter() p.Cmd = NewCmd(&types.CmdParams{Printer: p}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(p, tt.args.outputFormat, tt.args.clusterName, tt.args.kubeconfigPath, tt.args.respKubeconfig, tt.args.respLogin); (err != nil) != tt.wantErr { + if err := outputResult(p, tt.args.outputFormat, tt.args.clusterName, tt.args.kubeconfigPath, tt.args.respKubeconfig, tt.args.respLogin, tt.args.respIDP); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/ske/kubeconfig/login/login.go b/internal/cmd/ske/kubeconfig/login/login.go index 711ad56bd..268831202 100644 --- a/internal/cmd/ske/kubeconfig/login/login.go +++ b/internal/cmd/ske/kubeconfig/login/login.go @@ -8,34 +8,39 @@ import ( "encoding/pem" "errors" "fmt" + "net/http" "os" "strconv" "time" - "github.com/stackitcloud/stackit-cli/internal/pkg/types" - - "github.com/stackitcloud/stackit-cli/internal/pkg/cache" - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/spf13/cobra" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/auth/exec" + "k8s.io/client-go/tools/clientcmd" + + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" + "github.com/stackitcloud/stackit-cli/internal/pkg/cache" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" - - "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" - clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" - "k8s.io/client-go/tools/auth/exec" - "k8s.io/client-go/tools/clientcmd" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" ) const ( - expirationSeconds = 30 * 60 // 30 min - refreshBeforeDuration = 15 * time.Minute // 15 min + expirationSeconds = 30 * 60 // 30 min + refreshBeforeDuration = 15 * time.Minute // 15 min + refreshTokenBeforeDuration = 5 * time.Minute // 5 min + + idpFlag = "idp" ) func NewCmd(params *types.CmdParams) *cobra.Command { @@ -44,15 +49,19 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Short: "Login plugin for kubernetes clients", Long: fmt.Sprintf("%s\n%s\n%s", "Login plugin for kubernetes clients, that creates short-lived credentials to authenticate against a STACKIT Kubernetes Engine (SKE) cluster.", - "First you need to obtain a kubeconfig for use with the login command (first example).", - "Secondly you use the kubeconfig with your chosen Kubernetes client (second example), the client will automatically retrieve the credentials via the STACKIT CLI.", + "First you need to obtain a kubeconfig for use with the login command (first or second example).", + "Secondly you use the kubeconfig with your chosen Kubernetes client (third example), the client will automatically retrieve the credentials via the STACKIT CLI.", ), Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `Get a login kubeconfig for the SKE cluster with name "my-cluster". `+ - "This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command.", + `Get an admin, login kubeconfig for the SKE cluster with name "my-cluster". `+ + "This kubeconfig does not contain any credentials and instead obtains valid admin credentials via the `stackit ske kubeconfig login` command.", "$ stackit ske kubeconfig create my-cluster --login"), + examples.NewExample( + `Get an IDP kubeconfig for the SKE cluster with name "my-cluster". `+ + "This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command.", + "$ stackit ske kubeconfig create my-cluster --idp"), examples.NewExample( "Use the previously saved kubeconfig to authenticate to the SKE cluster, in this case with kubectl.", "$ kubectl cluster-info", @@ -72,64 +81,55 @@ func NewCmd(params *types.CmdParams) *cobra.Command { "See `stackit ske kubeconfig login --help` for detailed usage instructions.") } - clusterConfig, err := parseClusterConfig(params.Printer, cmd) + idpMode := flags.FlagToBoolValue(params.Printer, cmd, idpFlag) + clusterConfig, err := parseClusterConfig(params.Printer, cmd, idpMode) if err != nil { return fmt.Errorf("parseClusterConfig: %w", err) } + if idpMode { + accessToken, err := getAccessToken(params) + if err != nil { + return err + } + idpClient := &http.Client{} + token, err := retrieveTokenFromIDP(ctx, idpClient, accessToken, clusterConfig) + if err != nil { + return err + } + return outputTokenKubeconfig(params.Printer, clusterConfig.cacheKey, token) + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { return err } - - cachedKubeconfig := getCachedKubeConfig(clusterConfig.cacheKey) - - if cachedKubeconfig == nil { - return GetAndOutputKubeconfig(ctx, params.Printer, apiClient, clusterConfig, false, nil) - } - - certPem, _ := pem.Decode(cachedKubeconfig.CertData) - if certPem == nil { - _ = cache.DeleteObject(clusterConfig.cacheKey) - return GetAndOutputKubeconfig(ctx, params.Printer, apiClient, clusterConfig, false, nil) - } - - certificate, err := x509.ParseCertificate(certPem.Bytes) + kubeconfig, err := retrieveLoginKubeconfig(ctx, apiClient, clusterConfig) if err != nil { - _ = cache.DeleteObject(clusterConfig.cacheKey) - return GetAndOutputKubeconfig(ctx, params.Printer, apiClient, clusterConfig, false, nil) - } - - // cert is expired, request new - if time.Now().After(certificate.NotAfter.UTC()) { - _ = cache.DeleteObject(clusterConfig.cacheKey) - return GetAndOutputKubeconfig(ctx, params.Printer, apiClient, clusterConfig, false, nil) - } - // cert expires within the next 15min, refresh (try to get a new, use cache on failure) - if time.Now().Add(refreshBeforeDuration).After(certificate.NotAfter.UTC()) { - return GetAndOutputKubeconfig(ctx, params.Printer, apiClient, clusterConfig, true, cachedKubeconfig) - } - - // cert not expired, nor will it expire in the next 15min; therefore, use the cached kubeconfig - if err := output(params.Printer, clusterConfig.cacheKey, cachedKubeconfig); err != nil { return err } - return nil + return outputLoginKubeconfig(params.Printer, clusterConfig.cacheKey, kubeconfig) }, } + configureFlags(cmd) return cmd } +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Bool(idpFlag, false, "Use the STACKIT IdP for authentication to the cluster.") +} + type clusterConfig struct { STACKITProjectID string `json:"stackitProjectID"` ClusterName string `json:"clusterName"` Region string `json:"region"` + OrganizationID string `json:"organizationID"` cacheKey string } -func parseClusterConfig(p *print.Printer, cmd *cobra.Command) (*clusterConfig, error) { +func parseClusterConfig(p *print.Printer, cmd *cobra.Command, idpMode bool) (*clusterConfig, error) { obj, _, err := exec.LoadExecCredentialFromEnv() if err != nil { return nil, fmt.Errorf("LoadExecCredentialFromEnv: %w", err) @@ -161,8 +161,11 @@ func parseClusterConfig(p *print.Printer, cmd *cobra.Command) (*clusterConfig, e if err != nil { return nil, fmt.Errorf("error getting auth email: %w", err) } - - clusterConfig.cacheKey = fmt.Sprintf("ske-login-%x", sha256.Sum256([]byte(execCredential.Spec.Cluster.Server+"\x00"+authEmail))) + idpSuffix := "" + if idpMode { + idpSuffix = "\x00idp" + } + clusterConfig.cacheKey = fmt.Sprintf("ske-login-%x", sha256.Sum256([]byte(execCredential.Spec.Cluster.Server+"\x00"+authEmail+idpSuffix))) // NOTE: Fallback if region is not set in the kubeconfig (this was the case in the past) if clusterConfig.Region == "" { @@ -172,6 +175,30 @@ func parseClusterConfig(p *print.Printer, cmd *cobra.Command) (*clusterConfig, e return clusterConfig, nil } +func retrieveLoginKubeconfig(ctx context.Context, apiClient *ske.APIClient, clusterConfig *clusterConfig) (*rest.Config, error) { + cachedKubeconfig := getCachedKubeConfig(clusterConfig.cacheKey) + if cachedKubeconfig == nil { + return requestNewLoginKubeconfig(ctx, apiClient, clusterConfig) + } + + isValid, notAfter := checkKubeconfigExpiry(cachedKubeconfig.CertData) + if !isValid { + // cert is expired or invalid, request new + _ = cache.DeleteObject(clusterConfig.cacheKey) + return requestNewLoginKubeconfig(ctx, apiClient, clusterConfig) + } else if time.Now().Add(refreshBeforeDuration).After(notAfter.UTC()) { + // cert expires within the next 15min -> refresh + kubeconfig, err := requestNewLoginKubeconfig(ctx, apiClient, clusterConfig) + // try to get a new one but use cache on failure + if err != nil { + return cachedKubeconfig, nil + } + return kubeconfig, nil + } + // cert not expired, nor will it expire in the next 15min; therefore, use the cached kubeconfig + return cachedKubeconfig, nil +} + func getCachedKubeConfig(key string) *rest.Config { cachedKubeconfig, err := cache.GetObject(key) if err != nil { @@ -186,63 +213,64 @@ func getCachedKubeConfig(key string) *rest.Config { return restConfig } -func GetAndOutputKubeconfig(ctx context.Context, p *print.Printer, apiClient *ske.APIClient, clusterConfig *clusterConfig, fallbackToCache bool, cachedKubeconfig *rest.Config) error { - req := buildRequest(ctx, apiClient, clusterConfig) - kubeconfigResponse, err := req.Execute() +func checkKubeconfigExpiry(certData []byte) (bool, time.Time) { + certPem, _ := pem.Decode(certData) + if certPem == nil { + return false, time.Time{} + } + + certificate, err := x509.ParseCertificate(certPem.Bytes) if err != nil { - if fallbackToCache { - return output(p, clusterConfig.cacheKey, cachedKubeconfig) - } - return fmt.Errorf("request kubeconfig: %w", err) + return false, time.Time{} + } + + // cert is expired + if time.Now().After(certificate.NotAfter.UTC()) { + return false, time.Time{} } + return true, certificate.NotAfter.UTC() +} +func requestNewLoginKubeconfig(ctx context.Context, apiClient *ske.APIClient, clusterConfig *clusterConfig) (*rest.Config, error) { + req := buildLoginKubeconfigRequest(ctx, apiClient, clusterConfig) + kubeconfigResponse, err := req.Execute() + if err != nil { + return nil, fmt.Errorf("request kubeconfig: %w", err) + } kubeconfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(*kubeconfigResponse.Kubeconfig)) if err != nil { - if fallbackToCache { - return output(p, clusterConfig.cacheKey, cachedKubeconfig) - } - return fmt.Errorf("parse kubeconfig: %w", err) + return nil, fmt.Errorf("parse kubeconfig: %w", err) } if err = cache.PutObject(clusterConfig.cacheKey, []byte(*kubeconfigResponse.Kubeconfig)); err != nil { - if fallbackToCache { - return output(p, clusterConfig.cacheKey, cachedKubeconfig) - } - return fmt.Errorf("cache kubeconfig: %w", err) + return nil, fmt.Errorf("cache kubeconfig: %w", err) } - return output(p, clusterConfig.cacheKey, kubeconfig) + return kubeconfig, nil } -func buildRequest(ctx context.Context, apiClient *ske.APIClient, clusterConfig *clusterConfig) ske.ApiCreateKubeconfigRequest { - req := apiClient.CreateKubeconfig(ctx, clusterConfig.STACKITProjectID, clusterConfig.Region, clusterConfig.ClusterName) +func buildLoginKubeconfigRequest(ctx context.Context, apiClient *ske.APIClient, clusterConfig *clusterConfig) ske.ApiCreateKubeconfigRequest { + req := apiClient.DefaultAPI.CreateKubeconfig(ctx, clusterConfig.STACKITProjectID, clusterConfig.Region, clusterConfig.ClusterName) expirationSeconds := strconv.Itoa(expirationSeconds) return req.CreateKubeconfigPayload(ske.CreateKubeconfigPayload{ExpirationSeconds: &expirationSeconds}) } -func output(p *print.Printer, cacheKey string, kubeconfig *rest.Config) error { - if kubeconfig == nil { - _ = cache.DeleteObject(cacheKey) - return errors.New("kubeconfig is nil") - } - - outputExecCredential, err := parseKubeConfigToExecCredential(kubeconfig) +func outputLoginKubeconfig(p *print.Printer, cacheKey string, kubeconfig *rest.Config) error { + output, err := parseLoginKubeConfigToExecCredential(kubeconfig) if err != nil { _ = cache.DeleteObject(cacheKey) return fmt.Errorf("convert to ExecCredential: %w", err) } - output, err := json.Marshal(outputExecCredential) - if err != nil { - _ = cache.DeleteObject(cacheKey) - return fmt.Errorf("marshal ExecCredential: %w", err) - } - p.Outputf("%s", string(output)) return nil } -func parseKubeConfigToExecCredential(kubeconfig *rest.Config) (*clientauthenticationv1.ExecCredential, error) { +func parseLoginKubeConfigToExecCredential(kubeconfig *rest.Config) ([]byte, error) { + if kubeconfig == nil { + return nil, errors.New("kubeconfig is nil") + } + certPem, _ := pem.Decode(kubeconfig.CertData) if certPem == nil { return nil, fmt.Errorf("decoded pem is nil") @@ -259,10 +287,127 @@ func parseKubeConfigToExecCredential(kubeconfig *rest.Config) (*clientauthentica Kind: "ExecCredential", }, Status: &clientauthenticationv1.ExecCredentialStatus{ - ExpirationTimestamp: &v1.Time{Time: certificate.NotAfter.Add(-time.Minute * 15)}, + ExpirationTimestamp: &v1.Time{Time: certificate.NotAfter.Add(-refreshBeforeDuration)}, ClientCertificateData: string(kubeconfig.CertData), ClientKeyData: string(kubeconfig.KeyData), }, } - return &outputExecCredential, nil + + output, err := json.Marshal(outputExecCredential) + if err != nil { + return nil, fmt.Errorf("marshal: %w", err) + } + return output, nil +} + +func getAccessToken(params *types.CmdParams) (string, error) { + userSessionExpired, err := auth.UserSessionExpired() + if err != nil { + return "", err + } + if userSessionExpired { + return "", &cliErr.SessionExpiredError{} + } + + accessToken, err := auth.GetValidAccessToken(params.Printer) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get valid access token: %v", err) + return "", &cliErr.SessionExpiredError{} + } + + err = auth.EnsureIDPTokenEndpoint(params.Printer) + if err != nil { + return "", err + } + + return accessToken, nil +} + +func retrieveTokenFromIDP(ctx context.Context, idpClient *http.Client, accessToken string, clusterConfig *clusterConfig) (string, error) { + resource := resourceForCluster(clusterConfig) + + cachedToken := getCachedToken(clusterConfig.cacheKey) + if cachedToken == "" { + return exchangeAndCacheToken(ctx, idpClient, accessToken, resource, clusterConfig.cacheKey) + } + + expiry, err := auth.TokenExpirationTime(cachedToken) + if err != nil { + // token is expired or invalid, request new + _ = cache.DeleteObject(clusterConfig.cacheKey) + return exchangeAndCacheToken(ctx, idpClient, accessToken, resource, clusterConfig.cacheKey) + } else if time.Now().Add(refreshTokenBeforeDuration).After(expiry) { + // token expires soon -> refresh + token, err := exchangeAndCacheToken(ctx, idpClient, accessToken, resource, clusterConfig.cacheKey) + // try to get a new one but use cache on failure + if err != nil { + return cachedToken, nil + } + return token, nil + } + // cached token is valid and won't expire soon + return cachedToken, nil +} + +func resourceForCluster(config *clusterConfig) string { + return fmt.Sprintf( + "resource://organizations/%s/projects/%s/regions/%s/ske/%s", + config.OrganizationID, + config.STACKITProjectID, + config.Region, + config.ClusterName, + ) +} + +func getCachedToken(key string) string { + token, err := cache.GetObject(key) + if err != nil { + return "" + } + return string(token) +} + +func exchangeAndCacheToken(ctx context.Context, idpClient *http.Client, accessToken, resource, cacheKey string) (string, error) { + clusterToken, err := auth.ExchangeToken(ctx, idpClient, accessToken, resource) + if err != nil { + return "", err + } + if err = cache.PutObject(cacheKey, []byte(clusterToken)); err != nil { + return "", fmt.Errorf("cache token: %w", err) + } + return clusterToken, err +} + +func outputTokenKubeconfig(p *print.Printer, cacheKey, token string) error { + output, err := parseTokenToExecCredential(token) + if err != nil { + _ = cache.DeleteObject(cacheKey) + return fmt.Errorf("convert to ExecCredential: %w", err) + } + + p.Outputf("%s", string(output)) + return nil +} + +func parseTokenToExecCredential(clusterToken string) ([]byte, error) { + expiry, err := auth.TokenExpirationTime(clusterToken) + if err != nil { + return nil, fmt.Errorf("parse auth token for cluster: %w", err) + } + + outputExecCredential := clientauthenticationv1.ExecCredential{ + TypeMeta: v1.TypeMeta{ + APIVersion: clientauthenticationv1.SchemeGroupVersion.String(), + Kind: "ExecCredential", + }, + Status: &clientauthenticationv1.ExecCredentialStatus{ + ExpirationTimestamp: &v1.Time{Time: expiry.Add(-refreshTokenBeforeDuration)}, + Token: clusterToken, + }, + } + output, err := json.Marshal(&outputExecCredential) + if err != nil { + return nil, fmt.Errorf("marshal: %w", err) + } + return output, nil } diff --git a/internal/cmd/ske/kubeconfig/login/login_test.go b/internal/cmd/ske/kubeconfig/login/login_test.go index ce22fbc1f..683171b95 100644 --- a/internal/cmd/ske/kubeconfig/login/login_test.go +++ b/internal/cmd/ske/kubeconfig/login/login_test.go @@ -2,25 +2,29 @@ package login import ( "context" + "encoding/json" "testing" "time" + "github.com/golang-jwt/jwt/v5" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" "k8s.io/client-go/rest" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} var testProjectId = uuid.NewString() var testClusterName = "cluster" +var testOrganization = uuid.NewString() const testRegion = "eu01" @@ -30,6 +34,7 @@ func fixtureClusterConfig(mods ...func(clusterConfig *clusterConfig)) *clusterCo ClusterName: testClusterName, cacheKey: "", Region: testRegion, + OrganizationID: testOrganization, } for _, mod := range mods { mod(clusterConfig) @@ -37,8 +42,8 @@ func fixtureClusterConfig(mods ...func(clusterConfig *clusterConfig)) *clusterCo return clusterConfig } -func fixtureRequest(mods ...func(request *ske.ApiCreateKubeconfigRequest)) ske.ApiCreateKubeconfigRequest { - request := testClient.CreateKubeconfig(testCtx, testProjectId, testRegion, testClusterName) +func fixtureLoginRequest(mods ...func(request *ske.ApiCreateKubeconfigRequest)) ske.ApiCreateKubeconfigRequest { + request := testClient.DefaultAPI.CreateKubeconfig(testCtx, testProjectId, testRegion, testClusterName) request = request.CreateKubeconfigPayload(ske.CreateKubeconfigPayload{}) for _, mod := range mods { mod(&request) @@ -55,18 +60,19 @@ func TestBuildRequest(t *testing.T) { { description: "expiration time", clusterConfig: fixtureClusterConfig(), - expectedRequest: fixtureRequest().CreateKubeconfigPayload(ske.CreateKubeconfigPayload{ + expectedRequest: fixtureLoginRequest().CreateKubeconfigPayload(ske.CreateKubeconfigPayload{ ExpirationSeconds: utils.Ptr("1800")}), }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - request := buildRequest(testCtx, testClient, tt.clusterConfig) + request := buildLoginKubeconfigRequest(testCtx, testClient, tt.clusterConfig) diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) @@ -127,17 +133,78 @@ zbRjZmli7cnenEnfnNoFIGbgkbjGXRUCIC5zFtWXFK7kA+B2vDxD0DlLcQodNwi4 for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - execCredential, err := parseKubeConfigToExecCredential(tt.kubeconfig) + execCredential, err := parseLoginKubeConfigToExecCredential(tt.kubeconfig) if err != nil { t.Fatalf("func returned error: %s", err) } if execCredential == nil { t.Fatal("execCredential is nil") } - diff := cmp.Diff(execCredential, tt.expectedExecCredentialRequest) + expected, _ := json.Marshal(tt.expectedExecCredentialRequest) + diff := cmp.Diff(execCredential, expected) if diff != "" { t.Fatalf("Data does not match: %s", diff) } }) } } + +func TestParseTokenToExecCredential(t *testing.T) { + expirationTime := time.Now().Add(30 * time.Minute) + expectedTime := expirationTime.Add(-5 * time.Minute) + token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + }).SigningString() + if err != nil { + t.Fatalf("token generation failed: %v", err) + } + token += ".signatureAAA" + + tests := []struct { + description string + token string + expectedExecCredentialRequest *clientauthenticationv1.ExecCredential + }{ + { + description: "expiration time", + token: token, + expectedExecCredentialRequest: &clientauthenticationv1.ExecCredential{ + TypeMeta: v1.TypeMeta{ + APIVersion: clientauthenticationv1.SchemeGroupVersion.String(), + Kind: "ExecCredential", + }, + Status: &clientauthenticationv1.ExecCredentialStatus{ + ExpirationTimestamp: &v1.Time{Time: expectedTime}, + Token: token, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + execCredential, err := parseTokenToExecCredential(tt.token) + if err != nil { + t.Fatalf("func returned error: %s", err) + } + if execCredential == nil { + t.Fatal("execCredential is nil") + } + expected, _ := json.Marshal(tt.expectedExecCredentialRequest) + diff := cmp.Diff(execCredential, expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestResourceForCluster(t *testing.T) { + cc := fixtureClusterConfig() + resource := resourceForCluster(cc) + // somewhat redundant, but the resource string must not change unexpectedly + expectedResource := "resource://organizations/" + testOrganization + "/projects/" + testProjectId + "/regions/" + testRegion + "/ske/" + testClusterName + if resource != expectedResource { + t.Fatalf("unexpected resource, got %v expected %v", resource, expectedResource) + } +} diff --git a/internal/cmd/ske/options/availability_zones/availability_zones.go b/internal/cmd/ske/options/availability_zones/availability_zones.go new file mode 100644 index 000000000..272da1eeb --- /dev/null +++ b/internal/cmd/ske/options/availability_zones/availability_zones.go @@ -0,0 +1,104 @@ +package availability_zones + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" +) + +type inputModel struct { + globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "availability-zones", + Short: "Lists SKE provider options for availability-zones", + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for availability-zones`, + "$ stackit ske options availability-zones"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: utils.PtrValue(globalFlags), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.DefaultAPI.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if options == nil { + return fmt.Errorf("options is nil") + } + + options.KubernetesVersions = nil + options.MachineImages = nil + options.MachineTypes = nil + options.VolumeTypes = nil + + return p.OutputResult(model.OutputFormat, options, func() error { + zones := options.AvailabilityZones + + table := tables.NewTable() + table.SetHeader("ZONE") + for i := range zones { + z := zones[i] + table.AddRow(utils.PtrValue(z.Name)) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/availability_zones/availability_zones_test.go b/internal/cmd/ske/options/availability_zones/availability_zones_test.go new file mode 100644 index 000000000..03229b4cf --- /dev/null +++ b/internal/cmd/ske/options/availability_zones/availability_zones_test.go @@ -0,0 +1,204 @@ +package availability_zones + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.DefaultAPI.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + AvailabilityZones: []ske.AvailabilityZone{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + AvailabilityZones: []ske.AvailabilityZone{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + AvailabilityZones: []ske.AvailabilityZone{ + { + Name: utils.Ptr("zone1"), + }, + { + Name: utils.Ptr("zone2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go new file mode 100644 index 000000000..65b21b4b8 --- /dev/null +++ b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go @@ -0,0 +1,136 @@ +package kubernetes_versions + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" +) + +const ( + supportedFlag = "supported" +) + +type inputModel struct { + globalflags.GlobalFlagModel + Supported bool +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "kubernetes-versions", + Short: "Lists SKE provider options for kubernetes-versions", + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for kubernetes-versions`, + "$ stackit ske options kubernetes-versions"), + examples.NewExample( + `List SKE options for supported kubernetes-versions`, + "$ stackit ske options kubernetes-versions --supported"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Bool(supportedFlag, false, "List supported versions only") +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: utils.PtrValue(globalFlags), + Supported: flags.FlagToBoolValue(p, cmd, supportedFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.DefaultAPI.ListProviderOptions(ctx, model.Region) + if model.Supported { + req = req.VersionState("SUPPORTED") + } + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if options == nil { + return fmt.Errorf("options is nil") + } + + options.AvailabilityZones = nil + options.MachineImages = nil + options.MachineTypes = nil + options.VolumeTypes = nil + + return p.OutputResult(model.OutputFormat, options, func() error { + versions := options.KubernetesVersions + + table := tables.NewTable() + table.SetHeader("VERSION", "STATE", "EXPIRATION DATE", "FEATURE GATES") + for i := range versions { + v := versions[i] + featureGate, err := json.Marshal(utils.PtrValue(v.FeatureGates)) + if err != nil { + return fmt.Errorf("marshal featureGates of Kubernetes version %q: %w", utils.PtrValue(v.Version), err) + } + expirationDate := "" + if v.ExpirationDate != nil { + expirationDate = v.ExpirationDate.Format(time.RFC3339) + } + table.AddRow( + utils.PtrString(v.Version), + utils.PtrString(v.State), + expirationDate, + string(featureGate)) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go new file mode 100644 index 000000000..54f246933 --- /dev/null +++ b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go @@ -0,0 +1,232 @@ +package kubernetes_versions + +import ( + "context" + "testing" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + supportedFlag: "false", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + Supported: false, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + { + description: "supported only", + flagValues: map[string]string{ + supportedFlag: "true", + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Supported = true + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.DefaultAPI.ListProviderOptions(testCtx, testRegion), + }, + { + description: "base", + inputModel: fixtureInputModel(func(model *inputModel) { + model.Supported = true + }), + expectedRequest: testClient.DefaultAPI.ListProviderOptions(testCtx, testRegion).VersionState("SUPPORTED"), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + KubernetesVersions: []ske.KubernetesVersion{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + KubernetesVersions: []ske.KubernetesVersion{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + KubernetesVersions: []ske.KubernetesVersion{ + { + FeatureGates: &map[string]string{ + "featureGate1": "foo", + "featureGate2": "bar", + }, + State: utils.Ptr("supported"), + Version: utils.Ptr("0.00.0"), + }, + { + ExpirationDate: utils.Ptr(time.Now()), + State: utils.Ptr("deprecated"), + Version: utils.Ptr("0.00.0"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/machine_images/machine_images.go b/internal/cmd/ske/options/machine_images/machine_images.go new file mode 100644 index 000000000..94110cf6f --- /dev/null +++ b/internal/cmd/ske/options/machine_images/machine_images.go @@ -0,0 +1,128 @@ +package machine_images + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type inputModel struct { + globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "machine-images", + Short: "Lists SKE provider options for machine-images", + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for machine-images`, + "$ stackit ske options machine-images"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: utils.PtrValue(globalFlags), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.DefaultAPI.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if options == nil { + return fmt.Errorf("options is nil") + } + + options.AvailabilityZones = nil + options.KubernetesVersions = nil + options.MachineTypes = nil + options.VolumeTypes = nil + + return p.OutputResult(model.OutputFormat, options, func() error { + images := options.MachineImages + + table := tables.NewTable() + table.SetHeader("NAME", "VERSION", "STATE", "EXPIRATION DATE", "SUPPORTED CRI") + for i := range images { + image := images[i] + versions := image.Versions + for j := range versions { + version := versions[j] + criNames := make([]string, 0) + for i := range version.Cri { + cri := version.Cri[i] + criNames = append(criNames, utils.PtrString(cri.Name)) + } + criNamesString := strings.Join(criNames, ", ") + + expirationDate := "-" + if version.ExpirationDate != nil { + expirationDate = version.ExpirationDate.Format(time.RFC3339) + } + table.AddRow( + utils.PtrString(image.Name), + utils.PtrString(version.Version), + utils.PtrString(version.State), + expirationDate, + criNamesString, + ) + } + } + table.EnableAutoMergeOnColumns(1) + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/machine_images/machine_images_test.go b/internal/cmd/ske/options/machine_images/machine_images_test.go new file mode 100644 index 000000000..ad4658757 --- /dev/null +++ b/internal/cmd/ske/options/machine_images/machine_images_test.go @@ -0,0 +1,217 @@ +package machine_images + +import ( + "context" + "testing" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.DefaultAPI.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineImages: []ske.MachineImage{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineImages: []ske.MachineImage{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineImages: []ske.MachineImage{ + { + Name: utils.Ptr("image1"), + Versions: []ske.MachineImageVersion{ + { + Cri: []ske.CRI{ + { + Name: utils.Ptr("containerd"), + }, + }, + ExpirationDate: utils.Ptr(time.Now()), + State: utils.Ptr("supported"), + Version: utils.Ptr("0.00.0"), + }, + }, + }, + { + Name: utils.Ptr("zone2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/machine_types/machine_types.go b/internal/cmd/ske/options/machine_types/machine_types.go new file mode 100644 index 000000000..7fce1c279 --- /dev/null +++ b/internal/cmd/ske/options/machine_types/machine_types.go @@ -0,0 +1,108 @@ +package machine_types + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type inputModel struct { + globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "machine-types", + Short: "Lists SKE provider options for machine-types", + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for machine-types`, + "$ stackit ske options machine-types"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: utils.PtrValue(globalFlags), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.DefaultAPI.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if options == nil { + return fmt.Errorf("options is nil") + } + + options.AvailabilityZones = nil + options.KubernetesVersions = nil + options.MachineImages = nil + options.VolumeTypes = nil + + return p.OutputResult(model.OutputFormat, options, func() error { + machineTypes := options.MachineTypes + + table := tables.NewTable() + table.SetHeader("TYPE", "CPU", "MEMORY") + for i := range machineTypes { + t := machineTypes[i] + table.AddRow( + utils.PtrString(t.Name), + utils.PtrString(t.Cpu), + utils.PtrString(t.Memory), + ) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/machine_types/machine_types_test.go b/internal/cmd/ske/options/machine_types/machine_types_test.go new file mode 100644 index 000000000..ef405139f --- /dev/null +++ b/internal/cmd/ske/options/machine_types/machine_types_test.go @@ -0,0 +1,212 @@ +package machine_types + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.DefaultAPI.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineTypes: []ske.MachineType{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineTypes: []ske.MachineType{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineTypes: []ske.MachineType{ + { + Architecture: utils.Ptr("amd64"), + Cpu: utils.Ptr(int32(2)), + Gpu: utils.Ptr(int32(0)), + Memory: utils.Ptr(int32(16)), + Name: utils.Ptr("type1"), + }, + { + Architecture: utils.Ptr("amd64"), + Cpu: utils.Ptr(int32(2)), + Gpu: utils.Ptr(int32(0)), + Memory: utils.Ptr(int32(16)), + Name: utils.Ptr("type2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/options.go b/internal/cmd/ske/options/options.go index 21f04d028..8eb90ae9c 100644 --- a/internal/cmd/ske/options/options.go +++ b/internal/cmd/ske/options/options.go @@ -7,18 +7,23 @@ import ( "strings" "time" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/availability_zones" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/kubernetes_versions" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/machine_images" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/machine_types" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/volume_types" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) const ( @@ -30,7 +35,7 @@ const ( ) type inputModel struct { - *globalflags.GlobalFlagModel + globalflags.GlobalFlagModel AvailabilityZones bool KubernetesVersions bool MachineImages bool @@ -42,23 +47,15 @@ func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "options", Short: "Lists SKE provider options", - Long: fmt.Sprintf("%s\n%s", + Long: fmt.Sprintf("%s\n%s\n%s", + "Command \"options\" is deprecated, use the subcommands instead.", "Lists STACKIT Kubernetes Engine (SKE) provider options (availability zones, Kubernetes versions, machine images and types, volume types).", "Pass one or more flags to filter what categories are shown.", ), Args: args.NoArgs, - Example: examples.Build( - examples.NewExample( - `List SKE options for all categories`, - "$ stackit ske options"), - examples.NewExample( - `List SKE options regarding Kubernetes versions only`, - "$ stackit ske options --kubernetes-versions"), - examples.NewExample( - `List SKE options regarding Kubernetes versions and machine images`, - "$ stackit ske options --kubernetes-versions --machine-images"), - ), RunE: func(cmd *cobra.Command, args []string) error { + params.Printer.Info("Command \"options\" is deprecated, use the subcommands instead.\n") + ctx := context.Background() model, err := parseInput(params.Printer, cmd, args) if err != nil { @@ -82,15 +79,30 @@ func NewCmd(params *types.CmdParams) *cobra.Command { }, } configureFlags(cmd) + addSubcommands(cmd, params) return cmd } +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(availability_zones.NewCmd(params)) + cmd.AddCommand(kubernetes_versions.NewCmd(params)) + cmd.AddCommand(machine_images.NewCmd(params)) + cmd.AddCommand(machine_types.NewCmd(params)) + cmd.AddCommand(volume_types.NewCmd(params)) +} + func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(availabilityZonesFlag, false, "Lists availability zones") cmd.Flags().Bool(kubernetesVersionsFlag, false, "Lists supported kubernetes versions") cmd.Flags().Bool(machineImagesFlag, false, "Lists supported machine images") cmd.Flags().Bool(machineTypesFlag, false, "Lists supported machine types") cmd.Flags().Bool(volumeTypesFlag, false, "Lists supported volume types") + + cobra.CheckErr(cmd.Flags().MarkDeprecated(availabilityZonesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the availability-zone subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(kubernetesVersionsFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the kubernetes-versions subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(machineImagesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the machine-images subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(machineTypesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the machine-types subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(volumeTypesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the volume-types subcommand instead.")) } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { @@ -111,7 +123,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } model := inputModel{ - GlobalFlagModel: globalFlags, + GlobalFlagModel: utils.PtrValue(globalFlags), AvailabilityZones: availabilityZones, KubernetesVersions: kubernetesVersions, MachineImages: machineImages, @@ -124,12 +136,12 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { - req := apiClient.ListProviderOptions(ctx, model.Region) + req := apiClient.DefaultAPI.ListProviderOptions(ctx, model.Region) return req } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil || model.GlobalFlagModel == nil { + if model == nil { return fmt.Errorf("model is nil") } else if options == nil { return fmt.Errorf("options is nil") @@ -167,11 +179,11 @@ func outputResultAsTable(p *print.Printer, options *ske.ProviderOptions) error { } content := []tables.Table{} - if options.AvailabilityZones != nil && len(*options.AvailabilityZones) != 0 { + if len(options.AvailabilityZones) != 0 { content = append(content, buildAvailabilityZonesTable(options)) } - if options.KubernetesVersions != nil && len(*options.KubernetesVersions) != 0 { + if len(options.KubernetesVersions) != 0 { kubernetesVersionsTable, err := buildKubernetesVersionsTable(options) if err != nil { return fmt.Errorf("build Kubernetes versions table: %w", err) @@ -179,15 +191,15 @@ func outputResultAsTable(p *print.Printer, options *ske.ProviderOptions) error { content = append(content, kubernetesVersionsTable) } - if options.MachineImages != nil && len(*options.MachineImages) != 0 { + if len(options.MachineImages) != 0 { content = append(content, buildMachineImagesTable(options)) } - if options.MachineTypes != nil && len(*options.MachineTypes) != 0 { + if len(options.MachineTypes) != 0 { content = append(content, buildMachineTypesTable(options)) } - if options.VolumeTypes != nil && len(*options.VolumeTypes) != 0 { + if len(options.VolumeTypes) != 0 { content = append(content, buildVolumeTypesTable(options)) } @@ -200,7 +212,7 @@ func outputResultAsTable(p *print.Printer, options *ske.ProviderOptions) error { } func buildAvailabilityZonesTable(resp *ske.ProviderOptions) tables.Table { - zones := *resp.AvailabilityZones + zones := resp.AvailabilityZones table := tables.NewTable() table.SetTitle("Availability Zones") @@ -213,7 +225,7 @@ func buildAvailabilityZonesTable(resp *ske.ProviderOptions) tables.Table { } func buildKubernetesVersionsTable(resp *ske.ProviderOptions) (tables.Table, error) { - versions := *resp.KubernetesVersions + versions := resp.KubernetesVersions table := tables.NewTable() table.SetTitle("Kubernetes Versions") @@ -238,19 +250,19 @@ func buildKubernetesVersionsTable(resp *ske.ProviderOptions) (tables.Table, erro } func buildMachineImagesTable(resp *ske.ProviderOptions) tables.Table { - images := *resp.MachineImages + images := resp.MachineImages table := tables.NewTable() table.SetTitle("Machine Images") table.SetHeader("NAME", "VERSION", "STATE", "EXPIRATION DATE", "SUPPORTED CRI") for i := range images { image := images[i] - versions := *image.Versions + versions := image.Versions for j := range versions { version := versions[j] criNames := make([]string, 0) - for i := range *version.Cri { - cri := (*version.Cri)[i] + for i := range version.Cri { + cri := version.Cri[i] criNames = append(criNames, string(*cri.Name)) } criNamesString := strings.Join(criNames, ", ") @@ -273,7 +285,7 @@ func buildMachineImagesTable(resp *ske.ProviderOptions) tables.Table { } func buildMachineTypesTable(resp *ske.ProviderOptions) tables.Table { - machineTypes := *resp.MachineTypes + machineTypes := resp.MachineTypes table := tables.NewTable() table.SetTitle("Machine Types") @@ -290,7 +302,7 @@ func buildMachineTypesTable(resp *ske.ProviderOptions) tables.Table { } func buildVolumeTypesTable(resp *ske.ProviderOptions) tables.Table { - volumeTypes := *resp.VolumeTypes + volumeTypes := resp.VolumeTypes table := tables.NewTable() table.SetTitle("Volume Types") diff --git a/internal/cmd/ske/options/options_test.go b/internal/cmd/ske/options/options_test.go index 43f58c5b4..52ebe0a26 100644 --- a/internal/cmd/ske/options/options_test.go +++ b/internal/cmd/ske/options/options_test.go @@ -12,13 +12,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} const testRegion = "eu01" @@ -39,7 +39,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st func fixtureInputModelAllFalse(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, + GlobalFlagModel: globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, AvailabilityZones: false, KubernetesVersions: false, MachineImages: false, @@ -54,7 +54,7 @@ func fixtureInputModelAllFalse(mods ...func(model *inputModel)) *inputModel { func fixtureInputModelAllTrue(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, + GlobalFlagModel: globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, AvailabilityZones: true, KubernetesVersions: true, MachineImages: true, @@ -142,7 +142,7 @@ func TestBuildRequest(t *testing.T) { }{ { description: "base", - expectedRequest: testClient.ListProviderOptions(testCtx, testRegion), + expectedRequest: testClient.DefaultAPI.ListProviderOptions(testCtx, testRegion), }, } @@ -153,6 +153,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) @@ -187,24 +188,24 @@ func TestOutputResult(t *testing.T) { name: "missing options", args: args{ model: &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{}, + GlobalFlagModel: globalflags.GlobalFlagModel{}, }, }, wantErr: true, }, { - name: "missing global flags in model", + name: "empty input model", args: args{ model: &inputModel{}, options: &ske.ProviderOptions{}, }, - wantErr: true, + wantErr: false, }, { name: "set model and options", args: args{ model: &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{}, + GlobalFlagModel: globalflags.GlobalFlagModel{}, }, options: &ske.ProviderOptions{}, }, diff --git a/internal/cmd/ske/options/volume_types/volume_types.go b/internal/cmd/ske/options/volume_types/volume_types.go new file mode 100644 index 000000000..1b4943ae4 --- /dev/null +++ b/internal/cmd/ske/options/volume_types/volume_types.go @@ -0,0 +1,104 @@ +package volume_types + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" +) + +type inputModel struct { + globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "volume-types", + Short: "Lists SKE provider options for volume-types", + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for volume-types`, + "$ stackit ske options volume-types"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: utils.PtrValue(globalFlags), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.DefaultAPI.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if options == nil { + return fmt.Errorf("options is nil") + } + + options.AvailabilityZones = nil + options.KubernetesVersions = nil + options.MachineImages = nil + options.MachineTypes = nil + + return p.OutputResult(model.OutputFormat, options, func() error { + volumeTypes := options.VolumeTypes + + table := tables.NewTable() + table.SetHeader("TYPE") + for i := range volumeTypes { + z := volumeTypes[i] + table.AddRow(utils.PtrString(z.Name)) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/volume_types/volume_types_test.go b/internal/cmd/ske/options/volume_types/volume_types_test.go new file mode 100644 index 000000000..ea926f3f0 --- /dev/null +++ b/internal/cmd/ske/options/volume_types/volume_types_test.go @@ -0,0 +1,204 @@ +package volume_types + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{DefaultAPI: &ske.DefaultAPIService{}} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.DefaultAPI.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + cmpopts.EquateComparable(testClient.DefaultAPI), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + VolumeTypes: []ske.VolumeType{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + VolumeTypes: []ske.VolumeType{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + VolumeTypes: []ske.VolumeType{ + { + Name: utils.Ptr("type1"), + }, + { + Name: utils.Ptr("type2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/volume/backup/create/create.go b/internal/cmd/volume/backup/create/create.go index fc9e0b3cd..c2211a3ad 100644 --- a/internal/cmd/volume/backup/create/create.go +++ b/internal/cmd/volume/backup/create/create.go @@ -115,13 +115,12 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating backup") - resp, err = wait.CreateBackupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, volumeId).WaitWithContext(ctx) + resp, err = spinner.Run2(params.Printer, "Creating backup", func() (*iaas.Backup, error) { + return wait.CreateBackupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, volumeId).WaitWithContext(ctx) + }) if err != nil { return fmt.Errorf("wait for backup creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model.OutputFormat, model.Async, sourceLabel, projectLabel, resp) diff --git a/internal/cmd/volume/backup/delete/delete.go b/internal/cmd/volume/backup/delete/delete.go index 9c9717427..fdb2fe458 100644 --- a/internal/cmd/volume/backup/delete/delete.go +++ b/internal/cmd/volume/backup/delete/delete.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,9 +17,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - iaasutils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + + iaasutils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" ) const ( @@ -73,13 +75,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting backup") - _, err = wait.DeleteBackupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BackupId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting backup", func() error { + _, err = wait.DeleteBackupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BackupId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for backup deletion: %w", err) } - s.Stop() } if model.Async { diff --git a/internal/cmd/volume/backup/describe/describe.go b/internal/cmd/volume/backup/describe/describe.go index c8c1b66d1..78cc4790e 100644 --- a/internal/cmd/volume/backup/describe/describe.go +++ b/internal/cmd/volume/backup/describe/describe.go @@ -8,6 +8,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/volume/backup/list/list.go b/internal/cmd/volume/backup/list/list.go index c434b3df5..9da088d13 100644 --- a/internal/cmd/volume/backup/list/list.go +++ b/internal/cmd/volume/backup/list/list.go @@ -8,6 +8,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,7 +20,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/backup/restore/restore.go b/internal/cmd/volume/backup/restore/restore.go index 63fc0d915..d98478dee 100644 --- a/internal/cmd/volume/backup/restore/restore.go +++ b/internal/cmd/volume/backup/restore/restore.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,9 +17,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - iaasutils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + + iaasutils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" ) const ( @@ -86,13 +88,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Restoring backup") - _, err = wait.RestoreBackupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BackupId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Restoring backup", func() error { + _, err = wait.RestoreBackupWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BackupId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for backup restore: %w", err) } - s.Stop() } if model.Async { diff --git a/internal/cmd/volume/backup/update/update.go b/internal/cmd/volume/backup/update/update.go index 9141f9a2b..4a61e582a 100644 --- a/internal/cmd/volume/backup/update/update.go +++ b/internal/cmd/volume/backup/update/update.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/volume/create/create.go b/internal/cmd/volume/create/create.go index f0bda5f69..f0c672602 100644 --- a/internal/cmd/volume/create/create.go +++ b/internal/cmd/volume/create/create.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -104,13 +105,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating volume") - _, err = wait.CreateVolumeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, volumeId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Creating volume", func() error { + _, err = wait.CreateVolumeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, volumeId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for volume creation: %w", err) } - s.Stop() } return outputResult(params.Printer, model, projectLabel, resp) @@ -184,7 +185,11 @@ func outputResult(p *print.Printer, model *inputModel, projectLabel string, volu return fmt.Errorf("volume response is empty") } return p.OutputResult(model.OutputFormat, volume, func() error { - p.Outputf("Created volume for project %q.\nVolume ID: %s\n", projectLabel, utils.PtrString(volume.Id)) + operationState := "Created" + if model.Async { + operationState = "Triggered creation of" + } + p.Outputf("%s volume for project %q.\nVolume ID: %s\n", operationState, projectLabel, utils.PtrString(volume.Id)) return nil }) } diff --git a/internal/cmd/volume/create/create_test.go b/internal/cmd/volume/create/create_test.go index 9628fd508..ad088cdb8 100644 --- a/internal/cmd/volume/create/create_test.go +++ b/internal/cmd/volume/create/create_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/delete/delete.go b/internal/cmd/volume/delete/delete.go index 599f3bf56..11984713b 100644 --- a/internal/cmd/volume/delete/delete.go +++ b/internal/cmd/volume/delete/delete.go @@ -6,6 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,8 +18,6 @@ import ( iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/spf13/cobra" ) @@ -79,13 +80,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting volume") - _, err = wait.DeleteVolumeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.VolumeId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting volume", func() error { + _, err = wait.DeleteVolumeWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.VolumeId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for volume deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/volume/describe/describe.go b/internal/cmd/volume/describe/describe.go index 8ff8dbfab..e8c009a65 100644 --- a/internal/cmd/volume/describe/describe.go +++ b/internal/cmd/volume/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/volume/describe/describe_test.go b/internal/cmd/volume/describe/describe_test.go index eb595b37e..6aa31f41e 100644 --- a/internal/cmd/volume/describe/describe_test.go +++ b/internal/cmd/volume/describe/describe_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/list/list.go b/internal/cmd/volume/list/list.go index 5d82bad1a..fa4b2c1f0 100644 --- a/internal/cmd/volume/list/list.go +++ b/internal/cmd/volume/list/list.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/list/list_test.go b/internal/cmd/volume/list/list_test.go index d81ee310f..924acee29 100644 --- a/internal/cmd/volume/list/list_test.go +++ b/internal/cmd/volume/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/performance-class/describe/describe.go b/internal/cmd/volume/performance-class/describe/describe.go index 23c763e84..c75a3cb50 100644 --- a/internal/cmd/volume/performance-class/describe/describe.go +++ b/internal/cmd/volume/performance-class/describe/describe.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/volume/performance-class/describe/describe_test.go b/internal/cmd/volume/performance-class/describe/describe_test.go index d24d96eaf..aafac6197 100644 --- a/internal/cmd/volume/performance-class/describe/describe_test.go +++ b/internal/cmd/volume/performance-class/describe/describe_test.go @@ -11,9 +11,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/performance-class/list/list.go b/internal/cmd/volume/performance-class/list/list.go index 7062011aa..ae62dd65d 100644 --- a/internal/cmd/volume/performance-class/list/list.go +++ b/internal/cmd/volume/performance-class/list/list.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/volume/performance-class/list/list_test.go b/internal/cmd/volume/performance-class/list/list_test.go index 53004a31b..79a1b29c8 100644 --- a/internal/cmd/volume/performance-class/list/list_test.go +++ b/internal/cmd/volume/performance-class/list/list_test.go @@ -9,11 +9,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/resize/resize.go b/internal/cmd/volume/resize/resize.go index 0bfbc0797..1207eee46 100644 --- a/internal/cmd/volume/resize/resize.go +++ b/internal/cmd/volume/resize/resize.go @@ -6,6 +6,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -15,7 +17,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/cobra" ) diff --git a/internal/cmd/volume/snapshot/create/create.go b/internal/cmd/volume/snapshot/create/create.go index f63a38b16..836794612 100644 --- a/internal/cmd/volume/snapshot/create/create.go +++ b/internal/cmd/volume/snapshot/create/create.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -94,13 +95,12 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Creating snapshot") - resp, err = wait.CreateSnapshotWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *resp.Id).WaitWithContext(ctx) + resp, err = spinner.Run2(params.Printer, "Creating snapshot", func() (*iaas.Snapshot, error) { + return wait.CreateSnapshotWaitHandler(ctx, apiClient, model.ProjectId, model.Region, *resp.Id).WaitWithContext(ctx) + }) if err != nil { return fmt.Errorf("wait for snapshot creation: %w", err) } - s.Stop() } operationState := "Created" diff --git a/internal/cmd/volume/snapshot/create/create_test.go b/internal/cmd/volume/snapshot/create/create_test.go index d2ee79608..7dbc2681f 100644 --- a/internal/cmd/volume/snapshot/create/create_test.go +++ b/internal/cmd/volume/snapshot/create/create_test.go @@ -7,10 +7,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type testCtxKey struct{} diff --git a/internal/cmd/volume/snapshot/delete/delete.go b/internal/cmd/volume/snapshot/delete/delete.go index 7334cac51..9a60e5a13 100644 --- a/internal/cmd/volume/snapshot/delete/delete.go +++ b/internal/cmd/volume/snapshot/delete/delete.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -76,13 +77,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Wait for async operation, if async mode not enabled if !model.Async { - s := spinner.New(params.Printer) - s.Start("Deleting snapshot") - _, err = wait.DeleteSnapshotWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.SnapshotId).WaitWithContext(ctx) + err := spinner.Run(params.Printer, "Deleting snapshot", func() error { + _, err = wait.DeleteSnapshotWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.SnapshotId).WaitWithContext(ctx) + return err + }) if err != nil { return fmt.Errorf("wait for snapshot deletion: %w", err) } - s.Stop() } operationState := "Deleted" diff --git a/internal/cmd/volume/snapshot/describe/describe.go b/internal/cmd/volume/snapshot/describe/describe.go index c87336496..85e9e0fe2 100644 --- a/internal/cmd/volume/snapshot/describe/describe.go +++ b/internal/cmd/volume/snapshot/describe/describe.go @@ -8,6 +8,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/volume/snapshot/list/list.go b/internal/cmd/volume/snapshot/list/list.go index 70b97edf2..a2d312b26 100644 --- a/internal/cmd/volume/snapshot/list/list.go +++ b/internal/cmd/volume/snapshot/list/list.go @@ -8,6 +8,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -18,8 +19,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" ) const ( diff --git a/internal/cmd/volume/snapshot/snapshot.go b/internal/cmd/volume/snapshot/snapshot.go index 579233ca9..09640dc9b 100644 --- a/internal/cmd/volume/snapshot/snapshot.go +++ b/internal/cmd/volume/snapshot/snapshot.go @@ -2,6 +2,7 @@ package snapshot import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/volume/snapshot/create" "github.com/stackitcloud/stackit-cli/internal/cmd/volume/snapshot/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/volume/snapshot/describe" diff --git a/internal/cmd/volume/snapshot/update/update.go b/internal/cmd/volume/snapshot/update/update.go index f48649743..5b83aaeaf 100644 --- a/internal/cmd/volume/snapshot/update/update.go +++ b/internal/cmd/volume/snapshot/update/update.go @@ -7,6 +7,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" diff --git a/internal/cmd/volume/update/update.go b/internal/cmd/volume/update/update.go index 3c8f447d3..34fd7e31a 100644 --- a/internal/cmd/volume/update/update.go +++ b/internal/cmd/volume/update/update.go @@ -7,6 +7,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,7 +18,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/cmd/volume/update/update_test.go b/internal/cmd/volume/update/update_test.go index 0f34841aa..da2d05b7e 100644 --- a/internal/cmd/volume/update/update_test.go +++ b/internal/cmd/volume/update/update_test.go @@ -9,10 +9,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) const ( diff --git a/internal/pkg/auth/auth.go b/internal/pkg/auth/auth.go index dd56536d3..685a1737a 100644 --- a/internal/pkg/auth/auth.go +++ b/internal/pkg/auth/auth.go @@ -25,7 +25,7 @@ type tokenClaims struct { // // If the user was logged in and the user session expired, reauthorizeUserRoutine is called to reauthenticate the user again. // If the environment variable STACKIT_ACCESS_TOKEN is set this token is used instead. -func AuthenticationConfig(p *print.Printer, reauthorizeUserRoutine func(p *print.Printer, _ bool) error) (authCfgOption sdkConfig.ConfigurationOption, err error) { +func AuthenticationConfig(p *print.Printer, reauthorizeUserRoutine func(p *print.Printer, _ UserAuthConfig) error) (authCfgOption sdkConfig.ConfigurationOption, err error) { // Get access token from env and use this if present accessToken := os.Getenv(envAccessTokenName) if accessToken != "" { @@ -70,7 +70,10 @@ func AuthenticationConfig(p *print.Printer, reauthorizeUserRoutine func(p *print case AUTH_FLOW_USER_TOKEN: p.Debug(print.DebugLevel, "authenticating using user token") if userSessionExpired { - err = reauthorizeUserRoutine(p, true) + err = reauthorizeUserRoutine(p, UserAuthConfig{ + IsReauthentication: true, + Port: nil, + }) if err != nil { return nil, fmt.Errorf("user login: %w", err) } @@ -216,3 +219,18 @@ func GetValidAccessToken(p *print.Printer) (string, error) { // Return the new access token return utf.accessToken, nil } + +// EnsureIDPTokenEndpoint ensures that the `IDP_TOKEN_ENDPOINT` auth field is set. +// This field is by default only initialized for user accounts. Call this method to also +// initialize it for service accounts. +func EnsureIDPTokenEndpoint(p *print.Printer) error { + idpTokenEndpoint, err := GetAuthField(IDP_TOKEN_ENDPOINT) + if err != nil { + return fmt.Errorf("failed to check idp token endpoint configuration value: %w", err) + } + if idpTokenEndpoint == "" { + _, err := retrieveIDPWellKnownConfig(p) + return err + } + return nil +} diff --git a/internal/pkg/auth/auth_test.go b/internal/pkg/auth/auth_test.go index 4a7c87dbc..134850263 100644 --- a/internal/pkg/auth/auth_test.go +++ b/internal/pkg/auth/auth_test.go @@ -18,10 +18,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-sdk-go/core/clients" sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/zalando/go-keyring" + + "github.com/stackitcloud/stackit-cli/internal/pkg/print" ) const saKeyStrPattern = `{ @@ -191,7 +192,7 @@ func TestAuthenticationConfig(t *testing.T) { } reauthorizeUserCalled := false - reauthenticateUser := func(_ *print.Printer, _ bool) error { + reauthenticateUser := func(_ *print.Printer, _ UserAuthConfig) error { if reauthorizeUserCalled { t.Errorf("user reauthorized more than once") } diff --git a/internal/pkg/auth/exchange.go b/internal/pkg/auth/exchange.go new file mode 100644 index 000000000..0c005d237 --- /dev/null +++ b/internal/pkg/auth/exchange.go @@ -0,0 +1,90 @@ +package auth + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +func ExchangeToken(ctx context.Context, idpClient *http.Client, accessToken, resource string) (string, error) { + tokenEndpoint, err := GetAuthField(IDP_TOKEN_ENDPOINT) + if err != nil { + return "", fmt.Errorf("get idp token endpoint: %w", err) + } + + req, err := buildRequestToExchangeTokens(ctx, tokenEndpoint, accessToken, resource) + if err != nil { + return "", fmt.Errorf("build request: %w", err) + } + resp, err := idpClient.Do(req) + if err != nil { + return "", fmt.Errorf("call API: %w", err) + } + defer func() { + tempErr := resp.Body.Close() + if tempErr != nil { + err = fmt.Errorf("close response body: %w", tempErr) + } + }() + + clusterToken, err := parseTokenExchangeResponse(resp) + if err != nil { + return "", fmt.Errorf("parse API response: %w", err) + } + return clusterToken, nil +} + +func buildRequestToExchangeTokens(ctx context.Context, tokenEndpoint, accessToken, resource string) (*http.Request, error) { + idpClientID, err := getIDPClientID() + if err != nil { + return nil, err + } + + form := url.Values{} + form.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") + form.Set("client_id", idpClientID) + form.Set("subject_token_type", "urn:ietf:params:oauth:token-type:access_token") + form.Set("requested_token_type", "urn:ietf:params:oauth:token-type:id_token") + form.Set("scope", "openid profile email groups") + form.Set("subject_token", accessToken) + form.Set("resource", resource) + + req, err := http.NewRequestWithContext( + ctx, + http.MethodPost, + tokenEndpoint, + strings.NewReader(form.Encode()), + ) + if err != nil { + return nil, fmt.Errorf("build exchange request: %w", err) + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + return req, nil +} + +func parseTokenExchangeResponse(resp *http.Response) (accessToken string, err error) { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("read body: %w", err) + } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("non-OK %d status: %s", resp.StatusCode, string(respBody)) + } + + respContent := struct { + AccessToken string `json:"access_token"` + }{} + err = json.Unmarshal(respBody, &respContent) + if err != nil { + return "", fmt.Errorf("unmarshal body: %w", err) + } + if respContent.AccessToken == "" { + return "", fmt.Errorf("no access token found") + } + return respContent.AccessToken, nil +} diff --git a/internal/pkg/auth/exchange_test.go b/internal/pkg/auth/exchange_test.go new file mode 100644 index 000000000..77d49ac3b --- /dev/null +++ b/internal/pkg/auth/exchange_test.go @@ -0,0 +1,187 @@ +package auth + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/http/httputil" + "net/url" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/zalando/go-keyring" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + +var testAccessToken = "access-token-test-" + uuid.NewString() +var testExchangedToken = "access-token-exchanged-" + uuid.NewString() +var testExchangeResource = "resource://for/token/exchange" + +func fixtureTokenExchangeRequest(tokenEndpoint string) *http.Request { + form := url.Values{} + form.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") + form.Set("client_id", "stackit-cli-0000-0000-000000000001") + form.Set("subject_token_type", "urn:ietf:params:oauth:token-type:access_token") + form.Set("requested_token_type", "urn:ietf:params:oauth:token-type:id_token") + form.Set("scope", "openid profile email groups") + form.Set("subject_token", testAccessToken) + form.Set("resource", testExchangeResource) + + req, _ := http.NewRequestWithContext( + testCtx, + http.MethodPost, + tokenEndpoint, + strings.NewReader(form.Encode()), + ) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + return req +} + +func fixtureTokenExchangeResponse() string { + type exchangeReponse struct { + AccessToken string `json:"access_token"` + IssuedTokeType string `json:"issued_token_type"` + TokenType string `json:"token_type"` + } + response, _ := json.Marshal(exchangeReponse{ + AccessToken: testExchangedToken, + IssuedTokeType: "urn:ietf:params:oauth:token-type:id_token", + TokenType: "Bearer", + }) + return string(response) +} + +func TestBuildTokenExchangeRequest(t *testing.T) { + expectedRequest := fixtureTokenExchangeRequest(testTokenEndpoint) + req, err := buildRequestToExchangeTokens(testCtx, testTokenEndpoint, testAccessToken, testExchangeResource) + if err != nil { + t.Fatalf("func returned error: %s", err) + } + // directly using cmp.Diff is not possible, so dump the requests first + expected, err := httputil.DumpRequest(expectedRequest, true) + if err != nil { + t.Fatalf("fail to dump expected: %s", err) + } + actual, err := httputil.DumpRequest(req, true) + if err != nil { + t.Fatalf("fail to dump actual: %s", err) + } + diff := cmp.Diff(actual, expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } +} + +func TestParseTokenExchangeResponse(t *testing.T) { + response := fixtureTokenExchangeResponse() + + tests := []struct { + description string + response string + status int + expectError bool + }{ + { + description: "valid response", + response: response, + status: http.StatusOK, + }, + { + description: "error status", + response: response, // valid response to make sure the status code is checked + status: http.StatusForbidden, + expectError: true, + }, + { + description: "error content", + response: "{}", + status: http.StatusOK, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + w := httptest.NewRecorder() + w.WriteHeader(tt.status) + _, _ = w.WriteString(tt.response) + resp := w.Result() + + defer func() { + tempErr := resp.Body.Close() + if tempErr != nil { + t.Fatalf("failed to close response body: %v", tempErr) + } + }() + accessToken, err := parseTokenExchangeResponse(resp) + if tt.expectError { + if err == nil { + t.Fatal("expected error got nil") + } + } else { + if err != nil { + t.Fatalf("func returned error: %s", err) + } + diff := cmp.Diff(accessToken, testExchangedToken) + if diff != "" { + t.Fatalf("Token does not match: %s", diff) + } + } + }) + } +} + +func TestExchangeToken(t *testing.T) { + var request *http.Request + response := fixtureTokenExchangeResponse() + + handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // only compare body as the headers will differ + expected, err := io.ReadAll(request.Body) + if err != nil { + t.Errorf("fail to dump expected: %s", err) + } + actual, err := io.ReadAll(req.Body) + if err != nil { + t.Errorf("fail to dump actual: %s", err) + } + diff := cmp.Diff(actual, expected) + if diff != "" { + w.WriteHeader(http.StatusBadRequest) + t.Errorf("request mismatch: %v", diff) + return + } + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write([]byte(response)) + if err != nil { + t.Errorf("Failed to write response: %v", err) + } + }) + server := httptest.NewServer(handler) + defer server.Close() + + request = fixtureTokenExchangeRequest(server.URL) + // use mock keyring to inject the token endpoint URL + keyring.MockInit() + err := SetAuthField(IDP_TOKEN_ENDPOINT, server.URL) + if err != nil { + t.Errorf("failed to inject idp token endpoint: %s", err) + } + + idToken, err := ExchangeToken(testCtx, server.Client(), testAccessToken, testExchangeResource) + if err != nil { + t.Fatalf("func returned error: %s", err) + } + diff := cmp.Diff(idToken, testExchangedToken) + if diff != "" { + t.Fatalf("Exchanged token does not match: %s", diff) + } +} diff --git a/internal/pkg/auth/service_account.go b/internal/pkg/auth/service_account.go index 02449e2cc..8aecaf0b4 100644 --- a/internal/pkg/auth/service_account.go +++ b/internal/pkg/auth/service_account.go @@ -80,6 +80,8 @@ func AuthenticateServiceAccount(p *print.Printer, rt http.RoundTripper, disableW return "", "", fmt.Errorf("compute session expiration timestamp: %w", err) } authFields[SESSION_EXPIRES_AT_UNIX] = sessionExpiresAtUnix + // clear idp token endpoint as it is not set by default for service accounts + authFields[IDP_TOKEN_ENDPOINT] = "" if !disableWriting { err = SetAuthFlow(authFlowType) diff --git a/internal/pkg/auth/storage_test.go b/internal/pkg/auth/storage_test.go index 37eeee33e..d7320917e 100644 --- a/internal/pkg/auth/storage_test.go +++ b/internal/pkg/auth/storage_test.go @@ -6,8 +6,9 @@ import ( "testing" "time" - "github.com/stackitcloud/stackit-cli/internal/pkg/config" "github.com/zalando/go-keyring" + + "github.com/stackitcloud/stackit-cli/internal/pkg/config" ) func TestSetGetAuthField(t *testing.T) { diff --git a/internal/pkg/auth/user_login.go b/internal/pkg/auth/user_login.go index 054c74c89..822b7daff 100644 --- a/internal/pkg/auth/user_login.go +++ b/internal/pkg/auth/user_login.go @@ -16,9 +16,10 @@ import ( "strings" "time" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "golang.org/x/oauth2" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" ) @@ -45,29 +46,22 @@ type InputValues struct { Logo string } +type UserAuthConfig struct { + // IsReauthentication defines if an expired user session should be renewed + IsReauthentication bool + // Port defines which port should be used for the UserAuthFlow callback + Port *int +} + type apiClient interface { Do(req *http.Request) (*http.Response, error) } // AuthorizeUser implements the PKCE OAuth2 flow. -func AuthorizeUser(p *print.Printer, isReauthentication bool) error { - idpWellKnownConfigURL, err := getIDPWellKnownConfigURL() - if err != nil { - return fmt.Errorf("get IDP well-known configuration: %w", err) - } - if idpWellKnownConfigURL != defaultWellKnownConfig { - p.Warn("You are using a custom identity provider well-known configuration (%s) for authentication.\n", idpWellKnownConfigURL) - err := p.PromptForEnter("Press Enter to proceed with the login...") - if err != nil { - return err - } - } - - p.Debug(print.DebugLevel, "get IDP well-known configuration from %s", idpWellKnownConfigURL) - httpClient := &http.Client{} - idpWellKnownConfig, err := parseWellKnownConfiguration(httpClient, idpWellKnownConfigURL) +func AuthorizeUser(p *print.Printer, authConfig UserAuthConfig) error { + idpWellKnownConfig, err := retrieveIDPWellKnownConfig(p) if err != nil { - return fmt.Errorf("parse IDP well-known configuration: %w", err) + return err } idpClientID, err := getIDPClientID() @@ -82,7 +76,7 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { } } - if isReauthentication { + if authConfig.IsReauthentication { err := p.PromptForEnter("Your session has expired, press Enter to login again...") if err != nil { return err @@ -92,21 +86,38 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { var redirectURL string var listener net.Listener var listenerErr error + var ipv6Listener net.Listener + var ipv6ListenerErr error var port int - for i := range configuredPortRange { - port = defaultPort + i - portString := fmt.Sprintf(":%s", strconv.Itoa(port)) + startingPort := defaultPort + portRange := configuredPortRange + if authConfig.Port != nil { + startingPort = *authConfig.Port + portRange = 1 + } + for i := range portRange { + port = startingPort + i + ipv4addr := fmt.Sprintf("127.0.0.1:%d", port) + ipv6addr := fmt.Sprintf("[::1]:%d", port) p.Debug(print.DebugLevel, "trying to bind port %d for login redirect", port) - listener, listenerErr = net.Listen("tcp", portString) + ipv6Listener, ipv6ListenerErr = net.Listen("tcp6", ipv6addr) + if ipv6ListenerErr != nil { + continue + } + listener, listenerErr = net.Listen("tcp4", ipv4addr) if listenerErr == nil { + _ = ipv6Listener.Close() redirectURL = fmt.Sprintf("http://localhost:%d", port) p.Debug(print.DebugLevel, "bound port %d for login redirect", port) break } p.Debug(print.DebugLevel, "unable to bind port %d for login redirect: %s", port, listenerErr) } + if ipv6ListenerErr != nil { + return fmt.Errorf("unable to bind port for login redirect, tried from port %d to %d: %w", startingPort, port, ipv6ListenerErr) + } if listenerErr != nil { - return fmt.Errorf("unable to bind port for login redirect, tried from port %d to %d: %w", defaultPort, port, err) + return fmt.Errorf("unable to bind port for login redirect, tried from port %d to %d: %w", startingPort, port, listenerErr) } conf := &oauth2.Config{ @@ -332,7 +343,7 @@ func cleanup(server *http.Server) { func openBrowser(pageUrl string) error { var err error switch runtime.GOOS { - case "linux": + case "freebsd", "linux": // We need to use the windows way on WSL, otherwise we do not pass query // parameters correctly. https://github.com/microsoft/WSL/issues/3832 if _, ok := os.LookupEnv("WSL_DISTRO_NAME"); !ok { @@ -352,48 +363,3 @@ func openBrowser(pageUrl string) error { } return nil } - -// parseWellKnownConfiguration gets the well-known OpenID configuration from the provided URL and returns it as a JSON -// the method also stores the IDP token endpoint in the authentication storage -func parseWellKnownConfiguration(httpClient apiClient, wellKnownConfigURL string) (wellKnownConfig *wellKnownConfig, err error) { - req, _ := http.NewRequest("GET", wellKnownConfigURL, http.NoBody) - res, err := httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("make the request: %w", err) - } - - // Process the response - defer func() { - closeErr := res.Body.Close() - if closeErr != nil { - err = fmt.Errorf("close response body: %w", closeErr) - } - }() - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("read response body: %w", err) - } - - err = json.Unmarshal(body, &wellKnownConfig) - if err != nil { - return nil, fmt.Errorf("unmarshal response: %w", err) - } - if wellKnownConfig == nil { - return nil, fmt.Errorf("nil well-known configuration response") - } - if wellKnownConfig.Issuer == "" { - return nil, fmt.Errorf("found no issuer") - } - if wellKnownConfig.AuthorizationEndpoint == "" { - return nil, fmt.Errorf("found no authorization endpoint") - } - if wellKnownConfig.TokenEndpoint == "" { - return nil, fmt.Errorf("found no token endpoint") - } - - err = SetAuthField(IDP_TOKEN_ENDPOINT, wellKnownConfig.TokenEndpoint) - if err != nil { - return nil, fmt.Errorf("set token endpoint in the authentication storage: %w", err) - } - return wellKnownConfig, err -} diff --git a/internal/pkg/auth/user_login_test.go b/internal/pkg/auth/user_login_test.go index 7b61a4af5..e6f4bf040 100644 --- a/internal/pkg/auth/user_login_test.go +++ b/internal/pkg/auth/user_login_test.go @@ -5,10 +5,6 @@ import ( "io" "net/http" "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/zalando/go-keyring" ) type apiClientMocked struct { @@ -28,83 +24,3 @@ func (a *apiClientMocked) Do(_ *http.Request) (*http.Response, error) { Body: io.NopCloser(strings.NewReader(a.getResponse)), }, nil } - -func TestParseWellKnownConfig(t *testing.T) { - tests := []struct { - name string - getFails bool - getResponse string - isValid bool - expected *wellKnownConfig - }{ - { - name: "success", - getFails: false, - getResponse: `{"issuer":"issuer","authorization_endpoint":"auth","token_endpoint":"token"}`, - isValid: true, - expected: &wellKnownConfig{ - Issuer: "issuer", - AuthorizationEndpoint: "auth", - TokenEndpoint: "token", - }, - }, - { - name: "get_fails", - getFails: true, - getResponse: "", - isValid: false, - expected: nil, - }, - { - name: "empty_response", - getFails: true, - getResponse: "", - isValid: false, - expected: nil, - }, - { - name: "missing_issuer", - getFails: true, - getResponse: `{"authorization_endpoint":"auth","token_endpoint":"token"}`, - isValid: false, - expected: nil, - }, - { - name: "missing_authorization", - getFails: true, - getResponse: `{"issuer":"issuer","token_endpoint":"token"}`, - isValid: false, - expected: nil, - }, - { - name: "missing_token", - getFails: true, - getResponse: `{"issuer":"issuer","authorization_endpoint":"auth"}`, - isValid: false, - expected: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - keyring.MockInit() - - testClient := apiClientMocked{ - tt.getFails, - tt.getResponse, - } - - got, err := parseWellKnownConfiguration(&testClient, "") - - if tt.isValid && err != nil { - t.Fatalf("expected no error, got %v", err) - } - if !tt.isValid && err == nil { - t.Fatalf("expected error, got none") - } - - if tt.isValid && !cmp.Equal(*got, *tt.expected) { - t.Fatalf("expected %v, got %v", tt.expected, got) - } - }) - } -} diff --git a/internal/pkg/auth/user_token_flow.go b/internal/pkg/auth/user_token_flow.go index cdb852f77..2e12b59a4 100644 --- a/internal/pkg/auth/user_token_flow.go +++ b/internal/pkg/auth/user_token_flow.go @@ -10,12 +10,13 @@ import ( "time" "github.com/golang-jwt/jwt/v5" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" ) type userTokenFlow struct { printer *print.Printer - reauthorizeUserRoutine func(p *print.Printer, isReauthentication bool) error // Called if the user needs to login again + reauthorizeUserRoutine func(p *print.Printer, isReauthentication UserAuthConfig) error // Called if the user needs to login again client *http.Client authFlow AuthFlow accessToken string @@ -95,7 +96,12 @@ func loadVarsFromStorage(utf *userTokenFlow) error { } func reauthenticateUser(utf *userTokenFlow) error { - err := utf.reauthorizeUserRoutine(utf.printer, true) + err := utf.reauthorizeUserRoutine( + utf.printer, + UserAuthConfig{ + IsReauthentication: true, + }, + ) if err != nil { return fmt.Errorf("authenticate user: %w", err) } @@ -109,22 +115,28 @@ func reauthenticateUser(utf *userTokenFlow) error { return nil } -func TokenExpired(token string) (bool, error) { +func TokenExpirationTime(token string) (time.Time, error) { // We can safely use ParseUnverified because we are not authenticating the user at this point. // We're just checking the expiration time tokenParsed, _, err := jwt.NewParser().ParseUnverified(token, &jwt.RegisteredClaims{}) if err != nil { - return false, fmt.Errorf("parse access token: %w", err) + return time.Time{}, fmt.Errorf("parse access token: %w", err) } expirationTimestampNumeric, err := tokenParsed.Claims.GetExpirationTime() if err != nil { - return false, fmt.Errorf("get expiration timestamp from access token: %w", err) + return time.Time{}, fmt.Errorf("get expiration timestamp from access token: %w", err) } else if expirationTimestampNumeric == nil { - return false, nil + return time.Time{}, nil } - expirationTimestamp := expirationTimestampNumeric.Time - now := time.Now() - return now.After(expirationTimestamp), nil + return expirationTimestampNumeric.Time, nil +} + +func TokenExpired(token string) (bool, error) { + expirationTimestamp, err := TokenExpirationTime(token) + if err != nil || expirationTimestamp.Equal(time.Time{}) { + return false, err + } + return time.Now().After(expirationTimestamp), nil } // Refresh access and refresh tokens using a valid refresh token @@ -177,11 +189,11 @@ func buildRequestToRefreshTokens(utf *userTokenFlow) (*http.Request, error) { utf.tokenEndpoint, strings.NewReader(form.Encode()), ) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - if err != nil { return nil, err } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + return req, nil } diff --git a/internal/pkg/auth/user_token_flow_test.go b/internal/pkg/auth/user_token_flow_test.go index cd31350ad..d89015ec2 100644 --- a/internal/pkg/auth/user_token_flow_test.go +++ b/internal/pkg/auth/user_token_flow_test.go @@ -10,8 +10,9 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/zalando/go-keyring" + + "github.com/stackitcloud/stackit-cli/internal/pkg/print" ) const ( @@ -278,7 +279,7 @@ func TestRoundTrip(t *testing.T) { authorizeUserCalled: &authorizeUserCalled, tokensRefreshed: &tokensRefreshed, } - authorizeUserRoutine := func(_ *print.Printer, _ bool) error { + authorizeUserRoutine := func(_ *print.Printer, _ UserAuthConfig) error { return reauthorizeUser(authorizeUserContext) } diff --git a/internal/pkg/auth/utils.go b/internal/pkg/auth/utils.go index 4fa431262..faf24b687 100644 --- a/internal/pkg/auth/utils.go +++ b/internal/pkg/auth/utils.go @@ -1,10 +1,15 @@ package auth import ( + "encoding/json" "fmt" + "io" + "net/http" "github.com/spf13/viper" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) @@ -39,3 +44,70 @@ func getIDPClientID() (string, error) { return idpClientID, nil } + +func retrieveIDPWellKnownConfig(p *print.Printer) (*wellKnownConfig, error) { + idpWellKnownConfigURL, err := getIDPWellKnownConfigURL() + if err != nil { + return nil, fmt.Errorf("get IDP well-known configuration: %w", err) + } + if idpWellKnownConfigURL != defaultWellKnownConfig { + p.Warn("You are using a custom identity provider well-known configuration (%s) for authentication.\n", idpWellKnownConfigURL) + err := p.PromptForEnter("Press Enter to proceed with the login...") + if err != nil { + return nil, err + } + } + + p.Debug(print.DebugLevel, "get IDP well-known configuration from %s", idpWellKnownConfigURL) + httpClient := &http.Client{} + idpWellKnownConfig, err := parseWellKnownConfiguration(httpClient, idpWellKnownConfigURL) + if err != nil { + return nil, fmt.Errorf("parse IDP well-known configuration: %w", err) + } + return idpWellKnownConfig, nil +} + +// parseWellKnownConfiguration gets the well-known OpenID configuration from the provided URL and returns it as a JSON +// the method also stores the IDP token endpoint in the authentication storage +func parseWellKnownConfiguration(httpClient apiClient, wellKnownConfigURL string) (wellKnownConfig *wellKnownConfig, err error) { + req, _ := http.NewRequest("GET", wellKnownConfigURL, http.NoBody) + res, err := httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("make the request: %w", err) + } + + // Process the response + defer func() { + closeErr := res.Body.Close() + if closeErr != nil { + err = fmt.Errorf("close response body: %w", closeErr) + } + }() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("read response body: %w", err) + } + + err = json.Unmarshal(body, &wellKnownConfig) + if err != nil { + return nil, fmt.Errorf("unmarshal response: %w", err) + } + if wellKnownConfig == nil { + return nil, fmt.Errorf("nil well-known configuration response") + } + if wellKnownConfig.Issuer == "" { + return nil, fmt.Errorf("found no issuer") + } + if wellKnownConfig.AuthorizationEndpoint == "" { + return nil, fmt.Errorf("found no authorization endpoint") + } + if wellKnownConfig.TokenEndpoint == "" { + return nil, fmt.Errorf("found no token endpoint") + } + + err = SetAuthField(IDP_TOKEN_ENDPOINT, wellKnownConfig.TokenEndpoint) + if err != nil { + return nil, fmt.Errorf("set token endpoint in the authentication storage: %w", err) + } + return wellKnownConfig, err +} diff --git a/internal/pkg/auth/utils_test.go b/internal/pkg/auth/utils_test.go index 8112257d6..6fd67fc75 100644 --- a/internal/pkg/auth/utils_test.go +++ b/internal/pkg/auth/utils_test.go @@ -3,7 +3,10 @@ package auth import ( "testing" + "github.com/google/go-cmp/cmp" "github.com/spf13/viper" + "github.com/zalando/go-keyring" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" ) @@ -118,3 +121,83 @@ func TestGetIDPClientID(t *testing.T) { }) } } + +func TestParseWellKnownConfig(t *testing.T) { + tests := []struct { + name string + getFails bool + getResponse string + isValid bool + expected *wellKnownConfig + }{ + { + name: "success", + getFails: false, + getResponse: `{"issuer":"issuer","authorization_endpoint":"auth","token_endpoint":"token"}`, + isValid: true, + expected: &wellKnownConfig{ + Issuer: "issuer", + AuthorizationEndpoint: "auth", + TokenEndpoint: "token", + }, + }, + { + name: "get_fails", + getFails: true, + getResponse: "", + isValid: false, + expected: nil, + }, + { + name: "empty_response", + getFails: true, + getResponse: "", + isValid: false, + expected: nil, + }, + { + name: "missing_issuer", + getFails: true, + getResponse: `{"authorization_endpoint":"auth","token_endpoint":"token"}`, + isValid: false, + expected: nil, + }, + { + name: "missing_authorization", + getFails: true, + getResponse: `{"issuer":"issuer","token_endpoint":"token"}`, + isValid: false, + expected: nil, + }, + { + name: "missing_token", + getFails: true, + getResponse: `{"issuer":"issuer","authorization_endpoint":"auth"}`, + isValid: false, + expected: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + keyring.MockInit() + + testClient := apiClientMocked{ + tt.getFails, + tt.getResponse, + } + + got, err := parseWellKnownConfiguration(&testClient, "") + + if tt.isValid && err != nil { + t.Fatalf("expected no error, got %v", err) + } + if !tt.isValid && err == nil { + t.Fatalf("expected error, got none") + } + + if tt.isValid && !cmp.Equal(*got, *tt.expected) { + t.Fatalf("expected %v, got %v", tt.expected, got) + } + }) + } +} diff --git a/internal/pkg/cache/cache_test.go b/internal/pkg/cache/cache_test.go index 4ef45891b..fe0c143cc 100644 --- a/internal/pkg/cache/cache_test.go +++ b/internal/pkg/cache/cache_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" ) diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index e8532775f..638207898 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -17,6 +17,7 @@ const ( RegionKey = "region" SessionTimeLimitKey = "session_time_limit" VerbosityKey = "verbosity" + AssumeYesKey = "assume_yes" IdentityProviderCustomWellKnownConfigurationKey = "identity_provider_custom_well_known_configuration" IdentityProviderCustomClientIdKey = "identity_provider_custom_client_id" @@ -58,6 +59,7 @@ const ( DefaultProfileName = "default" AsyncDefault = false + AssumeYesDefault = false RegionDefault = "eu01" SessionTimeLimitDefault = "12h" @@ -82,6 +84,7 @@ var ConfigKeys = []string{ RegionKey, SessionTimeLimitKey, VerbosityKey, + AssumeYesKey, IdentityProviderCustomWellKnownConfigurationKey, IdentityProviderCustomClientIdKey, diff --git a/internal/pkg/config/profiles.go b/internal/pkg/config/profiles.go index 17d2fd352..83d48be2a 100644 --- a/internal/pkg/config/profiles.go +++ b/internal/pkg/config/profiles.go @@ -7,6 +7,8 @@ import ( "path/filepath" "regexp" + "github.com/spf13/viper" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/fileutils" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -434,7 +436,13 @@ func ExportProfile(p *print.Printer, profile, exportPath string) error { return &errors.FileAlreadyExistsError{Filename: exportPath} } - err = fileutils.CopyFile(configFile, exportPath) + _, err = os.Stat(configFile) + if os.IsNotExist(err) { + // viper.SafeWriteConfigAs would not overwrite the target, so we use WriteConfigAs for the same behavior as CopyFile + err = viper.WriteConfigAs(exportPath) + } else { + err = fileutils.CopyFile(configFile, exportPath) + } if err != nil { return fmt.Errorf("export config file to %q: %w", exportPath, err) } diff --git a/internal/pkg/flags/flag_to_value.go b/internal/pkg/flags/flag_to_value.go index f08904982..b7d8194ed 100644 --- a/internal/pkg/flags/flag_to_value.go +++ b/internal/pkg/flags/flag_to_value.go @@ -5,6 +5,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" ) @@ -75,6 +76,20 @@ func FlagToStringToStringPointer(p *print.Printer, cmd *cobra.Command, flag stri return nil } +// Returns a pointer to the flag's value. +// Returns nil if the flag is not set, if its value can not be converted to int, or if the flag does not exist. +func FlagToIntPointer(p *print.Printer, cmd *cobra.Command, flag string) *int { + value, err := cmd.Flags().GetInt(flag) + if err != nil { + p.Debug(print.ErrorLevel, "convert flag to Uint64 pointer: %v", err) + return nil + } + if cmd.Flag(flag).Changed { + return &value + } + return nil +} + // Returns a pointer to the flag's value. // Returns nil if the flag is not set, if its value can not be converted to int64, or if the flag does not exist. func FlagToInt64Pointer(p *print.Printer, cmd *cobra.Command, flag string) *int64 { diff --git a/internal/pkg/flags/flag_to_value_test.go b/internal/pkg/flags/flag_to_value_test.go index 08d25ed9b..6da23001a 100644 --- a/internal/pkg/flags/flag_to_value_test.go +++ b/internal/pkg/flags/flag_to_value_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) diff --git a/internal/pkg/generic-client/generic_client.go b/internal/pkg/generic-client/generic_client.go index 64ede2600..ba5185dcb 100644 --- a/internal/pkg/generic-client/generic_client.go +++ b/internal/pkg/generic-client/generic_client.go @@ -2,12 +2,13 @@ package genericclient import ( "github.com/spf13/viper" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/config" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" ) type CreateApiClient[T any] func(opts ...sdkConfig.ConfigurationOption) (T, error) diff --git a/internal/pkg/globalflags/global_flags.go b/internal/pkg/globalflags/global_flags.go index 9f53ec4f7..63b51c6c8 100644 --- a/internal/pkg/globalflags/global_flags.go +++ b/internal/pkg/globalflags/global_flags.go @@ -60,6 +60,10 @@ func Configure(flagSet *pflag.FlagSet) error { } flagSet.BoolP(AssumeYesFlag, "y", false, "If set, skips all confirmation prompts") + err = viper.BindPFlag(config.AssumeYesKey, flagSet.Lookup(AssumeYesFlag)) + if err != nil { + return fmt.Errorf("bind --%s flag to config: %w", AssumeYesFlag, err) + } flagSet.Var(flags.EnumFlag(true, VerbosityDefault, verbosityFlagOptions...), VerbosityFlag, fmt.Sprintf("Verbosity of the CLI, one of %q", verbosityFlagOptions)) err = viper.BindPFlag(config.VerbosityKey, flagSet.Lookup(VerbosityFlag)) @@ -76,10 +80,10 @@ func Configure(flagSet *pflag.FlagSet) error { return nil } -func Parse(p *print.Printer, cmd *cobra.Command) *GlobalFlagModel { +func Parse(_ *print.Printer, _ *cobra.Command) *GlobalFlagModel { return &GlobalFlagModel{ Async: viper.GetBool(config.AsyncKey), - AssumeYes: flags.FlagToBoolValue(p, cmd, AssumeYesFlag), + AssumeYes: viper.GetBool(config.AssumeYesKey), OutputFormat: viper.GetString(config.OutputFormatKey), ProjectId: viper.GetString(config.ProjectIdKey), Region: viper.GetString(config.RegionKey), diff --git a/internal/pkg/projectname/project_name_test.go b/internal/pkg/projectname/project_name_test.go index ae6d79251..9b84509ff 100644 --- a/internal/pkg/projectname/project_name_test.go +++ b/internal/pkg/projectname/project_name_test.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" "github.com/stackitcloud/stackit-cli/internal/pkg/print" ) diff --git a/internal/pkg/services/cdn/client/client.go b/internal/pkg/services/cdn/client/client.go index afefb7a92..844f077ef 100644 --- a/internal/pkg/services/cdn/client/client.go +++ b/internal/pkg/services/cdn/client/client.go @@ -2,10 +2,11 @@ package client import ( "github.com/spf13/viper" + "github.com/stackitcloud/stackit-sdk-go/services/cdn" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/cdn" ) func ConfigureClient(p *print.Printer, cliVersion string) (*cdn.APIClient, error) { diff --git a/internal/pkg/services/dns/utils/utils.go b/internal/pkg/services/dns/utils/utils.go index 141fb50e0..030c86b55 100644 --- a/internal/pkg/services/dns/utils/utils.go +++ b/internal/pkg/services/dns/utils/utils.go @@ -5,8 +5,9 @@ import ( "fmt" "math" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/dns" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) type DNSClient interface { diff --git a/internal/pkg/services/dns/utils/utils_test.go b/internal/pkg/services/dns/utils/utils_test.go index 12cae8fdc..b7c91bff9 100644 --- a/internal/pkg/services/dns/utils/utils_test.go +++ b/internal/pkg/services/dns/utils/utils_test.go @@ -6,8 +6,9 @@ import ( "testing" "github.com/google/uuid" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/dns" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) var ( diff --git a/internal/pkg/services/edge/client/client.go b/internal/pkg/services/edge/client/client.go index 88ec7e2c4..77d2677ea 100644 --- a/internal/pkg/services/edge/client/client.go +++ b/internal/pkg/services/edge/client/client.go @@ -7,10 +7,11 @@ import ( "context" "github.com/spf13/viper" + "github.com/stackitcloud/stackit-sdk-go/services/edge" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/edge" ) // APIClient is an interface that consolidates all client functionality to allow for mocking of the API client during testing. diff --git a/internal/pkg/services/edge/common/kubeconfig/kubeconfig_test.go b/internal/pkg/services/edge/common/kubeconfig/kubeconfig_test.go index 59bbba0b0..e196052c4 100755 --- a/internal/pkg/services/edge/common/kubeconfig/kubeconfig_test.go +++ b/internal/pkg/services/edge/common/kubeconfig/kubeconfig_test.go @@ -11,9 +11,10 @@ import ( "strings" "testing" + "k8s.io/client-go/tools/clientcmd" + testUtils "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "k8s.io/client-go/tools/clientcmd" ) var ( diff --git a/internal/pkg/services/edge/common/validation/input.go b/internal/pkg/services/edge/common/validation/input.go index 33a2d0c46..c32f8a9be 100644 --- a/internal/pkg/services/edge/common/validation/input.go +++ b/internal/pkg/services/edge/common/validation/input.go @@ -5,6 +5,7 @@ package validation import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" commonErr "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/error" diff --git a/internal/pkg/services/edge/common/validation/input_test.go b/internal/pkg/services/edge/common/validation/input_test.go index 0ccb2d3b2..b058f1427 100755 --- a/internal/pkg/services/edge/common/validation/input_test.go +++ b/internal/pkg/services/edge/common/validation/input_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" commonErr "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/error" commonInstance "github.com/stackitcloud/stackit-cli/internal/pkg/services/edge/common/instance" diff --git a/internal/pkg/services/git/utils/utils_test.go b/internal/pkg/services/git/utils/utils_test.go index 7ec5dc494..ed388263f 100644 --- a/internal/pkg/services/git/utils/utils_test.go +++ b/internal/pkg/services/git/utils/utils_test.go @@ -5,8 +5,9 @@ import ( "fmt" "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/git" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) type GitClientMocked struct { diff --git a/internal/pkg/services/iaas/utils/utils.go b/internal/pkg/services/iaas/utils/utils.go index b7973c265..8d04cd831 100644 --- a/internal/pkg/services/iaas/utils/utils.go +++ b/internal/pkg/services/iaas/utils/utils.go @@ -21,6 +21,7 @@ type IaaSClient interface { GetServerExecute(ctx context.Context, projectId, region, serverId string) (*iaas.Server, error) GetVolumeExecute(ctx context.Context, projectId, region, volumeId string) (*iaas.Volume, error) GetNetworkExecute(ctx context.Context, projectId, region, networkId string) (*iaas.Network, error) + GetRoutingTableOfAreaExecute(ctx context.Context, organizationId, areaId, region, routingTableId string) (*iaas.RoutingTable, error) GetNetworkAreaExecute(ctx context.Context, organizationId, areaId string) (*iaas.NetworkArea, error) ListNetworkAreaProjectsExecute(ctx context.Context, organizationId, areaId string) (*iaas.ProjectListResponse, error) GetNetworkAreaRangeExecute(ctx context.Context, organizationId, areaId, region, networkRangeId string) (*iaas.NetworkRange, error) @@ -95,6 +96,18 @@ func GetNetworkName(ctx context.Context, apiClient IaaSClient, projectId, region return *resp.Name, nil } +func GetRoutingTableOfAreaName(ctx context.Context, apiClient IaaSClient, organizationId, areaId, region, routingTableId string) (string, error) { + resp, err := apiClient.GetRoutingTableOfAreaExecute(ctx, organizationId, areaId, region, routingTableId) + if err != nil { + return "", fmt.Errorf("get routing-table: %w", err) + } else if resp == nil { + return "", ErrResponseNil + } else if resp.Name == nil { + return "", ErrNameNil + } + return *resp.Name, nil +} + func GetNetworkAreaName(ctx context.Context, apiClient IaaSClient, organizationId, areaId string) (string, error) { resp, err := apiClient.GetNetworkAreaExecute(ctx, organizationId, areaId) if err != nil { diff --git a/internal/pkg/services/iaas/utils/utils_test.go b/internal/pkg/services/iaas/utils/utils_test.go index a8f530533..61ec9b02d 100644 --- a/internal/pkg/services/iaas/utils/utils_test.go +++ b/internal/pkg/services/iaas/utils/utils_test.go @@ -6,39 +6,42 @@ import ( "reflect" "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) var _ IaaSClient = &IaaSClientMocked{} type IaaSClientMocked struct { - GetSecurityGroupRuleFails bool - GetSecurityGroupRuleResp *iaas.SecurityGroupRule - GetSecurityGroupFails bool - GetSecurityGroupResp *iaas.SecurityGroup - GetPublicIpFails bool - GetPublicIpResp *iaas.PublicIp - GetServerFails bool - GetServerResp *iaas.Server - GetVolumeFails bool - GetVolumeResp *iaas.Volume - GetNetworkFails bool - GetNetworkResp *iaas.Network - GetNetworkAreaFails bool - GetNetworkAreaResp *iaas.NetworkArea - GetAttachedProjectsFails bool - GetAttachedProjectsResp *iaas.ProjectListResponse - GetNetworkAreaRangeFails bool - GetNetworkAreaRangeResp *iaas.NetworkRange - GetImageFails bool - GetImageResp *iaas.Image - GetAffinityGroupsFails bool - GetAffinityGroupResp *iaas.AffinityGroup - GetBackupFails bool - GetBackupResp *iaas.Backup - GetSnapshotFails bool - GetSnapshotResp *iaas.Snapshot + GetSecurityGroupRuleFails bool + GetSecurityGroupRuleResp *iaas.SecurityGroupRule + GetSecurityGroupFails bool + GetSecurityGroupResp *iaas.SecurityGroup + GetPublicIpFails bool + GetPublicIpResp *iaas.PublicIp + GetServerFails bool + GetServerResp *iaas.Server + GetVolumeFails bool + GetVolumeResp *iaas.Volume + GetNetworkFails bool + GetNetworkResp *iaas.Network + GetRoutingTableOfAreaFails bool + GetRoutingTableOfAreaResp *iaas.RoutingTable + GetNetworkAreaFails bool + GetNetworkAreaResp *iaas.NetworkArea + GetAttachedProjectsFails bool + GetAttachedProjectsResp *iaas.ProjectListResponse + GetNetworkAreaRangeFails bool + GetNetworkAreaRangeResp *iaas.NetworkRange + GetImageFails bool + GetImageResp *iaas.Image + GetAffinityGroupsFails bool + GetAffinityGroupResp *iaas.AffinityGroup + GetBackupFails bool + GetBackupResp *iaas.Backup + GetSnapshotFails bool + GetSnapshotResp *iaas.Snapshot } func (m *IaaSClientMocked) GetAffinityGroupExecute(_ context.Context, _, _, _ string) (*iaas.AffinityGroup, error) { @@ -90,6 +93,13 @@ func (m *IaaSClientMocked) GetNetworkExecute(_ context.Context, _, _, _ string) return m.GetNetworkResp, nil } +func (m *IaaSClientMocked) GetRoutingTableOfAreaExecute(_ context.Context, _, _, _, _ string) (*iaas.RoutingTable, error) { + if m.GetRoutingTableOfAreaFails { + return nil, fmt.Errorf("could not get routing table") + } + return m.GetRoutingTableOfAreaResp, nil +} + func (m *IaaSClientMocked) GetNetworkAreaExecute(_ context.Context, _, _ string) (*iaas.NetworkArea, error) { if m.GetNetworkAreaFails { return nil, fmt.Errorf("could not get network area") @@ -1093,3 +1103,71 @@ func TestGetAffinityGroupName(t *testing.T) { }) } } + +func TestGetRoutingTableOfAreaName(t *testing.T) { + type args struct { + getInstanceFails bool + getInstanceResp *iaas.RoutingTable + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "base", + args: args{ + getInstanceResp: &iaas.RoutingTable{ + Name: utils.Ptr("test"), + }, + }, + want: "test", + }, + { + name: "get routing table fails", + args: args{ + getInstanceFails: true, + }, + wantErr: true, + want: "", + }, + { + name: "response is nil", + args: args{ + getInstanceResp: nil, + getInstanceFails: false, + }, + wantErr: true, + want: "", + }, + { + name: "name in response is nil", + args: args{ + getInstanceResp: &iaas.RoutingTable{ + Name: nil, + }, + getInstanceFails: false, + }, + wantErr: true, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &IaaSClientMocked{ + GetRoutingTableOfAreaFails: tt.args.getInstanceFails, + GetRoutingTableOfAreaResp: tt.args.getInstanceResp, + } + + got, err := GetRoutingTableOfAreaName(context.Background(), m, "", "", "", "") + if (err != nil) != tt.wantErr { + t.Errorf("GetRoutingTableOfAreaName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetRoutingTableOfAreaName() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/pkg/services/intake/client/client.go b/internal/pkg/services/intake/client/client.go index efb8d0cfd..2a0e89400 100644 --- a/internal/pkg/services/intake/client/client.go +++ b/internal/pkg/services/intake/client/client.go @@ -2,10 +2,11 @@ package client import ( "github.com/spf13/viper" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/intake" ) // ConfigureClient creates and configures a new Intake API client diff --git a/internal/pkg/services/logs/client/client.go b/internal/pkg/services/logs/client/client.go index 6bce9b246..a5297b31a 100644 --- a/internal/pkg/services/logs/client/client.go +++ b/internal/pkg/services/logs/client/client.go @@ -1,10 +1,11 @@ package client import ( + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/spf13/viper" ) diff --git a/internal/pkg/services/logs/utils/utils_test.go b/internal/pkg/services/logs/utils/utils_test.go index 78839fb10..0f780c1d6 100644 --- a/internal/pkg/services/logs/utils/utils_test.go +++ b/internal/pkg/services/logs/utils/utils_test.go @@ -5,9 +5,10 @@ import ( "fmt" "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/logs" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/google/uuid" ) diff --git a/internal/pkg/services/mongodbflex/client/client.go b/internal/pkg/services/mongodbflex/client/client.go index 58613728b..7bb81905e 100644 --- a/internal/pkg/services/mongodbflex/client/client.go +++ b/internal/pkg/services/mongodbflex/client/client.go @@ -2,10 +2,11 @@ package client import ( "github.com/spf13/viper" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) func ConfigureClient(p *print.Printer, cliVersion string) (*mongodbflex.APIClient, error) { diff --git a/internal/pkg/services/mongodbflex/utils/utils.go b/internal/pkg/services/mongodbflex/utils/utils.go index a5cbc7016..352cdc948 100644 --- a/internal/pkg/services/mongodbflex/utils/utils.go +++ b/internal/pkg/services/mongodbflex/utils/utils.go @@ -7,9 +7,10 @@ import ( "slices" "strings" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "golang.org/x/mod/semver" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) diff --git a/internal/pkg/services/network-area/routing-table/utils/utils.go b/internal/pkg/services/network-area/routing-table/utils/utils.go new file mode 100644 index 000000000..9783dd918 --- /dev/null +++ b/internal/pkg/services/network-area/routing-table/utils/utils.go @@ -0,0 +1,66 @@ +package utils + +import ( + "fmt" + "time" + + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type RouteDetails struct { + DestType string + DestValue string + HopType string + HopValue string + CreatedAt string + UpdatedAt string + Labels string +} + +func ExtractRouteDetails(route iaas.Route) RouteDetails { + var routeDetails RouteDetails + + if route.Destination != nil { + if route.Destination.DestinationCIDRv4 != nil { + routeDetails.DestType = utils.PtrString(route.Destination.DestinationCIDRv4.Type) + routeDetails.DestValue = utils.PtrString(route.Destination.DestinationCIDRv4.Value) + } else if route.Destination.DestinationCIDRv6 != nil { + routeDetails.DestType = utils.PtrString(route.Destination.DestinationCIDRv6.Type) + routeDetails.DestValue = utils.PtrString(route.Destination.DestinationCIDRv6.Value) + } + } + + if route.Nexthop != nil { + if route.Nexthop.NexthopIPv4 != nil { + routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopIPv4.Type) + routeDetails.HopValue = utils.PtrString(route.Nexthop.NexthopIPv4.Value) + } else if route.Nexthop.NexthopIPv6 != nil { + routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopIPv6.Type) + routeDetails.HopValue = utils.PtrString(route.Nexthop.NexthopIPv6.Value) + } else if route.Nexthop.NexthopInternet != nil { + routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopInternet.Type) + } else if route.Nexthop.NexthopBlackhole != nil { + routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopBlackhole.Type) + } + } + + if route.Labels != nil && len(*route.Labels) > 0 { + stringMap := make(map[string]string) + for key, value := range *route.Labels { + stringMap[key] = fmt.Sprintf("%v", value) + } + routeDetails.Labels = utils.JoinStringMap(stringMap, ": ", "\n") + } + + if route.CreatedAt != nil { + routeDetails.CreatedAt = route.CreatedAt.Format(time.RFC3339) + } + + if route.UpdatedAt != nil { + routeDetails.UpdatedAt = route.UpdatedAt.Format(time.RFC3339) + } + + return routeDetails +} diff --git a/internal/pkg/services/network-area/routing-table/utils/utils_test.go b/internal/pkg/services/network-area/routing-table/utils/utils_test.go new file mode 100644 index 000000000..d5deee061 --- /dev/null +++ b/internal/pkg/services/network-area/routing-table/utils/utils_test.go @@ -0,0 +1,269 @@ +package utils + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ipv4 = "ipv4" +const ipv6 = "ipv6" +const cidrv4 = "cidrv4" +const cidrv6 = "cidrv6" + +func TestExtractRouteDetails(t *testing.T) { + created := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC) + updated := time.Date(2024, 1, 2, 4, 5, 6, 0, time.UTC) + + tests := []struct { + description string + input *iaas.Route + want RouteDetails + }{ + { + description: "completely empty route (zero value)", + input: &iaas.Route{}, + want: RouteDetails{}, + }, + { + description: "destination only, no nexthop, no labels", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(cidrv4), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + }, + want: RouteDetails{ + DestType: cidrv4, + DestValue: "10.0.0.0/24", + }, + }, + { + description: "nexthop only, no destination, empty labels map", + input: &iaas.Route{ + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(ipv4), + Value: utils.Ptr("10.0.0.1"), + }, + }, + Labels: &map[string]interface{}{}, // empty but non-nil + }, + want: RouteDetails{ + HopType: ipv4, + HopValue: "10.0.0.1", + }, + }, + { + description: "destination present, nexthop struct nil, labels nil", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv6: &iaas.DestinationCIDRv6{ + Type: utils.Ptr(cidrv6), + Value: utils.Ptr("2001:db8::/32"), + }, + }, + Nexthop: nil, + Labels: nil, + }, + want: RouteDetails{ + DestType: cidrv6, + DestValue: "2001:db8::/32", + }, + }, + { + description: "CIDRv4 destination, IPv4 nexthop, with labels", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(cidrv4), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(ipv4), + Value: utils.Ptr("10.0.0.1"), + }, + }, + Labels: &map[string]interface{}{ + "key": "value", + }, + }, + want: RouteDetails{ + DestType: cidrv4, + DestValue: "10.0.0.0/24", + HopType: ipv4, + HopValue: "10.0.0.1", + Labels: "key: value", + }, + }, + { + description: "CIDRv6 destination, IPv6 nexthop, with no labels", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv6: &iaas.DestinationCIDRv6{ + Type: utils.Ptr(cidrv6), + Value: utils.Ptr("2001:db8::/32"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv6: &iaas.NexthopIPv6{ + Type: utils.Ptr(ipv6), + Value: utils.Ptr("2001:db8::1"), + }, + }, + Labels: nil, + }, + want: RouteDetails{ + DestType: cidrv6, + DestValue: "2001:db8::/32", + HopType: ipv6, + HopValue: "2001:db8::1", + }, + }, + { + description: "Internet nexthop without value", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(cidrv4), + Value: utils.Ptr("0.0.0.0/0"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopInternet: &iaas.NexthopInternet{ + Type: utils.Ptr("Internet"), + }, + }, + Labels: nil, + }, + want: RouteDetails{ + DestType: cidrv4, + DestValue: "0.0.0.0/0", + HopType: "Internet", + // HopValue empty + }, + }, + { + description: "Blackhole nexthop without value and nil labels map", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv6: &iaas.DestinationCIDRv6{ + Type: utils.Ptr(cidrv6), + Value: utils.Ptr("::/0"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopBlackhole: &iaas.NexthopBlackhole{ + Type: utils.Ptr("Blackhole"), + }, + }, + Labels: nil, + }, + want: RouteDetails{ + DestType: cidrv6, + DestValue: "::/0", + HopType: "Blackhole", + }, + }, + { + description: "route with created and updated timestamps", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(cidrv4), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(ipv4), + Value: utils.Ptr("10.0.0.1"), + }, + }, + CreatedAt: &created, + UpdatedAt: &updated, + }, + want: RouteDetails{ + DestType: cidrv4, + DestValue: "10.0.0.0/24", + HopType: ipv4, + HopValue: "10.0.0.1", + CreatedAt: created.Format(time.RFC3339), + UpdatedAt: updated.Format(time.RFC3339), + Labels: "", + }, + }, + { + description: "route with created timestamp only", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(cidrv4), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(ipv4), + Value: utils.Ptr("10.0.0.1"), + }, + }, + CreatedAt: &created, + }, + want: RouteDetails{ + DestType: cidrv4, + DestValue: "10.0.0.0/24", + HopType: ipv4, + HopValue: "10.0.0.1", + CreatedAt: created.Format(time.RFC3339), + UpdatedAt: "", + Labels: "", + }, + }, + { + description: "route with updated timestamp only", + input: &iaas.Route{ + Destination: &iaas.RouteDestination{ + DestinationCIDRv4: &iaas.DestinationCIDRv4{ + Type: utils.Ptr(cidrv4), + Value: utils.Ptr("10.0.0.0/24"), + }, + }, + Nexthop: &iaas.RouteNexthop{ + NexthopIPv4: &iaas.NexthopIPv4{ + Type: utils.Ptr(ipv4), + Value: utils.Ptr("10.0.0.1"), + }, + }, + UpdatedAt: &updated, + }, + want: RouteDetails{ + DestType: cidrv4, + DestValue: "10.0.0.0/24", + HopType: ipv4, + HopValue: "10.0.0.1", + CreatedAt: "", + UpdatedAt: updated.Format(time.RFC3339), + Labels: "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got := ExtractRouteDetails(*tt.input) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Fatalf("ExtractRouteDetails() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/pkg/services/object-storage/client/client.go b/internal/pkg/services/object-storage/client/client.go index 82b2447f8..81e5dbe95 100644 --- a/internal/pkg/services/object-storage/client/client.go +++ b/internal/pkg/services/object-storage/client/client.go @@ -6,9 +6,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/spf13/viper" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) func ConfigureClient(p *print.Printer, cliVersion string) (*objectstorage.APIClient, error) { - return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.ObjectStorageCustomEndpointKey), true, genericclient.CreateApiClient[*objectstorage.APIClient](objectstorage.NewAPIClient)) + return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.ObjectStorageCustomEndpointKey), false, genericclient.CreateApiClient[*objectstorage.APIClient](objectstorage.NewAPIClient)) } diff --git a/internal/pkg/services/object-storage/utils/utils.go b/internal/pkg/services/object-storage/utils/utils.go index bd23d0854..a122cb1ef 100644 --- a/internal/pkg/services/object-storage/utils/utils.go +++ b/internal/pkg/services/object-storage/utils/utils.go @@ -6,17 +6,11 @@ import ( "net/http" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" ) -type ObjectStorageClient interface { - GetServiceStatusExecute(ctx context.Context, projectId, region string) (*objectstorage.ProjectStatus, error) - ListCredentialsGroupsExecute(ctx context.Context, projectId, region string) (*objectstorage.ListCredentialsGroupsResponse, error) - ListAccessKeys(ctx context.Context, projectId, region string) objectstorage.ApiListAccessKeysRequest -} - -func ProjectEnabled(ctx context.Context, apiClient ObjectStorageClient, projectId, region string) (bool, error) { - _, err := apiClient.GetServiceStatusExecute(ctx, projectId, region) +func ProjectEnabled(ctx context.Context, apiClient objectstorage.DefaultAPI, projectId, region string) (bool, error) { + _, err := apiClient.GetServiceStatus(ctx, projectId, region).Execute() if err != nil { oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped if !ok { @@ -30,8 +24,8 @@ func ProjectEnabled(ctx context.Context, apiClient ObjectStorageClient, projectI return true, nil } -func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId, region string) (string, error) { - resp, err := apiClient.ListCredentialsGroupsExecute(ctx, projectId, region) +func GetCredentialsGroupName(ctx context.Context, apiClient objectstorage.DefaultAPI, projectId, credentialsGroupId, region string) (string, error) { + resp, err := apiClient.ListCredentialsGroups(ctx, projectId, region).Execute() if err != nil { return "", fmt.Errorf("list Object Storage credentials groups: %w", err) } @@ -41,16 +35,16 @@ func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, return "", fmt.Errorf("nil Object Storage credentials group list: %w", err) } - for _, group := range *credentialsGroups { - if group.CredentialsGroupId != nil && *group.CredentialsGroupId == credentialsGroupId && group.DisplayName != nil && *group.DisplayName != "" { - return *group.DisplayName, nil + for _, group := range credentialsGroups { + if group.CredentialsGroupId == credentialsGroupId && group.DisplayName != "" { + return group.DisplayName, nil } } return "", fmt.Errorf("could not find Object Storage credentials group name") } -func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId, keyId, region string) (string, error) { +func GetCredentialsName(ctx context.Context, apiClient objectstorage.DefaultAPI, projectId, credentialsGroupId, keyId, region string) (string, error) { req := apiClient.ListAccessKeys(ctx, projectId, region) req = req.CredentialsGroup(credentialsGroupId) resp, err := req.Execute() @@ -64,9 +58,9 @@ func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, proj return "", fmt.Errorf("nil Object Storage credentials list") } - for _, credential := range *credentials { - if credential.KeyId != nil && *credential.KeyId == keyId && credential.DisplayName != nil && *credential.DisplayName != "" { - return *credential.DisplayName, nil + for _, credential := range credentials { + if credential.KeyId == keyId && credential.DisplayName != "" { + return credential.DisplayName, nil } } diff --git a/internal/pkg/services/object-storage/utils/utils_test.go b/internal/pkg/services/object-storage/utils/utils_test.go index 65b176172..1a93b9968 100644 --- a/internal/pkg/services/object-storage/utils/utils_test.go +++ b/internal/pkg/services/object-storage/utils/utils_test.go @@ -2,59 +2,69 @@ package utils import ( "context" - "encoding/json" "fmt" "net/http" - "net/http/httptest" "testing" "github.com/google/uuid" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) var ( testProjectId = uuid.NewString() testCredentialsGroupId = uuid.NewString() - testCredentialsId = "credentialsID" //nolint:gosec // linter false positive - testRegion = "eu01" ) const ( testCredentialsGroupName = "testGroup" testCredentialsName = "testCredential" + testCredentialsId = "credentialsID" //nolint:gosec // linter false positive + testRegion = "eu01" ) -type objectStorageClientMocked struct { - serviceDisabled bool - getServiceStatusFails bool +type mockSettings struct { + serviceDisabled bool + getServiceStatusFails bool + listCredentialsGroupsFails bool listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse - listAccessKeysReq objectstorage.ApiListAccessKeysRequest -} -func (m *objectStorageClientMocked) GetServiceStatusExecute(_ context.Context, _, _ string) (*objectstorage.ProjectStatus, error) { - if m.getServiceStatusFails { - return nil, fmt.Errorf("could not get service status") - } - if m.serviceDisabled { - return nil, &oapierror.GenericOpenAPIError{StatusCode: 404} - } - return &objectstorage.ProjectStatus{}, nil + listAccessKeysFails bool + listAccessKeysResp *objectstorage.ListAccessKeysResponse } -func (m *objectStorageClientMocked) ListCredentialsGroupsExecute(_ context.Context, _, _ string) (*objectstorage.ListCredentialsGroupsResponse, error) { - if m.listCredentialsGroupsFails { - return nil, fmt.Errorf("could not list credentials groups") - } - return m.listCredentialsGroupsResp, nil -} +func newAPIMock(settings *mockSettings) objectstorage.DefaultAPI { + return &objectstorage.DefaultAPIServiceMock{ + GetServiceStatusExecuteMock: utils.Ptr(func(_ objectstorage.ApiGetServiceStatusRequest) (*objectstorage.ProjectStatus, error) { + if settings.getServiceStatusFails { + return nil, fmt.Errorf("could not get service status") + } -func (m *objectStorageClientMocked) ListAccessKeys(_ context.Context, _, _ string) objectstorage.ApiListAccessKeysRequest { - return m.listAccessKeysReq + if settings.serviceDisabled { + return nil, &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound} + } + + return &objectstorage.ProjectStatus{}, nil + }), + ListCredentialsGroupsExecuteMock: utils.Ptr(func(_ objectstorage.ApiListCredentialsGroupsRequest) (*objectstorage.ListCredentialsGroupsResponse, error) { + if settings.listCredentialsGroupsFails { + return nil, fmt.Errorf("could not list credentials groups") + } + + return settings.listCredentialsGroupsResp, nil + }), + ListAccessKeysExecuteMock: utils.Ptr(func(_ objectstorage.ApiListAccessKeysRequest) (*objectstorage.ListAccessKeysResponse, error) { + if settings.listAccessKeysFails { + return nil, &oapierror.GenericOpenAPIError{StatusCode: http.StatusBadGateway} + } + + return settings.listAccessKeysResp, nil + }), + } } func TestProjectEnabled(t *testing.T) { @@ -85,10 +95,10 @@ func TestProjectEnabled(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - client := &objectStorageClientMocked{ + client := newAPIMock(&mockSettings{ serviceDisabled: tt.serviceDisabled, getServiceStatusFails: tt.getProjectFails, - } + }) output, err := ProjectEnabled(context.Background(), client, testProjectId, testRegion) @@ -120,10 +130,10 @@ func TestGetCredentialsGroupName(t *testing.T) { { description: "base", listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ - CredentialsGroups: &[]objectstorage.CredentialsGroup{ + CredentialsGroups: []objectstorage.CredentialsGroup{ { - CredentialsGroupId: utils.Ptr(testCredentialsGroupId), - DisplayName: utils.Ptr(testCredentialsGroupName), + CredentialsGroupId: testCredentialsGroupId, + DisplayName: testCredentialsGroupName, }, }, }, @@ -138,14 +148,14 @@ func TestGetCredentialsGroupName(t *testing.T) { { description: "multiple credentials groups", listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ - CredentialsGroups: &[]objectstorage.CredentialsGroup{ + CredentialsGroups: []objectstorage.CredentialsGroup{ { - CredentialsGroupId: utils.Ptr("test-UUID"), - DisplayName: utils.Ptr("test-name"), + CredentialsGroupId: "test-UUID", + DisplayName: "test-name", }, { - CredentialsGroupId: utils.Ptr(testCredentialsGroupId), - DisplayName: utils.Ptr(testCredentialsGroupName), + CredentialsGroupId: testCredentialsGroupId, + DisplayName: testCredentialsGroupName, }, }, }, @@ -160,23 +170,11 @@ func TestGetCredentialsGroupName(t *testing.T) { isValid: false, }, { - description: "nil credentials group id", + description: "empty credentials group id", listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ - CredentialsGroups: &[]objectstorage.CredentialsGroup{ + CredentialsGroups: []objectstorage.CredentialsGroup{ { - CredentialsGroupId: nil, - }, - }, - }, - isValid: false, - }, - { - description: "nil credentials group name", - listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ - CredentialsGroups: &[]objectstorage.CredentialsGroup{ - { - CredentialsGroupId: utils.Ptr(testCredentialsGroupId), - DisplayName: nil, + CredentialsGroupId: "", }, }, }, @@ -185,10 +183,10 @@ func TestGetCredentialsGroupName(t *testing.T) { { description: "empty credentials group name", listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{ - CredentialsGroups: &[]objectstorage.CredentialsGroup{ + CredentialsGroups: []objectstorage.CredentialsGroup{ { - CredentialsGroupId: utils.Ptr(testCredentialsGroupId), - DisplayName: utils.Ptr(""), + CredentialsGroupId: testCredentialsGroupId, + DisplayName: "", }, }, }, @@ -198,10 +196,10 @@ func TestGetCredentialsGroupName(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - client := &objectStorageClientMocked{ + client := newAPIMock(&mockSettings{ listCredentialsGroupsFails: tt.listCredentialsGroupsFails, listCredentialsGroupsResp: tt.listCredentialsGroupsResp, - } + }) output, err := GetCredentialsGroupName(context.Background(), client, testProjectId, testCredentialsGroupId, testRegion) @@ -232,10 +230,10 @@ func TestGetCredentialsName(t *testing.T) { { description: "base", listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ - AccessKeys: &[]objectstorage.AccessKey{ + AccessKeys: []objectstorage.AccessKey{ { - KeyId: utils.Ptr(testCredentialsId), - DisplayName: utils.Ptr(testCredentialsName), + KeyId: testCredentialsId, + DisplayName: testCredentialsName, }, }, }, @@ -250,14 +248,14 @@ func TestGetCredentialsName(t *testing.T) { { description: "multiple credentials", listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ - AccessKeys: &[]objectstorage.AccessKey{ + AccessKeys: []objectstorage.AccessKey{ { - KeyId: utils.Ptr("test-UUID"), - DisplayName: utils.Ptr("test-name"), + KeyId: "test-UUID", + DisplayName: "test-name", }, { - KeyId: utils.Ptr(testCredentialsId), - DisplayName: utils.Ptr(testCredentialsName), + KeyId: testCredentialsId, + DisplayName: testCredentialsName, }, }, }, @@ -272,23 +270,11 @@ func TestGetCredentialsName(t *testing.T) { isValid: false, }, { - description: "nil credentials id", - listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ - AccessKeys: &[]objectstorage.AccessKey{ - { - KeyId: nil, - }, - }, - }, - isValid: false, - }, - { - description: "nil credentials name", + description: "empty credentials id", listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ - AccessKeys: &[]objectstorage.AccessKey{ + AccessKeys: []objectstorage.AccessKey{ { - KeyId: utils.Ptr(testCredentialsId), - DisplayName: nil, + KeyId: "", }, }, }, @@ -297,10 +283,10 @@ func TestGetCredentialsName(t *testing.T) { { description: "empty credentials name", listAccessKeysResp: &objectstorage.ListAccessKeysResponse{ - AccessKeys: &[]objectstorage.AccessKey{ + AccessKeys: []objectstorage.AccessKey{ { - KeyId: utils.Ptr(testCredentialsId), - DisplayName: utils.Ptr(""), + KeyId: testCredentialsId, + DisplayName: "", }, }, }, @@ -310,37 +296,10 @@ func TestGetCredentialsName(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - mockedRespBytes, err := json.Marshal(tt.listAccessKeysResp) - if err != nil { - t.Fatalf("Failed to marshal mocked response: %v", err) - } - - handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/json") - if tt.getCredentialsNameFails { - w.WriteHeader(http.StatusBadGateway) - w.Header().Set("Content-Type", "application/json") - _, err := w.Write([]byte("{\"message\": \"Something bad happened\"")) - if err != nil { - t.Errorf("Failed to write bad response: %v", err) - } - return - } - - _, err := w.Write(mockedRespBytes) - if err != nil { - t.Errorf("Failed to write response: %v", err) - } + client := newAPIMock(&mockSettings{ + listAccessKeysFails: tt.getCredentialsNameFails, + listAccessKeysResp: tt.listAccessKeysResp, }) - mockedServer := httptest.NewServer(handler) - defer mockedServer.Close() - client, err := objectstorage.NewAPIClient( - config.WithEndpoint(mockedServer.URL), - config.WithoutAuthentication(), - ) - if err != nil { - t.Fatalf("Failed to initialize client: %v", err) - } output, err := GetCredentialsName(context.Background(), client, testProjectId, testCredentialsGroupId, testCredentialsId, testRegion) diff --git a/internal/pkg/services/observability/client/client.go b/internal/pkg/services/observability/client/client.go index 83c496121..eae8204d7 100644 --- a/internal/pkg/services/observability/client/client.go +++ b/internal/pkg/services/observability/client/client.go @@ -1,10 +1,11 @@ package client import ( + "github.com/stackitcloud/stackit-sdk-go/services/observability" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/observability" "github.com/spf13/viper" ) diff --git a/internal/pkg/services/observability/utils/utils.go b/internal/pkg/services/observability/utils/utils.go index 234da09be..7a15180c5 100644 --- a/internal/pkg/services/observability/utils/utils.go +++ b/internal/pkg/services/observability/utils/utils.go @@ -5,9 +5,10 @@ import ( "fmt" "strings" + "github.com/stackitcloud/stackit-sdk-go/services/observability" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/stackitcloud/stackit-sdk-go/services/observability" ) const ( @@ -21,7 +22,7 @@ type ObservabilityClient interface { } var ( - defaultStaticConfigs = []observability.CreateScrapeConfigPayloadStaticConfigsInner{ + defaultStaticConfigs = []observability.PartialUpdateScrapeConfigsRequestInnerStaticConfigsInner{ { Targets: utils.Ptr([]string{ "url-target", @@ -120,14 +121,14 @@ func MapToUpdateScrapeConfigPayload(resp *observability.GetScrapeConfigResponse) return &payload, nil } -func mapMetricsRelabelConfig(metricsRelabelConfigs *[]observability.MetricsRelabelConfig) *[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner { +func mapMetricsRelabelConfig(metricsRelabelConfigs *[]observability.MetricsRelabelConfig) *[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner { if metricsRelabelConfigs == nil { return nil } - var mappedConfigs []observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner + var mappedConfigs []observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner for _, config := range *metricsRelabelConfigs { - mappedConfig := observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ - Action: observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInnerGetActionAttributeType(config.Action), + mappedConfig := observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{ + Action: observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInnerGetActionAttributeType(config.Action), Modulus: utils.ConvertInt64PToFloat64P(config.Modulus), Regex: config.Regex, Replacement: config.Replacement, @@ -160,23 +161,23 @@ func mapStaticConfig(staticConfigs *[]observability.StaticConfigs) *[]observabil return &mappedConfigs } -func mapBasicAuth(basicAuth *observability.BasicAuth) *observability.CreateScrapeConfigPayloadBasicAuth { +func mapBasicAuth(basicAuth *observability.BasicAuth) *observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth { if basicAuth == nil { return nil } - return &observability.CreateScrapeConfigPayloadBasicAuth{ + return &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{ Password: basicAuth.Password, Username: basicAuth.Username, } } -func mapTlsConfig(tlsConfig *observability.TLSConfig) *observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig { +func mapTlsConfig(tlsConfig *observability.TLSConfig) *observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig { if tlsConfig == nil { return nil } - return &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{ + return &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{ InsecureSkipVerify: tlsConfig.InsecureSkipVerify, } } diff --git a/internal/pkg/services/observability/utils/utils_test.go b/internal/pkg/services/observability/utils/utils_test.go index 0b5083aad..18919db62 100644 --- a/internal/pkg/services/observability/utils/utils_test.go +++ b/internal/pkg/services/observability/utils/utils_test.go @@ -89,7 +89,7 @@ func fixtureGetScrapeConfigResponse(mods ...func(*observability.GetScrapeConfigR func fixtureUpdateScrapeConfigPayload(mods ...func(*observability.UpdateScrapeConfigPayload)) *observability.UpdateScrapeConfigPayload { payload := &observability.UpdateScrapeConfigPayload{ - BasicAuth: &observability.CreateScrapeConfigPayloadBasicAuth{ + BasicAuth: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{ Username: utils.Ptr("username"), Password: utils.Ptr("password"), }, @@ -97,9 +97,9 @@ func fixtureUpdateScrapeConfigPayload(mods ...func(*observability.UpdateScrapeCo HonorLabels: utils.Ptr(true), HonorTimeStamps: utils.Ptr(true), MetricsPath: utils.Ptr("/metrics"), - MetricsRelabelConfigs: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + MetricsRelabelConfigs: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{ { - Action: utils.Ptr(observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInnerAction("replace")), + Action: observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInnerGetActionAttributeType(utils.Ptr("replace")), Modulus: utils.Ptr(1.0), Regex: utils.Ptr("regex"), Replacement: utils.Ptr("replacement"), @@ -125,7 +125,7 @@ func fixtureUpdateScrapeConfigPayload(mods ...func(*observability.UpdateScrapeCo Targets: &[]string{"target"}, }, }, - TlsConfig: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{ + TlsConfig: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{ InsecureSkipVerify: utils.Ptr(true), }, } @@ -426,7 +426,7 @@ func TestMapMetricsRelabelConfig(t *testing.T) { tests := []struct { description string config *[]observability.MetricsRelabelConfig - expected *[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner + expected *[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner }{ { description: "base case", @@ -441,9 +441,9 @@ func TestMapMetricsRelabelConfig(t *testing.T) { TargetLabel: utils.Ptr("targetLabel"), }, }, - expected: &[]observability.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + expected: &[]observability.PartialUpdateScrapeConfigsRequestInnerMetricsRelabelConfigsInner{ { - Action: observability.CREATESCRAPECONFIGPAYLOADMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(), + Action: observability.PARTIALUPDATESCRAPECONFIGSREQUESTINNERMETRICSRELABELCONFIGSINNERACTION_REPLACE.Ptr(), Modulus: utils.Float64Ptr(1.0), Regex: utils.Ptr("regex"), Replacement: utils.Ptr("replacement"), @@ -540,7 +540,7 @@ func TestMapBasicAuth(t *testing.T) { tests := []struct { description string auth *observability.BasicAuth - expected *observability.CreateScrapeConfigPayloadBasicAuth + expected *observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth }{ { description: "base case", @@ -548,7 +548,7 @@ func TestMapBasicAuth(t *testing.T) { Username: utils.Ptr("username"), Password: utils.Ptr("password"), }, - expected: &observability.CreateScrapeConfigPayloadBasicAuth{ + expected: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{ Username: utils.Ptr("username"), Password: utils.Ptr("password"), }, @@ -556,7 +556,7 @@ func TestMapBasicAuth(t *testing.T) { { description: "empty data", auth: &observability.BasicAuth{}, - expected: &observability.CreateScrapeConfigPayloadBasicAuth{}, + expected: &observability.PartialUpdateScrapeConfigsRequestInnerBasicAuth{}, }, { description: "nil", @@ -585,21 +585,21 @@ func TestMapTlsConfig(t *testing.T) { tests := []struct { description string config *observability.TLSConfig - expected *observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig + expected *observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig }{ { description: "base case", config: &observability.TLSConfig{ InsecureSkipVerify: utils.Ptr(true), }, - expected: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{ + expected: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{ InsecureSkipVerify: utils.Ptr(true), }, }, { description: "empty data", config: &observability.TLSConfig{}, - expected: &observability.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{}, + expected: &observability.PartialUpdateScrapeConfigsRequestInnerHttpSdConfigsInnerOauth2TlsConfig{}, }, { description: "nil", diff --git a/internal/pkg/services/resourcemanager/utils/utils_test.go b/internal/pkg/services/resourcemanager/utils/utils_test.go index bcd0ad2d0..5c79c7354 100644 --- a/internal/pkg/services/resourcemanager/utils/utils_test.go +++ b/internal/pkg/services/resourcemanager/utils/utils_test.go @@ -6,8 +6,9 @@ import ( "testing" "github.com/google/uuid" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) var ( diff --git a/internal/pkg/services/runcommand/utils/utils.go b/internal/pkg/services/runcommand/utils/utils.go index d6775f373..358e27c59 100644 --- a/internal/pkg/services/runcommand/utils/utils.go +++ b/internal/pkg/services/runcommand/utils/utils.go @@ -5,13 +5,15 @@ import ( "strings" ) -func ParseScriptParams(params map[string]string) (map[string]string, error) { +func ParseScriptParams(params *map[string]string) (*map[string]string, error) { //nolint:gocritic // flag value is a map pointer if params == nil { return nil, nil } + parsed := map[string]string{} - for k, v := range params { + for k, v := range *params { parsed[k] = v + if k == "script" && strings.HasPrefix(v, "@{") && strings.HasSuffix(v, "}") { // Check if a script file path was specified, like: --params script=@{/tmp/test.sh} fileContents, err := os.ReadFile(v[2 : len(v)-1]) @@ -21,5 +23,6 @@ func ParseScriptParams(params map[string]string) (map[string]string, error) { parsed[k] = string(fileContents) } } - return parsed, nil + + return &parsed, nil } diff --git a/internal/pkg/services/runcommand/utils/utils_test.go b/internal/pkg/services/runcommand/utils/utils_test.go index 5b1d1c69f..320bac18c 100644 --- a/internal/pkg/services/runcommand/utils/utils_test.go +++ b/internal/pkg/services/runcommand/utils/utils_test.go @@ -2,26 +2,34 @@ package utils import ( "testing" + + "github.com/google/go-cmp/cmp" ) func TestParseScriptParams(t *testing.T) { tests := []struct { description string - input map[string]string - expectedOutput map[string]string + input *map[string]string + expectedOutput *map[string]string isValid bool }{ { - "base-ok", - map[string]string{"script": "ls /"}, - map[string]string{"script": "ls /"}, - true, + description: "base-ok", + input: &map[string]string{"script": "ls /"}, + expectedOutput: &map[string]string{"script": "ls /"}, + isValid: true, + }, + { + description: "nil input", + input: nil, + expectedOutput: nil, + isValid: true, }, { - "not-ok-nonexistant-file-specified-for-script", - map[string]string{"script": "@{/some/file/which/does/not/exist/and/thus/fails}"}, - nil, - false, + description: "not-ok-nonexistant-file-specified-for-script", + input: &map[string]string{"script": "@{/some/file/which/does/not/exist/and/thus/fails}"}, + expectedOutput: nil, + isValid: false, }, } @@ -38,8 +46,9 @@ func TestParseScriptParams(t *testing.T) { if !tt.isValid { return } - if output["script"] != tt.expectedOutput["script"] { - t.Errorf("expected output to be %s, got %s", tt.expectedOutput["script"], output["script"]) + diff := cmp.Diff(output, tt.expectedOutput) + if diff != "" { + t.Fatalf("ParseScriptParams() output mismatch (-want +got):\n%s", diff) } }) } diff --git a/internal/pkg/services/secrets-manager/utils/utils_test.go b/internal/pkg/services/secrets-manager/utils/utils_test.go index d79ca49f7..f0d926b0b 100644 --- a/internal/pkg/services/secrets-manager/utils/utils_test.go +++ b/internal/pkg/services/secrets-manager/utils/utils_test.go @@ -6,8 +6,9 @@ import ( "testing" "github.com/google/uuid" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/secretsmanager" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) var ( diff --git a/internal/pkg/services/service-enablement/client/client.go b/internal/pkg/services/service-enablement/client/client.go index e0bced744..6aa7324b1 100644 --- a/internal/pkg/services/service-enablement/client/client.go +++ b/internal/pkg/services/service-enablement/client/client.go @@ -2,6 +2,7 @@ package client import ( "github.com/spf13/viper" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" diff --git a/internal/pkg/services/sfs/client/client.go b/internal/pkg/services/sfs/client/client.go index 3dc2ef801..0e9057501 100644 --- a/internal/pkg/services/sfs/client/client.go +++ b/internal/pkg/services/sfs/client/client.go @@ -1,10 +1,11 @@ package client import ( + "github.com/stackitcloud/stackit-sdk-go/services/sfs" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-sdk-go/services/sfs" "github.com/spf13/viper" ) diff --git a/internal/pkg/services/sfs/utils/utils_test.go b/internal/pkg/services/sfs/utils/utils_test.go index de4dbe11f..0f7ef08bb 100644 --- a/internal/pkg/services/sfs/utils/utils_test.go +++ b/internal/pkg/services/sfs/utils/utils_test.go @@ -6,8 +6,9 @@ import ( "testing" "github.com/google/uuid" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/sfs" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) const ( diff --git a/internal/pkg/services/ske/client/client.go b/internal/pkg/services/ske/client/client.go index 5b4b69f38..dd3af9872 100644 --- a/internal/pkg/services/ske/client/client.go +++ b/internal/pkg/services/ske/client/client.go @@ -6,7 +6,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/spf13/viper" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) func ConfigureClient(p *print.Printer, cliVersion string) (*ske.APIClient, error) { diff --git a/internal/pkg/services/ske/utils/utils.go b/internal/pkg/services/ske/utils/utils.go index 904ff97a1..11b4b3930 100644 --- a/internal/pkg/services/ske/utils/utils.go +++ b/internal/pkg/services/ske/utils/utils.go @@ -9,15 +9,16 @@ import ( "regexp" "strconv" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "k8s.io/client-go/tools/clientcmd" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" "golang.org/x/mod/semver" ) const ( - defaultNodepoolCRI = ske.CRINAME_CONTAINERD + defaultNodepoolCRI = "containerd" defaultNodepoolMachineImageName = "flatcar" defaultNodepoolMaxUnavailable = 0 defaultNodepoolMinimum = 1 @@ -28,17 +29,12 @@ const ( supportedState = "supported" ) -type SKEClient interface { - ListClustersExecute(ctx context.Context, projectId, region string) (*ske.ListClustersResponse, error) - ListProviderOptionsExecute(ctx context.Context, region string) (*ske.ProviderOptions, error) -} - -func ClusterExists(ctx context.Context, apiClient SKEClient, projectId, region, clusterName string) (bool, error) { - clusters, err := apiClient.ListClustersExecute(ctx, projectId, region) +func ClusterExists(ctx context.Context, apiClient ske.DefaultAPI, projectId, region, clusterName string) (bool, error) { + clusters, err := apiClient.ListClusters(ctx, projectId, region).Execute() if err != nil { return false, fmt.Errorf("list SKE clusters: %w", err) } - for _, cl := range *clusters.Items { + for _, cl := range clusters.Items { if cl.Name != nil && *cl.Name == clusterName { return true, nil } @@ -46,8 +42,8 @@ func ClusterExists(ctx context.Context, apiClient SKEClient, projectId, region, return false, nil } -func GetDefaultPayload(ctx context.Context, apiClient SKEClient, region string) (*ske.CreateOrUpdateClusterPayload, error) { - resp, err := apiClient.ListProviderOptionsExecute(ctx, region) +func GetDefaultPayload(ctx context.Context, apiClient ske.DefaultAPI, region string) (*ske.CreateOrUpdateClusterPayload, error) { + resp, err := apiClient.ListProviderOptions(ctx, region).Execute() if err != nil { return nil, fmt.Errorf("get SKE provider options: %w", err) } @@ -64,86 +60,91 @@ func GetDefaultPayload(ctx context.Context, apiClient SKEClient, region string) payload := &ske.CreateOrUpdateClusterPayload{ Extensions: &ske.Extension{ Acl: &ske.ACL{ - AllowedCidrs: &[]string{}, - Enabled: utils.Ptr(false), + AllowedCidrs: []string{}, + Enabled: false, }, }, Kubernetes: payloadKubernetes, - Nodepools: &[]ske.Nodepool{ + Nodepools: []ske.Nodepool{ *payloadNodepool, }, } return payload, nil } -func getDefaultPayloadKubernetes(resp *ske.ProviderOptions) (*ske.Kubernetes, error) { - output := &ske.Kubernetes{} +func getDefaultPayloadKubernetes(resp *ske.ProviderOptions) (ske.Kubernetes, error) { + output := ske.Kubernetes{} if resp.KubernetesVersions == nil { - return nil, fmt.Errorf("no supported Kubernetes version found") + return ske.Kubernetes{}, fmt.Errorf("no supported Kubernetes version found") } foundKubernetesVersion := false - versions := *resp.KubernetesVersions + versions := resp.KubernetesVersions for i := range versions { version := versions[i] if *version.State != supportedState { continue } - if output.Version != nil { - oldSemVer := fmt.Sprintf("v%s", *output.Version) - newSemVer := fmt.Sprintf("v%s", *version.Version) + if output.Version != "" { + oldSemVer := fmt.Sprintf("v%s", output.Version) + newSemVer := fmt.Sprintf("v%s", version.GetVersion()) if semver.Compare(newSemVer, oldSemVer) != 1 { continue } } foundKubernetesVersion = true - output.Version = version.Version + output.Version = version.GetVersion() } if !foundKubernetesVersion { - return nil, fmt.Errorf("no supported Kubernetes version found") + return ske.Kubernetes{}, fmt.Errorf("no supported Kubernetes version found") } return output, nil } func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error) { - if resp.AvailabilityZones == nil || len(*resp.AvailabilityZones) == 0 { + if len(resp.AvailabilityZones) == 0 { return nil, fmt.Errorf("no availability zones found") } var availabilityZones []string - for i := range *resp.AvailabilityZones { - azName := (*resp.AvailabilityZones)[i].GetName() + for i := range resp.AvailabilityZones { + azName := resp.AvailabilityZones[i].GetName() // don't include availability zones like eu01-m, eu02-m, not all flavors are available there if !regexp.MustCompile(`\w{2}\d{2}-m`).MatchString(azName) { availabilityZones = append(availabilityZones, azName) } } - if resp.MachineTypes == nil || len(*resp.MachineTypes) == 0 { + if len(resp.MachineTypes) == 0 { return nil, fmt.Errorf("no machine types found") } - machineType := (*resp.MachineTypes)[0].GetName() + azLen := len(availabilityZones) + if azLen > 1000 { + // check against a very large number to avoid gosec warning + return nil, fmt.Errorf("invalid number of availability zones") + } + machineType := resp.MachineTypes[0].GetName() output := &ske.Nodepool{ - AvailabilityZones: &availabilityZones, + AvailabilityZones: availabilityZones, Cri: &ske.CRI{ Name: utils.Ptr(defaultNodepoolCRI), }, - Machine: &ske.Machine{ - Type: &machineType, - Image: &ske.Image{ - Name: utils.Ptr(defaultNodepoolMachineImageName), + Machine: ske.Machine{ + Type: machineType, + Image: ske.Image{ + Name: defaultNodepoolMachineImageName, }, }, // there must be as many nodes as availability zones are given - MaxSurge: utils.Ptr(int64(len(availabilityZones))), - MaxUnavailable: utils.Ptr(int64(defaultNodepoolMaxUnavailable)), - Maximum: utils.Ptr(int64(len(availabilityZones))), - Minimum: utils.Ptr(int64(defaultNodepoolMinimum)), - Name: utils.Ptr(defaultNodepoolName), - Volume: &ske.Volume{ + MaxSurge: utils.Ptr(int32(azLen)), + MaxUnavailable: utils.Ptr(int32(defaultNodepoolMaxUnavailable)), + Maximum: int32(azLen), + Minimum: int32(defaultNodepoolMinimum), + Name: defaultNodepoolName, + Volume: ske.Volume{ Type: utils.Ptr(defaultNodepoolVolumeType), - Size: utils.Ptr(int64(defaultNodepoolVolumeSize)), + Size: int32(defaultNodepoolVolumeSize), }, } @@ -152,7 +153,7 @@ func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error) return nil, fmt.Errorf("no supported image versions found") } foundImageVersion := false - images := *resp.MachineImages + images := resp.MachineImages for i := range images { image := images[i] if *image.Name != defaultNodepoolMachineImageName { @@ -161,7 +162,7 @@ func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error) if image.Versions == nil { continue } - versions := *image.Versions + versions := image.Versions for j := range versions { version := versions[j] if *version.State != supportedState { @@ -169,12 +170,12 @@ func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error) } // Check if default CRI is supported - if version.Cri == nil || len(*version.Cri) == 0 { + if len(version.Cri) == 0 { continue } criSupported := false - for k := range *version.Cri { - cri := (*version.Cri)[k] + for k := range version.Cri { + cri := version.Cri[k] if *cri.Name == defaultNodepoolCRI { criSupported = true break @@ -184,8 +185,8 @@ func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error) continue } - if output.Machine.Image.Version != nil { - oldSemVer := fmt.Sprintf("v%s", *output.Machine.Image.Version) + if output.Machine.Image.Version != "" { + oldSemVer := fmt.Sprintf("v%s", output.Machine.Image.Version) newSemVer := fmt.Sprintf("v%s", *version.Version) if semver.Compare(newSemVer, oldSemVer) != 1 { continue @@ -193,7 +194,7 @@ func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error) } foundImageVersion = true - output.Machine.Image.Version = version.Version + output.Machine.Image.Version = version.GetVersion() } } if !foundImageVersion { diff --git a/internal/pkg/services/ske/utils/utils_test.go b/internal/pkg/services/ske/utils/utils_test.go index b150509ec..23f8adbac 100644 --- a/internal/pkg/services/ske/utils/utils_test.go +++ b/internal/pkg/services/ske/utils/utils_test.go @@ -7,12 +7,13 @@ import ( "path/filepath" "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "k8s.io/client-go/tools/clientcmd" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" + ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) var ( @@ -63,29 +64,8 @@ users: ` ) -type skeClientMocked struct { - listClustersFails bool - listClustersResp *ske.ListClustersResponse - listProviderOptionsFails bool - listProviderOptionsResp *ske.ProviderOptions -} - const testRegion = "eu01" -func (m *skeClientMocked) ListClustersExecute(_ context.Context, _, _ string) (*ske.ListClustersResponse, error) { - if m.listClustersFails { - return nil, fmt.Errorf("could not list clusters") - } - return m.listClustersResp, nil -} - -func (m *skeClientMocked) ListProviderOptionsExecute(_ context.Context, _ string) (*ske.ProviderOptions, error) { - if m.listProviderOptionsFails { - return nil, fmt.Errorf("could not list provider options") - } - return m.listProviderOptionsResp, nil -} - func TestClusterExists(t *testing.T) { tests := []struct { description string @@ -96,19 +76,19 @@ func TestClusterExists(t *testing.T) { }{ { description: "cluster exists", - getClustersResp: &ske.ListClustersResponse{Items: &[]ske.Cluster{{Name: utils.Ptr(testClusterName)}}}, + getClustersResp: &ske.ListClustersResponse{Items: []ske.Cluster{{Name: utils.Ptr(testClusterName)}}}, isValid: true, expectedExists: true, }, { description: "cluster exists 2", - getClustersResp: &ske.ListClustersResponse{Items: &[]ske.Cluster{{Name: utils.Ptr("some-cluster")}, {Name: utils.Ptr("some-other-cluster")}, {Name: utils.Ptr(testClusterName)}}}, + getClustersResp: &ske.ListClustersResponse{Items: []ske.Cluster{{Name: utils.Ptr("some-cluster")}, {Name: utils.Ptr("some-other-cluster")}, {Name: utils.Ptr(testClusterName)}}}, isValid: true, expectedExists: true, }, { description: "cluster does not exist", - getClustersResp: &ske.ListClustersResponse{Items: &[]ske.Cluster{{Name: utils.Ptr("some-cluster")}, {Name: utils.Ptr("some-other-cluster")}}}, + getClustersResp: &ske.ListClustersResponse{Items: []ske.Cluster{{Name: utils.Ptr("some-cluster")}, {Name: utils.Ptr("some-other-cluster")}}}, isValid: true, expectedExists: false, }, @@ -121,9 +101,13 @@ func TestClusterExists(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - client := &skeClientMocked{ - listClustersFails: tt.getClustersFails, - listClustersResp: tt.getClustersResp, + client := &ske.DefaultAPIServiceMock{ + ListClustersExecuteMock: utils.Ptr(func(_ ske.ApiListClustersRequest) (*ske.ListClustersResponse, error) { + if tt.getClustersFails { + return nil, fmt.Errorf("could not list clusters") + } + return tt.getClustersResp, nil + }), } exists, err := ClusterExists(context.Background(), client, testProjectId, testRegion, testClusterName) @@ -146,18 +130,18 @@ func TestClusterExists(t *testing.T) { func fixtureProviderOptions(mods ...func(*ske.ProviderOptions)) *ske.ProviderOptions { providerOptions := &ske.ProviderOptions{ - AvailabilityZones: &[]ske.AvailabilityZone{ + AvailabilityZones: []ske.AvailabilityZone{ {Name: utils.Ptr("eu01-m")}, {Name: utils.Ptr("eu01-1")}, {Name: utils.Ptr("eu01-2")}, {Name: utils.Ptr("eu01-3")}, }, - MachineTypes: &[]ske.MachineType{ + MachineTypes: []ske.MachineType{ { Name: utils.Ptr("b1.2"), }, }, - KubernetesVersions: &[]ske.KubernetesVersion{ + KubernetesVersions: []ske.KubernetesVersion{ { State: utils.Ptr("supported"), Version: utils.Ptr("1.2.3"), @@ -171,31 +155,31 @@ func fixtureProviderOptions(mods ...func(*ske.ProviderOptions)) *ske.ProviderOpt Version: utils.Ptr("4.4.4"), }, }, - MachineImages: &[]ske.MachineImage{ + MachineImages: []ske.MachineImage{ { Name: utils.Ptr("flatcar"), - Versions: &[]ske.MachineImageVersion{ + Versions: []ske.MachineImageVersion{ { State: utils.Ptr("supported"), Version: utils.Ptr("1.2.3"), - Cri: &[]ske.CRI{ + Cri: []ske.CRI{ { - Name: ske.CRINAME_DOCKER.Ptr(), + Name: utils.Ptr("docker"), }, { - Name: ske.CRINAME_CONTAINERD.Ptr(), + Name: utils.Ptr("containerd"), }, }, }, { State: utils.Ptr("supported"), Version: utils.Ptr("3.2.1"), - Cri: &[]ske.CRI{ + Cri: []ske.CRI{ { - Name: ske.CRINAME_DOCKER.Ptr(), + Name: utils.Ptr("docker"), }, { - Name: ske.CRINAME_CONTAINERD.Ptr(), + Name: utils.Ptr("containerd"), }, }, }, @@ -203,13 +187,13 @@ func fixtureProviderOptions(mods ...func(*ske.ProviderOptions)) *ske.ProviderOpt }, { Name: utils.Ptr("not-flatcar"), - Versions: &[]ske.MachineImageVersion{ + Versions: []ske.MachineImageVersion{ { State: utils.Ptr("supported"), Version: utils.Ptr("4.4.4"), - Cri: &[]ske.CRI{ + Cri: []ske.CRI{ { - Name: ske.CRINAME_CONTAINERD.Ptr(), + Name: utils.Ptr("containerd"), }, }, }, @@ -217,7 +201,7 @@ func fixtureProviderOptions(mods ...func(*ske.ProviderOptions)) *ske.ProviderOpt }, { Name: utils.Ptr("flatcar"), - Versions: &[]ske.MachineImageVersion{ + Versions: []ske.MachineImageVersion{ { State: utils.Ptr("supported"), Version: utils.Ptr("4.4.4"), @@ -226,13 +210,13 @@ func fixtureProviderOptions(mods ...func(*ske.ProviderOptions)) *ske.ProviderOpt }, { Name: utils.Ptr("flatcar"), - Versions: &[]ske.MachineImageVersion{ + Versions: []ske.MachineImageVersion{ { State: utils.Ptr("not-supported"), Version: utils.Ptr("4.4.4"), - Cri: &[]ske.CRI{ + Cri: []ske.CRI{ { - Name: ske.CRINAME_CONTAINERD.Ptr(), + Name: utils.Ptr("containerd"), }, }, }, @@ -240,13 +224,13 @@ func fixtureProviderOptions(mods ...func(*ske.ProviderOptions)) *ske.ProviderOpt }, { Name: utils.Ptr("flatcar"), - Versions: &[]ske.MachineImageVersion{ + Versions: []ske.MachineImageVersion{ { State: utils.Ptr("supported"), Version: utils.Ptr("4.4.4"), - Cri: &[]ske.CRI{ + Cri: []ske.CRI{ { - Name: ske.CRINAME_DOCKER.Ptr(), + Name: utils.Ptr("docker"), }, }, }, @@ -264,38 +248,38 @@ func fixtureGetDefaultPayload(mods ...func(*ske.CreateOrUpdateClusterPayload)) * payload := &ske.CreateOrUpdateClusterPayload{ Extensions: &ske.Extension{ Acl: &ske.ACL{ - AllowedCidrs: &[]string{}, - Enabled: utils.Ptr(false), + AllowedCidrs: []string{}, + Enabled: false, }, }, - Kubernetes: &ske.Kubernetes{ - Version: utils.Ptr("3.2.1"), + Kubernetes: ske.Kubernetes{ + Version: "3.2.1", }, - Nodepools: &[]ske.Nodepool{ + Nodepools: []ske.Nodepool{ { - AvailabilityZones: &[]string{ + AvailabilityZones: []string{ "eu01-1", "eu01-2", "eu01-3", }, Cri: &ske.CRI{ - Name: ske.CRINAME_CONTAINERD.Ptr(), + Name: utils.Ptr("containerd"), }, - Machine: &ske.Machine{ - Type: utils.Ptr("b1.2"), - Image: &ske.Image{ - Version: utils.Ptr("3.2.1"), - Name: utils.Ptr("flatcar"), + Machine: ske.Machine{ + Type: "b1.2", + Image: ske.Image{ + Version: "3.2.1", + Name: "flatcar", }, }, - MaxSurge: utils.Ptr(int64(3)), - MaxUnavailable: utils.Ptr(int64(0)), - Maximum: utils.Ptr(int64(3)), - Minimum: utils.Ptr(int64(1)), - Name: utils.Ptr("pool-default"), - Volume: &ske.Volume{ + MaxSurge: utils.Ptr(int32(3)), + MaxUnavailable: utils.Ptr(int32(0)), + Maximum: int32(3), + Minimum: int32(1), + Name: "pool-default", + Volume: ske.Volume{ Type: utils.Ptr("storage_premium_perf2"), - Size: utils.Ptr(int64(50)), + Size: int32(50), }, }, }, @@ -335,7 +319,7 @@ func TestGetDefaultPayload(t *testing.T) { { description: "no availability zones", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.AvailabilityZones = &[]ske.AvailabilityZone{} + po.AvailabilityZones = []ske.AvailabilityZone{} }), isValid: false, }, @@ -349,7 +333,7 @@ func TestGetDefaultPayload(t *testing.T) { { description: "no machine types", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.MachineTypes = &[]ske.MachineType{} + po.MachineTypes = []ske.MachineType{} }), isValid: false, }, @@ -363,14 +347,14 @@ func TestGetDefaultPayload(t *testing.T) { { description: "no Kubernetes versions 2", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.KubernetesVersions = &[]ske.KubernetesVersion{} + po.KubernetesVersions = []ske.KubernetesVersion{} }), isValid: false, }, { description: "no supported Kubernetes versions", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.KubernetesVersions = &[]ske.KubernetesVersion{ + po.KubernetesVersions = []ske.KubernetesVersion{ { State: utils.Ptr("not-supported"), Version: utils.Ptr("1.2.3"), @@ -382,7 +366,7 @@ func TestGetDefaultPayload(t *testing.T) { { description: "no machine images 1", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.MachineImages = &[]ske.MachineImage{} + po.MachineImages = []ske.MachineImage{} }), isValid: false, }, @@ -396,7 +380,7 @@ func TestGetDefaultPayload(t *testing.T) { { description: "no machine image versions 1", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.MachineImages = &[]ske.MachineImage{ + po.MachineImages = []ske.MachineImage{ { Name: utils.Ptr("image-1"), Versions: nil, @@ -408,10 +392,10 @@ func TestGetDefaultPayload(t *testing.T) { { description: "no machine image versions 2", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.MachineImages = &[]ske.MachineImage{ + po.MachineImages = []ske.MachineImage{ { Name: utils.Ptr("image-1"), - Versions: &[]ske.MachineImageVersion{}, + Versions: []ske.MachineImageVersion{}, }, } }), @@ -420,10 +404,10 @@ func TestGetDefaultPayload(t *testing.T) { { description: "no supported machine image versions", listProviderOptionsResp: fixtureProviderOptions(func(po *ske.ProviderOptions) { - po.MachineImages = &[]ske.MachineImage{ + po.MachineImages = []ske.MachineImage{ { Name: utils.Ptr("image-1"), - Versions: &[]ske.MachineImageVersion{ + Versions: []ske.MachineImageVersion{ { State: utils.Ptr("not-supported"), Version: utils.Ptr("1.2.3"), @@ -438,9 +422,13 @@ func TestGetDefaultPayload(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - client := &skeClientMocked{ - listProviderOptionsFails: tt.listProviderOptionsFails, - listProviderOptionsResp: tt.listProviderOptionsResp, + client := &ske.DefaultAPIServiceMock{ + ListProviderOptionsExecuteMock: utils.Ptr(func(_ ske.ApiListProviderOptionsRequest) (*ske.ProviderOptions, error) { + if tt.listProviderOptionsFails { + return nil, fmt.Errorf("could not list provider options") + } + return tt.listProviderOptionsResp, nil + }), } output, err := GetDefaultPayload(context.Background(), client, testRegion) @@ -724,6 +712,8 @@ func TestGetDefaultKubeconfigPath(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { + // prevent test from failing if user has set the environment variable + t.Setenv("KUBECONFIG", "") output, err := GetDefaultKubeconfigPath() if err != nil { diff --git a/internal/pkg/spinner/spinner.go b/internal/pkg/spinner/spinner.go index 9c530f1ca..fa76ef2b8 100644 --- a/internal/pkg/spinner/spinner.go +++ b/internal/pkg/spinner/spinner.go @@ -15,7 +15,30 @@ type Spinner struct { done chan bool } -func New(p *print.Printer) *Spinner { +// Run starts a spinner and stops it when f completes +func Run(p *print.Printer, message string, f func() error) error { + _, err := Run2(p, message, func() (struct{}, error) { + err := f() + return struct{}{}, err + }) + return err +} + +// Run2 starts a spinner and stops it when f (result arity 2) completes. +func Run2[T any](p *print.Printer, message string, f func() (T, error)) (T, error) { + var r T + spinner := newSpinner(p) + spinner.start(message) + r, err := f() + if err != nil { + spinner.stopWithError() + return r, err + } + spinner.stop() + return r, nil +} + +func newSpinner(p *print.Printer) *Spinner { return &Spinner{ printer: p, states: []string{"|", "/", "-", "\\"}, @@ -25,18 +48,18 @@ func New(p *print.Printer) *Spinner { } } -func (s *Spinner) Start(message string) { +func (s *Spinner) start(message string) { s.message = message go s.animate() } -func (s *Spinner) Stop() { +func (s *Spinner) stop() { s.done <- true close(s.done) s.printer.Info("\r%s ✓ \n", s.message) } -func (s *Spinner) StopWithError() { +func (s *Spinner) stopWithError() { s.done <- true close(s.done) s.printer.Info("\r%s ✗ \n", s.message) diff --git a/internal/pkg/testutils/parse_input.go b/internal/pkg/testutils/parse_input.go index a0deafbc7..df961b8b8 100755 --- a/internal/pkg/testutils/parse_input.go +++ b/internal/pkg/testutils/parse_input.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/types" diff --git a/internal/pkg/testutils/parse_input_test.go b/internal/pkg/testutils/parse_input_test.go index 32008eea3..6b5d6c36b 100755 --- a/internal/pkg/testutils/parse_input_test.go +++ b/internal/pkg/testutils/parse_input_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/types" ) diff --git a/internal/pkg/testutils/testutils.go b/internal/pkg/testutils/testutils.go index ceecf888a..d11a7ed6d 100644 --- a/internal/pkg/testutils/testutils.go +++ b/internal/pkg/testutils/testutils.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" ) diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go index 862b92c8f..0cb50a3d4 100644 --- a/internal/pkg/utils/utils.go +++ b/internal/pkg/utils/utils.go @@ -11,9 +11,10 @@ import ( "github.com/inhies/go-bytesize" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/stackitcloud/stackit-cli/internal/pkg/config" sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/services/iaas" + + "github.com/stackitcloud/stackit-cli/internal/pkg/config" ) // Ptr Returns the pointer to any type T diff --git a/internal/pkg/utils/utils_test.go b/internal/pkg/utils/utils_test.go index 4591c84c9..0b5a656af 100644 --- a/internal/pkg/utils/utils_test.go +++ b/internal/pkg/utils/utils_test.go @@ -9,6 +9,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/spf13/viper" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" )