diff --git a/AUTHENTICATION.md b/AUTHENTICATION.md index 170b2d9e2..f88cb3883 100644 --- a/AUTHENTICATION.md +++ b/AUTHENTICATION.md @@ -5,7 +5,7 @@ This document describes how you can configure authentication for the STACKIT CLI ## Service account You can use a [service account](https://docs.stackit.cloud/stackit/en/service-accounts-134415819.html) to authenticate to the STACKIT CLI. -The CLI will search for service account credentials similarly to the [STACKIT SDK](https://github.com/stackitcloud/stackit-sdk-go) and [Terraform Provider](https://github.com/stackitcloud/terraform-provider-stackit), so if you have setup you environment previously for those tools, you can just run: +The CLI will search for service account credentials similarly to the [STACKIT SDK](https://github.com/stackitcloud/stackit-sdk-go) and [STACKIT Terraform Provider](https://github.com/stackitcloud/terraform-provider-stackit), so if you have already set up your environment for those tools, you can just run: ```bash $ stackit auth activate-service-account @@ -15,7 +15,7 @@ You can also configure the service account credentials directly in the CLI. To g ### Overview -If you dont have a service account, create one in the STACKIT Portal an assign it the necessary permissions, e.g. `owner`. There are two ways to authenticate: +If you don't have a service account, create one in the [STACKIT Portal](https://portal.stackit.cloud/) and assign the necessary permissions to it, e.g. `owner`. There are two ways to authenticate: - Key flow (recommended) - Token flow @@ -39,20 +39,20 @@ When setting up authentication, the CLI will always try to use the key flow firs ### Key flow - The following instructions assume that you have created a service account and assigned it the necessary permissions, e.g. `owner`. + The following instructions assume that you have created a service account and assigned the necessary permissions to it, e.g. `owner`. To use the key flow, you need to have a service account key, which must have an RSA key-pair attached to it. When creating the service account key, a new RSA key-pair can be created automatically, which will be included in the service account key. This will make it much easier to configure the key flow authentication in the CLI, by just providing the service account key. -**Optionally**, you can provide your own private key when creating the service account key, which will then require you to also provide it explicitly to the CLI, additionaly to the service account key. Check the STACKIT Knowledge Base for an [example of how to create your own key-pair](https://docs.stackit.cloud/stackit/en/usage-of-the-service-account-keys-in-stackit-175112464.html#UsageoftheserviceaccountkeysinSTACKIT-CreatinganRSAkey-pair). +**Optionally**, you can provide your own private key when creating the service account key, which will then require you to also provide it explicitly to the CLI, additionally to the service account key. Check the STACKIT Knowledge Base for an [example of how to create your own key-pair](https://docs.stackit.cloud/stackit/en/usage-of-the-service-account-keys-in-stackit-175112464.html#UsageoftheserviceaccountkeysinSTACKIT-CreatinganRSAkey-pair). To configure the key flow, follow this steps: 1. Create a service account key: - In the CLI, run `stackit service-account key create --email ` -- As an alternative, use the STACKIT Portal: go to the `Service Accounts` tab, choose a `Service Account` and go to `Service Account Keys` to create a key. For more details, see [Create a service account key](https://docs.stackit.cloud/stackit/en/create-a-service-account-key-175112456.html) +- As an alternative, use the [STACKIT Portal](https://portal.stackit.cloud/): go to the `Service Accounts` tab, choose a `Service Account` and go to `Service Account Keys` to create a key. For more details, see [Create a service account key](https://docs.stackit.cloud/stackit/en/create-a-service-account-key-175112456.html) 2. Save the content of the service account key by copying it and saving it in a JSON file. diff --git a/AUTOCOMPLETION.md b/AUTOCOMPLETION.md index 011723b91..db6c04a49 100644 --- a/AUTOCOMPLETION.md +++ b/AUTOCOMPLETION.md @@ -42,7 +42,7 @@ stackit completion zsh > "${fpath[1]}/_stackit" stackit completion zsh > $(brew --prefix)/share/zsh/site-functions/_stackit ``` -Additionaly, you might also need to run: +Additionally, you might also need to run: ```shell source $(brew --prefix)/share/zsh/site-functions/_stackit >> ~/.zshrc diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 1e419364d..9c8488a9a 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -38,16 +38,16 @@ The CLI commands are located under `internal/cmd`, where each folder includes th ### Implementing a new command -Let's suppose you want to want to implement a new command `bar`, that would be the direct child of an existing command `stackit foo` (meaning it would be invoked as `stackit foo bar`): +Let's suppose you want to implement a new command `bar`, that would be the direct child of an existing command `stackit foo` (meaning it would be invoked as `stackit foo bar`): 1. You would start by creating a new folder `bar/` inside `internal/cmd/foo/` 2. Following with the creation of a file `bar.go` inside your new folder `internal/cmd/foo/bar/` 1. The Go package should be similar to the command usage, in this case `package bar` would be an adequate name - 2. Please refer to the [Command file structure](./CONTRIBUTION.md/#command-file-structure) section for details on the strcutre of the file itself + 2. Please refer to the [Command file structure](./CONTRIBUTION.md/#command-file-structure) section for details on the structure of the file itself 3. To register the command `bar` as a child of the existing command `foo`, add `cmd.AddCommand(bar.NewCmd(p))` to the `addSubcommands` method of the constructor of the `foo` command 1. In this case, `p` is the `printer` that is passed from the root command to all subcommands of the tree (refer to the [Outputs, prints and debug logs](./CONTRIBUTION.md/#outputs-prints-and-debug-logs) section for more details regarding the `printer`) -Please remeber to run `make generate-docs` after your changes to keep the commands' documentation updated. +Please remember to run `make generate-docs` after your changes to keep the commands' documentation updated. #### Command file structure @@ -212,7 +212,7 @@ If you want to add a command that uses a STACKIT service `foo` that was not yet 1. Add a `FooCustomEndpointKey` key in `internal/pkg/config/config.go` (and add it to `ConfigKeys` and set the to default to `""` using `viper.SetDefault`) 2. Update the `stackit config unset` and `stackit config unset` commands by adding flags to set and unset a custom endpoint for the `foo` service API, respectively, and update their unit tests -3. Setup the SDK client configuration, using the authentication method configured in the CLI +3. Set up the SDK client configuration, using the authentication method configured in the CLI 1. This is done in `internal/pkg/services/foo/client/client.go` 2. Below is an example of a typical `client.go` file structure: @@ -291,6 +291,6 @@ If you would like to report a bug, please open a [GitHub issue](https://github.c To ensure we can provide the best support to your issue, follow these guidelines: 1. Go through the existing issues to check if your issue has already been reported. -2. Make sure you are using the latest version of the provider, we will not provide bug fixes for older versions. Also, latest versions may have the fix for your bug. -3. Please provide as much information as you can about your environment, e.g. your version of Go, your version of the provider, which operating system you are using and the corresponding version. +2. Make sure you are using the latest version of the STACKIT CLI, we will not provide bug fixes for older versions. Also, latest versions may have the fix for your bug. +3. Please provide as much information as you can about your environment, e.g. your version of Go, your version of the CLI, which operating system you are using and the corresponding version. 4. Include in your issue the steps to reproduce it, along with code snippets and/or information about your specific use case. This will make the support process much easier and efficient. diff --git a/README.md b/README.md index 39f547afb..da0ca88d6 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Examples: - `stackit mongodbflex instance create --name my-instance --cpu 1 --ram 4 --acl 0.0.0.0/0 --assume-yes` - `stackit dns zone delete my-zone` -Some commands are implemented at the root, group or sub-group level: +Some commands are implemented at the root, group or subgroup level: - `stackit config` to define variables to be used in future commands. - `stackit ske enable` to enable the SKE engine on your project. @@ -71,9 +71,9 @@ Below you can find a list of the STACKIT services already available in the CLI ( ## Authentication -Most of the commands will require you to be authenticated. Currently it's possible to authenticate with your personal user or with a service account. +Most of the commands will require you to be authenticated. Currently, it's possible to authenticate with your personal user or with a service account. -After successful authentication, the CLI stores credentials in your OS keychain. You won't need to login again for the duration of your session, which is 2h by default but configurable by providing the `--session-time-limit` flag on the `config set` command (see [Configuration](#configuration)). +After successful authentication, the CLI stores credentials in your OS keychain. You won't need to log in again for the duration of your session, which is 2h by default but configurable by providing the `--session-time-limit` flag on the `config set` command (see [Configuration](#configuration)). ### Login with a personal user account @@ -91,7 +91,7 @@ To authenticate using a service account, run: stackit auth activate-service-account ``` -For more details on how to setup authentication using a service account, check our [authentication guide](./AUTHENTICATION.md). +For more details on how to set up authentication using a service account, check our [authentication guide](./AUTHENTICATION.md). ## Configuration @@ -101,7 +101,13 @@ You can configure the CLI using the command: stackit config ``` -The configurations are stored in `~/stackit/cli-config.json` and are valid for all commands. For example, you can set a default `project-id` by running: +The configuration is saved in a file. The file's location varies depending on the operating system: + +- Unix - `$XDG_CONFIG_HOME/stackit/cli-config.json` +- MacOS - `$HOME/Library/Application Support/stackit/cli-config.json` +- Windows - `%AppData%\stackit\cli-config.json` + +The configuration options apply to all commands and can be set using the `stackit config set` command. For example, you can set a default `project-id` by running: ```bash stackit config set --project-id xxxx-xxxx-xxxxx @@ -113,9 +119,9 @@ To remove it, you can run: stackit config unset --project-id ``` -Run the `config set` command with the flag `--help` to get a list of all of the available configuration options. +Run the `config set` command with the flag `--help` to get a list of all the available configuration options. -You can lookup your current configuration by checking the configuration file or by running: +You can look up your current configuration by checking the configuration file or by running: ```bash stackit config list @@ -125,11 +131,11 @@ You can also edit the configuration file manually. ## Autocompletion -If you wish to setup command autocompletion in your shell for the STACKIT CLI, please refer to our [autocompletion guide](./AUTOCOMPLETION.md). +If you wish to set up command autocompletion in your shell for the STACKIT CLI, please refer to our [autocompletion guide](./AUTOCOMPLETION.md). ## Reporting issues -If you encounter any issues or have suggestions for improvements, please reach out to the Developer Tools team or open a ticket through the [STACKIT Help Center](https://support.stackit.cloud/). +If you encounter any issues or have suggestions for improvements, please open an issue in the [repository](https://github.com/stackitcloud/stackit-cli/issues). ## Contribute @@ -138,3 +144,13 @@ Your contribution is welcome! For more details on how to contribute, refer to ou ## License Apache 2.0 + + +## Useful Links +- [STACKIT Portal](https://portal.stackit.cloud/) + +- [STACKIT](https://www.stackit.de/en/) + +- [STACKIT Knowledge Base](https://docs.stackit.cloud/stackit/en/knowledge-base-85301704.html) + +- [STACKIT Terraform Provider](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs) \ No newline at end of file diff --git a/docs/stackit_argus_grafana_describe.md b/docs/stackit_argus_grafana_describe.md index a9c2c3fa2..1245939fa 100644 --- a/docs/stackit_argus_grafana_describe.md +++ b/docs/stackit_argus_grafana_describe.md @@ -6,7 +6,7 @@ Shows details of the Grafana configuration of an Argus instance Shows details of the Grafana configuration of an Argus instance. The Grafana dashboard URL and initial credentials (admin user and password) will be shown in the "pretty" output format. These credentials are only valid for first login. Please change the password after first login. After changing, the initial password is no longer valid. -The initial password is shown by default, if you want to hide it use the "--hide-password" flag. +The initial password is hidden by default, if you want to show it use the "--show-password" flag. ``` stackit argus grafana describe INSTANCE_ID [flags] @@ -18,18 +18,18 @@ stackit argus grafana describe INSTANCE_ID [flags] Get details of the Grafana configuration of an Argus instance with ID "xxx" $ stackit argus credentials describe xxx - Get details of the Grafana configuration of an Argus instance with ID "xxx" in a table format - $ stackit argus credentials describe xxx --output-format pretty + Get details of the Grafana configuration of an Argus instance with ID "xxx" and show the initial admin password + $ stackit argus credentials describe xxx --show-password - Get details of the Grafana configuration of an Argus instance with ID "xxx" and hide the initial admin password - $ stackit argus credentials describe xxx --output-format pretty --hide-password + Get details of the Grafana configuration of an Argus instance with ID "xxx" in JSON format + $ stackit argus credentials describe xxx --output-format json ``` ### Options ``` -h, --help Help for "stackit argus grafana describe" - --hide-password Show the initial admin password in the "pretty" output format + -s, --show-password Show password in output ``` ### Options inherited from parent commands diff --git a/docs/stackit_argus_instance_describe.md b/docs/stackit_argus_instance_describe.md index 4a36f0f87..fec05a0d4 100644 --- a/docs/stackit_argus_instance_describe.md +++ b/docs/stackit_argus_instance_describe.md @@ -16,8 +16,8 @@ stackit argus instance describe INSTANCE_ID [flags] Get details of an Argus instance with ID "xxx" $ stackit argus instance describe xxx - Get details of an Argus instance with ID "xxx" in a table format - $ stackit argus instance describe xxx --output-format pretty + Get details of an Argus instance with ID "xxx" in JSON format + $ stackit argus instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_argus_scrape-config_describe.md b/docs/stackit_argus_scrape-config_describe.md index 0a608a4d9..94a3be074 100644 --- a/docs/stackit_argus_scrape-config_describe.md +++ b/docs/stackit_argus_scrape-config_describe.md @@ -16,8 +16,8 @@ stackit argus scrape-config describe JOB_NAME [flags] Get details of a scrape configuration with name "my-config" from Argus instance "xxx" $ stackit argus scrape-config describe my-config --instance-id xxx - Get details of a scrape configuration with name "my-config" from Argus instance "xxx" in a table format - $ stackit argus scrape-config describe my-config --output-format pretty + Get details of a scrape configuration with name "my-config" from Argus instance "xxx" in JSON format + $ stackit argus scrape-config describe my-config --output-format json ``` ### Options diff --git a/docs/stackit_config.md b/docs/stackit_config.md index d37248faa..ed8212d87 100644 --- a/docs/stackit_config.md +++ b/docs/stackit_config.md @@ -4,7 +4,12 @@ Provides functionality for CLI configuration options ### Synopsis -Provides functionality for CLI configuration options. +Provides functionality for CLI configuration options +The configuration is stored in a file in the user's config directory, which is OS dependent. +Windows: %APPDATA%\stackit +Linux: $XDG_CONFIG_HOME/stackit +macOS: $HOME/Library/Application Support/stackit +The configuration file is named `cli-config.json` and is created automatically in your first CLI run. ``` stackit config [flags] diff --git a/docs/stackit_dns_record-set_describe.md b/docs/stackit_dns_record-set_describe.md index 387381ca5..09473b59b 100644 --- a/docs/stackit_dns_record-set_describe.md +++ b/docs/stackit_dns_record-set_describe.md @@ -16,8 +16,8 @@ stackit dns record-set describe RECORD_SET_ID [flags] Get details of DNS record set with ID "xxx" in zone with ID "yyy" $ stackit dns record-set describe xxx --zone-id yyy - Get details of DNS record set with ID "xxx" in zone with ID "yyy" in a table format - $ stackit dns record-set describe xxx --zone-id yyy --output-format pretty + Get details of DNS record set with ID "xxx" in zone with ID "yyy" in JSON format + $ stackit dns record-set describe xxx --zone-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_dns_zone_describe.md b/docs/stackit_dns_zone_describe.md index 89d8d9ad5..312a1ec77 100644 --- a/docs/stackit_dns_zone_describe.md +++ b/docs/stackit_dns_zone_describe.md @@ -16,8 +16,8 @@ stackit dns zone describe ZONE_ID [flags] Get details of a DNS zone with ID "xxx" $ stackit dns zone describe xxx - Get details of a DNS zone with ID "xxx" in a table format - $ stackit dns zone describe xxx --output-format pretty + Get details of a DNS zone with ID "xxx" in JSON format + $ stackit dns zone describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_logme_credentials_create.md b/docs/stackit_logme_credentials_create.md index 40b1e9489..3a6a2ad6f 100644 --- a/docs/stackit_logme_credentials_create.md +++ b/docs/stackit_logme_credentials_create.md @@ -16,16 +16,16 @@ stackit logme credentials create [flags] Create credentials for a LogMe instance $ stackit logme credentials create --instance-id xxx - Create credentials for a LogMe instance and hide the password in the output - $ stackit logme credentials create --instance-id xxx --hide-password + Create credentials for a LogMe instance and show the password in the output + $ stackit logme credentials create --instance-id xxx --show-password ``` ### Options ``` -h, --help Help for "stackit logme credentials create" - --hide-password Hide password in output --instance-id string Instance ID + -s, --show-password Show password in output ``` ### Options inherited from parent commands diff --git a/docs/stackit_logme_credentials_describe.md b/docs/stackit_logme_credentials_describe.md index 1d14f86c3..828d46231 100644 --- a/docs/stackit_logme_credentials_describe.md +++ b/docs/stackit_logme_credentials_describe.md @@ -16,8 +16,8 @@ stackit logme credentials describe CREDENTIALS_ID [flags] Get details of credentials with ID "xxx" from instance with ID "yyy" $ stackit logme credentials describe xxx --instance-id yyy - Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format - $ stackit logme credentials describe xxx --instance-id yyy --output-format pretty + Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format + $ stackit logme credentials describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_logme_instance_describe.md b/docs/stackit_logme_instance_describe.md index 8ef8bc2d6..ca3105bec 100644 --- a/docs/stackit_logme_instance_describe.md +++ b/docs/stackit_logme_instance_describe.md @@ -16,8 +16,8 @@ stackit logme instance describe INSTANCE_ID [flags] Get details of a LogMe instance with ID "xxx" $ stackit logme instance describe xxx - Get details of a LogMe instance with ID "xxx" in a table format - $ stackit logme instance describe xxx --output-format pretty + Get details of a LogMe instance with ID "xxx" in JSON format + $ stackit logme instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_mariadb_credentials_create.md b/docs/stackit_mariadb_credentials_create.md index f6ad6d7c8..1ea118fb3 100644 --- a/docs/stackit_mariadb_credentials_create.md +++ b/docs/stackit_mariadb_credentials_create.md @@ -16,16 +16,16 @@ stackit mariadb credentials create [flags] Create credentials for a MariaDB instance $ stackit mariadb credentials create --instance-id xxx - Create credentials for a MariaDB instance and hide the password in the output - $ stackit mariadb credentials create --instance-id xxx --hide-password + Create credentials for a MariaDB instance and show the password in the output + $ stackit mariadb credentials create --instance-id xxx --show-password ``` ### Options ``` -h, --help Help for "stackit mariadb credentials create" - --hide-password Hide password in output --instance-id string Instance ID + -s, --show-password Show password in output ``` ### Options inherited from parent commands diff --git a/docs/stackit_mariadb_credentials_describe.md b/docs/stackit_mariadb_credentials_describe.md index 1fc65c0be..b89d54f4c 100644 --- a/docs/stackit_mariadb_credentials_describe.md +++ b/docs/stackit_mariadb_credentials_describe.md @@ -16,8 +16,8 @@ stackit mariadb credentials describe CREDENTIALS_ID [flags] Get details of credentials with ID "xxx" from instance with ID "yyy" $ stackit mariadb credentials describe xxx --instance-id yyy - Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format - $ stackit mariadb credentials describe xxx --instance-id yyy --output-format pretty + Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format + $ stackit mariadb credentials describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_mariadb_instance_describe.md b/docs/stackit_mariadb_instance_describe.md index 0984445f6..89e9468d2 100644 --- a/docs/stackit_mariadb_instance_describe.md +++ b/docs/stackit_mariadb_instance_describe.md @@ -16,8 +16,8 @@ stackit mariadb instance describe INSTANCE_ID [flags] Get details of a MariaDB instance with ID "xxx" $ stackit mariadb instance describe xxx - Get details of a MariaDB instance with ID "xxx" in a table format - $ stackit mariadb instance describe xxx --output-format pretty + Get details of a MariaDB instance with ID "xxx" in JSON format + $ stackit mariadb instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_mongodbflex_instance_describe.md b/docs/stackit_mongodbflex_instance_describe.md index 70a6e757c..0d07e5d7f 100644 --- a/docs/stackit_mongodbflex_instance_describe.md +++ b/docs/stackit_mongodbflex_instance_describe.md @@ -16,8 +16,8 @@ stackit mongodbflex instance describe INSTANCE_ID [flags] Get details of a MongoDB Flex instance with ID "xxx" $ stackit mongodbflex instance describe xxx - Get details of a MongoDB Flex instance with ID "xxx" in a table format - $ stackit mongodbflex instance describe xxx --output-format pretty + Get details of a MongoDB Flex instance with ID "xxx" in JSON format + $ stackit mongodbflex instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_mongodbflex_user_describe.md b/docs/stackit_mongodbflex_user_describe.md index 48b4102a7..7b19e5da9 100644 --- a/docs/stackit_mongodbflex_user_describe.md +++ b/docs/stackit_mongodbflex_user_describe.md @@ -18,8 +18,8 @@ stackit mongodbflex user describe USER_ID [flags] Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" $ stackit mongodbflex user list xxx --instance-id yyy - Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" in table format - $ stackit mongodbflex user list xxx --instance-id yyy --output-format pretty + Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" in JSON format + $ stackit mongodbflex user list xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_object-storage_bucket_describe.md b/docs/stackit_object-storage_bucket_describe.md index 286d54ae7..132161e79 100644 --- a/docs/stackit_object-storage_bucket_describe.md +++ b/docs/stackit_object-storage_bucket_describe.md @@ -16,8 +16,8 @@ stackit object-storage bucket describe BUCKET_NAME [flags] Get details of an Object Storage bucket with name "my-bucket" $ stackit object-storage bucket describe my-bucket - Get details of an Object Storage bucket with name "my-bucket" in a table format - $ stackit object-storage bucket describe my-bucket --output-format pretty + Get details of an Object Storage bucket with name "my-bucket" in JSON format + $ stackit object-storage bucket describe my-bucket --output-format json ``` ### Options diff --git a/docs/stackit_opensearch_credentials_create.md b/docs/stackit_opensearch_credentials_create.md index 3ca1ce306..2f2ab2c0d 100644 --- a/docs/stackit_opensearch_credentials_create.md +++ b/docs/stackit_opensearch_credentials_create.md @@ -16,16 +16,16 @@ stackit opensearch credentials create [flags] Create credentials for an OpenSearch instance $ stackit opensearch credentials create --instance-id xxx - Create credentials for an OpenSearch instance and hide the password in the output - $ stackit opensearch credentials create --instance-id xxx --hide-password + Create credentials for an OpenSearch instance and show the password in the output + $ stackit opensearch credentials create --instance-id xxx --show-password ``` ### Options ``` -h, --help Help for "stackit opensearch credentials create" - --hide-password Hide password in output --instance-id string Instance ID + -s, --show-password Show password in output ``` ### Options inherited from parent commands diff --git a/docs/stackit_opensearch_credentials_describe.md b/docs/stackit_opensearch_credentials_describe.md index 7ed5b4acf..87159d515 100644 --- a/docs/stackit_opensearch_credentials_describe.md +++ b/docs/stackit_opensearch_credentials_describe.md @@ -16,8 +16,8 @@ stackit opensearch credentials describe CREDENTIALS_ID [flags] Get details of credentials with ID "xxx" from instance with ID "yyy" $ stackit opensearch credentials describe xxx --instance-id yyy - Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format - $ stackit opensearch credentials describe xxx --instance-id yyy --output-format pretty + Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format + $ stackit opensearch credentials describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_opensearch_instance_describe.md b/docs/stackit_opensearch_instance_describe.md index 2a0363796..7dc48d49f 100644 --- a/docs/stackit_opensearch_instance_describe.md +++ b/docs/stackit_opensearch_instance_describe.md @@ -16,8 +16,8 @@ stackit opensearch instance describe INSTANCE_ID [flags] Get details of an OpenSearch instance with ID "xxx" $ stackit opensearch instance describe xxx - Get details of an OpenSearch instance with ID "xxx" in a table format - $ stackit opensearch instance describe xxx --output-format pretty + Get details of an OpenSearch instance with ID "xxx" in JSON format + $ stackit opensearch instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_postgresflex_backup_describe.md b/docs/stackit_postgresflex_backup_describe.md index 84f786c55..81a3309f0 100644 --- a/docs/stackit_postgresflex_backup_describe.md +++ b/docs/stackit_postgresflex_backup_describe.md @@ -16,8 +16,8 @@ stackit postgresflex backup describe BACKUP_ID [flags] Get details of a backup with ID "xxx" for a PostgreSQL Flex instance with ID "yyy" $ stackit postgresflex backup describe xxx --instance-id yyy - Get details of a backup with ID "xxx" for a PostgreSQL Flex instance with ID "yyy" in a table format - $ stackit postgresflex backup describe xxx --instance-id yyy --output-format pretty + Get details of a backup with ID "xxx" for a PostgreSQL Flex instance with ID "yyy" in JSON format + $ stackit postgresflex backup describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_postgresflex_instance_describe.md b/docs/stackit_postgresflex_instance_describe.md index bf59b5d32..df2e0434c 100644 --- a/docs/stackit_postgresflex_instance_describe.md +++ b/docs/stackit_postgresflex_instance_describe.md @@ -16,8 +16,8 @@ stackit postgresflex instance describe INSTANCE_ID [flags] Get details of a PostgreSQL Flex instance with ID "xxx" $ stackit postgresflex instance describe xxx - Get details of a PostgreSQL Flex instance with ID "xxx" in a table format - $ stackit postgresflex instance describe xxx --output-format pretty + Get details of a PostgreSQL Flex instance with ID "xxx" in JSON format + $ stackit postgresflex instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_postgresflex_user_describe.md b/docs/stackit_postgresflex_user_describe.md index dbf92c660..5fbcdc899 100644 --- a/docs/stackit_postgresflex_user_describe.md +++ b/docs/stackit_postgresflex_user_describe.md @@ -18,8 +18,8 @@ stackit postgresflex user describe USER_ID [flags] Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" $ stackit postgresflex user list xxx --instance-id yyy - Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" in table format - $ stackit postgresflex user list xxx --instance-id yyy --output-format pretty + Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" in JSON format + $ stackit postgresflex user list xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_rabbitmq_credentials_create.md b/docs/stackit_rabbitmq_credentials_create.md index 512ee227b..2f836291e 100644 --- a/docs/stackit_rabbitmq_credentials_create.md +++ b/docs/stackit_rabbitmq_credentials_create.md @@ -16,16 +16,16 @@ stackit rabbitmq credentials create [flags] Create credentials for a RabbitMQ instance $ stackit rabbitmq credentials create --instance-id xxx - Create credentials for a RabbitMQ instance and hide the password in the output - $ stackit rabbitmq credentials create --instance-id xxx --hide-password + Create credentials for a RabbitMQ instance and show the password in the output + $ stackit rabbitmq credentials create --instance-id xxx --show-password ``` ### Options ``` -h, --help Help for "stackit rabbitmq credentials create" - --hide-password Hide password in output --instance-id string Instance ID + -s, --show-password Show password in output ``` ### Options inherited from parent commands diff --git a/docs/stackit_rabbitmq_credentials_describe.md b/docs/stackit_rabbitmq_credentials_describe.md index 00f2d03c9..bca32624d 100644 --- a/docs/stackit_rabbitmq_credentials_describe.md +++ b/docs/stackit_rabbitmq_credentials_describe.md @@ -16,8 +16,8 @@ stackit rabbitmq credentials describe CREDENTIALS_ID [flags] Get details of credentials with ID "xxx" from instance with ID "yyy" $ stackit rabbitmq credentials describe xxx --instance-id yyy - Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format - $ stackit rabbitmq credentials describe xxx --instance-id yyy --output-format pretty + Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format + $ stackit rabbitmq credentials describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_rabbitmq_instance_describe.md b/docs/stackit_rabbitmq_instance_describe.md index fb04b9cc9..e491b2417 100644 --- a/docs/stackit_rabbitmq_instance_describe.md +++ b/docs/stackit_rabbitmq_instance_describe.md @@ -16,8 +16,8 @@ stackit rabbitmq instance describe INSTANCE_ID [flags] Get details of a RabbitMQ instance with ID "xxx" $ stackit rabbitmq instance describe xxx - Get details of a RabbitMQ instance with ID "xxx" in a table format - $ stackit rabbitmq instance describe xxx --output-format pretty + Get details of a RabbitMQ instance with ID "xxx" in JSON format + $ stackit rabbitmq instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_redis_credentials_create.md b/docs/stackit_redis_credentials_create.md index 377791b5b..0097e7eed 100644 --- a/docs/stackit_redis_credentials_create.md +++ b/docs/stackit_redis_credentials_create.md @@ -16,16 +16,16 @@ stackit redis credentials create [flags] Create credentials for a Redis instance $ stackit redis credentials create --instance-id xxx - Create credentials for a Redis instance and hide the password in the output - $ stackit redis credentials create --instance-id xxx --hide-password + Create credentials for a Redis instance and show the password in the output + $ stackit redis credentials create --instance-id xxx --show-password ``` ### Options ``` -h, --help Help for "stackit redis credentials create" - --hide-password Hide password in output --instance-id string Instance ID + -s, --show-password Show password in output ``` ### Options inherited from parent commands diff --git a/docs/stackit_redis_credentials_describe.md b/docs/stackit_redis_credentials_describe.md index 84fb0efd7..23968aeeb 100644 --- a/docs/stackit_redis_credentials_describe.md +++ b/docs/stackit_redis_credentials_describe.md @@ -16,8 +16,8 @@ stackit redis credentials describe CREDENTIALS_ID [flags] Get details of credentials with ID "xxx" from instance with ID "yyy" $ stackit redis credentials describe xxx --instance-id yyy - Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format - $ stackit redis credentials describe xxx --instance-id yyy --output-format pretty + Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format + $ stackit redis credentials describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_redis_instance_describe.md b/docs/stackit_redis_instance_describe.md index b80f6f18a..5a2a2f4d9 100644 --- a/docs/stackit_redis_instance_describe.md +++ b/docs/stackit_redis_instance_describe.md @@ -16,8 +16,8 @@ stackit redis instance describe INSTANCE_ID [flags] Get details of a Redis instance with ID "xxx" $ stackit redis instance describe xxx - Get details of a Redis instance with ID "xxx" in a table format - $ stackit redis instance describe xxx --output-format pretty + Get details of a Redis instance with ID "xxx" in JSON format + $ stackit redis instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_secrets-manager_instance_describe.md b/docs/stackit_secrets-manager_instance_describe.md index 4e987fce9..b19e49e06 100644 --- a/docs/stackit_secrets-manager_instance_describe.md +++ b/docs/stackit_secrets-manager_instance_describe.md @@ -16,8 +16,8 @@ stackit secrets-manager instance describe INSTANCE_ID [flags] Get details of a Secrets Manager instance with ID "xxx" $ stackit secrets-manager instance describe xxx - Get details of a Secrets Manager instance with ID "xxx" in a table format - $ stackit secrets-manager instance describe xxx --output-format pretty + Get details of a Secrets Manager instance with ID "xxx" in JSON format + $ stackit secrets-manager instance describe xxx --output-format json ``` ### Options diff --git a/docs/stackit_secrets-manager_user_describe.md b/docs/stackit_secrets-manager_user_describe.md index 898623a6c..75de3d2d0 100644 --- a/docs/stackit_secrets-manager_user_describe.md +++ b/docs/stackit_secrets-manager_user_describe.md @@ -16,8 +16,8 @@ stackit secrets-manager user describe USER_ID [flags] Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" $ stackit secrets-manager user describe xxx --instance-id yyy - Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in table format - $ stackit secrets-manager user describe xxx --instance-id yyy --output-format pretty + Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in JSON format + $ stackit secrets-manager user describe xxx --instance-id yyy --output-format json ``` ### Options diff --git a/docs/stackit_ske_cluster_describe.md b/docs/stackit_ske_cluster_describe.md index c87d1220b..8382dd33a 100644 --- a/docs/stackit_ske_cluster_describe.md +++ b/docs/stackit_ske_cluster_describe.md @@ -16,8 +16,8 @@ stackit ske cluster describe CLUSTER_NAME [flags] Get details of an SKE cluster with name "my-cluster" $ stackit ske cluster describe my-cluster - Get details of an SKE cluster with name "my-cluster" in a table format - $ stackit ske cluster describe my-cluster --output-format pretty + Get details of an SKE cluster with name "my-cluster" in JSON format + $ stackit ske cluster describe my-cluster --output-format json ``` ### Options diff --git a/go.mod b/go.mod index 398d588a3..be43f1a01 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,9 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf - github.com/jedib0t/go-pretty/v6 v6.5.8 + github.com/jedib0t/go-pretty/v6 v6.5.9 + github.com/lmittmann/tint v1.0.4 + github.com/mattn/go-colorable v0.1.13 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 @@ -20,14 +22,16 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0 github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0 github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0 - github.com/stackitcloud/stackit-sdk-go/services/ske v0.13.0 + github.com/stackitcloud/stackit-sdk-go/services/ske v0.14.0 github.com/zalando/go-keyring v0.2.4 golang.org/x/mod v0.17.0 - golang.org/x/oauth2 v0.19.0 - golang.org/x/term v0.19.0 - golang.org/x/text v0.14.0 + golang.org/x/oauth2 v0.20.0 + golang.org/x/term v0.17.0 + golang.org/x/text v0.15.0 ) +require github.com/mattn/go-isatty v0.0.17 // indirect + require ( github.com/alessio/shellescape v1.4.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect diff --git a/go.sum b/go.sum index b1c960f26..a45e3be91 100644 --- a/go.sum +++ b/go.sum @@ -26,16 +26,23 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= -github.com/jedib0t/go-pretty/v6 v6.5.8 h1:8BCzJdSvUbaDuRba4YVh+SKMGcAAKdkcF3SVFbrHAtQ= -github.com/jedib0t/go-pretty/v6 v6.5.8/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= +github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU= +github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= +github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -102,8 +109,8 @@ github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0 h1:1Ho+M4D github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.7.0/go.mod h1:LX0Mcyr7/QP77zf7e05fHCJO38RMuTxr7nEDUDZ3oPQ= github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0 h1:JB1O0E9+L50ZaO36uz7azurvUuB5JdX5s2ZXuIdb9t8= github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0/go.mod h1:Ni9RBJvcaXRIrDIuQBpJcuQvCQSj27crQSyc+WM4p0c= -github.com/stackitcloud/stackit-sdk-go/services/ske v0.13.0 h1:fL7vll37c1Wv8LM1/F/vcyhyUKSL2UqkXd44x1J/6OU= -github.com/stackitcloud/stackit-sdk-go/services/ske v0.13.0/go.mod h1:0fFs4R7kg+gU7FNAIzzFvlCZJz6gyZ8CFhbK3eSrAwQ= +github.com/stackitcloud/stackit-sdk-go/services/ske v0.14.0 h1:GH67aTvjXiXC2XmYhgmqNXfG13JHKB3wsk5JlTErsjg= +github.com/stackitcloud/stackit-sdk-go/services/ske v0.14.0/go.mod h1:0fFs4R7kg+gU7FNAIzzFvlCZJz6gyZ8CFhbK3eSrAwQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= @@ -122,18 +129,15 @@ golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRj golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= -golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cmd/argus/grafana/describe/describe.go b/internal/cmd/argus/grafana/describe/describe.go index baa75cab9..ef17e74b2 100644 --- a/internal/cmd/argus/grafana/describe/describe.go +++ b/internal/cmd/argus/grafana/describe/describe.go @@ -21,13 +21,13 @@ import ( const ( instanceIdArg = "INSTANCE_ID" - hidePasswordFlag = "hide-password" + showPasswordFlag = "show-password" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - HidePassword bool + ShowPassword bool } func NewCmd(p *print.Printer) *cobra.Command { @@ -37,7 +37,7 @@ func NewCmd(p *print.Printer) *cobra.Command { Long: fmt.Sprintf("%s\n%s\n%s", "Shows details of the Grafana configuration of an Argus instance.", `The Grafana dashboard URL and initial credentials (admin user and password) will be shown in the "pretty" output format. These credentials are only valid for first login. Please change the password after first login. After changing, the initial password is no longer valid.`, - `The initial password is shown by default, if you want to hide it use the "--hide-password" flag.`, + `The initial password is hidden by default, if you want to show it use the "--show-password" flag.`, ), Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( @@ -45,11 +45,11 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of the Grafana configuration of an Argus instance with ID "xxx"`, "$ stackit argus credentials describe xxx"), examples.NewExample( - `Get details of the Grafana configuration of an Argus instance with ID "xxx" in a table format`, - "$ stackit argus credentials describe xxx --output-format pretty"), + `Get details of the Grafana configuration of an Argus instance with ID "xxx" and show the initial admin password`, + "$ stackit argus credentials describe xxx --show-password"), examples.NewExample( - `Get details of the Grafana configuration of an Argus instance with ID "xxx" and hide the initial admin password`, - "$ stackit argus credentials describe xxx --output-format pretty --hide-password"), + `Get details of the Grafana configuration of an Argus instance with ID "xxx" in JSON format`, + "$ stackit argus credentials describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -84,7 +84,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - cmd.Flags().Bool(hidePasswordFlag, false, `Show the initial admin password in the "pretty" output format`) + cmd.Flags().BoolP(showPasswordFlag, "s", false, "Show password in output") } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -98,7 +98,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: instanceId, - HidePassword: flags.FlagToBoolValue(p, cmd, hidePasswordFlag), + ShowPassword: flags.FlagToBoolValue(p, cmd, showPasswordFlag), } if p.IsVerbosityDebug() { @@ -125,9 +125,17 @@ func buildGetInstanceRequest(ctx context.Context, model *inputModel, apiClient * func outputResult(p *print.Printer, inputModel *inputModel, grafanaConfigs *argus.GrafanaConfigs, instance *argus.GetInstanceResponse) error { switch inputModel.OutputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(grafanaConfigs, "", " ") + if err != nil { + return fmt.Errorf("marshal Grafana configs: %w", err) + } + p.Outputln(string(details)) + + return nil + default: initialAdminPassword := *instance.Instance.GrafanaAdminPassword - if inputModel.HidePassword { + if !inputModel.ShowPassword { initialAdminPassword = "" } @@ -146,14 +154,6 @@ func outputResult(p *print.Printer, inputModel *inputModel, grafanaConfigs *argu return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(grafanaConfigs, "", " ") - if err != nil { - return fmt.Errorf("marshal Grafana configs: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/argus/grafana/describe/describe_test.go b/internal/cmd/argus/grafana/describe/describe_test.go index ea5e53538..2bc97589b 100644 --- a/internal/cmd/argus/grafana/describe/describe_test.go +++ b/internal/cmd/argus/grafana/describe/describe_test.go @@ -94,14 +94,14 @@ func TestParseInput(t *testing.T) { isValid: false, }, { - description: "hide password", + description: "show password", argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[hidePasswordFlag] = "true" + flagValues[showPasswordFlag] = "true" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.HidePassword = true + model.ShowPassword = true }), }, { diff --git a/internal/cmd/argus/instance/describe/describe.go b/internal/cmd/argus/instance/describe/describe.go index d55d78ea9..c2ff58b7d 100644 --- a/internal/cmd/argus/instance/describe/describe.go +++ b/internal/cmd/argus/instance/describe/describe.go @@ -38,8 +38,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of an Argus instance with ID "xxx"`, "$ stackit argus instance describe xxx"), examples.NewExample( - `Get details of an Argus instance with ID "xxx" in a table format`, - "$ stackit argus instance describe xxx --output-format pretty"), + `Get details of an Argus instance with ID "xxx" in JSON format`, + "$ stackit argus instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -98,8 +98,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *argus.APICl func outputResult(p *print.Printer, outputFormat string, instance *argus.GetInstanceResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal Argus instance: %w", err) + } + p.Outputln(string(details)) + return nil + default: table := tables.NewTable() table.AddRow("ID", *instance.Id) table.AddSeparator() @@ -126,14 +133,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *argus.GetInst return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal Argus instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/argus/scrape-config/describe/describe.go b/internal/cmd/argus/scrape-config/describe/describe.go index 5792f9986..940c431c6 100644 --- a/internal/cmd/argus/scrape-config/describe/describe.go +++ b/internal/cmd/argus/scrape-config/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a scrape configuration with name "my-config" from Argus instance "xxx"`, "$ stackit argus scrape-config describe my-config --instance-id xxx"), examples.NewExample( - `Get details of a scrape configuration with name "my-config" from Argus instance "xxx" in a table format`, - "$ stackit argus scrape-config describe my-config --output-format pretty"), + `Get details of a scrape configuration with name "my-config" from Argus instance "xxx" in JSON format`, + "$ stackit argus scrape-config describe my-config --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -100,8 +100,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *argus.APICl func outputResult(p *print.Printer, outputFormat string, config *argus.Job) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("marshal scrape configuration: %w", err) + } + p.Outputln(string(details)) + return nil + default: saml2Enabled := "Enabled" if config.Params != nil { saml2 := (*config.Params)["saml2"] @@ -163,14 +170,6 @@ func outputResult(p *print.Printer, outputFormat string, config *argus.Job) erro return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(config, "", " ") - if err != nil { - return fmt.Errorf("marshal scrape configuration: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index e02153fb0..7c3fea56f 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -1,6 +1,8 @@ package config import ( + "fmt" + "github.com/stackitcloud/stackit-cli/internal/cmd/config/list" "github.com/stackitcloud/stackit-cli/internal/cmd/config/set" "github.com/stackitcloud/stackit-cli/internal/cmd/config/unset" @@ -15,9 +17,15 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "config", Short: "Provides functionality for CLI configuration options", - Long: "Provides functionality for CLI configuration options.", - Args: args.NoArgs, - Run: utils.CmdHelp, + Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", "Provides functionality for CLI configuration options", + "The configuration is stored in a file in the user's config directory, which is OS dependent.", + "Windows: %APPDATA%\\stackit", + "Linux: $XDG_CONFIG_HOME/stackit", + "macOS: $HOME/Library/Application Support/stackit", + "The configuration file is named `cli-config.json` and is created automatically in your first CLI run.", + ), + Args: args.NoArgs, + Run: utils.CmdHelp, } addSubcommands(cmd, p) return cmd diff --git a/internal/cmd/dns/record-set/describe/describe.go b/internal/cmd/dns/record-set/describe/describe.go index cb088029f..c10d003eb 100644 --- a/internal/cmd/dns/record-set/describe/describe.go +++ b/internal/cmd/dns/record-set/describe/describe.go @@ -43,8 +43,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of DNS record set with ID "xxx" in zone with ID "yyy"`, "$ stackit dns record-set describe xxx --zone-id yyy"), examples.NewExample( - `Get details of DNS record set with ID "xxx" in zone with ID "yyy" in a table format`, - "$ stackit dns record-set describe xxx --zone-id yyy --output-format pretty"), + `Get details of DNS record set with ID "xxx" in zone with ID "yyy" in JSON format`, + "$ stackit dns record-set describe xxx --zone-id yyy --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -114,7 +114,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClie func outputResult(p *print.Printer, outputFormat string, recordSet *dns.RecordSet) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(recordSet, "", " ") + if err != nil { + return fmt.Errorf("marshal DNS record set: %w", err) + } + p.Outputln(string(details)) + + return nil + default: recordsData := make([]string, 0, len(*recordSet.Records)) for _, r := range *recordSet.Records { recordsData = append(recordsData, *r.Content) @@ -138,14 +146,6 @@ func outputResult(p *print.Printer, outputFormat string, recordSet *dns.RecordSe return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(recordSet, "", " ") - if err != nil { - return fmt.Errorf("marshal DNS record set: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/dns/zone/describe/describe.go b/internal/cmd/dns/zone/describe/describe.go index d712c112f..1830471b4 100644 --- a/internal/cmd/dns/zone/describe/describe.go +++ b/internal/cmd/dns/zone/describe/describe.go @@ -38,8 +38,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a DNS zone with ID "xxx"`, "$ stackit dns zone describe xxx"), examples.NewExample( - `Get details of a DNS zone with ID "xxx" in a table format`, - "$ stackit dns zone describe xxx --output-format pretty"), + `Get details of a DNS zone with ID "xxx" in JSON format`, + "$ stackit dns zone describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -99,7 +99,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClie func outputResult(p *print.Printer, outputFormat string, zone *dns.Zone) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(zone, "", " ") + if err != nil { + return fmt.Errorf("marshal DNS zone: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *zone.Id) table.AddSeparator() @@ -135,14 +143,6 @@ func outputResult(p *print.Printer, outputFormat string, zone *dns.Zone) error { return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(zone, "", " ") - if err != nil { - return fmt.Errorf("marshal DNS zone: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/logme/credentials/create/create.go b/internal/cmd/logme/credentials/create/create.go index 5899b972f..e50efa5c7 100644 --- a/internal/cmd/logme/credentials/create/create.go +++ b/internal/cmd/logme/credentials/create/create.go @@ -13,6 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "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/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/logme" @@ -20,13 +21,13 @@ import ( const ( instanceIdFlag = "instance-id" - hidePasswordFlag = "hide-password" + showPasswordFlag = "show-password" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - HidePassword bool + ShowPassword bool } func NewCmd(p *print.Printer) *cobra.Command { @@ -40,8 +41,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Create credentials for a LogMe instance`, "$ stackit logme credentials create --instance-id xxx"), examples.NewExample( - `Create credentials for a LogMe instance and hide the password in the output`, - "$ stackit logme credentials create --instance-id xxx --hide-password"), + `Create credentials for a LogMe instance and show the password in the output`, + "$ stackit logme credentials create --instance-id xxx --show-password"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -86,7 +87,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") - cmd.Flags().Bool(hidePasswordFlag, false, "Hide password in output") + cmd.Flags().BoolP(showPasswordFlag, "s", false, "Show password in output") err := flags.MarkFlagsRequired(cmd, instanceIdFlag) cobra.CheckErr(err) @@ -101,7 +102,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), - HidePassword: flags.FlagToBoolValue(p, cmd, hidePasswordFlag), + ShowPassword: flags.FlagToBoolValue(p, cmd, showPasswordFlag), } if p.IsVerbosityDebug() { @@ -124,6 +125,9 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *logme.APICl func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *logme.CredentialsResponse) error { switch model.OutputFormat { case print.JSONOutputFormat: + if !model.ShowPassword { + resp.Raw.Credentials.Password = utils.Ptr("hidden") + } details, err := json.MarshalIndent(resp, "", " ") if err != nil { return fmt.Errorf("marshal LogMe credentials: %w", err) @@ -138,7 +142,7 @@ func outputResult(p *print.Printer, model *inputModel, instanceLabel string, res if username != "" { p.Outputf("Username: %s\n", *resp.Raw.Credentials.Username) } - if model.HidePassword { + if !model.ShowPassword { p.Outputf("Password: \n") } else { p.Outputf("Password: %s\n", *resp.Raw.Credentials.Password) diff --git a/internal/cmd/logme/credentials/create/create_test.go b/internal/cmd/logme/credentials/create/create_test.go index 0ff6c8872..ff0d16fbd 100644 --- a/internal/cmd/logme/credentials/create/create_test.go +++ b/internal/cmd/logme/credentials/create/create_test.go @@ -73,6 +73,16 @@ func TestParseInput(t *testing.T) { flagValues: map[string]string{}, isValid: false, }, + { + description: "show password", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[showPasswordFlag] = "true" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ShowPassword = true + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { diff --git a/internal/cmd/logme/credentials/describe/describe.go b/internal/cmd/logme/credentials/describe/describe.go index bb35ae0a1..e9b5f7987 100644 --- a/internal/cmd/logme/credentials/describe/describe.go +++ b/internal/cmd/logme/credentials/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of credentials with ID "xxx" from instance with ID "yyy"`, "$ stackit logme credentials describe xxx --instance-id yyy"), examples.NewExample( - `Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format`, - "$ stackit logme credentials describe xxx --instance-id yyy --output-format pretty"), + `Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format`, + "$ stackit logme credentials describe xxx --instance-id yyy --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -112,7 +112,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *logme.APICl func outputResult(p *print.Printer, outputFormat string, credentials *logme.CredentialsResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal LogMe credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *credentials.Id) table.AddSeparator() @@ -130,14 +138,6 @@ func outputResult(p *print.Printer, outputFormat string, credentials *logme.Cred return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(credentials, "", " ") - if err != nil { - return fmt.Errorf("marshal LogMe credentials: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/logme/instance/describe/describe.go b/internal/cmd/logme/instance/describe/describe.go index 1d3ed11d3..613091bc0 100644 --- a/internal/cmd/logme/instance/describe/describe.go +++ b/internal/cmd/logme/instance/describe/describe.go @@ -40,8 +40,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a LogMe instance with ID "xxx"`, "$ stackit logme instance describe xxx"), examples.NewExample( - `Get details of a LogMe instance with ID "xxx" in a table format`, - "$ stackit logme instance describe xxx --output-format pretty"), + `Get details of a LogMe instance with ID "xxx" in JSON format`, + "$ stackit logme instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -100,7 +100,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *logme.APICl func outputResult(p *print.Printer, outputFormat string, instance *logme.Instance) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal LogMe instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *instance.InstanceId) table.AddSeparator() @@ -125,14 +133,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *logme.Instanc return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal LogMe instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/mariadb/credentials/create/create.go b/internal/cmd/mariadb/credentials/create/create.go index 49c909c28..bddee6b38 100644 --- a/internal/cmd/mariadb/credentials/create/create.go +++ b/internal/cmd/mariadb/credentials/create/create.go @@ -13,6 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/mariadb/client" mariadbUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/mariadb/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/mariadb" @@ -20,13 +21,13 @@ import ( const ( instanceIdFlag = "instance-id" - hidePasswordFlag = "hide-password" + showPasswordFlag = "show-password" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - HidePassword bool + ShowPassword bool } func NewCmd(p *print.Printer) *cobra.Command { @@ -40,8 +41,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Create credentials for a MariaDB instance`, "$ stackit mariadb credentials create --instance-id xxx"), examples.NewExample( - `Create credentials for a MariaDB instance and hide the password in the output`, - "$ stackit mariadb credentials create --instance-id xxx --hide-password"), + `Create credentials for a MariaDB instance and show the password in the output`, + "$ stackit mariadb credentials create --instance-id xxx --show-password"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -86,7 +87,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") - cmd.Flags().Bool(hidePasswordFlag, false, "Hide password in output") + cmd.Flags().BoolP(showPasswordFlag, "s", false, "Show password in output") err := flags.MarkFlagsRequired(cmd, instanceIdFlag) cobra.CheckErr(err) @@ -101,7 +102,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), - HidePassword: flags.FlagToBoolValue(p, cmd, hidePasswordFlag), + ShowPassword: flags.FlagToBoolValue(p, cmd, showPasswordFlag), } if p.IsVerbosityDebug() { @@ -124,6 +125,9 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *mariadb.API func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *mariadb.CredentialsResponse) error { switch model.OutputFormat { case print.JSONOutputFormat: + if !model.ShowPassword { + resp.Raw.Credentials.Password = utils.Ptr("hidden") + } details, err := json.MarshalIndent(resp, "", " ") if err != nil { return fmt.Errorf("marshal MariaDB credentials: %w", err) @@ -138,7 +142,7 @@ func outputResult(p *print.Printer, model *inputModel, instanceLabel string, res if username != "" { p.Outputf("Username: %s\n", *resp.Raw.Credentials.Username) } - if model.HidePassword { + if !model.ShowPassword { p.Outputf("Password: \n") } else { p.Outputf("Password: %s\n", *resp.Raw.Credentials.Password) diff --git a/internal/cmd/mariadb/credentials/create/create_test.go b/internal/cmd/mariadb/credentials/create/create_test.go index d9604beec..9d012577b 100644 --- a/internal/cmd/mariadb/credentials/create/create_test.go +++ b/internal/cmd/mariadb/credentials/create/create_test.go @@ -73,6 +73,16 @@ func TestParseInput(t *testing.T) { flagValues: map[string]string{}, isValid: false, }, + { + description: "show password", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[showPasswordFlag] = "true" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ShowPassword = true + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { diff --git a/internal/cmd/mariadb/credentials/describe/describe.go b/internal/cmd/mariadb/credentials/describe/describe.go index ea5b2e5a0..ef37e64b4 100644 --- a/internal/cmd/mariadb/credentials/describe/describe.go +++ b/internal/cmd/mariadb/credentials/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of credentials with ID "xxx" from instance with ID "yyy"`, "$ stackit mariadb credentials describe xxx --instance-id yyy"), examples.NewExample( - `Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format`, - "$ stackit mariadb credentials describe xxx --instance-id yyy --output-format pretty"), + `Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format`, + "$ stackit mariadb credentials describe xxx --instance-id yyy --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -112,7 +112,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *mariadb.API func outputResult(p *print.Printer, outputFormat string, credentials *mariadb.CredentialsResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal MariaDB credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *credentials.Id) table.AddSeparator() @@ -130,14 +138,6 @@ func outputResult(p *print.Printer, outputFormat string, credentials *mariadb.Cr return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(credentials, "", " ") - if err != nil { - return fmt.Errorf("marshal MariaDB credentials: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/mariadb/instance/describe/describe.go b/internal/cmd/mariadb/instance/describe/describe.go index 9c6fe87d1..2affb1a98 100644 --- a/internal/cmd/mariadb/instance/describe/describe.go +++ b/internal/cmd/mariadb/instance/describe/describe.go @@ -40,8 +40,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a MariaDB instance with ID "xxx"`, "$ stackit mariadb instance describe xxx"), examples.NewExample( - `Get details of a MariaDB instance with ID "xxx" in a table format`, - "$ stackit mariadb instance describe xxx --output-format pretty"), + `Get details of a MariaDB instance with ID "xxx" in JSON format`, + "$ stackit mariadb instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -100,7 +100,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *mariadb.API func outputResult(p *print.Printer, outputFormat string, instance *mariadb.Instance) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal MariaDB instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *instance.InstanceId) table.AddSeparator() @@ -125,14 +133,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *mariadb.Insta return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal MariaDB instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/mongodbflex/instance/describe/describe.go b/internal/cmd/mongodbflex/instance/describe/describe.go index db4465434..6d7fda1d5 100644 --- a/internal/cmd/mongodbflex/instance/describe/describe.go +++ b/internal/cmd/mongodbflex/instance/describe/describe.go @@ -40,8 +40,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a MongoDB Flex instance with ID "xxx"`, "$ stackit mongodbflex instance describe xxx"), examples.NewExample( - `Get details of a MongoDB Flex instance with ID "xxx" in a table format`, - "$ stackit mongodbflex instance describe xxx --output-format pretty"), + `Get details of a MongoDB Flex instance with ID "xxx" in JSON format`, + "$ stackit mongodbflex instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -100,7 +100,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex func outputResult(p *print.Printer, outputFormat string, instance *mongodbflex.Instance) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal MongoDB Flex instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: aclsArray := *instance.Acl.Items acls := strings.Join(aclsArray, ",") @@ -138,14 +146,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *mongodbflex.I return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal MongoDB Flex instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/mongodbflex/user/describe/describe.go b/internal/cmd/mongodbflex/user/describe/describe.go index 4bdbf8249..c32eaace7 100644 --- a/internal/cmd/mongodbflex/user/describe/describe.go +++ b/internal/cmd/mongodbflex/user/describe/describe.go @@ -46,8 +46,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy"`, "$ stackit mongodbflex user list xxx --instance-id yyy"), examples.NewExample( - `Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" in table format`, - "$ stackit mongodbflex user list xxx --instance-id yyy --output-format pretty"), + `Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" in JSON format`, + "$ stackit mongodbflex user list xxx --instance-id yyy --output-format json"), ), Args: args.SingleArg(userIdArg, utils.ValidateUUID), RunE: func(cmd *cobra.Command, args []string) error { @@ -118,7 +118,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *mongodbflex func outputResult(p *print.Printer, outputFormat string, user mongodbflex.InstanceResponseUser) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(user, "", " ") + if err != nil { + return fmt.Errorf("marshal MongoDB Flex user: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *user.Id) table.AddSeparator() @@ -137,14 +145,6 @@ func outputResult(p *print.Printer, outputFormat string, user mongodbflex.Instan return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(user, "", " ") - if err != nil { - return fmt.Errorf("marshal MongoDB Flex user: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/object-storage/bucket/describe/describe.go b/internal/cmd/object-storage/bucket/describe/describe.go index 29fbb93d9..94220639c 100644 --- a/internal/cmd/object-storage/bucket/describe/describe.go +++ b/internal/cmd/object-storage/bucket/describe/describe.go @@ -37,8 +37,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of an Object Storage bucket with name "my-bucket"`, "$ stackit object-storage bucket describe my-bucket"), examples.NewExample( - `Get details of an Object Storage bucket with name "my-bucket" in a table format`, - "$ stackit object-storage bucket describe my-bucket --output-format pretty"), + `Get details of an Object Storage bucket with name "my-bucket" in JSON format`, + "$ stackit object-storage bucket describe my-bucket --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -97,7 +97,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstora func outputResult(p *print.Printer, outputFormat string, bucket *objectstorage.Bucket) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(bucket, "", " ") + if err != nil { + return fmt.Errorf("marshal Object Storage bucket: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("Name", *bucket.Name) table.AddSeparator() @@ -112,14 +120,6 @@ func outputResult(p *print.Printer, outputFormat string, bucket *objectstorage.B return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(bucket, "", " ") - if err != nil { - return fmt.Errorf("marshal Object Storage bucket: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/opensearch/credentials/create/create.go b/internal/cmd/opensearch/credentials/create/create.go index 0a922ac5c..70a5b8561 100644 --- a/internal/cmd/opensearch/credentials/create/create.go +++ b/internal/cmd/opensearch/credentials/create/create.go @@ -13,6 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "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/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/opensearch" @@ -20,13 +21,13 @@ import ( const ( instanceIdFlag = "instance-id" - hidePasswordFlag = "hide-password" + showPasswordFlag = "show-password" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - HidePassword bool + ShowPassword bool } func NewCmd(p *print.Printer) *cobra.Command { @@ -40,8 +41,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Create credentials for an OpenSearch instance`, "$ stackit opensearch credentials create --instance-id xxx"), examples.NewExample( - `Create credentials for an OpenSearch instance and hide the password in the output`, - "$ stackit opensearch credentials create --instance-id xxx --hide-password"), + `Create credentials for an OpenSearch instance and show the password in the output`, + "$ stackit opensearch credentials create --instance-id xxx --show-password"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -86,7 +87,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") - cmd.Flags().Bool(hidePasswordFlag, false, "Hide password in output") + cmd.Flags().BoolP(showPasswordFlag, "s", false, "Show password in output") err := flags.MarkFlagsRequired(cmd, instanceIdFlag) cobra.CheckErr(err) @@ -101,7 +102,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), - HidePassword: flags.FlagToBoolValue(p, cmd, hidePasswordFlag), + ShowPassword: flags.FlagToBoolValue(p, cmd, showPasswordFlag), } if p.IsVerbosityDebug() { @@ -124,6 +125,9 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *opensearch. func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *opensearch.CredentialsResponse) error { switch model.OutputFormat { case print.JSONOutputFormat: + if !model.ShowPassword { + resp.Raw.Credentials.Password = utils.Ptr("hidden") + } details, err := json.MarshalIndent(resp, "", " ") if err != nil { return fmt.Errorf("marshal OpenSearch credentials: %w", err) @@ -137,7 +141,7 @@ func outputResult(p *print.Printer, model *inputModel, instanceLabel string, res if username != "" { p.Outputf("Username: %s\n", *resp.Raw.Credentials.Username) } - if model.HidePassword { + if !model.ShowPassword { p.Outputf("Password: \n") } else { p.Outputf("Password: %s\n", *resp.Raw.Credentials.Password) diff --git a/internal/cmd/opensearch/credentials/create/create_test.go b/internal/cmd/opensearch/credentials/create/create_test.go index 77d9aa38e..cd8559b64 100644 --- a/internal/cmd/opensearch/credentials/create/create_test.go +++ b/internal/cmd/opensearch/credentials/create/create_test.go @@ -73,6 +73,16 @@ func TestParseInput(t *testing.T) { flagValues: map[string]string{}, isValid: false, }, + { + description: "show password", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[showPasswordFlag] = "true" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ShowPassword = true + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { diff --git a/internal/cmd/opensearch/credentials/describe/describe.go b/internal/cmd/opensearch/credentials/describe/describe.go index 6806bd235..73f72e1c5 100644 --- a/internal/cmd/opensearch/credentials/describe/describe.go +++ b/internal/cmd/opensearch/credentials/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of credentials with ID "xxx" from instance with ID "yyy"`, "$ stackit opensearch credentials describe xxx --instance-id yyy"), examples.NewExample( - `Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format`, - "$ stackit opensearch credentials describe xxx --instance-id yyy --output-format pretty"), + `Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format`, + "$ stackit opensearch credentials describe xxx --instance-id yyy --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -112,7 +112,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *opensearch. func outputResult(p *print.Printer, outputFormat string, credentials *opensearch.CredentialsResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal OpenSearch credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *credentials.Id) table.AddSeparator() @@ -130,14 +138,6 @@ func outputResult(p *print.Printer, outputFormat string, credentials *opensearch return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(credentials, "", " ") - if err != nil { - return fmt.Errorf("marshal OpenSearch credentials: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/opensearch/instance/describe/describe.go b/internal/cmd/opensearch/instance/describe/describe.go index a873fcbb5..593b85486 100644 --- a/internal/cmd/opensearch/instance/describe/describe.go +++ b/internal/cmd/opensearch/instance/describe/describe.go @@ -40,8 +40,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of an OpenSearch instance with ID "xxx"`, "$ stackit opensearch instance describe xxx"), examples.NewExample( - `Get details of an OpenSearch instance with ID "xxx" in a table format`, - "$ stackit opensearch instance describe xxx --output-format pretty"), + `Get details of an OpenSearch instance with ID "xxx" in JSON format`, + "$ stackit opensearch instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -100,7 +100,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *opensearch. func outputResult(p *print.Printer, outputFormat string, instance *opensearch.Instance) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal OpenSearch instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *instance.InstanceId) table.AddSeparator() @@ -125,14 +133,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *opensearch.In return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal OpenSearch instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/postgresflex/backup/describe/describe.go b/internal/cmd/postgresflex/backup/describe/describe.go index bf6322109..4e5d8bd49 100644 --- a/internal/cmd/postgresflex/backup/describe/describe.go +++ b/internal/cmd/postgresflex/backup/describe/describe.go @@ -47,8 +47,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a backup with ID "xxx" for a PostgreSQL Flex instance with ID "yyy"`, "$ stackit postgresflex backup describe xxx --instance-id yyy"), examples.NewExample( - `Get details of a backup with ID "xxx" for a PostgreSQL Flex instance with ID "yyy" in a table format`, - "$ stackit postgresflex backup describe xxx --instance-id yyy --output-format pretty"), + `Get details of a backup with ID "xxx" for a PostgreSQL Flex instance with ID "yyy" in JSON format`, + "$ stackit postgresflex backup describe xxx --instance-id yyy --output-format json"), ), Args: args.SingleArg(backupIdArg, nil), RunE: func(cmd *cobra.Command, args []string) error { @@ -114,7 +114,15 @@ func outputResult(p *print.Printer, cmd *cobra.Command, outputFormat string, bac backupExpireDate := backupStartTime.AddDate(backupExpireYearOffset, backupExpireMonthOffset, backupExpireDayOffset).Format(time.DateOnly) switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(backup, "", " ") + if err != nil { + return fmt.Errorf("marshal backup for PostgreSQL Flex backup: %w", err) + } + cmd.Println(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *backup.Id) table.AddSeparator() @@ -129,14 +137,6 @@ func outputResult(p *print.Printer, cmd *cobra.Command, outputFormat string, bac return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(backup, "", " ") - if err != nil { - return fmt.Errorf("marshal backup for PostgreSQL Flex instance: %w", err) - } - cmd.Println(string(details)) - return nil } } diff --git a/internal/cmd/postgresflex/instance/describe/describe.go b/internal/cmd/postgresflex/instance/describe/describe.go index e767538da..0e5687db1 100644 --- a/internal/cmd/postgresflex/instance/describe/describe.go +++ b/internal/cmd/postgresflex/instance/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a PostgreSQL Flex instance with ID "xxx"`, "$ stackit postgresflex instance describe xxx"), examples.NewExample( - `Get details of a PostgreSQL Flex instance with ID "xxx" in a table format`, - "$ stackit postgresflex instance describe xxx --output-format pretty"), + `Get details of a PostgreSQL Flex instance with ID "xxx" in JSON format`, + "$ stackit postgresflex instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -102,7 +102,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *postgresfle func outputResult(p *print.Printer, outputFormat string, instance *postgresflex.Instance) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal PostgreSQL Flex instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: aclsArray := *instance.Acl.Items acls := strings.Join(aclsArray, ",") @@ -142,14 +150,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *postgresflex. return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal PostgreSQL Flex instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/postgresflex/user/describe/describe.go b/internal/cmd/postgresflex/user/describe/describe.go index 72cbc226e..5b7b07af6 100644 --- a/internal/cmd/postgresflex/user/describe/describe.go +++ b/internal/cmd/postgresflex/user/describe/describe.go @@ -45,8 +45,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy"`, "$ stackit postgresflex user list xxx --instance-id yyy"), examples.NewExample( - `Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" in table format`, - "$ stackit postgresflex user list xxx --instance-id yyy --output-format pretty"), + `Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" in JSON format`, + "$ stackit postgresflex user list xxx --instance-id yyy --output-format json"), ), Args: args.SingleArg(userIdArg, nil), RunE: func(cmd *cobra.Command, args []string) error { @@ -117,7 +117,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *postgresfle func outputResult(p *print.Printer, outputFormat string, user postgresflex.UserResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(user, "", " ") + if err != nil { + return fmt.Errorf("marshal PostgreSQL Flex user: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *user.Id) table.AddSeparator() @@ -134,14 +142,6 @@ func outputResult(p *print.Printer, outputFormat string, user postgresflex.UserR return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(user, "", " ") - if err != nil { - return fmt.Errorf("marshal MongoDB Flex user: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/project/describe/describe.go b/internal/cmd/project/describe/describe.go index 9a6ef826f..2e1e5e94b 100644 --- a/internal/cmd/project/describe/describe.go +++ b/internal/cmd/project/describe/describe.go @@ -119,7 +119,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *resourceman func outputResult(p *print.Printer, outputFormat string, project *resourcemanager.ProjectResponseWithParents) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(project, "", " ") + if err != nil { + return fmt.Errorf("marshal project details: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *project.ProjectId) table.AddSeparator() @@ -135,14 +143,6 @@ func outputResult(p *print.Printer, outputFormat string, project *resourcemanage return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(project, "", " ") - if err != nil { - return fmt.Errorf("marshal project details: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/rabbitmq/credentials/create/create.go b/internal/cmd/rabbitmq/credentials/create/create.go index ee6f27a99..63199b8af 100644 --- a/internal/cmd/rabbitmq/credentials/create/create.go +++ b/internal/cmd/rabbitmq/credentials/create/create.go @@ -13,6 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "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/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/rabbitmq" @@ -20,13 +21,13 @@ import ( const ( instanceIdFlag = "instance-id" - hidePasswordFlag = "hide-password" + showPasswordFlag = "show-password" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - HidePassword bool + ShowPassword bool } func NewCmd(p *print.Printer) *cobra.Command { @@ -40,8 +41,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Create credentials for a RabbitMQ instance`, "$ stackit rabbitmq credentials create --instance-id xxx"), examples.NewExample( - `Create credentials for a RabbitMQ instance and hide the password in the output`, - "$ stackit rabbitmq credentials create --instance-id xxx --hide-password"), + `Create credentials for a RabbitMQ instance and show the password in the output`, + "$ stackit rabbitmq credentials create --instance-id xxx --show-password"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -86,7 +87,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") - cmd.Flags().Bool(hidePasswordFlag, false, "Hide password in output") + cmd.Flags().BoolP(showPasswordFlag, "s", false, "Show password in output") err := flags.MarkFlagsRequired(cmd, instanceIdFlag) cobra.CheckErr(err) @@ -101,7 +102,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), - HidePassword: flags.FlagToBoolValue(p, cmd, hidePasswordFlag), + ShowPassword: flags.FlagToBoolValue(p, cmd, showPasswordFlag), } if p.IsVerbosityDebug() { @@ -124,6 +125,9 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *rabbitmq.AP func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *rabbitmq.CredentialsResponse) error { switch model.OutputFormat { case print.JSONOutputFormat: + if !model.ShowPassword { + resp.Raw.Credentials.Password = utils.Ptr("hidden") + } details, err := json.MarshalIndent(resp, "", " ") if err != nil { return fmt.Errorf("marshal RabbitMQ credentials: %w", err) @@ -138,7 +142,7 @@ func outputResult(p *print.Printer, model *inputModel, instanceLabel string, res if username != "" { p.Outputf("Username: %s\n", *resp.Raw.Credentials.Username) } - if model.HidePassword { + if !model.ShowPassword { p.Outputf("Password: \n") } else { p.Outputf("Password: %s\n", *resp.Raw.Credentials.Password) diff --git a/internal/cmd/rabbitmq/credentials/create/create_test.go b/internal/cmd/rabbitmq/credentials/create/create_test.go index 8106c2cc8..494647b00 100644 --- a/internal/cmd/rabbitmq/credentials/create/create_test.go +++ b/internal/cmd/rabbitmq/credentials/create/create_test.go @@ -73,6 +73,16 @@ func TestParseInput(t *testing.T) { flagValues: map[string]string{}, isValid: false, }, + { + description: "show password", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[showPasswordFlag] = "true" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ShowPassword = true + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { diff --git a/internal/cmd/rabbitmq/credentials/describe/describe.go b/internal/cmd/rabbitmq/credentials/describe/describe.go index 077ee9a71..9b08752e6 100644 --- a/internal/cmd/rabbitmq/credentials/describe/describe.go +++ b/internal/cmd/rabbitmq/credentials/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of credentials with ID "xxx" from instance with ID "yyy"`, "$ stackit rabbitmq credentials describe xxx --instance-id yyy"), examples.NewExample( - `Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format`, - "$ stackit rabbitmq credentials describe xxx --instance-id yyy --output-format pretty"), + `Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format`, + "$ stackit rabbitmq credentials describe xxx --instance-id yyy --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -112,7 +112,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *rabbitmq.AP func outputResult(p *print.Printer, outputFormat string, credentials *rabbitmq.CredentialsResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal RabbitMQ credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *credentials.Id) table.AddSeparator() @@ -130,14 +138,6 @@ func outputResult(p *print.Printer, outputFormat string, credentials *rabbitmq.C return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(credentials, "", " ") - if err != nil { - return fmt.Errorf("marshal RabbitMQ credentials: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/rabbitmq/instance/describe/describe.go b/internal/cmd/rabbitmq/instance/describe/describe.go index 1b689315d..65b04e49d 100644 --- a/internal/cmd/rabbitmq/instance/describe/describe.go +++ b/internal/cmd/rabbitmq/instance/describe/describe.go @@ -40,8 +40,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a RabbitMQ instance with ID "xxx"`, "$ stackit rabbitmq instance describe xxx"), examples.NewExample( - `Get details of a RabbitMQ instance with ID "xxx" in a table format`, - "$ stackit rabbitmq instance describe xxx --output-format pretty"), + `Get details of a RabbitMQ instance with ID "xxx" in JSON format`, + "$ stackit rabbitmq instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -100,7 +100,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *rabbitmq.AP func outputResult(p *print.Printer, outputFormat string, instance *rabbitmq.Instance) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal RabbitMQ instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *instance.InstanceId) table.AddSeparator() @@ -125,14 +133,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *rabbitmq.Inst return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal RabbitMQ instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/redis/credentials/create/create.go b/internal/cmd/redis/credentials/create/create.go index 109c7793a..508d24130 100644 --- a/internal/cmd/redis/credentials/create/create.go +++ b/internal/cmd/redis/credentials/create/create.go @@ -13,6 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/redis/client" redisUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/redis/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/redis" @@ -20,13 +21,13 @@ import ( const ( instanceIdFlag = "instance-id" - hidePasswordFlag = "hide-password" + showPasswordFlag = "show-password" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - HidePassword bool + ShowPassword bool } func NewCmd(p *print.Printer) *cobra.Command { @@ -40,8 +41,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Create credentials for a Redis instance`, "$ stackit redis credentials create --instance-id xxx"), examples.NewExample( - `Create credentials for a Redis instance and hide the password in the output`, - "$ stackit redis credentials create --instance-id xxx --hide-password"), + `Create credentials for a Redis instance and show the password in the output`, + "$ stackit redis credentials create --instance-id xxx --show-password"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -86,7 +87,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") - cmd.Flags().Bool(hidePasswordFlag, false, "Hide password in output") + cmd.Flags().BoolP(showPasswordFlag, "s", false, "Show password in output") err := flags.MarkFlagsRequired(cmd, instanceIdFlag) cobra.CheckErr(err) @@ -101,7 +102,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), - HidePassword: flags.FlagToBoolValue(p, cmd, hidePasswordFlag), + ShowPassword: flags.FlagToBoolValue(p, cmd, showPasswordFlag), } if p.IsVerbosityDebug() { @@ -124,6 +125,9 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *redis.APICl func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *redis.CredentialsResponse) error { switch model.OutputFormat { case print.JSONOutputFormat: + if !model.ShowPassword { + resp.Raw.Credentials.Password = utils.Ptr("hidden") + } details, err := json.MarshalIndent(resp, "", " ") if err != nil { return fmt.Errorf("marshal Redis credentials: %w", err) @@ -138,7 +142,7 @@ func outputResult(p *print.Printer, model *inputModel, instanceLabel string, res if username != "" { p.Outputf("Username: %s\n", *resp.Raw.Credentials.Username) } - if model.HidePassword { + if !model.ShowPassword { p.Outputf("Password: \n") } else { p.Outputf("Password: %s\n", *resp.Raw.Credentials.Password) diff --git a/internal/cmd/redis/credentials/create/create_test.go b/internal/cmd/redis/credentials/create/create_test.go index 9c4707e54..bb1ee454a 100644 --- a/internal/cmd/redis/credentials/create/create_test.go +++ b/internal/cmd/redis/credentials/create/create_test.go @@ -73,6 +73,16 @@ func TestParseInput(t *testing.T) { flagValues: map[string]string{}, isValid: false, }, + { + description: "show password", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[showPasswordFlag] = "true" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ShowPassword = true + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { diff --git a/internal/cmd/redis/credentials/describe/describe.go b/internal/cmd/redis/credentials/describe/describe.go index 948eb5427..a6831e741 100644 --- a/internal/cmd/redis/credentials/describe/describe.go +++ b/internal/cmd/redis/credentials/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of credentials with ID "xxx" from instance with ID "yyy"`, "$ stackit redis credentials describe xxx --instance-id yyy"), examples.NewExample( - `Get details of credentials with ID "xxx" from instance with ID "yyy" in a table format`, - "$ stackit redis credentials describe xxx --instance-id yyy --output-format pretty"), + `Get details of credentials with ID "xxx" from instance with ID "yyy" in JSON format`, + "$ stackit redis credentials describe xxx --instance-id yyy --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -112,7 +112,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *redis.APICl func outputResult(p *print.Printer, outputFormat string, credentials *redis.CredentialsResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal Redis credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *credentials.Id) table.AddSeparator() @@ -130,14 +138,6 @@ func outputResult(p *print.Printer, outputFormat string, credentials *redis.Cred return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(credentials, "", " ") - if err != nil { - return fmt.Errorf("marshal Redis credentials: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/redis/instance/describe/describe.go b/internal/cmd/redis/instance/describe/describe.go index 07586230e..eb832bf38 100644 --- a/internal/cmd/redis/instance/describe/describe.go +++ b/internal/cmd/redis/instance/describe/describe.go @@ -40,8 +40,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a Redis instance with ID "xxx"`, "$ stackit redis instance describe xxx"), examples.NewExample( - `Get details of a Redis instance with ID "xxx" in a table format`, - "$ stackit redis instance describe xxx --output-format pretty"), + `Get details of a Redis instance with ID "xxx" in JSON format`, + "$ stackit redis instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -100,7 +100,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *redis.APICl func outputResult(p *print.Printer, outputFormat string, instance *redis.Instance) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instance, "", " ") + if err != nil { + return fmt.Errorf("marshal Redis instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *instance.InstanceId) table.AddSeparator() @@ -125,14 +133,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *redis.Instanc return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(instance, "", " ") - if err != nil { - return fmt.Errorf("marshal Redis instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/root.go b/internal/cmd/root.go index f08381730..5845d0c80 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -51,6 +51,9 @@ func NewRootCmd(version, date string, p *print.Printer) *cobra.Command { argsString := print.BuildDebugStrFromSlice(os.Args) p.Debug(print.DebugLevel, "arguments: %s", argsString) + configFilePath := viper.ConfigFileUsed() + p.Debug(print.DebugLevel, "using config file: %s", configFilePath) + configKeys := viper.AllSettings() configKeysStr := print.BuildDebugStrFromMap(configKeys) p.Debug(print.DebugLevel, "config keys: %s", configKeysStr) diff --git a/internal/cmd/secrets-manager/instance/describe/describe.go b/internal/cmd/secrets-manager/instance/describe/describe.go index c9e8cdf82..b484bdea6 100644 --- a/internal/cmd/secrets-manager/instance/describe/describe.go +++ b/internal/cmd/secrets-manager/instance/describe/describe.go @@ -39,8 +39,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a Secrets Manager instance with ID "xxx"`, "$ stackit secrets-manager instance describe xxx"), examples.NewExample( - `Get details of a Secrets Manager instance with ID "xxx" in a table format`, - "$ stackit secrets-manager instance describe xxx --output-format pretty"), + `Get details of a Secrets Manager instance with ID "xxx" in JSON format`, + "$ stackit secrets-manager instance describe xxx --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -111,8 +111,20 @@ func buildListACLsRequest(ctx context.Context, model *inputModel, apiClient *sec func outputResult(p *print.Printer, outputFormat string, instance *secretsmanager.Instance, aclList *secretsmanager.AclList) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + output := struct { + *secretsmanager.Instance + *secretsmanager.AclList + }{instance, aclList} + details, err := json.MarshalIndent(output, "", " ") + if err != nil { + return fmt.Errorf("marshal Secrets Manager instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *instance.Id) table.AddSeparator() @@ -141,19 +153,6 @@ func outputResult(p *print.Printer, outputFormat string, instance *secretsmanage return fmt.Errorf("render table: %w", err) } - return nil - default: - output := struct { - *secretsmanager.Instance - *secretsmanager.AclList - }{instance, aclList} - - details, err := json.MarshalIndent(output, "", " ") - if err != nil { - return fmt.Errorf("marshal Secrets Manager instance: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/secrets-manager/user/describe/describe.go b/internal/cmd/secrets-manager/user/describe/describe.go index b90c5b833..c3b0c0b96 100644 --- a/internal/cmd/secrets-manager/user/describe/describe.go +++ b/internal/cmd/secrets-manager/user/describe/describe.go @@ -42,8 +42,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy"`, "$ stackit secrets-manager user describe xxx --instance-id yyy"), examples.NewExample( - `Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in table format`, - "$ stackit secrets-manager user describe xxx --instance-id yyy --output-format pretty"), + `Get details of a Secrets Manager user with ID "xxx" of instance with ID "yyy" in JSON format`, + "$ stackit secrets-manager user describe xxx --instance-id yyy --output-format json"), ), Args: args.SingleArg(userIdArg, utils.ValidateUUID), RunE: func(cmd *cobra.Command, args []string) error { @@ -114,7 +114,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmana func outputResult(p *print.Printer, outputFormat string, user secretsmanager.User) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(user, "", " ") + if err != nil { + return fmt.Errorf("marshal Secrets Manager user: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *user.Id) table.AddSeparator() @@ -135,14 +143,6 @@ func outputResult(p *print.Printer, outputFormat string, user secretsmanager.Use return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(user, "", " ") - if err != nil { - return fmt.Errorf("marshal Secrets Manager user: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/ske/cluster/describe/describe.go b/internal/cmd/ske/cluster/describe/describe.go index df9e2a2a7..1d24f4563 100644 --- a/internal/cmd/ske/cluster/describe/describe.go +++ b/internal/cmd/ske/cluster/describe/describe.go @@ -37,8 +37,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of an SKE cluster with name "my-cluster"`, "$ stackit ske cluster describe my-cluster"), examples.NewExample( - `Get details of an SKE cluster with name "my-cluster" in a table format`, - "$ stackit ske cluster describe my-cluster --output-format pretty"), + `Get details of an SKE cluster with name "my-cluster" in JSON format`, + "$ stackit ske cluster describe my-cluster --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -97,8 +97,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClie func outputResult(p *print.Printer, outputFormat string, cluster *ske.Cluster) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(cluster, "", " ") + if err != nil { + return fmt.Errorf("marshal SKE cluster: %w", err) + } + p.Outputln(string(details)) + return nil + default: acl := []string{} if cluster.Extensions != nil && cluster.Extensions.Acl != nil { acl = *cluster.Extensions.Acl.AllowedCidrs @@ -117,14 +124,6 @@ func outputResult(p *print.Printer, outputFormat string, cluster *ske.Cluster) e return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(cluster, "", " ") - if err != nil { - return fmt.Errorf("marshal SKE cluster: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/ske/credentials/describe/describe.go b/internal/cmd/ske/credentials/describe/describe.go index ea9aadef5..cb7233bcd 100644 --- a/internal/cmd/ske/credentials/describe/describe.go +++ b/internal/cmd/ske/credentials/describe/describe.go @@ -44,8 +44,8 @@ func NewCmd(p *print.Printer) *cobra.Command { `Get details of the credentials associated to the SKE cluster with name "my-cluster"`, "$ stackit ske credentials describe my-cluster"), examples.NewExample( - `Get details of the credentials associated to the SKE cluster with name "my-cluster" in a table format`, - "$ stackit ske credentials describe my-cluster --output-format pretty"), + `Get details of the credentials associated to the SKE cluster with name "my-cluster" in JSON format`, + "$ stackit ske credentials describe my-cluster --output-format json"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -114,7 +114,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClie func outputResult(p *print.Printer, outputFormat string, credentials *ske.Credentials) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal SKE credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("SERVER", *credentials.Server) table.AddSeparator() @@ -124,14 +132,6 @@ func outputResult(p *print.Printer, outputFormat string, credentials *ske.Creden return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(credentials, "", " ") - if err != nil { - return fmt.Errorf("marshal SKE credentials: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/cmd/ske/describe/describe.go b/internal/cmd/ske/describe/describe.go index 35d09ead7..4b83c1306 100644 --- a/internal/cmd/ske/describe/describe.go +++ b/internal/cmd/ske/describe/describe.go @@ -86,7 +86,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClie func outputResult(p *print.Printer, outputFormat string, project *ske.ProjectResponse) error { switch outputFormat { - case print.PrettyOutputFormat: + case print.JSONOutputFormat: + details, err := json.MarshalIndent(project, "", " ") + if err != nil { + return fmt.Errorf("marshal SKE project details: %w", err) + } + p.Outputln(string(details)) + + return nil + default: table := tables.NewTable() table.AddRow("ID", *project.ProjectId) table.AddSeparator() @@ -96,14 +104,6 @@ func outputResult(p *print.Printer, outputFormat string, project *ske.ProjectRes return fmt.Errorf("render table: %w", err) } - return nil - default: - details, err := json.MarshalIndent(project, "", " ") - if err != nil { - return fmt.Errorf("marshal SKE project details: %w", err) - } - p.Outputln(string(details)) - return nil } } diff --git a/internal/pkg/auth/service_account.go b/internal/pkg/auth/service_account.go index c0496a69b..747832f6e 100644 --- a/internal/pkg/auth/service_account.go +++ b/internal/pkg/auth/service_account.go @@ -72,6 +72,8 @@ func AuthenticateServiceAccount(p *print.Printer, rt http.RoundTripper) (email s return "", fmt.Errorf("get email from access token: %w", err) } + p.Debug(print.DebugLevel, "successfully authenticated service account %s", email) + authFields[SERVICE_ACCOUNT_EMAIL] = email sessionExpiresAtUnix, err := getStartingSessionExpiresAtUnix() diff --git a/internal/pkg/auth/storage.go b/internal/pkg/auth/storage.go index 1bcb6e784..9d08ca070 100644 --- a/internal/pkg/auth/storage.go +++ b/internal/pkg/auth/storage.go @@ -18,7 +18,7 @@ type AuthFlow string const ( keyringService = "stackit-cli" - textFileFolderName = ".stackit" + textFileFolderName = "stackit" textFileName = "cli-auth-storage.txt" ) @@ -78,11 +78,11 @@ func setAuthFieldInEncodedTextFile(key authFieldKey, value string) error { return err } - homeDir, err := os.UserHomeDir() + configDir, err := os.UserConfigDir() if err != nil { - return fmt.Errorf("get home dir: %w", err) + return fmt.Errorf("get config dir: %w", err) } - textFileDir := filepath.Join(homeDir, textFileFolderName) + textFileDir := filepath.Join(configDir, textFileFolderName) textFilePath := filepath.Join(textFileDir, textFileName) contentEncoded, err := os.ReadFile(textFilePath) @@ -152,11 +152,11 @@ func getAuthFieldFromEncodedTextFile(key authFieldKey) (string, error) { return "", err } - homeDir, err := os.UserHomeDir() + configDir, err := os.UserConfigDir() if err != nil { - return "", fmt.Errorf("get home dir: %w", err) + return "", fmt.Errorf("get config dir: %w", err) } - textFileDir := filepath.Join(homeDir, textFileFolderName) + textFileDir := filepath.Join(configDir, textFileFolderName) textFilePath := filepath.Join(textFileDir, textFileName) contentEncoded, err := os.ReadFile(textFilePath) @@ -183,11 +183,11 @@ func getAuthFieldFromEncodedTextFile(key authFieldKey) (string, error) { // If it doesn't, creates it with the content "{}" encoded. // If it does, does nothing (and returns nil). func createEncodedTextFile() error { - homeDir, err := os.UserHomeDir() + configDir, err := os.UserConfigDir() if err != nil { - return fmt.Errorf("get home dir: %w", err) + return fmt.Errorf("get config dir: %w", err) } - textFileDir := filepath.Join(homeDir, textFileFolderName) + textFileDir := filepath.Join(configDir, textFileFolderName) textFilePath := filepath.Join(textFileDir, textFileName) err = os.MkdirAll(textFileDir, os.ModePerm) diff --git a/internal/pkg/auth/storage_test.go b/internal/pkg/auth/storage_test.go index 5b09c3c3d..1d3d4dab9 100644 --- a/internal/pkg/auth/storage_test.go +++ b/internal/pkg/auth/storage_test.go @@ -345,11 +345,11 @@ func deleteAuthFieldInEncodedTextFile(key authFieldKey) error { return err } - homeDir, err := os.UserHomeDir() + configDir, err := os.UserConfigDir() if err != nil { - return fmt.Errorf("get home dir: %w", err) + return fmt.Errorf("get config dir: %w", err) } - textFileDir := filepath.Join(homeDir, textFileFolderName) + textFileDir := filepath.Join(configDir, textFileFolderName) textFilePath := filepath.Join(textFileDir, textFileName) contentEncoded, err := os.ReadFile(textFilePath) diff --git a/internal/pkg/auth/templates/login-successful.html b/internal/pkg/auth/templates/login-successful.html index d111ce4e7..45cf2721c 100644 --- a/internal/pkg/auth/templates/login-successful.html +++ b/internal/pkg/auth/templates/login-successful.html @@ -336,5 +336,13 @@

