From e75c912d4450d5decefbe824d7aa70ad6939147c Mon Sep 17 00:00:00 2001 From: Alexander Dahmen Date: Mon, 17 Aug 2026 10:42:39 +0200 Subject: [PATCH] chore(sqlserverflex): Deprecate fields, flags and commands STACKITCLI-436 Signed-off-by: Alexander Dahmen --- docs/stackit_beta_sqlserverflex.md | 3 +- docs/stackit_beta_sqlserverflex_flavor.md | 35 +++ ...ckit_beta_sqlserverflex_flavor_describe.md | 40 +++ .../stackit_beta_sqlserverflex_flavor_list.md | 44 +++ docs/stackit_beta_sqlserverflex_instance.md | 3 + ...ckit_beta_sqlserverflex_instance_create.md | 14 +- ...eta_sqlserverflex_instance_db-collation.md | 34 +++ ...qlserverflex_instance_db-collation_list.md | 48 ++++ ...sqlserverflex_instance_db-compatibility.md | 34 +++ ...rverflex_instance_db-compatibility_list.md | 48 ++++ ...ckit_beta_sqlserverflex_instance_update.md | 2 - ...t_beta_sqlserverflex_instance_user-role.md | 34 +++ ...a_sqlserverflex_instance_user-role_list.md | 48 ++++ docs/stackit_beta_sqlserverflex_options.md | 58 ---- .../stackit_beta_sqlserverflex_user_create.md | 2 +- docs/stackit_beta_sqlserverflex_version.md | 34 +++ ...stackit_beta_sqlserverflex_version_list.md | 40 +++ .../sqlserverflex/flavor/describe/describe.go | 138 ++++++++++ .../flavor/describe/describe_test.go | 253 ++++++++++++++++++ .../cmd/beta/sqlserverflex/flavor/flavor.go | 28 ++ .../beta/sqlserverflex/flavor/list/list.go | 139 ++++++++++ .../sqlserverflex/flavor/list/list_test.go | 174 ++++++++++++ .../sqlserverflex/instance/create/create.go | 40 ++- .../instance/db-collation/db_collation.go | 26 ++ .../instance/db-collation/list/list.go | 149 +++++++++++ .../instance/db-collation/list/list_test.go | 189 +++++++++++++ .../db-compatibility/db_compatibility.go | 26 ++ .../instance/db-compatibility/list/list.go | 149 +++++++++++ .../db-compatibility/list/list_test.go | 189 +++++++++++++ .../beta/sqlserverflex/instance/instance.go | 6 + .../sqlserverflex/instance/update/update.go | 27 +- .../instance/user-role/list/list.go | 148 ++++++++++ .../instance/user-role/list/list_test.go | 189 +++++++++++++ .../instance/user-role/user_role.go | 26 ++ .../cmd/beta/sqlserverflex/options/options.go | 39 ++- .../cmd/beta/sqlserverflex/sqlserverflex.go | 6 +- .../beta/sqlserverflex/user/create/create.go | 2 +- .../beta/sqlserverflex/version/list/list.go | 98 +++++++ .../sqlserverflex/version/list/list_test.go | 165 ++++++++++++ .../cmd/beta/sqlserverflex/version/version.go | 26 ++ 40 files changed, 2661 insertions(+), 92 deletions(-) create mode 100644 docs/stackit_beta_sqlserverflex_flavor.md create mode 100644 docs/stackit_beta_sqlserverflex_flavor_describe.md create mode 100644 docs/stackit_beta_sqlserverflex_flavor_list.md create mode 100644 docs/stackit_beta_sqlserverflex_instance_db-collation.md create mode 100644 docs/stackit_beta_sqlserverflex_instance_db-collation_list.md create mode 100644 docs/stackit_beta_sqlserverflex_instance_db-compatibility.md create mode 100644 docs/stackit_beta_sqlserverflex_instance_db-compatibility_list.md create mode 100644 docs/stackit_beta_sqlserverflex_instance_user-role.md create mode 100644 docs/stackit_beta_sqlserverflex_instance_user-role_list.md delete mode 100644 docs/stackit_beta_sqlserverflex_options.md create mode 100644 docs/stackit_beta_sqlserverflex_version.md create mode 100644 docs/stackit_beta_sqlserverflex_version_list.md create mode 100644 internal/cmd/beta/sqlserverflex/flavor/describe/describe.go create mode 100644 internal/cmd/beta/sqlserverflex/flavor/describe/describe_test.go create mode 100644 internal/cmd/beta/sqlserverflex/flavor/flavor.go create mode 100644 internal/cmd/beta/sqlserverflex/flavor/list/list.go create mode 100644 internal/cmd/beta/sqlserverflex/flavor/list/list_test.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/db-collation/db_collation.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/db-collation/list/list.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/db-collation/list/list_test.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/db-compatibility/db_compatibility.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list_test.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/user-role/list/list.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/user-role/list/list_test.go create mode 100644 internal/cmd/beta/sqlserverflex/instance/user-role/user_role.go create mode 100644 internal/cmd/beta/sqlserverflex/version/list/list.go create mode 100644 internal/cmd/beta/sqlserverflex/version/list/list_test.go create mode 100644 internal/cmd/beta/sqlserverflex/version/version.go diff --git a/docs/stackit_beta_sqlserverflex.md b/docs/stackit_beta_sqlserverflex.md index 88f836410..d25971ba7 100644 --- a/docs/stackit_beta_sqlserverflex.md +++ b/docs/stackit_beta_sqlserverflex.md @@ -31,7 +31,8 @@ stackit beta sqlserverflex [flags] * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands * [stackit beta sqlserverflex database](./stackit_beta_sqlserverflex_database.md) - Provides functionality for SQLServer Flex databases +* [stackit beta sqlserverflex flavor](./stackit_beta_sqlserverflex_flavor.md) - Provides functionality for SQLServer Flex flavors * [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances -* [stackit beta sqlserverflex options](./stackit_beta_sqlserverflex_options.md) - Lists SQL Server Flex options * [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users +* [stackit beta sqlserverflex version](./stackit_beta_sqlserverflex_version.md) - Provides functionality for SQLServer Flex versions diff --git a/docs/stackit_beta_sqlserverflex_flavor.md b/docs/stackit_beta_sqlserverflex_flavor.md new file mode 100644 index 000000000..bc7a83b95 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_flavor.md @@ -0,0 +1,35 @@ +## stackit beta sqlserverflex flavor + +Provides functionality for SQLServer Flex flavors + +### Synopsis + +Provides functionality for SQLServer Flex flavors. + +``` +stackit beta sqlserverflex flavor [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex flavor" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex +* [stackit beta sqlserverflex flavor describe](./stackit_beta_sqlserverflex_flavor_describe.md) - Show details of a SQLServer Flex flavor +* [stackit beta sqlserverflex flavor list](./stackit_beta_sqlserverflex_flavor_list.md) - Lists SQLServer Flex flavors + diff --git a/docs/stackit_beta_sqlserverflex_flavor_describe.md b/docs/stackit_beta_sqlserverflex_flavor_describe.md new file mode 100644 index 000000000..56022aa3d --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_flavor_describe.md @@ -0,0 +1,40 @@ +## stackit beta sqlserverflex flavor describe + +Show details of a SQLServer Flex flavor + +### Synopsis + +Show details of a SQLServer Flex flavor. + +``` +stackit beta sqlserverflex flavor describe [flags] +``` + +### Examples + +``` + Show SQLServer Flex flavor details + $ stackit beta sqlserverflex flavor describe xxx +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex flavor describe" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex flavor](./stackit_beta_sqlserverflex_flavor.md) - Provides functionality for SQLServer Flex flavors + diff --git a/docs/stackit_beta_sqlserverflex_flavor_list.md b/docs/stackit_beta_sqlserverflex_flavor_list.md new file mode 100644 index 000000000..f60b2582f --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_flavor_list.md @@ -0,0 +1,44 @@ +## stackit beta sqlserverflex flavor list + +Lists SQLServer Flex flavors + +### Synopsis + +Lists SQLServer Flex flavors. + +``` +stackit beta sqlserverflex flavor list [flags] +``` + +### Examples + +``` + List SQLServer Flex flavors + $ stackit beta sqlserverflex flavor list + + List up to 10 SQLServer Flex flavors + $ stackit beta sqlserverflex flavor list --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex flavor list" + --limit int Maximum number of entries to list +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex flavor](./stackit_beta_sqlserverflex_flavor.md) - Provides functionality for SQLServer Flex flavors + diff --git a/docs/stackit_beta_sqlserverflex_instance.md b/docs/stackit_beta_sqlserverflex_instance.md index 227b3a487..45a99559e 100644 --- a/docs/stackit_beta_sqlserverflex_instance.md +++ b/docs/stackit_beta_sqlserverflex_instance.md @@ -31,8 +31,11 @@ stackit beta sqlserverflex instance [flags] * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex * [stackit beta sqlserverflex instance create](./stackit_beta_sqlserverflex_instance_create.md) - Creates a SQLServer Flex instance +* [stackit beta sqlserverflex instance db-collation](./stackit_beta_sqlserverflex_instance_db-collation.md) - Provides functionality for SQLServer Flex database collations +* [stackit beta sqlserverflex instance db-compatibility](./stackit_beta_sqlserverflex_instance_db-compatibility.md) - Provides functionality for SQLServer Flex database compatibilities * [stackit beta sqlserverflex instance delete](./stackit_beta_sqlserverflex_instance_delete.md) - Deletes a SQLServer Flex instance * [stackit beta sqlserverflex instance describe](./stackit_beta_sqlserverflex_instance_describe.md) - Shows details of a SQLServer Flex instance * [stackit beta sqlserverflex instance list](./stackit_beta_sqlserverflex_instance_list.md) - Lists all SQLServer Flex instances * [stackit beta sqlserverflex instance update](./stackit_beta_sqlserverflex_instance_update.md) - Updates a SQLServer Flex instance +* [stackit beta sqlserverflex instance user-role](./stackit_beta_sqlserverflex_instance_user-role.md) - Provides functionality for SQLServer Flex user roles diff --git a/docs/stackit_beta_sqlserverflex_instance_create.md b/docs/stackit_beta_sqlserverflex_instance_create.md index 9918cac35..76abfe1c5 100644 --- a/docs/stackit_beta_sqlserverflex_instance_create.md +++ b/docs/stackit_beta_sqlserverflex_instance_create.md @@ -13,12 +13,12 @@ stackit beta sqlserverflex instance create [flags] ### Examples ``` - Create a SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values. - The flavor ID can be retrieved by running "$ stackit beta sqlserverflex options --flavors" - $ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx --backup-schedule "0 1-3 * * *" --retention-days 30 --storage-class premium-perf2-stackit --storage-size 10 --version 2022 + Create a SQLServer Flex instance with name "my-instance" and specify flavor by ID. + The flavor ID can be retrieved by running "$ stackit beta sqlserverflex flavor list" + $ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx --backup-schedule "0 2 * * *" --retention-days 30 --storage-class premium-perf2-stackit --storage-size 10 --version 2022 --acl 1.2.3.0/24 - Create a SQLServer Flex instance with name "my-instance", specify flavor by CPU and RAM, set storage size to 20 GB, and restrict access to a specific range of IP addresses. Other parameters are set to default values - $ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 --storage-size 20 --backup-schedule "0 1-3 * * *" --retention-days 30 --storage-class premium-perf2-stackit --storage-size 10 --version 2022 --acl 1.2.3.0/24 + Create a SQLServer Flex instance with name "my-instance", specify flavor by ID, set storage size to 20 GB, and restrict access to a specific range of IP addresses. + $ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx --storage-size 20 --backup-schedule "0 2 * * *" --retention-days 30 --storage-class premium-perf2-stackit --version 2022 --acl 1.2.3.0/24 ``` ### Options @@ -26,16 +26,14 @@ stackit beta sqlserverflex instance create [flags] ``` --acl strings The access control list (ACL). Must contain at least one valid subnet, for instance '0.0.0.0/0' for open access (discouraged), '1.2.3.0/24 for a public IP range of an organization, '1.2.3.4/32' for a single IP range, etc. (default []) --backup-schedule string Backup schedule - --cpu int Number of CPUs --edition string Edition of the SQLServer instance --encryption-kek-key-id string The key identifier --encryption-kek-key-version string The key version --encryption-kek-keyring-id string The keyring identifier --encryption-service-account string The service account - --flavor-id string ID of the flavor + --flavor-id string ID of the flavor. This flag will be required after 2027-02-28. -h, --help Help for "stackit beta sqlserverflex instance create" -n, --name string Instance name - --ram int Amount of RAM (in GB) --retention-days int32 The days for how long the backup files should be stored before being cleaned up --storage-class string Storage class --storage-size int Storage size (in GB) diff --git a/docs/stackit_beta_sqlserverflex_instance_db-collation.md b/docs/stackit_beta_sqlserverflex_instance_db-collation.md new file mode 100644 index 000000000..702c9896c --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_instance_db-collation.md @@ -0,0 +1,34 @@ +## stackit beta sqlserverflex instance db-collation + +Provides functionality for SQLServer Flex database collations + +### Synopsis + +Provides functionality for SQLServer Flex database collations. + +``` +stackit beta sqlserverflex instance db-collation [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex instance db-collation" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances +* [stackit beta sqlserverflex instance db-collation list](./stackit_beta_sqlserverflex_instance_db-collation_list.md) - Lists supported SQLServer Flex database collations of an instance + diff --git a/docs/stackit_beta_sqlserverflex_instance_db-collation_list.md b/docs/stackit_beta_sqlserverflex_instance_db-collation_list.md new file mode 100644 index 000000000..2383661dd --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_instance_db-collation_list.md @@ -0,0 +1,48 @@ +## stackit beta sqlserverflex instance db-collation list + +Lists supported SQLServer Flex database collations of an instance + +### Synopsis + +Lists supported SQLServer Flex database collations of an instance. + +``` +stackit beta sqlserverflex instance db-collation list [flags] +``` + +### Examples + +``` + List SQLServer Flex database collations of instance with ID "xxx" + $ stackit beta sqlserverflex instance db-collation list --instance-id xxx + + List SQLServer Flex database collations of instance with ID "xxx" in JSON format + $ stackit beta sqlserverflex instance db-collation list --instance-id xxx --output-format json + + List up to 10 SQLServer Flex database collations of instance with ID "xxx" + $ stackit beta sqlserverflex instance db-collation list --instance-id xxx --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex instance db-collation list" + --instance-id string Instance ID + --limit int Maximum number of entries to list +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex instance db-collation](./stackit_beta_sqlserverflex_instance_db-collation.md) - Provides functionality for SQLServer Flex database collations + diff --git a/docs/stackit_beta_sqlserverflex_instance_db-compatibility.md b/docs/stackit_beta_sqlserverflex_instance_db-compatibility.md new file mode 100644 index 000000000..fabf3a909 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_instance_db-compatibility.md @@ -0,0 +1,34 @@ +## stackit beta sqlserverflex instance db-compatibility + +Provides functionality for SQLServer Flex database compatibilities + +### Synopsis + +Provides functionality for SQLServer Flex database compatibilities. + +``` +stackit beta sqlserverflex instance db-compatibility [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex instance db-compatibility" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances +* [stackit beta sqlserverflex instance db-compatibility list](./stackit_beta_sqlserverflex_instance_db-compatibility_list.md) - Lists supported SQLServer Flex database compatibilities of an instance + diff --git a/docs/stackit_beta_sqlserverflex_instance_db-compatibility_list.md b/docs/stackit_beta_sqlserverflex_instance_db-compatibility_list.md new file mode 100644 index 000000000..a6ac50418 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_instance_db-compatibility_list.md @@ -0,0 +1,48 @@ +## stackit beta sqlserverflex instance db-compatibility list + +Lists supported SQLServer Flex database compatibilities of an instance + +### Synopsis + +Lists supported SQLServer Flex database compatibilities of an instance. + +``` +stackit beta sqlserverflex instance db-compatibility list [flags] +``` + +### Examples + +``` + List SQLServer Flex database compatibilities of instance with ID "xxx" + $ stackit beta sqlserverflex instance db-compatibility list --instance-id xxx + + List SQLServer Flex database compatibilities of instance with ID "xxx" in JSON format + $ stackit beta sqlserverflex instance db-compatibility list --instance-id xxx --output-format json + + List up to 10 SQLServer Flex database compatibilities of instance with ID "xxx" + $ stackit beta sqlserverflex instance db-compatibility list --instance-id xxx --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex instance db-compatibility list" + --instance-id string Instance ID + --limit int Maximum number of entries to list +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex instance db-compatibility](./stackit_beta_sqlserverflex_instance_db-compatibility.md) - Provides functionality for SQLServer Flex database compatibilities + diff --git a/docs/stackit_beta_sqlserverflex_instance_update.md b/docs/stackit_beta_sqlserverflex_instance_update.md index 9ff3c5b25..336140e3d 100644 --- a/docs/stackit_beta_sqlserverflex_instance_update.md +++ b/docs/stackit_beta_sqlserverflex_instance_update.md @@ -25,11 +25,9 @@ stackit beta sqlserverflex instance update INSTANCE_ID [flags] ``` --acl strings Lists of IP networks in CIDR notation which are allowed to access this instance (default []) --backup-schedule string Backup schedule - --cpu int Number of CPUs --flavor-id string ID of the flavor -h, --help Help for "stackit beta sqlserverflex instance update" -n, --name string Instance name - --ram int Amount of RAM (in GB) --version string Version ``` diff --git a/docs/stackit_beta_sqlserverflex_instance_user-role.md b/docs/stackit_beta_sqlserverflex_instance_user-role.md new file mode 100644 index 000000000..cb1a63566 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_instance_user-role.md @@ -0,0 +1,34 @@ +## stackit beta sqlserverflex instance user-role + +Provides functionality for SQLServer Flex user roles + +### Synopsis + +Provides functionality for SQLServer Flex user roles. + +``` +stackit beta sqlserverflex instance user-role [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex instance user-role" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex instance](./stackit_beta_sqlserverflex_instance.md) - Provides functionality for SQLServer Flex instances +* [stackit beta sqlserverflex instance user-role list](./stackit_beta_sqlserverflex_instance_user-role_list.md) - Lists supported SQLServer Flex user roles of an instance + diff --git a/docs/stackit_beta_sqlserverflex_instance_user-role_list.md b/docs/stackit_beta_sqlserverflex_instance_user-role_list.md new file mode 100644 index 000000000..cb493ab62 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_instance_user-role_list.md @@ -0,0 +1,48 @@ +## stackit beta sqlserverflex instance user-role list + +Lists supported SQLServer Flex user roles of an instance + +### Synopsis + +Lists supported SQLServer Flex user roles of an instance. + +``` +stackit beta sqlserverflex instance user-role list [flags] +``` + +### Examples + +``` + List SQLServer Flex user roles of instance with ID "xxx" + $ stackit beta sqlserverflex instance user-role list --instance-id xxx + + List SQLServer Flex user roles of instance with ID "xxx" in JSON format + $ stackit beta sqlserverflex instance user-role list --instance-id xxx --output-format json + + List up to 10 SQLServer Flex user roles of instance with ID "xxx" + $ stackit beta sqlserverflex instance user-role list --instance-id xxx --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex instance user-role list" + --instance-id string Instance ID + --limit int Maximum number of entries to list +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex instance user-role](./stackit_beta_sqlserverflex_instance_user-role.md) - Provides functionality for SQLServer Flex user roles + diff --git a/docs/stackit_beta_sqlserverflex_options.md b/docs/stackit_beta_sqlserverflex_options.md deleted file mode 100644 index 8caf2ad97..000000000 --- a/docs/stackit_beta_sqlserverflex_options.md +++ /dev/null @@ -1,58 +0,0 @@ -## stackit beta sqlserverflex options - -Lists SQL Server Flex options - -### Synopsis - -Lists SQL Server Flex options (flavors, versions and storages for a given flavor) -Pass one or more flags to filter what categories are shown. - -``` -stackit beta sqlserverflex options [flags] -``` - -### Examples - -``` - List SQL Server Flex flavors options - $ stackit beta sqlserverflex options --flavors - - List SQL Server Flex available versions - $ stackit beta sqlserverflex options --versions - - List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit beta sqlserverflex options --flavors" - $ stackit beta sqlserverflex options --storages --flavor-id - - List SQL Server Flex user roles and database compatibilities for a given instance. The IDs of existing instances can be obtained by running "$ stackit beta sqlserverflex instance list" - $ stackit beta sqlserverflex options --user-roles --db-compatibilities --instance-id -``` - -### Options - -``` - --db-collations Lists supported database collations for a given instance - --db-compatibilities Lists supported database compatibilities for a given instance - --flavor-id string The flavor ID to show storages for. Only relevant when "--storages" is passed - --flavors Lists supported flavors - -h, --help Help for "stackit beta sqlserverflex options" - --instance-id string The instance ID to show user roles, database collations and database compatibilities for. Only relevant when "--user-roles", "--db-collations" or "--db-compatibilities" is passed - --storages Lists supported storages for a given flavor - --user-roles Lists supported user roles for a given instance - --versions Lists supported versions -``` - -### Options inherited from parent commands - -``` - -y, --assume-yes If set, skips all confirmation prompts - --async If set, runs the command asynchronously - -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) - -p, --project-id string Project ID - --region string Target region for region-specific requests - --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") -``` - -### SEE ALSO - -* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex - diff --git a/docs/stackit_beta_sqlserverflex_user_create.md b/docs/stackit_beta_sqlserverflex_user_create.md index 5ddc1ad26..086d7bfd7 100644 --- a/docs/stackit_beta_sqlserverflex_user_create.md +++ b/docs/stackit_beta_sqlserverflex_user_create.md @@ -12,7 +12,7 @@ Alternatively, you can reset the password and access the new one by running: Please refer to https://docs.stackit.cloud/products/databases/sqlserver-flex/how-tos/create-logins-and-users-in-sqlserver-flex-instances/ for additional information. The allowed user roles for your instance can be obtained by running: - $ stackit beta sqlserverflex options --user-roles --instance-id INSTANCE_ID + $ stackit beta sqlserverflex instance user-role list --instance-id INSTANCE_ID ``` stackit beta sqlserverflex user create [flags] diff --git a/docs/stackit_beta_sqlserverflex_version.md b/docs/stackit_beta_sqlserverflex_version.md new file mode 100644 index 000000000..5a859f99a --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_version.md @@ -0,0 +1,34 @@ +## stackit beta sqlserverflex version + +Provides functionality for SQLServer Flex versions + +### Synopsis + +Provides functionality for SQLServer Flex versions. + +``` +stackit beta sqlserverflex version [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex version" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex +* [stackit beta sqlserverflex version list](./stackit_beta_sqlserverflex_version_list.md) - Lists SQLServer Flex versions + diff --git a/docs/stackit_beta_sqlserverflex_version_list.md b/docs/stackit_beta_sqlserverflex_version_list.md new file mode 100644 index 000000000..c2b308832 --- /dev/null +++ b/docs/stackit_beta_sqlserverflex_version_list.md @@ -0,0 +1,40 @@ +## stackit beta sqlserverflex version list + +Lists SQLServer Flex versions + +### Synopsis + +Lists SQLServer Flex versions. + +``` +stackit beta sqlserverflex version list [flags] +``` + +### Examples + +``` + List SQLServer Flex version options + $ stackit beta sqlserverflex version list +``` + +### Options + +``` + -h, --help Help for "stackit beta sqlserverflex version list" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit beta sqlserverflex version](./stackit_beta_sqlserverflex_version.md) - Provides functionality for SQLServer Flex versions + diff --git a/internal/cmd/beta/sqlserverflex/flavor/describe/describe.go b/internal/cmd/beta/sqlserverflex/flavor/describe/describe.go new file mode 100644 index 000000000..9e7984545 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/flavor/describe/describe.go @@ -0,0 +1,138 @@ +package describe + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" +) + +const ( + flavorIdArg = "FLAVOR_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + FlavorId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "describe", + Short: "Show details of a SQLServer Flex flavor", + Long: "Show details of a SQLServer Flex flavor.", + Args: args.SingleArg(flavorIdArg, nil), + Example: examples.Build( + examples.NewExample( + `Show SQLServer Flex flavor details`, + "$ stackit beta sqlserverflex flavor describe xxx"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + flavor, err := buildAndExecuteRequest(ctx, model, apiClient.DefaultAPI) + if err != nil { + return fmt.Errorf("get SQLServer Flex flavor: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, flavor) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + flavorId := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + model := inputModel{ + GlobalFlagModel: globalFlags, + FlavorId: flavorId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildAndExecuteRequest(ctx context.Context, model *inputModel, apiClient sqlserverflex.DefaultAPI) (*sqlserverflex.ListFlavors, error) { + // Note: There's currently no GET endpoint for a single flavor. So we have to use the list endpoint and + // do client-side filtering for the correct flavor. + + flavors, err := apiClient.ListFlavors(ctx, model.ProjectId, model.Region).Size(100).Execute() + if err != nil { + return nil, fmt.Errorf("listing flavors: %w", err) + } + + for _, flavor := range flavors.Flavors { + if flavor.Id == model.FlavorId { + return &flavor, nil + } + } + + return nil, fmt.Errorf("flavor \"%s\" not found", model.FlavorId) +} + +func outputResult(p *print.Printer, outputFormat string, flavor *sqlserverflex.ListFlavors) error { + return p.OutputResult(outputFormat, flavor, func() error { + if flavor == nil { + return fmt.Errorf("flavor is nil") + } + + table := tables.NewTable() + table.SetTitle("Flavor") + table.AddRow("ID", flavor.Id) + table.AddSeparator() + table.AddRow("CPU", flavor.Cpu) + table.AddSeparator() + table.AddRow("MEMORY (GiB)", flavor.Memory) + table.AddSeparator() + table.AddRow("NODE TYPE", flavor.NodeType) + table.AddSeparator() + table.AddRow("MIN STORAGE (GB)", flavor.MinGB) + table.AddSeparator() + table.AddRow("MAX STORAGE (GB)", flavor.MaxGB) + table.AddSeparator() + table.AddRow("DESCRIPTION", flavor.Description) + + storageClassesTable := tables.NewTable() + storageClassesTable.SetTitle("Storages") + storageClassesTable.SetHeader("STORAGE CLASS", "MAX IO PER SEC", "MAX THROUGH (MB)") + + for _, sc := range flavor.StorageClasses { + storageClassesTable.AddRow(sc.Class, sc.MaxIoPerSec, sc.MaxThroughInMb) + storageClassesTable.AddSeparator() + } + + err := tables.DisplayTables(p, []tables.Table{table, storageClassesTable}) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/beta/sqlserverflex/flavor/describe/describe_test.go b/internal/cmd/beta/sqlserverflex/flavor/describe/describe_test.go new file mode 100644 index 000000000..8e4978294 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/flavor/describe/describe_test.go @@ -0,0 +1,253 @@ +package describe + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var ( + testProjectId = uuid.NewString() +) + +const ( + testRegion = "eu01" + testFlavorId = "1.4" +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testFlavorId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + FlavorId: testFlavorId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureTestFlavor() sqlserverflex.ListFlavors { + return sqlserverflex.ListFlavors{ + Id: testFlavorId, + Cpu: 1, + Memory: 4, + Description: "SQLServer-Flex-1.4-Single-EU01", + MaxGB: 4000, + MinGB: 5, + NodeType: "Single", + StorageClasses: []sqlserverflex.FlavorStorageClassesStorageClass{ + { + Class: "premium-perf2-stackit", + MaxIoPerSec: 1000, + MaxThroughInMb: 100, + }, + { + Class: "premium-perf4-stackit", + MaxIoPerSec: 2000, + MaxThroughInMb: 150, + }, + }, + } +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "no flag values", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +type mockSettings struct { + listFlavorsFails bool + flavors []sqlserverflex.ListFlavors +} + +func newApiMock(s mockSettings) sqlserverflex.DefaultAPI { + return sqlserverflex.DefaultAPIServiceMock{ + ListFlavorsExecuteMock: utils.Ptr(func(_ sqlserverflex.ApiListFlavorsRequest) (*sqlserverflex.ListFlavorsResponse, error) { + if s.listFlavorsFails { + return nil, fmt.Errorf("mock list flavors fails") + } + + return &sqlserverflex.ListFlavorsResponse{ + Flavors: s.flavors, + }, nil + }), + } +} + +func TestBuildAndExecuteRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + mockSettings mockSettings + want *sqlserverflex.ListFlavors + wantErr bool + }{ + { + description: "base", + model: fixtureInputModel(), + mockSettings: mockSettings{ + flavors: []sqlserverflex.ListFlavors{ + fixtureTestFlavor(), + }, + }, + want: utils.Ptr(fixtureTestFlavor()), + wantErr: false, + }, + { + description: "flavor not found", + model: fixtureInputModel(func(model *inputModel) { + model.FlavorId = "foo-bar-does-not-exist" + }), + mockSettings: mockSettings{ + flavors: []sqlserverflex.ListFlavors{ + fixtureTestFlavor(), + }, + }, + want: nil, + wantErr: true, + }, + { + description: "list flavors fails", + model: fixtureInputModel(), + mockSettings: mockSettings{ + listFlavorsFails: true, + }, + want: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + actual, err := buildAndExecuteRequest(context.Background(), tt.model, newApiMock(tt.mockSettings)) + if err != nil && !tt.wantErr { + t.Errorf("want no error, got %v", err) + } + + if err == nil && tt.wantErr { + t.Errorf("want error, got nil") + } + + diff := cmp.Diff(actual, tt.want) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func Test_outputResult(t *testing.T) { + type args struct { + outputFormat string + flavor *sqlserverflex.ListFlavors + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "flavor is nil", + args: args{ + flavor: nil, + }, + wantErr: true, + }, + { + name: "flavor is empty", + args: args{ + flavor: &sqlserverflex.ListFlavors{}, + }, + wantErr: false, + }, + { + name: "complete", + args: args{ + flavor: utils.Ptr(fixtureTestFlavor()), + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.flavor); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/sqlserverflex/flavor/flavor.go b/internal/cmd/beta/sqlserverflex/flavor/flavor.go new file mode 100644 index 000000000..89e6862dc --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/flavor/flavor.go @@ -0,0 +1,28 @@ +package flavor + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/flavor/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/flavor/list" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "flavor", + Short: "Provides functionality for SQLServer Flex flavors", + Long: "Provides functionality for SQLServer Flex flavors.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(list.NewCmd(params)) + cmd.AddCommand(describe.NewCmd(params)) +} diff --git a/internal/cmd/beta/sqlserverflex/flavor/list/list.go b/internal/cmd/beta/sqlserverflex/flavor/list/list.go new file mode 100644 index 000000000..4d0c58789 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/flavor/list/list.go @@ -0,0 +1,139 @@ +package list + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" +) + +const ( + limitFlag = "limit" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Limit *int64 +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists SQLServer Flex flavors", + Long: "Lists SQLServer Flex flavors.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SQLServer Flex flavors`, + "$ stackit beta sqlserverflex flavor list"), + examples.NewExample( + `List up to 10 SQLServer Flex flavors`, + "$ stackit beta sqlserverflex flavor list --limit 10"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + flavors, err := buildRequest(ctx, model, apiClient.DefaultAPI).Execute() + if err != nil { + return fmt.Errorf("get SQLServer Flex flavors: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, flavors.Flavors) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &cliErr.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Limit: limit, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient sqlserverflex.DefaultAPI) sqlserverflex.ApiListFlavorsRequest { + req := apiClient.ListFlavors(ctx, model.ProjectId, model.Region) + + if model.Limit != nil { + req = req.Size(*model.Limit) + } else { + // the default page size is only 10 + req = req.Size(100) + } + + return req +} + +func outputResult(p *print.Printer, outputFormat string, flavors []sqlserverflex.ListFlavors) error { + return p.OutputResult(outputFormat, flavors, func() error { + if len(flavors) == 0 { + p.Outputf("No SQLServer Flex flavors found.") + return nil + } + + table := tables.NewTable() + table.SetTitle("Flavors") + table.SetHeader("ID", "CPU", "MEMORY", "NODE TYPE", "DESCRIPTION") + for _, f := range flavors { + table.AddRow( + f.Id, + f.Cpu, + f.Memory, + f.NodeType, + f.Description, + ) + table.AddSeparator() + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/beta/sqlserverflex/flavor/list/list_test.go b/internal/cmd/beta/sqlserverflex/flavor/list/list_test.go new file mode 100644 index 000000000..323f53a07 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/flavor/list/list_test.go @@ -0,0 +1,174 @@ +package list + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &sqlserverflex.APIClient{DefaultAPI: &sqlserverflex.DefaultAPIService{}} +var testProjectId = uuid.NewString() + +const ( + testRegion = "eu01" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *sqlserverflex.ApiListFlavorsRequest)) sqlserverflex.ApiListFlavorsRequest { + request := testClient.DefaultAPI.ListFlavors(testCtx, testProjectId, testRegion).Size(100) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no flag values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest sqlserverflex.ApiListFlavorsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "limit flag set", + model: fixtureInputModel(func(model *inputModel) { + model.Limit = new(int64(12)) + }), + expectedRequest: fixtureRequest(func(request *sqlserverflex.ApiListFlavorsRequest) { + *request = request.Size(12) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient.DefaultAPI) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx, sqlserverflex.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func Test_outputResult(t *testing.T) { + type args struct { + outputFormat string + flavors []sqlserverflex.ListFlavors + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "flavors slice is nil", + args: args{ + flavors: nil, + }, + wantErr: false, + }, + { + name: "flavors slice is empty", + args: args{ + flavors: []sqlserverflex.ListFlavors{}, + }, + wantErr: false, + }, + { + name: "empty flavor in flavors slice", + args: args{ + flavors: []sqlserverflex.ListFlavors{{}}, + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.flavors); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/sqlserverflex/instance/create/create.go b/internal/cmd/beta/sqlserverflex/instance/create/create.go index 91741fdf3..d121b57c9 100644 --- a/internal/cmd/beta/sqlserverflex/instance/create/create.go +++ b/internal/cmd/beta/sqlserverflex/instance/create/create.go @@ -29,8 +29,6 @@ const ( aclFlag = "acl" backupScheduleFlag = "backup-schedule" flavorIdFlag = "flavor-id" - cpuFlag = "cpu" - ramFlag = "ram" storageClassFlag = "storage-class" storageSizeFlag = "storage-size" versionFlag = "version" @@ -41,6 +39,11 @@ const ( encryptionKekKeyringIdFlag = "encryption-kek-keyring-id" encryptionKekKeyVersionFlag = "encryption-kek-key-version" encryptionServiceAccountFlag = "encryption-service-account" + + // Deprecated: cpuFlag is deprecated and will be removed after 2027-02-28. Use flavorIdFlag instead. + cpuFlag = "cpu" + // Deprecated: ramFlag is deprecated and will be removed after 2027-02-28. Use flavorIdFlag instead. + ramFlag = "ram" ) type inputModel struct { @@ -50,8 +53,6 @@ type inputModel struct { ACL []string BackupSchedule string FlavorId *string - CPU *int64 - RAM *int64 StorageClass string StorageSize *int64 Version string @@ -61,6 +62,11 @@ type inputModel struct { EncryptionKekKeyringId *string EncryptionKekKeyVersion *string EncryptionServiceAccount *string + + // Deprecated: CPU is deprecated and will be removed after 2027-02-28. Use FlavorId instead. + CPU *int64 + // Deprecated: RAM is deprecated and will be removed after 2027-02-28. Use FlavorId instead. + RAM *int64 } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -71,12 +77,12 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `Create a SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values. - The flavor ID can be retrieved by running "$ stackit beta sqlserverflex options --flavors"`, - `$ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx --backup-schedule "0 1-3 * * *" --retention-days 30 --storage-class premium-perf2-stackit --storage-size 10 --version 2022`), + `Create a SQLServer Flex instance with name "my-instance" and specify flavor by ID. + The flavor ID can be retrieved by running "$ stackit beta sqlserverflex flavor list"`, + `$ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx --backup-schedule "0 2 * * *" --retention-days 30 --storage-class premium-perf2-stackit --storage-size 10 --version 2022 --acl 1.2.3.0/24`), examples.NewExample( - `Create a SQLServer Flex instance with name "my-instance", specify flavor by CPU and RAM, set storage size to 20 GB, and restrict access to a specific range of IP addresses. Other parameters are set to default values`, - `$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 --storage-size 20 --backup-schedule "0 1-3 * * *" --retention-days 30 --storage-class premium-perf2-stackit --storage-size 10 --version 2022 --acl 1.2.3.0/24`), + `Create a SQLServer Flex instance with name "my-instance", specify flavor by ID, set storage size to 20 GB, and restrict access to a specific range of IP addresses.`, + `$ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx --storage-size 20 --backup-schedule "0 2 * * *" --retention-days 30 --storage-class premium-perf2-stackit --version 2022 --acl 1.2.3.0/24`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -85,6 +91,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } + // Deprecated: remove after 2027-02-28, once flavor-id is the only supported way to select a flavor. + if model.FlavorId == nil { + params.Printer.Warn("The --%s flag is not set, determining flavor ID by CPU and RAM. This behavior is deprecated, the --%s flag will be required after 2027-02-28.\n", flavorIdFlag, flavorIdFlag) + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { @@ -136,7 +147,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name") cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "The access control list (ACL). Must contain at least one valid subnet, for instance '0.0.0.0/0' for open access (discouraged), '1.2.3.0/24 for a public IP range of an organization, '1.2.3.4/32' for a single IP range, etc.") cmd.Flags().String(backupScheduleFlag, "", "Backup schedule") - cmd.Flags().String(flavorIdFlag, "", "ID of the flavor") + cmd.Flags().String(flavorIdFlag, "", "ID of the flavor. This flag will be required after 2027-02-28.") cmd.Flags().Int64(cpuFlag, 0, "Number of CPUs") cmd.Flags().Int64(ramFlag, 0, "Amount of RAM (in GB)") cmd.Flags().Int64(storageSizeFlag, 0, "Storage size (in GB)") @@ -153,6 +164,15 @@ func configureFlags(cmd *cobra.Command) { cobra.CheckErr(err) cmd.MarkFlagsRequiredTogether(encryptionKekKeyIdFlag, encryptionKekKeyringIdFlag, encryptionKekKeyVersionFlag, encryptionServiceAccountFlag) + + // Deprecated: remove after 2027-02-28 + err = cmd.Flags().MarkDeprecated(cpuFlag, fmt.Sprintf("will be removed after 2027-02-28. Use the --%s flag instead.", flavorIdFlag)) + cobra.CheckErr(err) + err = cmd.Flags().MarkDeprecated(ramFlag, fmt.Sprintf("will be removed after 2027-02-28. Use the --%s flag instead.", flavorIdFlag)) + cobra.CheckErr(err) + cmd.MarkFlagsRequiredTogether(cpuFlag, ramFlag) + cmd.MarkFlagsMutuallyExclusive(flavorIdFlag, cpuFlag) + cmd.MarkFlagsMutuallyExclusive(flavorIdFlag, ramFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { diff --git a/internal/cmd/beta/sqlserverflex/instance/db-collation/db_collation.go b/internal/cmd/beta/sqlserverflex/instance/db-collation/db_collation.go new file mode 100644 index 000000000..a31f558e4 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/db-collation/db_collation.go @@ -0,0 +1,26 @@ +package dbcollation + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/db-collation/list" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "db-collation", + Short: "Provides functionality for SQLServer Flex database collations", + Long: "Provides functionality for SQLServer Flex database collations.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(list.NewCmd(params)) +} diff --git a/internal/cmd/beta/sqlserverflex/instance/db-collation/list/list.go b/internal/cmd/beta/sqlserverflex/instance/db-collation/list/list.go new file mode 100644 index 000000000..e80c3962c --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/db-collation/list/list.go @@ -0,0 +1,149 @@ +package list + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + instanceIdFlag = "instance-id" + limitFlag = "limit" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + InstanceId *string + Limit *int64 +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists supported SQLServer Flex database collations of an instance", + Long: "Lists supported SQLServer Flex database collations of an instance.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SQLServer Flex database collations of instance with ID "xxx"`, + "$ stackit beta sqlserverflex instance db-collation list --instance-id xxx"), + examples.NewExample( + `List SQLServer Flex database collations of instance with ID "xxx" in JSON format`, + "$ stackit beta sqlserverflex instance db-collation list --instance-id xxx --output-format json"), + examples.NewExample( + `List up to 10 SQLServer Flex database collations of instance with ID "xxx"`, + "$ stackit beta sqlserverflex instance db-collation list --instance-id xxx --limit 10"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SQLServer Flex database collations: %w", err) + } + collations := resp.Collations + + instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient.DefaultAPI, model.ProjectId, *model.InstanceId, model.Region) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err) + instanceLabel = *model.InstanceId + } + + // Truncate output + if model.Limit != nil && len(collations) > int(*model.Limit) { + collations = collations[:*model.Limit] + } + + return outputResult(params.Printer, model.OutputFormat, instanceLabel, collations) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + + err := flags.MarkFlagsRequired(cmd, instanceIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: flags.FlagToStringPointer(p, cmd, instanceIdFlag), + Limit: limit, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiListCollationsRequest { + return apiClient.DefaultAPI.ListCollations(ctx, model.ProjectId, model.Region, *model.InstanceId) +} + +func outputResult(p *print.Printer, outputFormat, instanceLabel string, collations []sqlserverflex.DatabaseGetcollation) error { + return p.OutputResult(outputFormat, collations, func() error { + if len(collations) == 0 { + p.Outputf("No database collations found for instance %q\n", instanceLabel) + return nil + } + + table := tables.NewTable() + table.SetTitle("Database Collations") + table.SetHeader("NAME", "DESCRIPTION") + for _, c := range collations { + table.AddRow(utils.PtrString(c.CollationName), utils.PtrString(c.Description)) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/beta/sqlserverflex/instance/db-collation/list/list_test.go b/internal/cmd/beta/sqlserverflex/instance/db-collation/list/list_test.go new file mode 100644 index 000000000..1ff9f6025 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/db-collation/list/list_test.go @@ -0,0 +1,189 @@ +package list + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &sqlserverflex.APIClient{DefaultAPI: &sqlserverflex.DefaultAPIService{}} + +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + instanceIdFlag: testInstanceId, + limitFlag: "10", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: utils.Ptr(testInstanceId), + Limit: utils.Ptr(int64(10)), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *sqlserverflex.ApiListCollationsRequest)) sqlserverflex.ApiListCollationsRequest { + request := testClient.DefaultAPI.ListCollations(testCtx, testProjectId, testRegion, testInstanceId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "instance id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, instanceIdFlag) + }), + isValid: false, + }, + { + description: "instance id invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "limit invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "limit invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest sqlserverflex.ApiListCollationsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx, sqlserverflex.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + instanceLabel string + collations []sqlserverflex.DatabaseGetcollation + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "empty collation in the collations slice", + args: args{ + collations: []sqlserverflex.DatabaseGetcollation{{}}, + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.instanceLabel, tt.args.collations); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/sqlserverflex/instance/db-compatibility/db_compatibility.go b/internal/cmd/beta/sqlserverflex/instance/db-compatibility/db_compatibility.go new file mode 100644 index 000000000..9f2ede561 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/db-compatibility/db_compatibility.go @@ -0,0 +1,26 @@ +package dbcompatibility + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/db-compatibility/list" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "db-compatibility", + Short: "Provides functionality for SQLServer Flex database compatibilities", + Long: "Provides functionality for SQLServer Flex database compatibilities.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(list.NewCmd(params)) +} diff --git a/internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list.go b/internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list.go new file mode 100644 index 000000000..39c54a7f1 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list.go @@ -0,0 +1,149 @@ +package list + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + instanceIdFlag = "instance-id" + limitFlag = "limit" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + InstanceId *string + Limit *int64 +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists supported SQLServer Flex database compatibilities of an instance", + Long: "Lists supported SQLServer Flex database compatibilities of an instance.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SQLServer Flex database compatibilities of instance with ID "xxx"`, + "$ stackit beta sqlserverflex instance db-compatibility list --instance-id xxx"), + examples.NewExample( + `List SQLServer Flex database compatibilities of instance with ID "xxx" in JSON format`, + "$ stackit beta sqlserverflex instance db-compatibility list --instance-id xxx --output-format json"), + examples.NewExample( + `List up to 10 SQLServer Flex database compatibilities of instance with ID "xxx"`, + "$ stackit beta sqlserverflex instance db-compatibility list --instance-id xxx --limit 10"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SQLServer Flex database compatibilities: %w", err) + } + compatibilities := resp.Compatibilities + + instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient.DefaultAPI, model.ProjectId, *model.InstanceId, model.Region) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err) + instanceLabel = *model.InstanceId + } + + // Truncate output + if model.Limit != nil && len(compatibilities) > int(*model.Limit) { + compatibilities = compatibilities[:*model.Limit] + } + + return outputResult(params.Printer, model.OutputFormat, instanceLabel, compatibilities) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + + err := flags.MarkFlagsRequired(cmd, instanceIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: flags.FlagToStringPointer(p, cmd, instanceIdFlag), + Limit: limit, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiListCompatibilitiesRequest { + return apiClient.DefaultAPI.ListCompatibilities(ctx, model.ProjectId, model.Region, *model.InstanceId) +} + +func outputResult(p *print.Printer, outputFormat, instanceLabel string, compatibilities []sqlserverflex.DatabaseGetcompatibility) error { + return p.OutputResult(outputFormat, compatibilities, func() error { + if len(compatibilities) == 0 { + p.Outputf("No database compatibilities found for instance %q\n", instanceLabel) + return nil + } + + table := tables.NewTable() + table.SetTitle("Database Compatibilities") + table.SetHeader("COMPATIBILITY LEVEL", "DESCRIPTION") + for _, c := range compatibilities { + table.AddRow(utils.PtrString(c.CompatibilityLevel), utils.PtrString(c.Description)) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list_test.go b/internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list_test.go new file mode 100644 index 000000000..226486e3a --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/db-compatibility/list/list_test.go @@ -0,0 +1,189 @@ +package list + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &sqlserverflex.APIClient{DefaultAPI: &sqlserverflex.DefaultAPIService{}} + +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + instanceIdFlag: testInstanceId, + limitFlag: "10", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: utils.Ptr(testInstanceId), + Limit: utils.Ptr(int64(10)), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *sqlserverflex.ApiListCompatibilitiesRequest)) sqlserverflex.ApiListCompatibilitiesRequest { + request := testClient.DefaultAPI.ListCompatibilities(testCtx, testProjectId, testRegion, testInstanceId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "instance id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, instanceIdFlag) + }), + isValid: false, + }, + { + description: "instance id invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "limit invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "limit invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest sqlserverflex.ApiListCompatibilitiesRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx, sqlserverflex.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + instanceLabel string + compatibilities []sqlserverflex.DatabaseGetcompatibility + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "empty compatibility in the compatibilities slice", + args: args{ + compatibilities: []sqlserverflex.DatabaseGetcompatibility{{}}, + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.instanceLabel, tt.args.compatibilities); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/sqlserverflex/instance/instance.go b/internal/cmd/beta/sqlserverflex/instance/instance.go index 9d8784bc7..f3aa733f4 100644 --- a/internal/cmd/beta/sqlserverflex/instance/instance.go +++ b/internal/cmd/beta/sqlserverflex/instance/instance.go @@ -2,10 +2,13 @@ package instance import ( "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/create" + dbcollation "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/db-collation" + dbcompatibility "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/db-compatibility" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/describe" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/list" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/update" + userrole "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/user-role" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -31,4 +34,7 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(describe.NewCmd(params)) cmd.AddCommand(list.NewCmd(params)) cmd.AddCommand(update.NewCmd(params)) + cmd.AddCommand(dbcollation.NewCmd(params)) + cmd.AddCommand(dbcompatibility.NewCmd(params)) + cmd.AddCommand(userrole.NewCmd(params)) } diff --git a/internal/cmd/beta/sqlserverflex/instance/update/update.go b/internal/cmd/beta/sqlserverflex/instance/update/update.go index e6330848a..5f4302125 100644 --- a/internal/cmd/beta/sqlserverflex/instance/update/update.go +++ b/internal/cmd/beta/sqlserverflex/instance/update/update.go @@ -30,9 +30,12 @@ const ( aclFlag = "acl" backupScheduleFlag = "backup-schedule" flavorIdFlag = "flavor-id" - cpuFlag = "cpu" - ramFlag = "ram" versionFlag = "version" + + // Deprecated: cpuFlag is deprecated and will be removed after 2027-02-28. Use flavorIdFlag instead. + cpuFlag = "cpu" + // Deprecated: ramFlag is deprecated and will be removed after 2027-02-28. Use flavorIdFlag instead. + ramFlag = "ram" ) type inputModel struct { @@ -43,9 +46,12 @@ type inputModel struct { ACL []string BackupSchedule *string FlavorId *string - CPU *int64 - RAM *int64 Version *string + + // Deprecated: CPU is deprecated and will be removed after 2027-02-28. Use FlavorId instead. + CPU *int64 + // Deprecated: RAM is deprecated and will be removed after 2027-02-28. Use FlavorId instead. + RAM *int64 } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -70,6 +76,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } + // Deprecated: remove after 2027-02-28, once flavor-id is the only supported way to select a flavor. + if model.FlavorId == nil && (model.CPU != nil || model.RAM != nil) { + params.Printer.Warn("The --%s/--%s flags are deprecated, determining flavor ID by CPU and RAM. This behavior is deprecated, the --%s flag will be required after 2027-02-28.\n", cpuFlag, ramFlag, flavorIdFlag) + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { @@ -131,6 +142,14 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(cpuFlag, 0, "Number of CPUs") cmd.Flags().Int64(ramFlag, 0, "Amount of RAM (in GB)") cmd.Flags().String(versionFlag, "", "Version") + + // Deprecated: remove after 2027-02-28 + err := cmd.Flags().MarkDeprecated(cpuFlag, fmt.Sprintf("will be removed after 2027-02-28. Use the --%s flag instead.", flavorIdFlag)) + cobra.CheckErr(err) + err = cmd.Flags().MarkDeprecated(ramFlag, fmt.Sprintf("will be removed after 2027-02-28. Use the --%s flag instead.", flavorIdFlag)) + cobra.CheckErr(err) + cmd.MarkFlagsMutuallyExclusive(flavorIdFlag, cpuFlag) + cmd.MarkFlagsMutuallyExclusive(flavorIdFlag, ramFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { diff --git a/internal/cmd/beta/sqlserverflex/instance/user-role/list/list.go b/internal/cmd/beta/sqlserverflex/instance/user-role/list/list.go new file mode 100644 index 000000000..5d77495fa --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/user-role/list/list.go @@ -0,0 +1,148 @@ +package list + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + sqlserverflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" +) + +const ( + instanceIdFlag = "instance-id" + limitFlag = "limit" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + InstanceId *string + Limit *int64 +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists supported SQLServer Flex user roles of an instance", + Long: "Lists supported SQLServer Flex user roles of an instance.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SQLServer Flex user roles of instance with ID "xxx"`, + "$ stackit beta sqlserverflex instance user-role list --instance-id xxx"), + examples.NewExample( + `List SQLServer Flex user roles of instance with ID "xxx" in JSON format`, + "$ stackit beta sqlserverflex instance user-role list --instance-id xxx --output-format json"), + examples.NewExample( + `List up to 10 SQLServer Flex user roles of instance with ID "xxx"`, + "$ stackit beta sqlserverflex instance user-role list --instance-id xxx --limit 10"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SQLServer Flex user roles: %w", err) + } + roles := resp.Roles + + instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient.DefaultAPI, model.ProjectId, *model.InstanceId, model.Region) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err) + instanceLabel = *model.InstanceId + } + + // Truncate output + if model.Limit != nil && len(roles) > int(*model.Limit) { + roles = roles[:*model.Limit] + } + + return outputResult(params.Printer, model.OutputFormat, instanceLabel, roles) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + + err := flags.MarkFlagsRequired(cmd, instanceIdFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: flags.FlagToStringPointer(p, cmd, instanceIdFlag), + Limit: limit, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiListRolesRequest { + return apiClient.DefaultAPI.ListRoles(ctx, model.ProjectId, model.Region, *model.InstanceId) +} + +func outputResult(p *print.Printer, outputFormat, instanceLabel string, roles []string) error { + return p.OutputResult(outputFormat, roles, func() error { + if len(roles) == 0 { + p.Outputf("No user roles found for instance %q\n", instanceLabel) + return nil + } + + table := tables.NewTable() + table.SetTitle("User Roles") + table.SetHeader("ROLE") + for _, role := range roles { + table.AddRow(role) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/beta/sqlserverflex/instance/user-role/list/list_test.go b/internal/cmd/beta/sqlserverflex/instance/user-role/list/list_test.go new file mode 100644 index 000000000..e1a1c3f6d --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/user-role/list/list_test.go @@ -0,0 +1,189 @@ +package list + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &sqlserverflex.APIClient{DefaultAPI: &sqlserverflex.DefaultAPIService{}} + +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + instanceIdFlag: testInstanceId, + limitFlag: "10", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: utils.Ptr(testInstanceId), + Limit: utils.Ptr(int64(10)), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *sqlserverflex.ApiListRolesRequest)) sqlserverflex.ApiListRolesRequest { + request := testClient.DefaultAPI.ListRoles(testCtx, testProjectId, testRegion, testInstanceId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "instance id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, instanceIdFlag) + }), + isValid: false, + }, + { + description: "instance id invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "limit invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "limit invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest sqlserverflex.ApiListRolesRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx, sqlserverflex.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + instanceLabel string + roles []string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "roles", + args: args{ + roles: []string{"dbo", "db_owner"}, + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.instanceLabel, tt.args.roles); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/sqlserverflex/instance/user-role/user_role.go b/internal/cmd/beta/sqlserverflex/instance/user-role/user_role.go new file mode 100644 index 000000000..8e13a353d --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/instance/user-role/user_role.go @@ -0,0 +1,26 @@ +package userrole + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance/user-role/list" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "user-role", + Short: "Provides functionality for SQLServer Flex user roles", + Long: "Provides functionality for SQLServer Flex user roles.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(list.NewCmd(params)) +} diff --git a/internal/cmd/beta/sqlserverflex/options/options.go b/internal/cmd/beta/sqlserverflex/options/options.go index 7ec5249c3..7dbb8e4a5 100644 --- a/internal/cmd/beta/sqlserverflex/options/options.go +++ b/internal/cmd/beta/sqlserverflex/options/options.go @@ -21,17 +21,26 @@ import ( ) const ( - flavorsFlag = "flavors" - versionsFlag = "versions" - storagesFlag = "storages" - userRolesFlag = "user-roles" - dbCollationsFlag = "db-collations" + // Deprecated: Will be removed after 2027-02-28. + flavorsFlag = "flavors" + // Deprecated: Will be removed after 2027-02-28. + versionsFlag = "versions" + // Deprecated: Will be removed after 2027-02-28. + storagesFlag = "storages" + // Deprecated: Will be removed after 2027-02-28. + userRolesFlag = "user-roles" + // Deprecated: Will be removed after 2027-02-28. + dbCollationsFlag = "db-collations" + // Deprecated: Will be removed after 2027-02-28. dbCompatibilitiesFlag = "db-compatibilities" - flavorIdFlag = "flavor-id" + // Deprecated: Will be removed after 2027-02-28. + flavorIdFlag = "flavor-id" + // Deprecated: Will be removed after 2027-02-28. instanceIdFlag = "instance-id" ) +// Deprecated: Will be removed after 2027-02-28. type inputModel struct { *globalflags.GlobalFlagModel @@ -46,6 +55,7 @@ type inputModel struct { InstanceId *string } +// Deprecated: Will be removed after 2027-02-28. type options struct { Flavors []sqlserverflex.ListFlavors `json:"flavors,omitempty"` Versions []sqlserverflex.Version `json:"versions,omitempty"` @@ -55,26 +65,32 @@ type options struct { DBCompatibilities *instanceDBCompatibilities `json:"dbCompatibilities,omitempty"` } +// Deprecated: Will be removed after 2027-02-28. type flavorStorages struct { FlavorId string `json:"flavorId"` Storages *sqlserverflex.ListStoragesResponse `json:"storages"` } +// Deprecated: Will be removed after 2027-02-28. type instanceUserRoles struct { InstanceId string `json:"instanceId"` UserRoles []string `json:"userRoles"` } +// Deprecated: Will be removed after 2027-02-28. type instanceDBCollations struct { InstanceId string `json:"instanceId"` DBCollations []sqlserverflex.DatabaseGetcollation `json:"dbCollations"` } +// Deprecated: Will be removed after 2027-02-28. type instanceDBCompatibilities struct { InstanceId string `json:"instanceId"` DBCompatibilities []sqlserverflex.DatabaseGetcompatibility `json:"dbCompatibilities"` } +// Deprecated: Will be removed after 2027-02-28. Use "flavor list", "flavor describe", "version list", +// "instance user-role list", "instance db-collation list" and "instance db-compatibility list" instead. func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "options", @@ -95,6 +111,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `List SQL Server Flex user roles and database compatibilities for a given instance. The IDs of existing instances can be obtained by running "$ stackit beta sqlserverflex instance list"`, "$ stackit beta sqlserverflex options --user-roles --db-compatibilities --instance-id "), ), + Deprecated: `command "stackit beta sqlserverflex options" is deprecated and will be removed after 2027-02-28. Please use "stackit beta sqlserverflex flavor list", "stackit beta sqlserverflex flavor describe", "stackit beta sqlserverflex version list", "stackit beta sqlserverflex instance user-role list", "stackit beta sqlserverflex instance db-collation list" and "stackit beta sqlserverflex instance db-compatibility list" instead.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() model, err := parseInput(params.Printer, cmd, args) @@ -121,6 +138,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return cmd } +// Deprecated: Will be removed after 2027-02-28. func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(flavorsFlag, false, "Lists supported flavors") cmd.Flags().Bool(versionsFlag, false, "Lists supported versions") @@ -132,6 +150,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(instanceIdFlag, "", `The instance ID to show user roles, database collations and database compatibilities for. Only relevant when "--user-roles", "--db-collations" or "--db-compatibilities" is passed`) } +// Deprecated: Will be removed after 2027-02-28. func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) @@ -181,6 +200,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, return &model, nil } +// Deprecated: Will be removed after 2027-02-28. func buildAndExecuteRequest(ctx context.Context, p *print.Printer, model *inputModel, apiClient sqlserverflex.DefaultAPI) error { var flavors []sqlserverflex.ListFlavors var versions *sqlserverflex.ListVersionsResponse @@ -230,6 +250,7 @@ func buildAndExecuteRequest(ctx context.Context, p *print.Printer, model *inputM return outputResult(p, model, flavors, versions, storages, userRoles, dbCollations, dbCompatibilities) } +// Deprecated: Will be removed after 2027-02-28. func outputResult(p *print.Printer, model *inputModel, flavors []sqlserverflex.ListFlavors, versions *sqlserverflex.ListVersionsResponse, storages *sqlserverflex.ListStoragesResponse, userRoles *sqlserverflex.ListRolesResponse, dbCollations *sqlserverflex.ListCollationsResponse, dbCompatibilities *sqlserverflex.ListCompatibilityResponse) error { options := &options{} if flavors != nil { @@ -294,6 +315,7 @@ func outputResult(p *print.Printer, model *inputModel, flavors []sqlserverflex.L }) } +// Deprecated: Will be removed after 2027-02-28. func buildFlavorsTable(flavors []sqlserverflex.ListFlavors) tables.Table { table := tables.NewTable() table.SetTitle("Flavors") @@ -305,6 +327,7 @@ func buildFlavorsTable(flavors []sqlserverflex.ListFlavors) tables.Table { return table } +// Deprecated: Will be removed after 2027-02-28. func buildVersionsTable(versions []sqlserverflex.Version) tables.Table { table := tables.NewTable() table.SetTitle("Versions") @@ -316,6 +339,7 @@ func buildVersionsTable(versions []sqlserverflex.Version) tables.Table { return table } +// Deprecated: Will be removed after 2027-02-28. func buildStoragesTable(storagesResp sqlserverflex.ListStoragesResponse) tables.Table { storages := storagesResp.StorageClasses table := tables.NewTable() @@ -329,6 +353,7 @@ func buildStoragesTable(storagesResp sqlserverflex.ListStoragesResponse) tables. return table } +// Deprecated: Will be removed after 2027-02-28. func buildUserRoles(roles *instanceUserRoles) tables.Table { table := tables.NewTable() table.SetTitle("User Roles") @@ -339,6 +364,7 @@ func buildUserRoles(roles *instanceUserRoles) tables.Table { return table } +// Deprecated: Will be removed after 2027-02-28. func buildDBCollationsTable(dbCollations []sqlserverflex.DatabaseGetcollation) tables.Table { table := tables.NewTable() table.SetTitle("DB Collations") @@ -352,6 +378,7 @@ func buildDBCollationsTable(dbCollations []sqlserverflex.DatabaseGetcollation) t return table } +// Deprecated: Will be removed after 2027-02-28. func buildDBCompatibilitiesTable(dbCompatibilities []sqlserverflex.DatabaseGetcompatibility) tables.Table { table := tables.NewTable() table.SetTitle("DB Compatibilities") diff --git a/internal/cmd/beta/sqlserverflex/sqlserverflex.go b/internal/cmd/beta/sqlserverflex/sqlserverflex.go index 29404a045..8b329a33d 100644 --- a/internal/cmd/beta/sqlserverflex/sqlserverflex.go +++ b/internal/cmd/beta/sqlserverflex/sqlserverflex.go @@ -2,9 +2,11 @@ package sqlserverflex import ( "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/database" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/flavor" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/instance" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/options" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/version" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -27,6 +29,8 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(database.NewCmd(params)) cmd.AddCommand(instance.NewCmd(params)) - cmd.AddCommand(options.NewCmd(params)) + cmd.AddCommand(options.NewCmd(params)) //nolint:staticcheck // Command is deprecated but must be kept for backward compatibility cmd.AddCommand(user.NewCmd(params)) + cmd.AddCommand(version.NewCmd(params)) + cmd.AddCommand(flavor.NewCmd(params)) } diff --git a/internal/cmd/beta/sqlserverflex/user/create/create.go b/internal/cmd/beta/sqlserverflex/user/create/create.go index cb425d4ee..4a9800c81 100644 --- a/internal/cmd/beta/sqlserverflex/user/create/create.go +++ b/internal/cmd/beta/sqlserverflex/user/create/create.go @@ -47,7 +47,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { " $ stackit beta sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID", "Please refer to https://docs.stackit.cloud/products/databases/sqlserver-flex/how-tos/create-logins-and-users-in-sqlserver-flex-instances/ for additional information.", "The allowed user roles for your instance can be obtained by running:", - " $ stackit beta sqlserverflex options --user-roles --instance-id INSTANCE_ID", + " $ stackit beta sqlserverflex instance user-role list --instance-id INSTANCE_ID", ), Example: examples.Build( examples.NewExample( diff --git a/internal/cmd/beta/sqlserverflex/version/list/list.go b/internal/cmd/beta/sqlserverflex/version/list/list.go new file mode 100644 index 000000000..7213332ff --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/version/list/list.go @@ -0,0 +1,98 @@ +package list + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/sqlserverflex/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists SQLServer Flex versions", + Long: "Lists SQLServer Flex versions.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SQLServer Flex version options`, + "$ stackit beta sqlserverflex version list"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + versions, err := buildRequest(ctx, model, apiClient.DefaultAPI).Execute() + if err != nil { + return fmt.Errorf("get SQLServer Flex versions: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, versions.Versions) + }, + } + + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient sqlserverflex.DefaultAPI) sqlserverflex.ApiListVersionsRequest { + return apiClient.ListVersions(ctx, model.ProjectId, model.Region) +} + +func outputResult(p *print.Printer, outputFormat string, versions []sqlserverflex.Version) error { + return p.OutputResult(outputFormat, versions, func() error { + if len(versions) == 0 { + p.Outputf("No SQLServer Flex versions found.") + return nil + } + + table := tables.NewTable() + table.SetTitle("Versions") + table.SetHeader("VERSION", "RECOMMEND", "BETA", "DEPRECATED") + + for _, v := range versions { + table.AddRow(v.Version, v.Recommend, v.Beta, v.Deprecated) + table.AddSeparator() + } + + return table.Display(p) + }) +} diff --git a/internal/cmd/beta/sqlserverflex/version/list/list_test.go b/internal/cmd/beta/sqlserverflex/version/list/list_test.go new file mode 100644 index 000000000..04a8a87b3 --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/version/list/list_test.go @@ -0,0 +1,165 @@ +package list + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &sqlserverflex.APIClient{DefaultAPI: &sqlserverflex.DefaultAPIService{}} +var testProjectId = uuid.NewString() + +const ( + testRegion = "eu01" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *sqlserverflex.ApiListVersionsRequest)) sqlserverflex.ApiListVersionsRequest { + request := testClient.DefaultAPI.ListVersions(testCtx, testProjectId, testRegion) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no flag values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest sqlserverflex.ApiListVersionsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient.DefaultAPI) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx, sqlserverflex.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func Test_outputResult(t *testing.T) { + type args struct { + outputFormat string + versions []sqlserverflex.Version + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "versions slice is nil", + args: args{ + versions: nil, + }, + wantErr: false, + }, + { + name: "versions slice is empty", + args: args{ + versions: []sqlserverflex.Version{}, + }, + wantErr: false, + }, + { + name: "empty version in versions slice", + args: args{ + versions: []sqlserverflex.Version{{}}, + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.versions); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/beta/sqlserverflex/version/version.go b/internal/cmd/beta/sqlserverflex/version/version.go new file mode 100644 index 000000000..6add582ec --- /dev/null +++ b/internal/cmd/beta/sqlserverflex/version/version.go @@ -0,0 +1,26 @@ +package version + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/version/list" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Short: "Provides functionality for SQLServer Flex versions", + Long: "Provides functionality for SQLServer Flex versions.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(list.NewCmd(params)) +}