Hello {{.Email}},

+ diff --git a/internal/pkg/auth/user_login.go b/internal/pkg/auth/user_login.go index 49dec2177..577a47846 100644 --- a/internal/pkg/auth/user_login.go +++ b/internal/pkg/auth/user_login.go @@ -11,8 +11,9 @@ import ( "net/http" "os" "os/exec" - "path/filepath" + "path" "runtime" + "strconv" "strings" "time" @@ -82,6 +83,7 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { // Define a handler that will get the authorization code, call the token endpoint, and close the HTTP server var errServer error mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + p.Debug(print.DebugLevel, "received request from authentication server") // Close the server only if there was an error // Otherwise, it will redirect to the succesfull login page defer func() { @@ -98,6 +100,8 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { return } + p.Debug(print.DebugLevel, "trading authorization code for access and refresh tokens") + // Trade the authorization code and the code verifier for access and refresh tokens accessToken, refreshToken, err := getUserAccessAndRefreshTokens(authDomain, clientId, codeVerifier, code, redirectURL) if err != nil { @@ -105,12 +109,22 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { return } + p.Debug(print.DebugLevel, "received response from the authentication server") + sessionExpiresAtUnix, err := getStartingSessionExpiresAtUnix() if err != nil { errServer = fmt.Errorf("compute session expiration timestamp: %w", err) return } + sessionExpiresAtUnixInt, err := strconv.Atoi(sessionExpiresAtUnix) + if err != nil { + p.Debug(print.ErrorLevel, "parse session expiration value \"%s\": %s", sessionExpiresAtUnix, err) + } else { + sessionExpiresAt := time.Unix(int64(sessionExpiresAtUnixInt), 0) + p.Debug(print.DebugLevel, "session expires at %s", sessionExpiresAt) + } + err = SetAuthFlow(AUTH_FLOW_USER_TOKEN) if err != nil { errServer = fmt.Errorf("set auth flow type: %w", err) @@ -123,6 +137,8 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { return } + p.Debug(print.DebugLevel, "user %s logged in successfully", email) + authFields := map[authFieldKey]string{ SESSION_EXPIRES_AT_UNIX: sessionExpiresAtUnix, ACCESS_TOKEN: accessToken, @@ -137,6 +153,8 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { // Redirect the user to the successful login page loginSuccessURL := redirectURL + loginSuccessPath + + p.Debug(print.DebugLevel, "redirecting browser to login successful page") http.Redirect(w, r, loginSuccessURL, http.StatusSeeOther) }) @@ -152,7 +170,9 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { Email: email, } - htmlTemplate, err := template.ParseFS(htmlContent, filepath.Join(htmlTemplatesPath, loginSuccessfulHTMLFile)) + // ParseFS expects paths using forward slashes, even on Windows + // See: https://github.com/golang/go/issues/44305#issuecomment-780111748 + htmlTemplate, err := template.ParseFS(htmlContent, path.Join(htmlTemplatesPath, loginSuccessfulHTMLFile)) if err != nil { errServer = fmt.Errorf("parse html file: %w", err) } @@ -163,6 +183,9 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { } }) + p.Debug(print.DebugLevel, "opening browser for authentication") + p.Debug(print.DebugLevel, "using authentication server on %s", authDomain) + // Open a browser window to the authorizationURL err = openBrowser(authorizationURL) if err != nil { @@ -171,6 +194,7 @@ func AuthorizeUser(p *print.Printer, isReauthentication bool) error { // Start the blocking web server loop // It will exit when the handlers get fired and call server.Close() + p.Debug(print.DebugLevel, "listening for response from authentication server on %s", redirectURL) err = server.Serve(listener) if !errors.Is(err, http.ErrServerClosed) { return fmt.Errorf("server for PKCE flow closed unexpectedly: %w", err) diff --git a/internal/pkg/auth/user_token_flow.go b/internal/pkg/auth/user_token_flow.go index 8abba51d0..d3e3a5a1d 100644 --- a/internal/pkg/auth/user_token_flow.go +++ b/internal/pkg/auth/user_token_flow.go @@ -50,6 +50,7 @@ func (utf *userTokenFlow) RoundTrip(req *http.Request) (*http.Response, error) { } else if refreshTokenExpired, err := tokenExpired(utf.refreshToken); err != nil { return nil, fmt.Errorf("check if refresh token has expired: %w", err) } else if !refreshTokenExpired { + utf.printer.Debug(print.DebugLevel, "access token expired, refreshing...") err = refreshTokens(utf) if err == nil { accessTokenValid = true @@ -59,6 +60,7 @@ func (utf *userTokenFlow) RoundTrip(req *http.Request) (*http.Response, error) { } if !accessTokenValid { + utf.printer.Debug(print.DebugLevel, "user access token is not valid, reauthenticating...") err = reauthenticateUser(utf) if err != nil { return nil, fmt.Errorf("reauthenticate user: %w", err) diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index b53c5a573..fa367857d 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -40,7 +40,7 @@ const ( // Backend config keys const ( - configFolder = ".stackit" + configFolder = "stackit" configFileName = "cli-config" configFileExtension = "json" ProjectNameKey = "project_name" @@ -75,9 +75,9 @@ var ConfigKeys = []string{ var folderPath string func InitConfig() { - home, err := os.UserHomeDir() + configDir, err := os.UserConfigDir() cobra.CheckErr(err) - configFolderPath := filepath.Join(home, configFolder) + configFolderPath := filepath.Join(configDir, configFolder) configFilePath := filepath.Join(configFolderPath, fmt.Sprintf("%s.%s", configFileName, configFileExtension)) // Write config dir path to global variable diff --git a/internal/pkg/print/debug.go b/internal/pkg/print/debug.go index 36686ed4f..59352682e 100644 --- a/internal/pkg/print/debug.go +++ b/internal/pkg/print/debug.go @@ -1,12 +1,20 @@ package print import ( + "bytes" "encoding/json" "fmt" + "io" + "net/http" + "slices" "sort" "strings" + + "github.com/stackitcloud/stackit-sdk-go/core/config" ) +var defaultHTTPHeaders = []string{"Accept", "Content-Type", "Content-Length", "User-Agent", "Date", "Referrer-Policy"} + // BuildDebugStrFromInputModel converts an input model to a user-friendly string representation. // This function converts the input model to a map, removes empty values, and generates a string representation of the map. // The purpose of this function is to provide a more readable output than the default JSON representation. @@ -31,6 +39,9 @@ func BuildDebugStrFromInputModel(model any) (string, error) { // The string representation is in the format: [key1: value1, key2: value2, ...] // The keys are ordered alphabetically to make the output deterministic. func BuildDebugStrFromMap(inputMap map[string]any) string { + if inputMap == nil { + return "[]" + } // Sort the keys to make the output deterministic keys := make([]string, 0, len(inputMap)) for key := range inputMap { @@ -44,7 +55,25 @@ func BuildDebugStrFromMap(inputMap map[string]any) string { if isEmpty(value) { continue } - keyValues = append(keyValues, fmt.Sprintf("%s: %v", key, value)) + + valueStr := fmt.Sprintf("%v", value) + + switch value := value.(type) { + case map[string]any: + valueStr = BuildDebugStrFromMap(value) + case []any: + sliceStr := make([]string, len(value)) + for i, item := range value { + if itemMap, ok := item.(map[string]any); ok { + sliceStr[i] = BuildDebugStrFromMap(itemMap) + } else { + sliceStr[i] = fmt.Sprintf("%v", item) + } + } + valueStr = BuildDebugStrFromSlice(sliceStr) + } + + keyValues = append(keyValues, fmt.Sprintf("%s: %v", key, valueStr)) } result := strings.Join(keyValues, ", ") @@ -57,6 +86,174 @@ func BuildDebugStrFromSlice(inputSlice []string) string { return fmt.Sprintf("[%s]", sliceStr) } +// buildHeaderMap converts a map to a user-friendly string representation. +// This function also filters the headers based on the includeHeaders parameter. +// If includeHeaders is empty, the default header filters are used. +func buildHeaderMap(headers http.Header, includeHeaders []string) map[string]any { + headersMap := make(map[string]any) + for key, values := range headers { + headersMap[key] = strings.Join(values, ", ") + } + + headersToInclude := defaultHTTPHeaders + if len(includeHeaders) != 0 { + headersToInclude = includeHeaders + } + for key := range headersMap { + if !slices.Contains(headersToInclude, key) { + delete(headersMap, key) + } + } + + return headersMap +} + +// drainBody reads all of b to memory and then returns two equivalent +// ReadClosers yielding the same bytes. +// +// It returns an error if the initial slurp of all bytes fails. It does not attempt +// to make the returned ReadClosers have identical error-matching behavior. +// Taken directly from the httputil package +// https://cs.opensource.google/go/go/+/refs/tags/go1.22.2:src/net/http/httputil/dump.go;drc=1d45a7ef560a76318ed59dfdb178cecd58caf948;l=25 +func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { + if b == nil || b == http.NoBody { + // No copying needed. Preserve the magic sentinel meaning of NoBody. + return http.NoBody, http.NoBody, nil + } + var buf bytes.Buffer + if _, err = buf.ReadFrom(b); err != nil { + return nil, b, err + } + if err := b.Close(); err != nil { + return nil, b, err + } + return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil +} + +// BuildDebugStrFromHTTPRequest converts an HTTP request to a user-friendly string representation. +// This function also receives a list of headers to include in the output, if empty, the default headers are used. +// The return value is a list of strings that should be printed separately. +func BuildDebugStrFromHTTPRequest(req *http.Request, includeHeaders []string) ([]string, error) { + if req == nil { + return nil, fmt.Errorf("request is nil") + } + if req.URL == nil || req.Proto == "" || req.Method == "" { + return nil, fmt.Errorf("request is invalid") + } + + status := fmt.Sprintf("request to %s: %s %s", req.URL, req.Method, req.Proto) + + headersMap := buildHeaderMap(req.Header, includeHeaders) + headers := fmt.Sprintf("request headers: %v", BuildDebugStrFromMap(headersMap)) + + var save io.ReadCloser + var err error + + save, req.Body, err = drainBody(req.Body) + if err != nil { + return []string{status, headers}, fmt.Errorf("drain response body: %w", err) + } + bodyBytes, err := io.ReadAll(req.Body) + if err != nil { + return []string{status, headers}, fmt.Errorf("read response body: %w", err) + } + req.Body = save + var bodyMap map[string]any + if len(bodyBytes) != 0 { + if err := json.Unmarshal(bodyBytes, &bodyMap); err != nil { + return nil, fmt.Errorf("unmarshal response body: %w", err) + } + } + if len(bodyMap) == 0 { + return []string{status, headers}, nil + } + body := fmt.Sprintf("request body: %s", BuildDebugStrFromMap(bodyMap)) + + return []string{status, headers, body}, nil +} + +// BuildDebugStrFromHTTPResponse converts an HTTP response to a user-friendly string representation. +// This function also receives a list of headers to include in the output, if empty, the default headers are used. +// The return value is a list of strings that should be printed separately. +func BuildDebugStrFromHTTPResponse(resp *http.Response, includeHeaders []string) ([]string, error) { + if resp == nil { + return nil, fmt.Errorf("response is nil") + } + + if resp.Request == nil || resp.Proto == "" || resp.Status == "" { + return nil, fmt.Errorf("response is invalid") + } + + status := fmt.Sprintf("response from %s: %s %s", resp.Request.URL, resp.Proto, resp.Status) + + headersMap := buildHeaderMap(resp.Header, includeHeaders) + headers := fmt.Sprintf("response headers: %v", BuildDebugStrFromMap(headersMap)) + + var save io.ReadCloser + var err error + + save, resp.Body, err = drainBody(resp.Body) + if err != nil { + return []string{status, headers}, fmt.Errorf("drain response body: %w", err) + } + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return []string{status, headers}, fmt.Errorf("read response body: %w", err) + } + resp.Body = save + var bodyMap map[string]any + if len(bodyBytes) != 0 { + if err := json.Unmarshal(bodyBytes, &bodyMap); err != nil { + return nil, fmt.Errorf("unmarshal response body: %w", err) + } + } + if len(bodyMap) == 0 { + return []string{status, headers}, nil + } + body := fmt.Sprintf("response body: %s", BuildDebugStrFromMap(bodyMap)) + + return []string{status, headers, body}, nil +} + +// RequestResponseCapturer is a middleware that captures the request and response of an HTTP request. +// Receives a printer and a list of headers to include in the output +// If the list of headers is empty, the default headers are used. +// The printer is used to print the captured data. +func RequestResponseCapturer(p *Printer, includeHeaders []string) config.Middleware { + return func(rt http.RoundTripper) http.RoundTripper { + return &roundTripperWithCapture{rt, p, includeHeaders} + } +} + +type roundTripperWithCapture struct { + transport http.RoundTripper + p *Printer + debugHttpHeaders []string +} + +func (rt roundTripperWithCapture) RoundTrip(req *http.Request) (*http.Response, error) { + reqStr, err := BuildDebugStrFromHTTPRequest(req, rt.debugHttpHeaders) + if err != nil { + rt.p.Debug(ErrorLevel, "printing request to debug logs: %v", err) + } + for _, line := range reqStr { + rt.p.Debug(DebugLevel, line) + } + resp, err := rt.transport.RoundTrip(req) + defer func() { + if err == nil { + respStrSlice, tempErr := BuildDebugStrFromHTTPResponse(resp, rt.debugHttpHeaders) + if tempErr != nil { + rt.p.Debug(ErrorLevel, "printing HTTP response to debug logs: %v", tempErr) + } + for _, line := range respStrSlice { + rt.p.Debug(DebugLevel, line) + } + } + }() + return resp, err +} + // isEmpty checks if a value is empty (nil, empty string, zero value for other types) func isEmpty(value interface{}) bool { if value == nil { diff --git a/internal/pkg/print/debug_test.go b/internal/pkg/print/debug_test.go index 61ad30450..35c3dfb6a 100644 --- a/internal/pkg/print/debug_test.go +++ b/internal/pkg/print/debug_test.go @@ -1,8 +1,14 @@ package print import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/url" "testing" + "github.com/google/go-cmp/cmp" "github.com/google/uuid" ) @@ -51,6 +57,53 @@ func fixtureInputModel(mods ...func(model *testInputModel)) *testInputModel { return model } +func fixtureHTTPRequest(mods ...func(req *http.Request)) *http.Request { + testBody, err := json.Marshal(map[string]string{"key": "value"}) + if err != nil { + return nil + } + request, err := http.NewRequest("GET", "http://example.com", bytes.NewReader(testBody)) + if err != nil { + return nil + } + + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + request.Header.Set("Content-Length", "15") + + for _, mod := range mods { + mod(request) + } + + return request +} + +func fixtureHTTPResponse(mods ...func(resp *http.Response)) *http.Response { + testBody, err := json.Marshal(map[string]string{"key": "value"}) + if err != nil { + return nil + } + response := &http.Response{ + Body: io.NopCloser(bytes.NewReader(testBody)), + StatusCode: http.StatusOK, + Proto: "HTTP/1.1", + Status: "200 OK", + ContentLength: int64(len(testBody)), + Request: &http.Request{Method: "GET", URL: &url.URL{Host: "example.com", Scheme: "http"}}, + Header: http.Header{ + "Content-Type": []string{"application/json"}, + "Accept": []string{"application/json"}, + "Content-Length": []string{"15"}, + }, + } + + for _, mod := range mods { + mod(response) + } + + return response +} + func TestBuildDebugStrFromInputModel(t *testing.T) { tests := []struct { description string @@ -129,6 +182,48 @@ func TestBuildDebugStrFromMap(t *testing.T) { }, expected: "[key1: value1, key2: value2, key3: 123, key4: false]", }, + { + description: "nested map", + inputMap: map[string]any{ + "key1": "value1", + "key2": map[string]any{ + "nestedKey1": "nestedValue1", + "nestedKey2": "nestedValue2", + }, + }, + expected: "[key1: value1, key2: [nestedKey1: nestedValue1, nestedKey2: nestedValue2]]", + }, + { + description: "nested slice of string", + inputMap: map[string]any{ + "key1": "value1", + "key2": []any{"value1", "value2"}, + }, + expected: "[key1: value1, key2: [value1, value2]]", + }, + { + description: "nested slice of int", + inputMap: map[string]any{ + "key1": "value1", + "key2": []any{1, 2}, + }, + expected: "[key1: value1, key2: [1, 2]]", + }, + { + description: "nested slice of map", + inputMap: map[string]any{ + "key1": "value1", + "key2": []any{ + map[string]any{ + "nestedKey1": "nestedValue1", + }, + map[string]any{ + "nestedKey2": "nestedValue2", + }, + }, + }, + expected: "[key1: value1, key2: [[nestedKey1: nestedValue1], [nestedKey2: nestedValue2]]]", + }, { description: "empty values", inputMap: map[string]any{ @@ -184,6 +279,230 @@ func TestBuildDebugStrFromSlice(t *testing.T) { } } +func TestBuildHeaderMap(t *testing.T) { + tests := []struct { + description string + inputHeader http.Header + inputIncludeHeaders []string + expected map[string]any + }{ + { + description: "base", + inputHeader: http.Header{ + "key1": []string{"value1"}, + "key2": []string{"value2"}, + "key3": []string{"value3"}, + }, + inputIncludeHeaders: []string{"key1", "key2"}, + expected: map[string]any{ + "key1": "value1", + "key2": "value2", + }, + }, + { + description: "no include headers", + inputHeader: http.Header{ + "Accept": []string{"value1"}, + "key2": []string{"value2"}, + "Date": []string{"value3"}, + }, + inputIncludeHeaders: []string{}, + expected: map[string]any{ + "Accept": "value1", + "Date": "value3", + }, + }, + { + description: "empty header", + inputHeader: http.Header{}, + inputIncludeHeaders: []string{}, + expected: map[string]any{}, + }, + { + description: "empty header, some include headers", + inputHeader: http.Header{}, + inputIncludeHeaders: []string{ + "key1", + "key2", + }, + expected: map[string]any{}, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + actual := buildHeaderMap(tt.inputHeader, tt.inputIncludeHeaders) + diff := cmp.Diff(actual, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildDebugStrFromHTTPRequest(t *testing.T) { + tests := []struct { + description string + inputReq *http.Request + inputIncludeHeaders []string + expected []string + isValid bool + }{ + { + description: "base", + inputReq: fixtureHTTPRequest(), + expected: []string{ + "request to http://example.com: GET HTTP/1.1", + "request headers: [Accept: application/json, Content-Length: 15, Content-Type: application/json]", + "request body: [key: value]", + }, + isValid: true, + }, + { + description: "include headers", + inputReq: fixtureHTTPRequest(), + inputIncludeHeaders: []string{"Content-Type", "Accept"}, + expected: []string{ + "request to http://example.com: GET HTTP/1.1", + "request headers: [Accept: application/json, Content-Type: application/json]", + "request body: [key: value]", + }, + isValid: true, + }, + { + description: "empty request", + inputReq: &http.Request{}, + isValid: false, + }, + { + description: "nil request", + inputReq: nil, + isValid: false, + }, + { + description: "empty headers", + inputReq: fixtureHTTPRequest(func(req *http.Request) { + req.Header = http.Header{} + }), + expected: []string{ + "request to http://example.com: GET HTTP/1.1", + "request headers: []", + "request body: [key: value]", + }, + isValid: true, + }, + { + description: "empty body", + inputReq: fixtureHTTPRequest(func(req *http.Request) { + req.Body = nil + }), + expected: []string{ + "request to http://example.com: GET HTTP/1.1", + "request headers: [Accept: application/json, Content-Length: 15, Content-Type: application/json]", + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + actual, err := BuildDebugStrFromHTTPRequest(tt.inputReq, tt.inputIncludeHeaders) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("unexpected error: %v", err) + } + if !tt.isValid { + t.Fatalf("expected error, got nil") + } + diff := cmp.Diff(actual, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildDebugStrFromHTTPResponse(t *testing.T) { + tests := []struct { + description string + inputResp *http.Response + inputIncludeHeaders []string + expected []string + isValid bool + }{ + { + description: "base", + inputResp: fixtureHTTPResponse(), // nolint:bodyclose // false positive, body is closed in the test + expected: []string{ + "response from http://example.com: HTTP/1.1 200 OK", + "response headers: [Accept: application/json, Content-Length: 15, Content-Type: application/json]", + "response body: [key: value]", + }, + isValid: true, + }, + { + description: "empty response", + inputResp: &http.Response{}, + isValid: false, + }, + { + description: "nil response", + inputResp: nil, + isValid: false, + }, + { + description: "empty headers", + inputResp: fixtureHTTPResponse(func(resp *http.Response) { // nolint:bodyclose // false positive, body is closed in the test + resp.Header = http.Header{} + }), + expected: []string{ + "response from http://example.com: HTTP/1.1 200 OK", + "response headers: []", + "response body: [key: value]", + }, + isValid: true, + }, + { + description: "empty body", + inputResp: fixtureHTTPResponse(func(resp *http.Response) { // nolint:bodyclose // false positive, body is closed in the test + resp.Body = nil + }), + expected: []string{ + "response from http://example.com: HTTP/1.1 200 OK", + "response headers: [Accept: application/json, Content-Length: 15, Content-Type: application/json]", + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + var err error + if tt.inputResp != nil && tt.inputResp.Body != nil { + defer func() { + err = tt.inputResp.Body.Close() + }() + } + actual, err := BuildDebugStrFromHTTPResponse(tt.inputResp, tt.inputIncludeHeaders) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("unexpected error: %v", err) + } + if !tt.isValid { + t.Fatalf("expected error, got nil") + } + diff := cmp.Diff(actual, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + func TestIsEmpty(t *testing.T) { tests := []struct { description string diff --git a/internal/pkg/print/print.go b/internal/pkg/print/print.go index a1062c676..4ca7ba433 100644 --- a/internal/pkg/print/print.go +++ b/internal/pkg/print/print.go @@ -11,6 +11,8 @@ import ( "os/exec" "strings" + "github.com/lmittmann/tint" + "github.com/mattn/go-colorable" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/stackitcloud/stackit-cli/internal/pkg/config" @@ -39,7 +41,10 @@ type Printer struct { // Creates a new printer, including setting up the default logger. func NewPrinter() *Printer { - logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{AddSource: false, Level: slog.LevelDebug})) + w := os.Stderr + logger := slog.New( + tint.NewHandler(colorable.NewColorable(w), &tint.Options{AddSource: false, Level: slog.LevelDebug}), + ) slog.SetDefault(logger) return &Printer{} @@ -173,13 +178,15 @@ func (p *Printer) PagerDisplay(content string) error { if outputFormat == NoneOutputFormat { return nil } - lessCmd := exec.Command("less", "-F", "-S", "-w") - lessCmd.Stdin = strings.NewReader(content) - lessCmd.Stdout = p.Cmd.OutOrStdout() + pagerCmd := exec.Command("less", "-F", "-S", "-w") - err := lessCmd.Run() + pagerCmd.Stdin = strings.NewReader(content) + pagerCmd.Stdout = p.Cmd.OutOrStdout() + + err := pagerCmd.Run() if err != nil { - return fmt.Errorf("run less command: %w", err) + p.Debug(ErrorLevel, "run pager command: %v", err) + p.Outputln(content) } return nil } diff --git a/internal/pkg/services/argus/client/client.go b/internal/pkg/services/argus/client/client.go index cf852d45f..c2f3a11da 100644 --- a/internal/pkg/services/argus/client/client.go +++ b/internal/pkg/services/argus/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*argus.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = argus.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/authorization/client/client.go b/internal/pkg/services/authorization/client/client.go index b4f475da7..19c13d663 100644 --- a/internal/pkg/services/authorization/client/client.go +++ b/internal/pkg/services/authorization/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*authorization.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = authorization.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/dns/client/client.go b/internal/pkg/services/dns/client/client.go index 6555222dd..384bc2cca 100644 --- a/internal/pkg/services/dns/client/client.go +++ b/internal/pkg/services/dns/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*dns.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = dns.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/logme/client/client.go b/internal/pkg/services/logme/client/client.go index 36bcd8a15..e99e46bf7 100644 --- a/internal/pkg/services/logme/client/client.go +++ b/internal/pkg/services/logme/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*logme.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = logme.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/mariadb/client/client.go b/internal/pkg/services/mariadb/client/client.go index 06046a36d..4d4dbce7e 100644 --- a/internal/pkg/services/mariadb/client/client.go +++ b/internal/pkg/services/mariadb/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*mariadb.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = mariadb.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/mongodbflex/client/client.go b/internal/pkg/services/mongodbflex/client/client.go index 77bec7c9b..addcfe34a 100644 --- a/internal/pkg/services/mongodbflex/client/client.go +++ b/internal/pkg/services/mongodbflex/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*mongodbflex.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = mongodbflex.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/object-storage/client/client.go b/internal/pkg/services/object-storage/client/client.go index 100db6111..f1a3a2147 100644 --- a/internal/pkg/services/object-storage/client/client.go +++ b/internal/pkg/services/object-storage/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*objectstorage.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = objectstorage.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/opensearch/client/client.go b/internal/pkg/services/opensearch/client/client.go index e18dc4d70..b4036b37c 100644 --- a/internal/pkg/services/opensearch/client/client.go +++ b/internal/pkg/services/opensearch/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*opensearch.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = opensearch.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/postgresflex/client/client.go b/internal/pkg/services/postgresflex/client/client.go index eb4d52960..3698b1a46 100644 --- a/internal/pkg/services/postgresflex/client/client.go +++ b/internal/pkg/services/postgresflex/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*postgresflex.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = postgresflex.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/rabbitmq/client/client.go b/internal/pkg/services/rabbitmq/client/client.go index 0c82aa2cd..821037064 100644 --- a/internal/pkg/services/rabbitmq/client/client.go +++ b/internal/pkg/services/rabbitmq/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*rabbitmq.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = rabbitmq.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/redis/client/client.go b/internal/pkg/services/redis/client/client.go index 58113d565..90e523c85 100644 --- a/internal/pkg/services/redis/client/client.go +++ b/internal/pkg/services/redis/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*redis.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = redis.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/resourcemanager/client/client.go b/internal/pkg/services/resourcemanager/client/client.go index a250411ba..ce1ae5620 100644 --- a/internal/pkg/services/resourcemanager/client/client.go +++ b/internal/pkg/services/resourcemanager/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*resourcemanager.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = resourcemanager.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/secrets-manager/client/client.go b/internal/pkg/services/secrets-manager/client/client.go index c5a380803..e6aa7f2b5 100644 --- a/internal/pkg/services/secrets-manager/client/client.go +++ b/internal/pkg/services/secrets-manager/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*secretsmanager.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = secretsmanager.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/service-account/client/client.go b/internal/pkg/services/service-account/client/client.go index 984394f03..b4ba6919d 100644 --- a/internal/pkg/services/service-account/client/client.go +++ b/internal/pkg/services/service-account/client/client.go @@ -29,6 +29,12 @@ func ConfigureClient(p *print.Printer) (*serviceaccount.APIClient, error) { cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = serviceaccount.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err) diff --git a/internal/pkg/services/ske/client/client.go b/internal/pkg/services/ske/client/client.go index 9d5c26eb6..36175a964 100644 --- a/internal/pkg/services/ske/client/client.go +++ b/internal/pkg/services/ske/client/client.go @@ -30,6 +30,12 @@ func ConfigureClient(p *print.Printer) (*ske.APIClient, error) { cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01")) } + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + apiClient, err = ske.NewAPIClient(cfgOptions...) if err != nil { p.Debug(print.ErrorLevel, "create new API client: %v", err)