mygithub.libinneed.workers.dev/stackitcloud/stackit-cli
Advanced tools
| ## stackit beta server backup disable | ||
| Disables Server Backup service | ||
| ### Synopsis | ||
| Disables Server Backup service. | ||
| ``` | ||
| stackit beta server backup disable [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Disable Server Backup functionality for your server. | ||
| $ stackit beta server backup disable --server-id=zzz | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server backup disable" | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup](./stackit_beta_server_backup.md) - Provides functionality for Server Backup | ||
| ## stackit beta server backup enable | ||
| Enables Server Backup service | ||
| ### Synopsis | ||
| Enables Server Backup service. | ||
| ``` | ||
| stackit beta server backup enable [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Enable Server Backup functionality for your server | ||
| $ stackit beta server backup enable --server-id=zzz | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server backup enable" | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup](./stackit_beta_server_backup.md) - Provides functionality for Server Backup | ||
| ## stackit beta server backup schedule create | ||
| Creates a Server Backup Schedule | ||
| ### Synopsis | ||
| Creates a Server Backup Schedule. | ||
| ``` | ||
| stackit beta server backup schedule create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a Server Backup Schedule with name "myschedule" and backup name "mybackup" | ||
| $ stackit beta server backup schedule create --server-id xxx --backup-name=mybackup --backup-schedule-name=myschedule | ||
| Create a Server Backup Schedule with name "myschedule", backup name "mybackup" and retention period of 5 days | ||
| $ stackit beta server backup schedule create --server-id xxx --backup-name=mybackup --backup-schedule-name=myschedule --backup-retention-period=5 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -b, --backup-name string Backup name | ||
| -d, --backup-retention-period int Backup retention period (in days) (default 14) | ||
| -n, --backup-schedule-name string Backup schedule name | ||
| -i, --backup-volume-ids string Backup volume ids, as comma separated UUID values. | ||
| -e, --enabled Is the server backup schedule enabled (default true) | ||
| -h, --help Help for "stackit beta server backup schedule create" | ||
| -r, --rrule string Backup RRULE (recurrence rule) (default "DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1") | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup schedule](./stackit_beta_server_backup_schedule.md) - Provides functionality for Server Backup Schedule | ||
| ## stackit beta server backup schedule delete | ||
| Deletes a Server Backup Schedule | ||
| ### Synopsis | ||
| Deletes a Server Backup Schedule. | ||
| ``` | ||
| stackit beta server backup schedule delete SCHEDULE_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete a Server Backup Schedule with ID "xxx" for server "zzz" | ||
| $ stackit beta server backup schedule delete xxx --server-id=zzz | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server backup schedule delete" | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup schedule](./stackit_beta_server_backup_schedule.md) - Provides functionality for Server Backup Schedule | ||
| ## stackit beta server backup schedule describe | ||
| Shows details of a Server Backup Schedule | ||
| ### Synopsis | ||
| Shows details of a Server Backup Schedule. | ||
| ``` | ||
| stackit beta server backup schedule describe BACKUP_SCHEDULE_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Get details of a Server Backup Schedule with id "my-schedule-id" | ||
| $ stackit beta server backup schedule describe my-schedule-id | ||
| Get details of a Server Backup Schedule with id "my-schedule-id" in JSON format | ||
| $ stackit beta server backup schedule describe my-schedule-id --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server backup schedule describe" | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup schedule](./stackit_beta_server_backup_schedule.md) - Provides functionality for Server Backup Schedule | ||
| ## stackit beta server backup schedule list | ||
| Lists all server backup schedules | ||
| ### Synopsis | ||
| Lists all server backup schedules. | ||
| ``` | ||
| stackit beta server backup schedule list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all backup schedules for a server with ID "xxx" | ||
| $ stackit beta server backup schedule list --server-id xxx | ||
| List all backup schedules for a server with ID "xxx" in JSON format | ||
| $ stackit beta server backup schedule list --server-id xxx --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server backup schedule list" | ||
| --limit int Maximum number of entries to list | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup schedule](./stackit_beta_server_backup_schedule.md) - Provides functionality for Server Backup Schedule | ||
| ## stackit beta server backup schedule update | ||
| Updates a Server Backup Schedule | ||
| ### Synopsis | ||
| Updates a Server Backup Schedule. | ||
| ``` | ||
| stackit beta server backup schedule update SCHEDULE_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Update the retention period of the backup schedule "zzz" of server "xxx" | ||
| $ stackit beta server backup schedule update zzz --server-id=xxx --backup-retention-period=20 | ||
| Update the backup name of the backup schedule "zzz" of server "xxx" | ||
| $ stackit beta server backup schedule update zzz --server-id=xxx --backup-name=newname | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -b, --backup-name string Backup name | ||
| -d, --backup-retention-period int Backup retention period (in days) (default 14) | ||
| -n, --backup-schedule-name string Backup schedule name | ||
| -i, --backup-volume-ids string Backup volume ids, as comma separated UUID values. | ||
| -e, --enabled Is the server backup schedule enabled (default true) | ||
| -h, --help Help for "stackit beta server backup schedule update" | ||
| -r, --rrule string Backup RRULE (recurrence rule) (default "DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1") | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup schedule](./stackit_beta_server_backup_schedule.md) - Provides functionality for Server Backup Schedule | ||
| ## stackit beta server backup schedule | ||
| Provides functionality for Server Backup Schedule | ||
| ### Synopsis | ||
| Provides functionality for Server Backup Schedule. | ||
| ``` | ||
| stackit beta server backup schedule [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server backup schedule" | ||
| ``` | ||
| ### 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 | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server backup](./stackit_beta_server_backup.md) - Provides functionality for Server Backup | ||
| * [stackit beta server backup schedule create](./stackit_beta_server_backup_schedule_create.md) - Creates a Server Backup Schedule | ||
| * [stackit beta server backup schedule delete](./stackit_beta_server_backup_schedule_delete.md) - Deletes a Server Backup Schedule | ||
| * [stackit beta server backup schedule describe](./stackit_beta_server_backup_schedule_describe.md) - Shows details of a Server Backup Schedule | ||
| * [stackit beta server backup schedule list](./stackit_beta_server_backup_schedule_list.md) - Lists all server backup schedules | ||
| * [stackit beta server backup schedule update](./stackit_beta_server_backup_schedule_update.md) - Updates a Server Backup Schedule | ||
| ## stackit beta server backup | ||
| Provides functionality for Server Backup | ||
| ### Synopsis | ||
| Provides functionality for Server Backup. | ||
| ``` | ||
| stackit beta server backup [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server backup" | ||
| ``` | ||
| ### 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 | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server](./stackit_beta_server.md) - Provides functionality for Server | ||
| * [stackit beta server backup disable](./stackit_beta_server_backup_disable.md) - Disables Server Backup service | ||
| * [stackit beta server backup enable](./stackit_beta_server_backup_enable.md) - Enables Server Backup service | ||
| * [stackit beta server backup schedule](./stackit_beta_server_backup_schedule.md) - Provides functionality for Server Backup Schedule | ||
| ## stackit beta server | ||
| Provides functionality for Server | ||
| ### Synopsis | ||
| Provides functionality for Server. | ||
| ``` | ||
| stackit beta server [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server" | ||
| ``` | ||
| ### 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 | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands | ||
| * [stackit beta server backup](./stackit_beta_server_backup.md) - Provides functionality for Server Backup | ||
| ## stackit beta sqlserverflex database create | ||
| Creates a SQLServer Flex database | ||
| ### Synopsis | ||
| Creates a SQLServer Flex database. | ||
| This operation cannot be triggered asynchronously (the "--async" flag will have no effect). | ||
| ``` | ||
| stackit beta sqlserverflex database create DATABASE_NAME [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a SQLServer Flex database with name "my-database" on instance with ID "xxx" | ||
| $ stackit beta sqlserverflex database create my-database --instance-id xxx --owner some-username | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex database create" | ||
| --instance-id string SQLServer Flex instance ID | ||
| --owner string Username of the owner user | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex database](./stackit_beta_sqlserverflex_database.md) - Provides functionality for SQLServer Flex databases | ||
| ## stackit beta sqlserverflex database delete | ||
| Deletes a SQLServer Flex database | ||
| ### Synopsis | ||
| Deletes a SQLServer Flex database. | ||
| This operation cannot be triggered asynchronously (the "--async" flag will have no effect). | ||
| ``` | ||
| stackit beta sqlserverflex database delete DATABASE_NAME [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete a SQLServer Flex database with name "my-database" of instance with ID "xxx" | ||
| $ stackit beta sqlserverflex database delete my-database --instance-id xxx | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex database delete" | ||
| --instance-id string SQLServer Flex instance ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex database](./stackit_beta_sqlserverflex_database.md) - Provides functionality for SQLServer Flex databases | ||
| ## stackit beta sqlserverflex database | ||
| Provides functionality for SQLServer Flex databases | ||
| ### Synopsis | ||
| Provides functionality for SQLServer Flex databases. | ||
| ``` | ||
| stackit beta sqlserverflex database [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex database" | ||
| ``` | ||
| ### 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 | ||
| --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 database create](./stackit_beta_sqlserverflex_database_create.md) - Creates a SQLServer Flex database | ||
| * [stackit beta sqlserverflex database delete](./stackit_beta_sqlserverflex_database_delete.md) - Deletes a SQLServer Flex database | ||
| ## stackit beta sqlserverflex user create | ||
| Creates a SQLServer Flex user | ||
| ### Synopsis | ||
| Creates a SQLServer Flex user for an instance. | ||
| The password is only visible upon creation and cannot be retrieved later. | ||
| Alternatively, you can reset the password and access the new one by running: | ||
| $ stackit beta sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID | ||
| Please refer to https://docs.stackit.cloud/stackit/en/creating-logins-and-users-in-sqlserver-flex-instances-210862358.html for additional information. | ||
| ``` | ||
| stackit beta sqlserverflex user create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a SQLServer Flex user for instance with ID "xxx" and specify the username, role and database | ||
| $ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles my-role --database my-database | ||
| Create a SQLServer Flex user for instance with ID "xxx", specifying multiple roles | ||
| $ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles "my-role-1,my-role-2" | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex user create" | ||
| --instance-id string ID of the instance | ||
| --roles strings Roles of the user | ||
| --username string Username of the user | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users | ||
| ## stackit beta sqlserverflex user delete | ||
| Deletes a SQLServer Flex user | ||
| ### Synopsis | ||
| Deletes a SQLServer Flex user by ID. You can get the IDs of users for an instance by running: | ||
| $ stackit beta sqlserverflex user list --instance-id <INSTANCE_ID> | ||
| ``` | ||
| stackit beta sqlserverflex user delete USER_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete a SQLServer Flex user with ID "xxx" for instance with ID "yyy" | ||
| $ stackit beta sqlserverflex user delete xxx --instance-id yyy | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex user delete" | ||
| --instance-id string Instance ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users | ||
| ## stackit beta sqlserverflex user describe | ||
| Shows details of a SQLServer Flex user | ||
| ### Synopsis | ||
| Shows details of a SQLServer Flex user. | ||
| The user password is only visible upon creation. You can reset it by running: | ||
| $ stackit beta sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID | ||
| ``` | ||
| stackit beta sqlserverflex user describe USER_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Get details of a SQLServer Flex user with ID "xxx" of instance with ID "yyy" | ||
| $ stackit beta sqlserverflex user describe xxx --instance-id yyy | ||
| Get details of a SQLServer Flex user with ID "xxx" of instance with ID "yyy" in JSON format | ||
| $ stackit beta sqlserverflex user describe xxx --instance-id yyy --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex user describe" | ||
| --instance-id string ID of the instance | ||
| ``` | ||
| ### 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 | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users | ||
| ## stackit beta sqlserverflex user list | ||
| Lists all SQLServer Flex users of an instance | ||
| ### Synopsis | ||
| Lists all SQLServer Flex users of an instance. | ||
| ``` | ||
| stackit beta sqlserverflex user list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all SQLServer Flex users of instance with ID "xxx" | ||
| $ stackit beta sqlserverflex user list --instance-id xxx | ||
| List all SQLServer Flex users of instance with ID "xxx" in JSON format | ||
| $ stackit beta sqlserverflex user list --instance-id xxx --output-format json | ||
| List up to 10 SQLServer Flex users of instance with ID "xxx" | ||
| $ stackit beta sqlserverflex user list --instance-id xxx --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex user 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 | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users | ||
| ## stackit beta sqlserverflex user reset-password | ||
| Resets the password of a SQLServer Flex user | ||
| ### Synopsis | ||
| Resets the password of a SQLServer Flex user. | ||
| The new password is visible after resetting and cannot be retrieved later. | ||
| ``` | ||
| stackit beta sqlserverflex user reset-password USER_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Reset the password of a SQLServer Flex user with ID "xxx" of instance with ID "yyy" | ||
| $ stackit beta sqlserverflex user reset-password xxx --instance-id yyy | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex user reset-password" | ||
| --instance-id string ID of the instance | ||
| ``` | ||
| ### 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 | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta sqlserverflex user](./stackit_beta_sqlserverflex_user.md) - Provides functionality for SQLServer Flex users | ||
| ## stackit beta sqlserverflex user | ||
| Provides functionality for SQLServer Flex users | ||
| ### Synopsis | ||
| Provides functionality for SQLServer Flex users. | ||
| ``` | ||
| stackit beta sqlserverflex user [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta sqlserverflex user" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --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 user create](./stackit_beta_sqlserverflex_user_create.md) - Creates a SQLServer Flex user | ||
| * [stackit beta sqlserverflex user delete](./stackit_beta_sqlserverflex_user_delete.md) - Deletes a SQLServer Flex user | ||
| * [stackit beta sqlserverflex user describe](./stackit_beta_sqlserverflex_user_describe.md) - Shows details of a SQLServer Flex user | ||
| * [stackit beta sqlserverflex user list](./stackit_beta_sqlserverflex_user_list.md) - Lists all SQLServer Flex users of an instance | ||
| * [stackit beta sqlserverflex user reset-password](./stackit_beta_sqlserverflex_user_reset-password.md) - Resets the password of a SQLServer Flex user | ||
| package backup | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/disable" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/enable" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/schedule" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "backup", | ||
| Short: "Provides functionality for Server Backup", | ||
| Long: "Provides functionality for Server Backup.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(enable.NewCmd(p)) | ||
| cmd.AddCommand(disable.NewCmd(p)) | ||
| cmd.AddCommand(schedule.NewCmd(p)) | ||
| } |
| package disable | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &serverbackup.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *serverbackup.ApiDisableServiceRequest)) serverbackup.ApiDisableServiceRequest { | ||
| request := testClient.DisableService(testCtx, testProjectId, testServerId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ServerId = "" | ||
| }), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := &cobra.Command{} | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| p := print.NewPrinter() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest serverbackup.ApiDisableServiceRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package disable | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/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/serverbackup/client" | ||
| serverbackupUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/serverbackup/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| const ( | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "disable", | ||
| Short: "Disables Server Backup service", | ||
| Long: "Disables Server Backup service.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Disable Server Backup functionality for your server.`, | ||
| "$ stackit beta server backup disable --server-id=zzz"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| canDisable, err := serverbackupUtils.CanDisableBackupService(ctx, apiClient, model.ProjectId, model.ServerId) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !canDisable { | ||
| p.Info("Cannot disable backup service for server %s - existing backups or existing backup schedules found\n", model.ServerId) | ||
| return nil | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to disable the backup service for server %s?", model.ServerId) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("disable server backup service: %w", err) | ||
| } | ||
| p.Info("Disabled Server Backup service for server %s\n", model.ServerId) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *serverbackup.APIClient) serverbackup.ApiDisableServiceRequest { | ||
| req := apiClient.DisableService(ctx, model.ProjectId, model.ServerId) | ||
| return req | ||
| } |
| package enable | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &serverbackup.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *serverbackup.ApiEnableServiceRequest)) serverbackup.ApiEnableServiceRequest { | ||
| request := testClient.EnableService(testCtx, testProjectId, testServerId).EnableServicePayload(serverbackup.EnableServicePayload{}) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ServerId = "" | ||
| }), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := &cobra.Command{} | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| p := print.NewPrinter() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest serverbackup.ApiEnableServiceRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package enable | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
| "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/serverbackup/client" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| const ( | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "enable", | ||
| Short: "Enables Server Backup service", | ||
| Long: "Enables Server Backup service.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Enable Server Backup functionality for your server`, | ||
| "$ stackit beta server backup enable --server-id=zzz"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to enable the Server Backup service for server %s?", model.ServerId) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| if !strings.Contains(err.Error(), "Tried to activate already active service") { | ||
| return fmt.Errorf("enable Server Backup: %w", err) | ||
| } | ||
| } | ||
| p.Info("Enabled backup service for server %s\n", model.ServerId) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *serverbackup.APIClient) serverbackup.ApiEnableServiceRequest { | ||
| payload := serverbackup.EnableServicePayload{} | ||
| req := apiClient.EnableService(ctx, model.ProjectId, model.ServerId).EnableServicePayload(payload) | ||
| return req | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &serverbackup.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| serverIdFlag: testServerId, | ||
| backupScheduleNameFlag: "example-backup-schedule-name", | ||
| enabledFlag: "true", | ||
| rruleFlag: defaultRrule, | ||
| backupNameFlag: "example-backup-name", | ||
| backupRetentionPeriodFlag: "14", | ||
| backupVolumeIdsFlag: defaultVolumeIds, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| BackupScheduleName: "example-backup-schedule-name", | ||
| Enabled: defaultEnabled, | ||
| Rrule: defaultRrule, | ||
| BackupName: "example-backup-name", | ||
| BackupRetentionPeriod: int64(14), | ||
| BackupVolumeIds: defaultVolumeIds, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *serverbackup.ApiCreateBackupScheduleRequest)) serverbackup.ApiCreateBackupScheduleRequest { | ||
| request := testClient.CreateBackupSchedule(testCtx, testProjectId, testServerId) | ||
| request = request.CreateBackupSchedulePayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *serverbackup.CreateBackupSchedulePayload)) serverbackup.CreateBackupSchedulePayload { | ||
| payload := serverbackup.CreateBackupSchedulePayload{ | ||
| Name: utils.Ptr("example-backup-schedule-name"), | ||
| Enabled: utils.Ptr(defaultEnabled), | ||
| Rrule: utils.Ptr("DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1"), | ||
| BackupProperties: &serverbackup.BackupProperties{ | ||
| Name: utils.Ptr("example-backup-name"), | ||
| RetentionPeriod: utils.Ptr(int64(14)), | ||
| VolumeIds: nil, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "with defaults", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, backupRetentionPeriodFlag) | ||
| }), | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest serverbackup.ApiCreateBackupScheduleRequest | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request, err := buildRequest(testCtx, tt.model, testClient) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error building request: %v", err) | ||
| } | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "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/serverbackup/client" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| const ( | ||
| backupScheduleNameFlag = "backup-schedule-name" | ||
| enabledFlag = "enabled" | ||
| rruleFlag = "rrule" | ||
| backupNameFlag = "backup-name" | ||
| backupVolumeIdsFlag = "backup-volume-ids" | ||
| backupRetentionPeriodFlag = "backup-retention-period" | ||
| serverIdFlag = "server-id" | ||
| defaultRrule = "DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1" | ||
| defaultRetentionPeriod = 14 | ||
| defaultEnabled = true | ||
| defaultVolumeIds = "" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| BackupScheduleName string | ||
| Enabled bool | ||
| Rrule string | ||
| BackupName string | ||
| BackupRetentionPeriod int64 | ||
| BackupVolumeIds string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a Server Backup Schedule", | ||
| Long: "Creates a Server Backup Schedule.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a Server Backup Schedule with name "myschedule" and backup name "mybackup"`, | ||
| `$ stackit beta server backup schedule create --server-id xxx --backup-name=mybackup --backup-schedule-name=myschedule`), | ||
| examples.NewExample( | ||
| `Create a Server Backup Schedule with name "myschedule", backup name "mybackup" and retention period of 5 days`, | ||
| `$ stackit beta server backup schedule create --server-id xxx --backup-name=mybackup --backup-schedule-name=myschedule --backup-retention-period=5`), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a Backup Schedule for server %s?", model.ServerId) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req, err := buildRequest(ctx, model, apiClient) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create Server Backup Schedule: %w", err) | ||
| } | ||
| return outputResult(p, model, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| cmd.Flags().StringP(backupScheduleNameFlag, "n", "", "Backup schedule name") | ||
| cmd.Flags().StringP(backupNameFlag, "b", "", "Backup name") | ||
| cmd.Flags().Int64P(backupRetentionPeriodFlag, "d", defaultRetentionPeriod, "Backup retention period (in days)") | ||
| cmd.Flags().BoolP(enabledFlag, "e", defaultEnabled, "Is the server backup schedule enabled") | ||
| cmd.Flags().StringP(rruleFlag, "r", defaultRrule, "Backup RRULE (recurrence rule)") | ||
| cmd.Flags().StringP(backupVolumeIdsFlag, "i", defaultVolumeIds, "Backup volume ids, as comma separated UUID values.") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag, backupScheduleNameFlag, backupNameFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &cliErr.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| BackupRetentionPeriod: flags.FlagWithDefaultToInt64Value(p, cmd, backupRetentionPeriodFlag), | ||
| BackupScheduleName: flags.FlagToStringValue(p, cmd, backupScheduleNameFlag), | ||
| BackupName: flags.FlagToStringValue(p, cmd, backupNameFlag), | ||
| Rrule: flags.FlagWithDefaultToStringValue(p, cmd, rruleFlag), | ||
| Enabled: flags.FlagToBoolValue(p, cmd, enabledFlag), | ||
| BackupVolumeIds: flags.FlagToStringValue(p, cmd, backupVolumeIdsFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *serverbackup.APIClient) (serverbackup.ApiCreateBackupScheduleRequest, error) { | ||
| req := apiClient.CreateBackupSchedule(ctx, model.ProjectId, model.ServerId) | ||
| backupProperties := serverbackup.BackupProperties{ | ||
| Name: &model.BackupName, | ||
| RetentionPeriod: &model.BackupRetentionPeriod, | ||
| } | ||
| if model.BackupVolumeIds == "" { | ||
| backupProperties.VolumeIds = nil | ||
| } else { | ||
| ids := strings.Split(model.BackupVolumeIds, ",") | ||
| backupProperties.VolumeIds = &ids | ||
| } | ||
| req = req.CreateBackupSchedulePayload(serverbackup.CreateBackupSchedulePayload{ | ||
| Enabled: &model.Enabled, | ||
| Name: &model.BackupScheduleName, | ||
| Rrule: &model.Rrule, | ||
| BackupProperties: &backupProperties, | ||
| }) | ||
| return req, nil | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, resp *serverbackup.BackupSchedule) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(resp, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server backup schedule: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server backup schedule: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created server backup schedule for server %s. Backup Schedule ID: %d\n", model.ServerId, *resp.Id) | ||
| return nil | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &serverbackup.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| var testBackupScheduleId = "5" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testBackupScheduleId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| serverIdFlag: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| ScheduleId: testBackupScheduleId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *serverbackup.ApiDeleteBackupScheduleRequest)) serverbackup.ApiDeleteBackupScheduleRequest { | ||
| request := testClient.DeleteBackupSchedule(testCtx, testProjectId, testServerId, testBackupScheduleId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest serverbackup.ApiDeleteBackupScheduleRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/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/serverbackup/client" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| const ( | ||
| scheduleIdArg = "SCHEDULE_ID" | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ScheduleId string | ||
| ServerId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("delete %s", scheduleIdArg), | ||
| Short: "Deletes a Server Backup Schedule", | ||
| Long: "Deletes a Server Backup Schedule.", | ||
| Args: args.SingleArg(scheduleIdArg, nil), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete a Server Backup Schedule with ID "xxx" for server "zzz"`, | ||
| "$ stackit beta server backup schedule delete xxx --server-id=zzz"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete server backup schedule %q? (This cannot be undone)", model.ScheduleId) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete Server Backup Schedule: %w", err) | ||
| } | ||
| p.Info("Deleted server backup schedule %q\n", model.ScheduleId) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| scheduleId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ScheduleId: scheduleId, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *serverbackup.APIClient) serverbackup.ApiDeleteBackupScheduleRequest { | ||
| req := apiClient.DeleteBackupSchedule(ctx, model.ProjectId, model.ServerId, model.ScheduleId) | ||
| return req | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &serverbackup.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| var testBackupScheduleId = "5" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testBackupScheduleId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| serverIdFlag: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| BackupScheduleId: testBackupScheduleId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *serverbackup.ApiGetBackupScheduleRequest)) serverbackup.ApiGetBackupScheduleRequest { | ||
| request := testClient.GetBackupSchedule(testCtx, testProjectId, testServerId, testBackupScheduleId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| isValid bool | ||
| expectedRequest serverbackup.ApiGetBackupScheduleRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "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/serverbackup/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| const ( | ||
| backupScheduleIdArg = "BACKUP_SCHEDULE_ID" | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| BackupScheduleId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("describe %s", backupScheduleIdArg), | ||
| Short: "Shows details of a Server Backup Schedule", | ||
| Long: "Shows details of a Server Backup Schedule.", | ||
| Args: args.SingleArg(backupScheduleIdArg, nil), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Get details of a Server Backup Schedule with id "my-schedule-id"`, | ||
| "$ stackit beta server backup schedule describe my-schedule-id"), | ||
| examples.NewExample( | ||
| `Get details of a Server Backup Schedule with id "my-schedule-id" in JSON format`, | ||
| "$ stackit beta server backup schedule describe my-schedule-id --output-format json"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("read server backup schedule: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| backupScheduleId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| BackupScheduleId: backupScheduleId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *serverbackup.APIClient) serverbackup.ApiGetBackupScheduleRequest { | ||
| req := apiClient.GetBackupSchedule(ctx, model.ProjectId, model.ServerId, model.BackupScheduleId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, schedule *serverbackup.BackupSchedule) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(schedule, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server backup schedule: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(schedule, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server backup schedule: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.AddRow("SCHEDULE ID", *schedule.Id) | ||
| table.AddSeparator() | ||
| table.AddRow("SCHEDULE NAME", *schedule.Name) | ||
| table.AddSeparator() | ||
| table.AddRow("ENABLED", *schedule.Enabled) | ||
| table.AddSeparator() | ||
| table.AddRow("RRULE", *schedule.Rrule) | ||
| table.AddSeparator() | ||
| if schedule.BackupProperties != nil { | ||
| table.AddRow("BACKUP NAME", *schedule.BackupProperties.Name) | ||
| table.AddSeparator() | ||
| table.AddRow("BACKUP RETENTION DAYS", *schedule.BackupProperties.RetentionPeriod) | ||
| table.AddSeparator() | ||
| ids := schedule.BackupProperties.VolumeIds | ||
| if ids == nil || len(*ids) == 0 { | ||
| table.AddRow("BACKUP VOLUME IDS", "") | ||
| } else { | ||
| table.AddRow("BACKUP VOLUME IDS", strings.Join(*ids, "\n")) | ||
| } | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &serverbackup.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| limitFlag: "10", | ||
| serverIdFlag: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Limit: utils.Ptr(int64(10)), | ||
| ServerId: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *serverbackup.ApiListBackupSchedulesRequest)) serverbackup.ApiListBackupSchedulesRequest { | ||
| request := testClient.ListBackupSchedules(testCtx, testProjectId, testServerId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "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) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest serverbackup.ApiListBackupSchedulesRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "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/serverbackup/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| Limit *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all server backup schedules", | ||
| Long: "Lists all server backup schedules.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all backup schedules for a server with ID "xxx"`, | ||
| "$ stackit beta server backup schedule list --server-id xxx"), | ||
| examples.NewExample( | ||
| `List all backup schedules for a server with ID "xxx" in JSON format`, | ||
| "$ stackit beta server backup schedule list --server-id xxx --output-format json"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list server backup schedules: %w", err) | ||
| } | ||
| schedules := *resp.Items | ||
| if len(schedules) == 0 { | ||
| p.Info("No backup schedules found for server %s\n", model.ServerId) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| if model.Limit != nil && len(schedules) > int(*model.Limit) { | ||
| schedules = schedules[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, schedules) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*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, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| Limit: limit, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *serverbackup.APIClient) serverbackup.ApiListBackupSchedulesRequest { | ||
| req := apiClient.ListBackupSchedules(ctx, model.ProjectId, model.ServerId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, schedules []serverbackup.BackupSchedule) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(schedules, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Server Backup Schedules list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(schedules, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Server Backup Schedules list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("SCHEDULE ID", "SCHEDULE NAME", "ENABLED", "RRULE", "BACKUP NAME", "BACKUP RETENTION DAYS", "BACKUP VOLUME IDS") | ||
| for i := range schedules { | ||
| s := schedules[i] | ||
| ids := "" | ||
| if s.BackupProperties.VolumeIds != nil && len(*s.BackupProperties.VolumeIds) != 0 { | ||
| ids = strings.Join(*s.BackupProperties.VolumeIds, ",") | ||
| } | ||
| table.AddRow(*s.Id, *s.Name, *s.Enabled, *s.Rrule, *s.BackupProperties.Name, *s.BackupProperties.RetentionPeriod, ids) | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package schedule | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/schedule/create" | ||
| del "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/schedule/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/schedule/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/schedule/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup/schedule/update" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "schedule", | ||
| Short: "Provides functionality for Server Backup Schedule", | ||
| Long: "Provides functionality for Server Backup Schedule.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(del.NewCmd(p)) | ||
| cmd.AddCommand(update.NewCmd(p)) | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "strconv" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &serverbackup.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| var testBackupScheduleId = "5" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testBackupScheduleId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| serverIdFlag: testServerId, | ||
| backupScheduleNameFlag: "example-backup-schedule-name", | ||
| enabledFlag: "true", | ||
| rruleFlag: defaultRrule, | ||
| backupNameFlag: "example-backup-name", | ||
| backupRetentionPeriodFlag: "14", | ||
| backupVolumeIdsFlag: defaultVolumeIds, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| BackupScheduleId: testBackupScheduleId, | ||
| ServerId: testServerId, | ||
| BackupScheduleName: utils.Ptr("example-backup-schedule-name"), | ||
| Enabled: utils.Ptr(defaultEnabled), | ||
| Rrule: utils.Ptr(defaultRrule), | ||
| BackupName: utils.Ptr("example-backup-name"), | ||
| BackupRetentionPeriod: utils.Ptr(int64(14)), | ||
| BackupVolumeIds: utils.Ptr(defaultVolumeIds), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureBackupSchedule(mods ...func(schedule *serverbackup.BackupSchedule)) *serverbackup.BackupSchedule { | ||
| id, _ := strconv.ParseInt(testBackupScheduleId, 10, 64) | ||
| schedule := &serverbackup.BackupSchedule{ | ||
| Name: utils.Ptr("example-backup-schedule-name"), | ||
| Id: utils.Ptr(id), | ||
| Enabled: utils.Ptr(defaultEnabled), | ||
| Rrule: utils.Ptr(defaultRrule), | ||
| BackupProperties: &serverbackup.BackupProperties{ | ||
| Name: utils.Ptr("example-backup-name"), | ||
| RetentionPeriod: utils.Ptr(int64(14)), | ||
| VolumeIds: nil, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(schedule) | ||
| } | ||
| return schedule | ||
| } | ||
| func fixturePayload(mods ...func(payload *serverbackup.UpdateBackupSchedulePayload)) serverbackup.UpdateBackupSchedulePayload { | ||
| payload := serverbackup.UpdateBackupSchedulePayload{ | ||
| Name: utils.Ptr("example-backup-schedule-name"), | ||
| Enabled: utils.Ptr(defaultEnabled), | ||
| Rrule: utils.Ptr("DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1"), | ||
| BackupProperties: &serverbackup.BackupProperties{ | ||
| Name: utils.Ptr("example-backup-name"), | ||
| RetentionPeriod: utils.Ptr(int64(14)), | ||
| VolumeIds: nil, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func fixtureRequest(mods ...func(request *serverbackup.ApiUpdateBackupScheduleRequest)) serverbackup.ApiUpdateBackupScheduleRequest { | ||
| request := testClient.UpdateBackupSchedule(testCtx, testProjectId, testServerId, testBackupScheduleId) | ||
| request = request.UpdateBackupSchedulePayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "backup schedule id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| err = cmd.ValidateFlagGroups() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flag groups: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest serverbackup.ApiUpdateBackupScheduleRequest | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| isValid: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request, err := buildRequest(testCtx, tt.model, testClient, *fixtureBackupSchedule()) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error building request: %v", err) | ||
| } | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "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/serverbackup/client" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| const ( | ||
| scheduleIdArg = "SCHEDULE_ID" | ||
| backupScheduleNameFlag = "backup-schedule-name" | ||
| enabledFlag = "enabled" | ||
| rruleFlag = "rrule" | ||
| backupNameFlag = "backup-name" | ||
| backupVolumeIdsFlag = "backup-volume-ids" | ||
| backupRetentionPeriodFlag = "backup-retention-period" | ||
| serverIdFlag = "server-id" | ||
| defaultRrule = "DTSTART;TZID=Europe/Sofia:20200803T023000 RRULE:FREQ=DAILY;INTERVAL=1" | ||
| defaultRetentionPeriod = 14 | ||
| defaultEnabled = true | ||
| defaultVolumeIds = "" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| BackupScheduleId string | ||
| BackupScheduleName *string | ||
| Enabled *bool | ||
| Rrule *string | ||
| BackupName *string | ||
| BackupRetentionPeriod *int64 | ||
| BackupVolumeIds *string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("update %s", scheduleIdArg), | ||
| Short: "Updates a Server Backup Schedule", | ||
| Long: "Updates a Server Backup Schedule.", | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Update the retention period of the backup schedule "zzz" of server "xxx"`, | ||
| "$ stackit beta server backup schedule update zzz --server-id=xxx --backup-retention-period=20"), | ||
| examples.NewExample( | ||
| `Update the backup name of the backup schedule "zzz" of server "xxx"`, | ||
| "$ stackit beta server backup schedule update zzz --server-id=xxx --backup-name=newname"), | ||
| ), | ||
| Args: args.SingleArg(scheduleIdArg, nil), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| currentBackupSchedule, err := apiClient.GetBackupScheduleExecute(ctx, model.ProjectId, model.ServerId, model.BackupScheduleId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get current server backup schedule: %v", err) | ||
| return err | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to update Server Backup Schedule %q?", model.BackupScheduleId) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req, err := buildRequest(ctx, model, apiClient, *currentBackupSchedule) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("update Server Backup Schedule: %w", err) | ||
| } | ||
| return outputResult(p, model, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| cmd.Flags().StringP(backupScheduleNameFlag, "n", "", "Backup schedule name") | ||
| cmd.Flags().StringP(backupNameFlag, "b", "", "Backup name") | ||
| cmd.Flags().Int64P(backupRetentionPeriodFlag, "d", defaultRetentionPeriod, "Backup retention period (in days)") | ||
| cmd.Flags().BoolP(enabledFlag, "e", defaultEnabled, "Is the server backup schedule enabled") | ||
| cmd.Flags().StringP(rruleFlag, "r", defaultRrule, "Backup RRULE (recurrence rule)") | ||
| cmd.Flags().StringP(backupVolumeIdsFlag, "i", defaultVolumeIds, "Backup volume ids, as comma separated UUID values.") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| scheduleId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &cliErr.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| BackupScheduleId: scheduleId, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| BackupRetentionPeriod: flags.FlagToInt64Pointer(p, cmd, backupRetentionPeriodFlag), | ||
| BackupScheduleName: flags.FlagToStringPointer(p, cmd, backupScheduleNameFlag), | ||
| BackupName: flags.FlagToStringPointer(p, cmd, backupNameFlag), | ||
| Rrule: flags.FlagToStringPointer(p, cmd, rruleFlag), | ||
| Enabled: flags.FlagToBoolPointer(p, cmd, enabledFlag), | ||
| BackupVolumeIds: flags.FlagToStringPointer(p, cmd, backupVolumeIdsFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *serverbackup.APIClient, old serverbackup.BackupSchedule) (serverbackup.ApiUpdateBackupScheduleRequest, error) { | ||
| req := apiClient.UpdateBackupSchedule(ctx, model.ProjectId, model.ServerId, model.BackupScheduleId) | ||
| if model.BackupName != nil { | ||
| old.BackupProperties.Name = model.BackupName | ||
| } | ||
| if model.BackupRetentionPeriod != nil { | ||
| old.BackupProperties.RetentionPeriod = model.BackupRetentionPeriod | ||
| } | ||
| if model.BackupVolumeIds != nil { | ||
| if *model.BackupVolumeIds == "" { | ||
| old.BackupProperties.VolumeIds = nil | ||
| } else { | ||
| ids := strings.Split(*model.BackupVolumeIds, ",") | ||
| old.BackupProperties.VolumeIds = &ids | ||
| } | ||
| } | ||
| if model.Enabled != nil { | ||
| old.Enabled = model.Enabled | ||
| } | ||
| if model.BackupScheduleName != nil { | ||
| old.Name = model.BackupScheduleName | ||
| } | ||
| if model.Rrule != nil { | ||
| old.Rrule = model.Rrule | ||
| } | ||
| req = req.UpdateBackupSchedulePayload(serverbackup.UpdateBackupSchedulePayload{ | ||
| Enabled: old.Enabled, | ||
| Name: old.Name, | ||
| Rrule: old.Rrule, | ||
| BackupProperties: old.BackupProperties, | ||
| }) | ||
| return req, nil | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, resp *serverbackup.BackupSchedule) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(resp, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal update server backup schedule: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal update server backup schedule: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Info("Updated server backup schedule %d\n", *resp.Id) | ||
| return nil | ||
| } | ||
| } |
| package server | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "server", | ||
| Short: "Provides functionality for Server", | ||
| Long: "Provides functionality for Server.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(backup.NewCmd(p)) | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testDatabaseName = "my-database" | ||
| var testOwner = "owner" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testDatabaseName, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| ownerFlag: testOwner, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| DatabaseName: testDatabaseName, | ||
| InstanceId: testInstanceId, | ||
| Owner: testOwner, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiCreateDatabaseRequest)) sqlserverflex.ApiCreateDatabaseRequest { | ||
| request := testClient.CreateDatabase(testCtx, testProjectId, testInstanceId) | ||
| payload := sqlserverflex.CreateDatabasePayload{ | ||
| Name: &testDatabaseName, | ||
| Options: utils.Ptr(map[string]string{ | ||
| "owner": testOwner, | ||
| }), | ||
| } | ||
| request = request.CreateDatabasePayload(payload) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "owner missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, ownerFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "database name invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiCreateDatabaseRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "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/spinner" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| databaseNameArg = "DATABASE_NAME" | ||
| instanceIdFlag = "instance-id" | ||
| ownerFlag = "owner" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| DatabaseName string | ||
| InstanceId string | ||
| Owner string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("create %s", databaseNameArg), | ||
| Short: "Creates a SQLServer Flex database", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Creates a SQLServer Flex database.", | ||
| `This operation cannot be triggered asynchronously (the "--async" flag will have no effect).`, | ||
| ), | ||
| Args: args.SingleArg(databaseNameArg, nil), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a SQLServer Flex database with name "my-database" on instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex database create my-database --instance-id xxx --owner some-username"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create database %q? (This cannot be undone)", model.DatabaseName) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| s := spinner.New(p) | ||
| s.Start("Creating database") | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| s.StopWithError() | ||
| return fmt.Errorf("create SQLServer Flex database: %w", err) | ||
| } | ||
| s.Stop() | ||
| return outputResult(p, model, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "SQLServer Flex instance ID") | ||
| cmd.Flags().String(ownerFlag, "", "Username of the owner user") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag, ownerFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| databaseName := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| DatabaseName: databaseName, | ||
| InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), | ||
| Owner: flags.FlagToStringValue(p, cmd, ownerFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiCreateDatabaseRequest { | ||
| req := apiClient.CreateDatabase(ctx, model.ProjectId, model.InstanceId) | ||
| payload := sqlserverflex.CreateDatabasePayload{ | ||
| Name: &model.DatabaseName, | ||
| Options: utils.Ptr(map[string]string{ | ||
| "owner": model.Owner, | ||
| }), | ||
| } | ||
| req = req.CreateDatabasePayload(payload) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, resp *sqlserverflex.CreateDatabaseResponse) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(resp, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex database: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex database: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created database %q\n", model.DatabaseName) | ||
| return nil | ||
| } | ||
| } |
| package database | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/database/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/database/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "database", | ||
| Short: "Provides functionality for SQLServer Flex databases", | ||
| Long: "Provides functionality for SQLServer Flex databases.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(delete.NewCmd(p)) | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testDatabaseName = "my-database" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testDatabaseName, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| DatabaseName: testDatabaseName, | ||
| InstanceId: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiDeleteDatabaseRequest)) sqlserverflex.ApiDeleteDatabaseRequest { | ||
| request := testClient.DeleteDatabase(testCtx, testProjectId, testInstanceId, testDatabaseName) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "database name invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiDeleteDatabaseRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/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/spinner" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| databaseNameArg = "DATABASE_NAME" | ||
| instanceIdFlag = "instance-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| DatabaseName string | ||
| InstanceId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("delete %s", databaseNameArg), | ||
| Short: "Deletes a SQLServer Flex database", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Deletes a SQLServer Flex database.", | ||
| `This operation cannot be triggered asynchronously (the "--async" flag will have no effect).`, | ||
| ), | ||
| Args: args.SingleArg(databaseNameArg, nil), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete a SQLServer Flex database with name "my-database" of instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex database delete my-database --instance-id xxx"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete database %q? (This cannot be undone)", model.DatabaseName) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| s := spinner.New(p) | ||
| s.Start("Deleting database") | ||
| err = req.Execute() | ||
| if err != nil { | ||
| s.StopWithError() | ||
| return fmt.Errorf("delete SQLServer Flex database: %w", err) | ||
| } | ||
| s.Stop() | ||
| p.Info("Deleted database %q\n", model.DatabaseName) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "SQLServer Flex instance ID") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| databaseName := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| DatabaseName: databaseName, | ||
| InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiDeleteDatabaseRequest { | ||
| req := apiClient.DeleteDatabase(ctx, model.ProjectId, model.InstanceId, model.DatabaseName) | ||
| return req | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| usernameFlag: "johndoe", | ||
| rolesFlag: "read", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| Username: utils.Ptr("johndoe"), | ||
| Roles: utils.Ptr([]string{"read"}), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiCreateUserRequest)) sqlserverflex.ApiCreateUserRequest { | ||
| request := testClient.CreateUser(testCtx, testProjectId, testInstanceId) | ||
| request = request.CreateUserPayload(sqlserverflex.CreateUserPayload{ | ||
| Username: utils.Ptr("johndoe"), | ||
| Roles: utils.Ptr([]sqlserverflex.Role{"read"}), | ||
| }) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no username specified", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, usernameFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no roles specified", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, rolesFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.Roles = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| cmd := &cobra.Command{} | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| configureFlags(cmd) | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| p := print.NewPrinter() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiCreateUserRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| { | ||
| description: "no username specified", | ||
| model: fixtureInputModel(func(model *inputModel) { | ||
| model.Username = nil | ||
| }), | ||
| expectedRequest: fixtureRequest().CreateUserPayload(sqlserverflex.CreateUserPayload{ | ||
| Roles: utils.Ptr([]sqlserverflex.Role{"read"}), | ||
| }), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "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-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| instanceIdFlag = "instance-id" | ||
| usernameFlag = "username" | ||
| rolesFlag = "roles" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| Username *string | ||
| Roles *[]string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a SQLServer Flex user", | ||
| Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s", | ||
| "Creates a SQLServer Flex user for an instance.", | ||
| "The password is only visible upon creation and cannot be retrieved later.", | ||
| "Alternatively, you can reset the password and access the new one by running:", | ||
| " $ stackit beta sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID", | ||
| "Please refer to https://docs.stackit.cloud/stackit/en/creating-logins-and-users-in-sqlserver-flex-instances-210862358.html for additional information.", | ||
| ), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a SQLServer Flex user for instance with ID "xxx" and specify the username, role and database`, | ||
| "$ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles my-role --database my-database"), | ||
| examples.NewExample( | ||
| `Create a SQLServer Flex user for instance with ID "xxx", specifying multiple roles`, | ||
| `$ stackit beta sqlserverflex user create --instance-id xxx --username johndoe --roles "my-role-1,my-role-2"`), | ||
| ), | ||
| Args: args.NoArgs, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get instance name: %v", err) | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a user for instance %q?", instanceLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create SQLServer Flex user: %w", err) | ||
| } | ||
| user := resp.Item | ||
| return outputResult(p, model, instanceLabel, user) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") | ||
| cmd.Flags().String(usernameFlag, "", "Username of the user") | ||
| cmd.Flags().StringSlice(rolesFlag, []string{}, "Roles of the user") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag, usernameFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), | ||
| Username: flags.FlagToStringPointer(p, cmd, usernameFlag), | ||
| Roles: flags.FlagToStringSlicePointer(p, cmd, rolesFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiCreateUserRequest { | ||
| req := apiClient.CreateUser(ctx, model.ProjectId, model.InstanceId) | ||
| var roles []sqlserverflex.Role | ||
| if model.Roles != nil { | ||
| for _, r := range *model.Roles { | ||
| roles = append(roles, sqlserverflex.Role(r)) | ||
| } | ||
| } | ||
| req = req.CreateUserPayload(sqlserverflex.CreateUserPayload{ | ||
| Username: model.Username, | ||
| Roles: &roles, | ||
| }) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, instanceLabel string, user *sqlserverflex.User) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(user, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex user: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(user, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex user: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created user for instance %q. User ID: %s\n\n", instanceLabel, *user.Id) | ||
| p.Outputf("Username: %s\n", *user.Username) | ||
| p.Outputf("Password: %s\n", *user.Password) | ||
| if user.Roles != nil && len(*user.Roles) != 0 { | ||
| p.Outputf("Roles: %v\n", *user.Roles) | ||
| } | ||
| if user.Host != nil && *user.Host != "" { | ||
| p.Outputf("Host: %s\n", *user.Host) | ||
| } | ||
| if user.Port != nil { | ||
| p.Outputf("Port: %d\n", *user.Port) | ||
| } | ||
| if user.Uri != nil && *user.Uri != "" { | ||
| p.Outputf("URI: %s\n", *user.Uri) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testUserId = "my-user-id" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| UserId: testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiDeleteUserRequest)) sqlserverflex.ApiDeleteUserRequest { | ||
| request := testClient.DeleteUser(testCtx, testProjectId, testInstanceId, testUserId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiDeleteUserRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/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/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| userIdArg = "USER_ID" | ||
| instanceIdFlag = "instance-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| UserId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("delete %s", userIdArg), | ||
| Short: "Deletes a SQLServer Flex user", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Deletes a SQLServer Flex user by ID. You can get the IDs of users for an instance by running:", | ||
| " $ stackit beta sqlserverflex user list --instance-id <INSTANCE_ID>", | ||
| ), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete a SQLServer Flex user with ID "xxx" for instance with ID "yyy"`, | ||
| "$ stackit beta sqlserverflex user delete xxx --instance-id yyy"), | ||
| ), | ||
| Args: args.SingleArg(userIdArg, nil), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get instance name: %v", err) | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| userLabel, err := sqlserverflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get user name: %v", err) | ||
| userLabel = model.UserId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete user %q of instance %q? (This cannot be undone)", userLabel, instanceLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete SQLServer Flex user: %w", err) | ||
| } | ||
| p.Info("Deleted user %q of instance %q\n", userLabel, instanceLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| userId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), | ||
| UserId: userId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiDeleteUserRequest { | ||
| req := apiClient.DeleteUser(ctx, model.ProjectId, model.InstanceId, model.UserId) | ||
| return req | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testUserId = "my-user-id" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| UserId: testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiGetUserRequest)) sqlserverflex.ApiGetUserRequest { | ||
| request := testClient.GetUser(testCtx, testProjectId, testInstanceId, testUserId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiGetUserRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "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" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| userIdArg = "USER_ID" | ||
| instanceIdFlag = "instance-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| UserId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("describe %s", userIdArg), | ||
| Short: "Shows details of a SQLServer Flex user", | ||
| Long: fmt.Sprintf("%s\n%s\n%s", | ||
| "Shows details of a SQLServer Flex user.", | ||
| `The user password is only visible upon creation. You can reset it by running:`, | ||
| " $ stackit beta sqlserverflex user reset-password USER_ID --instance-id INSTANCE_ID", | ||
| ), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Get details of a SQLServer Flex user with ID "xxx" of instance with ID "yyy"`, | ||
| "$ stackit beta sqlserverflex user describe xxx --instance-id yyy"), | ||
| examples.NewExample( | ||
| `Get details of a SQLServer Flex user with ID "xxx" of instance with ID "yyy" in JSON format`, | ||
| "$ stackit beta sqlserverflex user describe xxx --instance-id yyy --output-format json"), | ||
| ), | ||
| Args: args.SingleArg(userIdArg, nil), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| 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: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, *resp.Item) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| userId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), | ||
| UserId: userId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiGetUserRequest { | ||
| req := apiClient.GetUser(ctx, model.ProjectId, model.InstanceId, model.UserId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, user sqlserverflex.InstanceResponseUser) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(user, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex user: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(user, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex user: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *user.Id) | ||
| table.AddSeparator() | ||
| table.AddRow("USERNAME", *user.Username) | ||
| if user.Roles != nil && len(*user.Roles) != 0 { | ||
| table.AddSeparator() | ||
| table.AddRow("ROLES", strings.Join(*user.Roles, "\n")) | ||
| } | ||
| if user.Database != nil && *user.Database != "" { | ||
| table.AddSeparator() | ||
| table.AddRow("DATABASE", *user.Database) | ||
| } | ||
| if user.Host != nil && *user.Host != "" { | ||
| table.AddSeparator() | ||
| table.AddRow("HOST", *user.Host) | ||
| } | ||
| if user.Port != nil { | ||
| table.AddSeparator() | ||
| table.AddRow("PORT", *user.Port) | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| 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, | ||
| 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.ApiListUsersRequest)) sqlserverflex.ApiListUsersRequest { | ||
| request := testClient.ListUsers(testCtx, testProjectId, testInstanceId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| 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) { | ||
| cmd := &cobra.Command{} | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| configureFlags(cmd) | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| p := print.NewPrinter() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiListUsersRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "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/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| instanceIdFlag = "instance-id" | ||
| limitFlag = "limit" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId *string | ||
| Limit *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all SQLServer Flex users of an instance", | ||
| Long: "Lists all SQLServer Flex users of an instance.", | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all SQLServer Flex users of instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex user list --instance-id xxx"), | ||
| examples.NewExample( | ||
| `List all SQLServer Flex users of instance with ID "xxx" in JSON format`, | ||
| "$ stackit beta sqlserverflex user list --instance-id xxx --output-format json"), | ||
| examples.NewExample( | ||
| `List up to 10 SQLServer Flex users of instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex user list --instance-id xxx --limit 10"), | ||
| ), | ||
| Args: args.NoArgs, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| 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 users: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, *model.InstanceId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get instance name: %v", err) | ||
| instanceLabel = *model.InstanceId | ||
| } | ||
| p.Info("No users found for instance %q\n", instanceLabel) | ||
| return nil | ||
| } | ||
| users := *resp.Items | ||
| // Truncate output | ||
| if model.Limit != nil && len(users) > int(*model.Limit) { | ||
| users = users[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, users) | ||
| }, | ||
| } | ||
| 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) (*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: flags.FlagToInt64Pointer(p, cmd, limitFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiListUsersRequest { | ||
| req := apiClient.ListUsers(ctx, model.ProjectId, *model.InstanceId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, users []sqlserverflex.InstanceListUser) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(users, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex user list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(users, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex user list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "USERNAME") | ||
| for i := range users { | ||
| user := users[i] | ||
| table.AddRow(*user.Id, *user.Username) | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package resetpassword | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &sqlserverflex.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testInstanceId = uuid.NewString() | ||
| var testUserId = "my-user-id" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| instanceIdFlag: testInstanceId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| InstanceId: testInstanceId, | ||
| UserId: testUserId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *sqlserverflex.ApiResetUserRequest)) sqlserverflex.ApiResetUserRequest { | ||
| request := testClient.ResetUser(testCtx, testProjectId, testInstanceId, testUserId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| 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, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[instanceIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest sqlserverflex.ApiResetUserRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package resetpassword | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "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/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
| ) | ||
| const ( | ||
| userIdArg = "USER_ID" | ||
| instanceIdFlag = "instance-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| InstanceId string | ||
| UserId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("reset-password %s", userIdArg), | ||
| Short: "Resets the password of a SQLServer Flex user", | ||
| Long: fmt.Sprintf("%s\n%s", | ||
| "Resets the password of a SQLServer Flex user.", | ||
| "The new password is visible after resetting and cannot be retrieved later.", | ||
| ), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Reset the password of a SQLServer Flex user with ID "xxx" of instance with ID "yyy"`, | ||
| "$ stackit beta sqlserverflex user reset-password xxx --instance-id yyy"), | ||
| ), | ||
| Args: args.SingleArg(userIdArg, nil), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| instanceLabel, err := sqlserverflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get instance name: %v", err) | ||
| instanceLabel = model.InstanceId | ||
| } | ||
| userLabel, err := sqlserverflexUtils.GetUserName(ctx, apiClient, model.ProjectId, model.InstanceId, model.UserId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get user name: %v", err) | ||
| userLabel = model.UserId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to reset the password of user %q of instance %q? (This cannot be undone)", userLabel, instanceLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| user, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("reset SQLServer Flex user password: %w", err) | ||
| } | ||
| return outputResult(p, model, userLabel, instanceLabel, user.Item) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "ID of the instance") | ||
| err := flags.MarkFlagsRequired(cmd, instanceIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| userId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| InstanceId: flags.FlagToStringValue(p, cmd, instanceIdFlag), | ||
| UserId: userId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *sqlserverflex.APIClient) sqlserverflex.ApiResetUserRequest { | ||
| req := apiClient.ResetUser(ctx, model.ProjectId, model.InstanceId, model.UserId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, userLabel, instanceLabel string, user *sqlserverflex.User) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(user, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex reset password: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(user, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal SQLServer Flex reset password: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Reset password for user %q of instance %q\n\n", userLabel, instanceLabel) | ||
| p.Outputf("Username: %s\n", *user.Username) | ||
| p.Outputf("New password: %s\n", *user.Password) | ||
| if user.Uri != nil && *user.Uri != "" { | ||
| p.Outputf("New URI: %s\n", *user.Uri) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package user | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user/list" | ||
| resetpassword "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/user/reset-password" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "user", | ||
| Short: "Provides functionality for SQLServer Flex users", | ||
| Long: "Provides functionality for SQLServer Flex users.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(delete.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| cmd.AddCommand(resetpassword.NewCmd(p)) | ||
| } |
| package client | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/auth" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/config" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/spf13/viper" | ||
| sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| func ConfigureClient(p *print.Printer) (*serverbackup.APIClient, error) { | ||
| var err error | ||
| var apiClient *serverbackup.APIClient | ||
| var cfgOptions []sdkConfig.ConfigurationOption | ||
| authCfgOption, err := auth.AuthenticationConfig(p, auth.AuthorizeUser) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "configure authentication: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| cfgOptions = append(cfgOptions, authCfgOption) | ||
| customEndpoint := viper.GetString(config.ServerBackupCustomEndpointKey) | ||
| if customEndpoint != "" { | ||
| cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) | ||
| } else { | ||
| cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01")) | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| cfgOptions = append(cfgOptions, | ||
| sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), | ||
| ) | ||
| } | ||
| apiClient, err = serverbackup.NewAPIClient(cfgOptions...) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "create new API client: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| return apiClient, nil | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| var ( | ||
| testProjectId = uuid.NewString() | ||
| testServerId = uuid.NewString() | ||
| ) | ||
| type serverbackupClientMocked struct { | ||
| listBackupSchedulesFails bool | ||
| listBackupSchedulesResp *serverbackup.ListBackupSchedules200Response | ||
| listBackupsFails bool | ||
| listBackupsResp *serverbackup.ListBackups200Response | ||
| } | ||
| func (m *serverbackupClientMocked) ListBackupSchedulesExecute(_ context.Context, _, _ string) (*serverbackup.ListBackupSchedules200Response, error) { | ||
| if m.listBackupSchedulesFails { | ||
| return nil, fmt.Errorf("could not list backup schedules") | ||
| } | ||
| return m.listBackupSchedulesResp, nil | ||
| } | ||
| func (m *serverbackupClientMocked) ListBackupsExecute(_ context.Context, _, _ string) (*serverbackup.ListBackups200Response, error) { | ||
| if m.listBackupsFails { | ||
| return nil, fmt.Errorf("could not list backups") | ||
| } | ||
| return m.listBackupsResp, nil | ||
| } | ||
| func TestCanDisableBackupService(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| listBackupsFails bool | ||
| listBackupSchedulesFails bool | ||
| listBackups *serverbackup.ListBackups200Response | ||
| listBackupSchedules *serverbackup.ListBackupSchedules200Response | ||
| isValid bool // isValid ==> err == nil | ||
| expectedOutput bool // expectedCanDisable | ||
| }{ | ||
| { | ||
| description: "base-ok-can-disable-backups-service-no-backups-no-backup-schedules", | ||
| listBackupsFails: false, | ||
| listBackupSchedulesFails: false, | ||
| listBackups: &serverbackup.ListBackups200Response{Items: &[]serverbackup.Backup{}}, | ||
| listBackupSchedules: &serverbackup.ListBackupSchedules200Response{Items: &[]serverbackup.BackupSchedule{}}, | ||
| isValid: true, | ||
| expectedOutput: true, | ||
| }, | ||
| { | ||
| description: "not-ok-api-error-list-backups", | ||
| listBackupsFails: true, | ||
| listBackupSchedulesFails: false, | ||
| listBackups: &serverbackup.ListBackups200Response{Items: &[]serverbackup.Backup{}}, | ||
| listBackupSchedules: &serverbackup.ListBackupSchedules200Response{Items: &[]serverbackup.BackupSchedule{}}, | ||
| isValid: false, | ||
| expectedOutput: false, | ||
| }, | ||
| { | ||
| description: "not-ok-api-error-list-backup-schedules", | ||
| listBackupsFails: true, | ||
| listBackupSchedulesFails: false, | ||
| listBackups: &serverbackup.ListBackups200Response{Items: &[]serverbackup.Backup{}}, | ||
| listBackupSchedules: &serverbackup.ListBackupSchedules200Response{Items: &[]serverbackup.BackupSchedule{}}, | ||
| isValid: false, | ||
| expectedOutput: false, | ||
| }, | ||
| { | ||
| description: "not-ok-has-backups-cannot-disable", | ||
| listBackupsFails: false, | ||
| listBackupSchedulesFails: false, | ||
| listBackups: &serverbackup.ListBackups200Response{ | ||
| Items: &[]serverbackup.Backup{ | ||
| { | ||
| CreatedAt: utils.Ptr("test timestamp"), | ||
| ExpireAt: utils.Ptr("test timestamp"), | ||
| Id: utils.Ptr("5"), | ||
| LastRestoredAt: utils.Ptr("test timestamp"), | ||
| Name: utils.Ptr("test name"), | ||
| Size: utils.Ptr(int64(5)), | ||
| Status: utils.Ptr("test status"), | ||
| VolumeBackups: nil, | ||
| }, | ||
| }, | ||
| }, | ||
| listBackupSchedules: &serverbackup.ListBackupSchedules200Response{Items: &[]serverbackup.BackupSchedule{}}, | ||
| isValid: true, | ||
| expectedOutput: false, | ||
| }, | ||
| { | ||
| description: "not-ok-has-backups-schedules-cannot-disable", | ||
| listBackupsFails: false, | ||
| listBackupSchedulesFails: false, | ||
| listBackups: &serverbackup.ListBackups200Response{Items: &[]serverbackup.Backup{}}, | ||
| listBackupSchedules: &serverbackup.ListBackupSchedules200Response{ | ||
| Items: &[]serverbackup.BackupSchedule{ | ||
| { | ||
| BackupProperties: nil, | ||
| Enabled: utils.Ptr(false), | ||
| Id: utils.Ptr(int64(5)), | ||
| Name: utils.Ptr("some name"), | ||
| Rrule: utils.Ptr("some rrule"), | ||
| }, | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &serverbackupClientMocked{ | ||
| listBackupsFails: tt.listBackupsFails, | ||
| listBackupSchedulesFails: tt.listBackupSchedulesFails, | ||
| listBackupsResp: tt.listBackups, | ||
| listBackupSchedulesResp: tt.listBackupSchedules, | ||
| } | ||
| output, err := CanDisableBackupService(context.Background(), client, testProjectId, testServerId) | ||
| if tt.isValid && err != nil { | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Errorf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Errorf("expected output to be %t, got %t", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/serverbackup" | ||
| ) | ||
| type ServerBackupClient interface { | ||
| ListBackupSchedulesExecute(ctx context.Context, projectId, serverId string) (*serverbackup.ListBackupSchedules200Response, error) | ||
| ListBackupsExecute(ctx context.Context, projectId, serverId string) (*serverbackup.ListBackups200Response, error) | ||
| } | ||
| func CanDisableBackupService(ctx context.Context, client ServerBackupClient, projectId, serverId string) (bool, error) { | ||
| schedules, err := client.ListBackupSchedulesExecute(ctx, projectId, serverId) | ||
| if err != nil { | ||
| return false, fmt.Errorf("list backup schedules: %w", err) | ||
| } | ||
| if *schedules.Items != nil && len(*schedules.Items) > 0 { | ||
| return false, nil | ||
| } | ||
| backups, err := client.ListBackupsExecute(ctx, projectId, serverId) | ||
| if err != nil { | ||
| return false, fmt.Errorf("list backups: %w", err) | ||
| } | ||
| if *backups.Items != nil && len(*backups.Items) > 0 { | ||
| return false, nil | ||
| } | ||
| // no backups and no backup schedules found for this server => can disable backup service | ||
| return true, nil | ||
| } |
@@ -32,3 +32,3 @@ # STACKIT CLI release workflow. | ||
| fetch-depth: 0 | ||
| - uses: actions/setup-go@v4 | ||
| - uses: actions/setup-go@v5 | ||
| with: | ||
@@ -66,3 +66,3 @@ go-version-file: "go.mod" | ||
| - name: Run GoReleaser | ||
| uses: goreleaser/goreleaser-action@v5 | ||
| uses: goreleaser/goreleaser-action@v6 | ||
| with: | ||
@@ -69,0 +69,0 @@ args: release --clean |
@@ -16,5 +16,5 @@ name: Renovate | ||
| - name: Self-hosted Renovate | ||
| uses: renovatebot/github-action@v40.1.11 | ||
| uses: renovatebot/github-action@v40.1.12 | ||
| with: | ||
| configurationFile: .github/renovate.json | ||
| token: ${{ secrets.RENOVATE_TOKEN }} |
+2
-1
@@ -0,1 +1,3 @@ | ||
| version: 2 | ||
| before: | ||
@@ -146,3 +148,2 @@ hooks: | ||
| enabled: true | ||
| draft: true | ||
| base: | ||
@@ -149,0 +150,0 @@ owner: microsoft |
| ## stackit beta sqlserverflex instance create | ||
| Creates an SQLServer Flex instance | ||
| Creates a SQLServer Flex instance | ||
| ### Synopsis | ||
| Creates an SQLServer Flex instance. | ||
| Creates a SQLServer Flex instance. | ||
@@ -16,9 +16,9 @@ ``` | ||
| ``` | ||
| Create an SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values | ||
| Create a SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values | ||
| $ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4 | ||
| Create an SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values | ||
| Create a SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values | ||
| $ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx | ||
| Create an 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 | ||
| 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 --acl 1.2.3.0/24 | ||
@@ -25,0 +25,0 @@ ``` |
| ## stackit beta sqlserverflex instance delete | ||
| Deletes an SQLServer Flex instance | ||
| Deletes a SQLServer Flex instance | ||
| ### Synopsis | ||
| Deletes an SQLServer Flex instance. | ||
| Deletes a SQLServer Flex instance. | ||
@@ -16,3 +16,3 @@ ``` | ||
| ``` | ||
| Delete an SQLServer Flex instance with ID "xxx" | ||
| Delete a SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance delete xxx | ||
@@ -19,0 +19,0 @@ ``` |
| ## stackit beta sqlserverflex instance describe | ||
| Shows details of an SQLServer Flex instance | ||
| Shows details of a SQLServer Flex instance | ||
| ### Synopsis | ||
| Shows details of an SQLServer Flex instance. | ||
| Shows details of a SQLServer Flex instance. | ||
@@ -16,6 +16,6 @@ ``` | ||
| ``` | ||
| Get details of an SQLServer Flex instance with ID "xxx" | ||
| Get details of a SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance describe xxx | ||
| Get details of an SQLServer Flex instance with ID "xxx" in JSON format | ||
| Get details of a SQLServer Flex instance with ID "xxx" in JSON format | ||
| $ stackit beta sqlserverflex instance describe xxx --output-format json | ||
@@ -22,0 +22,0 @@ ``` |
| ## stackit beta sqlserverflex instance update | ||
| Updates an SQLServer Flex instance | ||
| Updates a SQLServer Flex instance | ||
| ### Synopsis | ||
| Updates an SQLServer Flex instance. | ||
| Updates a SQLServer Flex instance. | ||
@@ -16,6 +16,6 @@ ``` | ||
| ``` | ||
| Update the name of an SQLServer Flex instance with ID "xxx" | ||
| Update the name of a SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance update xxx --name my-new-name | ||
| Update the backup schedule of an SQLServer Flex instance with ID "xxx" | ||
| Update the backup schedule of a SQLServer Flex instance with ID "xxx" | ||
| $ stackit beta sqlserverflex instance update xxx --backup-schedule "30 0 * * *" | ||
@@ -22,0 +22,0 @@ ``` |
@@ -32,7 +32,7 @@ ## stackit beta sqlserverflex instance | ||
| * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex | ||
| * [stackit beta sqlserverflex instance create](./stackit_beta_sqlserverflex_instance_create.md) - Creates an SQLServer Flex instance | ||
| * [stackit beta sqlserverflex instance delete](./stackit_beta_sqlserverflex_instance_delete.md) - Deletes an SQLServer Flex instance | ||
| * [stackit beta sqlserverflex instance describe](./stackit_beta_sqlserverflex_instance_describe.md) - Shows details of an SQLServer Flex instance | ||
| * [stackit beta sqlserverflex instance create](./stackit_beta_sqlserverflex_instance_create.md) - Creates a SQLServer Flex instance | ||
| * [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 an SQLServer Flex instance | ||
| * [stackit beta sqlserverflex instance update](./stackit_beta_sqlserverflex_instance_update.md) - Updates a SQLServer Flex instance | ||
@@ -18,9 +18,12 @@ ## stackit beta sqlserverflex options | ||
| List SQL Server Flex flavors options | ||
| $ stackit sqlserverflex options --flavors | ||
| $ stackit beta sqlserverflex options --flavors | ||
| List SQL Server Flex available versions | ||
| $ stackit sqlserverflex options --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 sqlserverflex options --flavors" | ||
| $ stackit sqlserverflex options --storages --flavor-id <FLAVOR_ID> | ||
| 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 <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 <INSTANCE_ID> | ||
| ``` | ||
@@ -31,7 +34,11 @@ | ||
| ``` | ||
| --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" | ||
| --storages Lists supported storages for a given flavor | ||
| --versions Lists supported versions | ||
| --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 | ||
| ``` | ||
@@ -38,0 +45,0 @@ |
@@ -32,4 +32,6 @@ ## stackit beta sqlserverflex | ||
| * [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 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 | ||
@@ -43,3 +43,4 @@ ## stackit beta | ||
| * [stackit](./stackit.md) - Manage STACKIT resources using the command line | ||
| * [stackit beta server](./stackit_beta_server.md) - Provides functionality for Server | ||
| * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex | ||
@@ -47,2 +47,3 @@ ## stackit config set | ||
| --secrets-manager-custom-endpoint string Secrets Manager API base URL, used in calls to this API | ||
| --serverbackup-custom-endpoint string Server Backup API base URL, used in calls to this API | ||
| --service-account-custom-endpoint string Service Account API base URL, used in calls to this API | ||
@@ -49,0 +50,0 @@ --session-time-limit string Maximum time before authentication is required again. After this time, you will be prompted to login again to execute commands that require authentication. Can't be larger than 24h. Requires authentication after being set to take effect. Examples: 3h, 5h30m40s (BETA: currently values greater than 2h have no effect) |
@@ -47,2 +47,3 @@ ## stackit config unset | ||
| --secrets-manager-custom-endpoint Secrets Manager API base URL. If unset, uses the default base URL | ||
| --serverbackup-custom-endpoint Server Backup base URL. If unset, uses the default base URL | ||
| --service-account-custom-endpoint SKE API base URL. If unset, uses the default base URL | ||
@@ -49,0 +50,0 @@ --session-time-limit Maximum time before authentication is required again. If unset, defaults to 2h |
@@ -19,6 +19,6 @@ ## stackit mongodbflex user describe | ||
| Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" | ||
| $ stackit mongodbflex user list xxx --instance-id yyy | ||
| $ stackit mongodbflex user describe xxx --instance-id yyy | ||
| Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" in JSON format | ||
| $ stackit mongodbflex user list xxx --instance-id yyy --output-format json | ||
| $ stackit mongodbflex user describe xxx --instance-id yyy --output-format json | ||
| ``` | ||
@@ -25,0 +25,0 @@ |
@@ -19,6 +19,6 @@ ## stackit postgresflex user describe | ||
| Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" | ||
| $ stackit postgresflex user list xxx --instance-id yyy | ||
| $ stackit postgresflex user describe xxx --instance-id yyy | ||
| Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" in JSON format | ||
| $ stackit postgresflex user list xxx --instance-id yyy --output-format json | ||
| $ stackit postgresflex user describe xxx --instance-id yyy --output-format json | ||
| ``` | ||
@@ -25,0 +25,0 @@ |
+14
-13
@@ -15,3 +15,3 @@ module github.com/stackitcloud/stackit-cli | ||
| github.com/mattn/go-colorable v0.1.13 | ||
| github.com/spf13/cobra v1.8.0 | ||
| github.com/spf13/cobra v1.8.1 | ||
| github.com/spf13/pflag v1.0.5 | ||
@@ -24,13 +24,14 @@ github.com/spf13/viper v1.19.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.14.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.15.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.9.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/serverbackup v0.1.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/ske v0.16.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.2.0 | ||
| github.com/zalando/go-keyring v0.2.4 | ||
| golang.org/x/mod v0.17.0 | ||
| golang.org/x/oauth2 v0.20.0 | ||
| golang.org/x/term v0.20.0 | ||
| golang.org/x/text v0.15.0 | ||
| github.com/zalando/go-keyring v0.2.5 | ||
| golang.org/x/mod v0.18.0 | ||
| golang.org/x/oauth2 v0.21.0 | ||
| golang.org/x/term v0.21.0 | ||
| golang.org/x/text v0.16.0 | ||
| k8s.io/apimachinery v0.29.2 | ||
@@ -53,3 +54,3 @@ k8s.io/client-go v0.29.2 | ||
| github.com/alessio/shellescape v1.4.2 // indirect | ||
| github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect | ||
| github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | ||
| github.com/danieljoos/wincred v1.2.1 // indirect | ||
@@ -80,6 +81,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect | ||
| github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/logme v0.15.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.13.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/logme v0.16.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.15.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.9.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.10.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.15.0 | ||
@@ -90,3 +91,3 @@ github.com/stackitcloud/stackit-sdk-go/services/redis v0.15.0 | ||
| golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect | ||
| golang.org/x/sys v0.20.0 // indirect | ||
| golang.org/x/sys v0.21.0 // indirect | ||
| gopkg.in/ini.v1 v1.67.0 // indirect | ||
@@ -93,0 +94,0 @@ gopkg.in/yaml.v2 v2.4.0 // indirect |
+28
-26
| github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= | ||
| github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= | ||
| github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= | ||
| github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
| github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= | ||
| github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
| github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= | ||
@@ -120,4 +120,4 @@ github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= | ||
| github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= | ||
| github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= | ||
| github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= | ||
| github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= | ||
| github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= | ||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
@@ -135,6 +135,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0/go.mod h1:MdZcRbs19s2NLeJmSLSoqTzm9IPIQhE1ZEMpo9gePq0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0 h1:LAteZO46XmqTsmPw0QV8n8WiGM205pxrcqHqWznNmyY= | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.12.0/go.mod h1:wsO3+vXe1XiKLeCIctWAptaHQZ07Un7kmLTQ+drbj7w= | ||
| github.com/stackitcloud/stackit-sdk-go/services/logme v0.15.0 h1:7gii3PZshOesHPCYlPycilXglk28imITIqjewySZwZ4= | ||
| github.com/stackitcloud/stackit-sdk-go/services/logme v0.15.0/go.mod h1:bj9cn1treNSxKTRCEmESwqfENN8vCYn60HUnEA0P83c= | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.13.0 h1:W7tyIIIXgAilHpALRyrW3CrtQ2UAGZBjAG+P4tcK+QQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.13.0/go.mod h1:wsO3+vXe1XiKLeCIctWAptaHQZ07Un7kmLTQ+drbj7w= | ||
| github.com/stackitcloud/stackit-sdk-go/services/logme v0.16.0 h1:Cz4zQnEax6L3Y9gL7jtETPmiTERB7WNQtIzZ1UWeNNk= | ||
| github.com/stackitcloud/stackit-sdk-go/services/logme v0.16.0/go.mod h1:bj9cn1treNSxKTRCEmESwqfENN8vCYn60HUnEA0P83c= | ||
| github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.15.0 h1:eYYyVUTS9Gjovg3z9+r6ctvsm1p1J4fHLa5QJbWHi0A= | ||
@@ -144,8 +144,8 @@ github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.15.0/go.mod h1:kPetkX9hNm9HkRyiKQL/tlgdi8frZdMP8afg0mEvQ9s= | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0/go.mod h1:iFerEzGmkg6R13ldFUyHUWHm0ac9cS4ftTDLhP0k/dU= | ||
| github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.9.0 h1:rWgy4/eCIgyA2dUuc4a30pldmS6taQDwiLqoeZmyeP8= | ||
| github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.9.0/go.mod h1:dkVMJI88eJ3Xs0ZV15r4tUpgitUGJXcvrX3RL4Zq2bQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.10.0 h1:tn1MD1nu+gYEbT3lslRI6BrapKwuvHv5Wi2Zw9uVPPc= | ||
| github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.10.0/go.mod h1:dkVMJI88eJ3Xs0ZV15r4tUpgitUGJXcvrX3RL4Zq2bQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.14.0 h1:zkhm0r0OZ5NbHJFrm+7B+h11QL0bNLC53nzXhqCaLWo= | ||
| github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.14.0/go.mod h1:ZecMIf9oYj2DGZqWh93l97WdVaRdLl+tW5Fq3YKGwBM= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0 h1:PZAqXd8TVyTZo8qty4bM2sSoLlLG+Nc9tcpxbQhO+GY= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.14.0/go.mod h1:SdrqGLCkilL6wl1+jcxmLtks2IocgIg+bsyeyYUIzR4= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.15.0 h1:05wQYhO37Z4y8xAD+4OTYz6rYu6eJEmwMfCG4tjETEc= | ||
| github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.15.0/go.mod h1:SdrqGLCkilL6wl1+jcxmLtks2IocgIg+bsyeyYUIzR4= | ||
| github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.15.0 h1:Q7JxjVwb+9ugAX71AXdbfPL87HHmIIwb9LNahn6H/2o= | ||
@@ -155,6 +155,8 @@ github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.15.0/go.mod h1:eSgnPBknTJh7t+jVKN+xzeAh+Cg1USOlH3QCyfvG20g= | ||
| github.com/stackitcloud/stackit-sdk-go/services/redis v0.15.0/go.mod h1:3LhiTR/DMbKR2HuleTzlFHltR1MT1KD0DeW46X6K2GE= | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0 h1:7AIvLkB7JZ5lYKtYLwI0rgJ0185hwQC1PFiUrjcinDM= | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.8.0/go.mod h1:p16qz/pAW8b1gEhqMpIgJfutRPeDPqQLlbVGyCo3f8o= | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.9.0 h1:qCbvGqdG9saRB++UlhXt5ieCCDCITROqL5K2nm38efU= | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.9.0/go.mod h1:p16qz/pAW8b1gEhqMpIgJfutRPeDPqQLlbVGyCo3f8o= | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0 h1:pJBG455kmtbQFpCxcBfBK8wOuEnmsMv3h90LFcdj3q0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0/go.mod h1:LX0Mcyr7/QP77zf7e05fHCJO38RMuTxr7nEDUDZ3oPQ= | ||
| github.com/stackitcloud/stackit-sdk-go/services/serverbackup v0.1.0 h1:fYCBNvh4tqE+DXYDfbJEjC3n/I78zTZajdcPTPB/yig= | ||
| github.com/stackitcloud/stackit-sdk-go/services/serverbackup v0.1.0/go.mod h1:ZYI3wj/NnhhWi25ugbdcniwnY/7mF6zN582c5HPe00o= | ||
| github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0 h1:JB1O0E9+L50ZaO36uz7azurvUuB5JdX5s2ZXuIdb9t8= | ||
@@ -181,4 +183,4 @@ github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.4.0/go.mod h1:Ni9RBJvcaXRIrDIuQBpJcuQvCQSj27crQSyc+WM4p0c= | ||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
| github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= | ||
| github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= | ||
| github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= | ||
| github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= | ||
| go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= | ||
@@ -195,4 +197,4 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= | ||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
| golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= | ||
| golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||
| golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= | ||
| golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
@@ -204,4 +206,4 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
| golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= | ||
| golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= | ||
| golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||
| golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= | ||
| golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
@@ -215,10 +217,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
| golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= | ||
| golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
| golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= | ||
| golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= | ||
| golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= | ||
| golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
| golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= | ||
| golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= | ||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
| golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= | ||
| golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||
| golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= | ||
| golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= | ||
| golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= | ||
@@ -225,0 +227,0 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= |
@@ -5,4 +5,6 @@ package create | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
@@ -81,11 +83,3 @@ cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| p.Outputf("Created credentials for instance %q.\n\n", instanceLabel) | ||
| // The username field cannot be set by the user so we only display it if it's not returned empty | ||
| username := *resp.Credentials.Username | ||
| if username != "" { | ||
| p.Outputf("Username: %s\n", username) | ||
| } | ||
| p.Outputf("Password: %s\n", *resp.Credentials.Password) | ||
| return nil | ||
| return outputResult(p, model, instanceLabel, resp) | ||
| }, | ||
@@ -120,1 +114,32 @@ } | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *argus.CreateCredentialsResponse) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(resp, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Argus credentials: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal Argus credentials: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created credentials for instance %q.\n\n", instanceLabel) | ||
| // The username field cannot be set by the user so we only display it if it's not returned empty | ||
| username := *resp.Credentials.Username | ||
| if username != "" { | ||
| p.Outputf("Username: %s\n", username) | ||
| } | ||
| p.Outputf("Password: %s\n", *resp.Credentials.Password) | ||
| return nil | ||
| } | ||
| } |
@@ -26,2 +26,10 @@ package login | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| p.Warn(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n", | ||
| "Starting on July 9 2024, the new STACKIT Identity Provider (IDP) will be available.", | ||
| "On this date, we will release a new version of the STACKIT CLI that will use the new IDP for user authentication.", | ||
| "This also means that the user authentication on STACKIT CLI versions released before July 9 2024 is no longer guaranteed to work for all services.", | ||
| "Please make sure to update your STACKIT CLI to the latest version after July 9 2024 to ensure that you can continue to use all STACKIT services.", | ||
| "You can find more information regarding the new IDP at https://docs.stackit.cloud/stackit/en/release-notes-23101442.html#ReleaseNotes-2024-06-21-identity-provider", | ||
| )) | ||
| err := auth.AuthorizeUser(p, false) | ||
@@ -28,0 +36,0 @@ if err != nil { |
@@ -6,2 +6,3 @@ package beta | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" | ||
@@ -40,2 +41,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| cmd.AddCommand(sqlserverflex.NewCmd(p)) | ||
| cmd.AddCommand(server.NewCmd(p)) | ||
| } |
@@ -60,14 +60,14 @@ package create | ||
| Use: "create", | ||
| Short: "Creates an SQLServer Flex instance", | ||
| Long: "Creates an SQLServer Flex instance.", | ||
| Short: "Creates a SQLServer Flex instance", | ||
| Long: "Creates a SQLServer Flex instance.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create an SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values`, | ||
| `Create a SQLServer Flex instance with name "my-instance" and specify flavor by CPU and RAM. Other parameters are set to default values`, | ||
| `$ stackit beta sqlserverflex instance create --name my-instance --cpu 1 --ram 4`), | ||
| examples.NewExample( | ||
| `Create an SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values`, | ||
| `Create a SQLServer Flex instance with name "my-instance" and specify flavor by ID. Other parameters are set to default values`, | ||
| `$ stackit beta sqlserverflex instance create --name my-instance --flavor-id xxx`), | ||
| examples.NewExample( | ||
| `Create an 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`, | ||
| `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 --acl 1.2.3.0/24`), | ||
@@ -96,3 +96,3 @@ ), | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create an SQLServer Flex instance for project %q?", projectLabel) | ||
| prompt := fmt.Sprintf("Are you sure you want to create a SQLServer Flex instance for project %q?", projectLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
@@ -99,0 +99,0 @@ if err != nil { |
@@ -34,8 +34,8 @@ package delete | ||
| Use: fmt.Sprintf("delete %s", instanceIdArg), | ||
| Short: "Deletes an SQLServer Flex instance", | ||
| Long: "Deletes an SQLServer Flex instance.", | ||
| Short: "Deletes a SQLServer Flex instance", | ||
| Long: "Deletes a SQLServer Flex instance.", | ||
| Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete an SQLServer Flex instance with ID "xxx"`, | ||
| `Delete a SQLServer Flex instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex instance delete xxx"), | ||
@@ -42,0 +42,0 @@ ), |
@@ -35,11 +35,11 @@ package describe | ||
| Use: fmt.Sprintf("describe %s", instanceIdArg), | ||
| Short: "Shows details of an SQLServer Flex instance", | ||
| Long: "Shows details of an SQLServer Flex instance.", | ||
| Short: "Shows details of a SQLServer Flex instance", | ||
| Long: "Shows details of a SQLServer Flex instance.", | ||
| Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Get details of an SQLServer Flex instance with ID "xxx"`, | ||
| `Get details of a SQLServer Flex instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex instance describe xxx"), | ||
| examples.NewExample( | ||
| `Get details of an SQLServer Flex instance with ID "xxx" in JSON format`, | ||
| `Get details of a SQLServer Flex instance with ID "xxx" in JSON format`, | ||
| "$ stackit beta sqlserverflex instance describe xxx --output-format json"), | ||
@@ -46,0 +46,0 @@ ), |
@@ -54,10 +54,10 @@ package update | ||
| Use: fmt.Sprintf("update %s", instanceIdArg), | ||
| Short: "Updates an SQLServer Flex instance", | ||
| Long: "Updates an SQLServer Flex instance.", | ||
| Short: "Updates a SQLServer Flex instance", | ||
| Long: "Updates a SQLServer Flex instance.", | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Update the name of an SQLServer Flex instance with ID "xxx"`, | ||
| `Update the name of a SQLServer Flex instance with ID "xxx"`, | ||
| "$ stackit beta sqlserverflex instance update xxx --name my-new-name"), | ||
| examples.NewExample( | ||
| `Update the backup schedule of an SQLServer Flex instance with ID "xxx"`, | ||
| `Update the backup schedule of a SQLServer Flex instance with ID "xxx"`, | ||
| `$ stackit beta sqlserverflex instance update xxx --backup-schedule "30 0 * * *"`), | ||
@@ -64,0 +64,0 @@ ), |
@@ -13,2 +13,3 @@ package options | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex" | ||
@@ -20,11 +21,18 @@ ) | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testInstanceId = uuid.NewString() | ||
| type sqlServerFlexClientMocked struct { | ||
| listFlavorsFails bool | ||
| listVersionsFails bool | ||
| listStoragesFails bool | ||
| listFlavorsFails bool | ||
| listVersionsFails bool | ||
| listStoragesFails bool | ||
| listUserRolesFails bool | ||
| listDBCollationsFails bool | ||
| listDBCompatibilitiesFails bool | ||
| listFlavorsCalled bool | ||
| listVersionsCalled bool | ||
| listStoragesCalled bool | ||
| listFlavorsCalled bool | ||
| listVersionsCalled bool | ||
| listStoragesCalled bool | ||
| listUserRolesCalled bool | ||
| listDBCollationsCalled bool | ||
| listDBCompatibilitiesCalled bool | ||
| } | ||
@@ -66,8 +74,42 @@ | ||
| func (c *sqlServerFlexClientMocked) ListRolesExecute(_ context.Context, _, _ string) (*sqlserverflex.ListRolesResponse, error) { | ||
| c.listUserRolesCalled = true | ||
| if c.listUserRolesFails { | ||
| return nil, fmt.Errorf("list roles failed") | ||
| } | ||
| return utils.Ptr(sqlserverflex.ListRolesResponse{ | ||
| Roles: utils.Ptr([]string{}), | ||
| }), nil | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListCollationsExecute(_ context.Context, _, _ string) (*sqlserverflex.ListCollationsResponse, error) { | ||
| c.listDBCollationsCalled = true | ||
| if c.listDBCollationsFails { | ||
| return nil, fmt.Errorf("list collations failed") | ||
| } | ||
| return utils.Ptr(sqlserverflex.ListCollationsResponse{ | ||
| Collations: utils.Ptr([]sqlserverflex.MssqlDatabaseCollation{}), | ||
| }), nil | ||
| } | ||
| func (c *sqlServerFlexClientMocked) ListCompatibilityExecute(_ context.Context, _, _ string) (*sqlserverflex.ListCompatibilityResponse, error) { | ||
| c.listDBCompatibilitiesCalled = true | ||
| if c.listDBCompatibilitiesFails { | ||
| return nil, fmt.Errorf("list compatibilities failed") | ||
| } | ||
| return utils.Ptr(sqlserverflex.ListCompatibilityResponse{ | ||
| Compatibilities: utils.Ptr([]sqlserverflex.MssqlDatabaseCompatibility{}), | ||
| }), nil | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| flavorsFlag: "true", | ||
| versionsFlag: "true", | ||
| storagesFlag: "true", | ||
| flavorIdFlag: "2.4", | ||
| flavorsFlag: "true", | ||
| versionsFlag: "true", | ||
| storagesFlag: "true", | ||
| userRolesFlag: "true", | ||
| dbCollationsFlag: "true", | ||
| dbCompatibilitiesFlag: "true", | ||
| flavorIdFlag: "2.4", | ||
| instanceIdFlag: testInstanceId, | ||
| } | ||
@@ -82,6 +124,9 @@ for _, mod := range mods { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault}, | ||
| Flavors: false, | ||
| Versions: false, | ||
| Storages: false, | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault}, | ||
| Flavors: false, | ||
| Versions: false, | ||
| Storages: false, | ||
| UserRoles: false, | ||
| DBCollations: false, | ||
| DBCompatibilities: false, | ||
| } | ||
@@ -96,7 +141,11 @@ for _, mod := range mods { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault}, | ||
| Flavors: true, | ||
| Versions: true, | ||
| Storages: true, | ||
| FlavorId: utils.Ptr("2.4"), | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault}, | ||
| Flavors: true, | ||
| Versions: true, | ||
| Storages: true, | ||
| UserRoles: true, | ||
| DBCollations: true, | ||
| DBCompatibilities: true, | ||
| FlavorId: utils.Ptr("2.4"), | ||
| InstanceId: utils.Ptr(testInstanceId), | ||
| } | ||
@@ -134,5 +183,5 @@ for _, mod := range mods { | ||
| isValid: true, | ||
| expectedModel: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.Flavors = true | ||
| model.Versions = true | ||
| expectedModel: fixtureInputModelAllTrue(func(model *inputModel) { | ||
| model.Storages = false | ||
| model.FlavorId = nil | ||
| }), | ||
@@ -145,2 +194,3 @@ }, | ||
| delete(flagValues, versionsFlag) | ||
| delete(flagValues, userRolesFlag) | ||
| flagValues[storagesFlag] = "true" | ||
@@ -150,5 +200,6 @@ flagValues[flavorIdFlag] = "2.4" | ||
| isValid: true, | ||
| expectedModel: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.Storages = true | ||
| model.FlavorId = utils.Ptr("2.4") | ||
| expectedModel: fixtureInputModelAllTrue(func(model *inputModel) { | ||
| model.Flavors = false | ||
| model.Versions = false | ||
| model.UserRoles = false | ||
| }), | ||
@@ -173,2 +224,43 @@ }, | ||
| }, | ||
| { | ||
| description: "user roles without instance id", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| delete(flagValues, dbCollationsFlag) | ||
| delete(flagValues, dbCompatibilitiesFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "db collations without instance id", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| delete(flagValues, userRolesFlag) | ||
| delete(flagValues, dbCompatibilitiesFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "db compatibilities without instance id", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, instanceIdFlag) | ||
| delete(flagValues, userRolesFlag) | ||
| delete(flagValues, dbCollationsFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id without user roles, db collations and db compatibilities", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, userRolesFlag) | ||
| delete(flagValues, dbCollationsFlag) | ||
| delete(flagValues, dbCompatibilitiesFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModelAllTrue(func(model *inputModel) { | ||
| model.UserRoles = false | ||
| model.DBCollations = false | ||
| model.DBCompatibilities = false | ||
| }), | ||
| }, | ||
| } | ||
@@ -224,27 +316,40 @@ | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| isValid bool | ||
| listFlavorsFails bool | ||
| listVersionsFails bool | ||
| listStoragesFails bool | ||
| expectListFlavorsCalled bool | ||
| expectListVersionsCalled bool | ||
| expectListStoragesCalled bool | ||
| description string | ||
| model *inputModel | ||
| isValid bool | ||
| listFlavorsFails bool | ||
| listVersionsFails bool | ||
| listStoragesFails bool | ||
| listUserRolesFails bool | ||
| listDBCollationsFails bool | ||
| listDBCompatibilitiesFails bool | ||
| expectListFlavorsCalled bool | ||
| expectListVersionsCalled bool | ||
| expectListStoragesCalled bool | ||
| expectListUserRolesCalled bool | ||
| expectListDBCollationsCalled bool | ||
| expectListDBCompatibilitiesCalled bool | ||
| }{ | ||
| { | ||
| description: "all values", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| description: "all values", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| expectListUserRolesCalled: true, | ||
| expectListDBCollationsCalled: true, | ||
| expectListDBCompatibilitiesCalled: true, | ||
| }, | ||
| { | ||
| description: "no values", | ||
| model: fixtureInputModelAllFalse(), | ||
| isValid: true, | ||
| expectListFlavorsCalled: false, | ||
| expectListVersionsCalled: false, | ||
| expectListStoragesCalled: false, | ||
| description: "no values", | ||
| model: fixtureInputModelAllFalse(), | ||
| isValid: true, | ||
| expectListFlavorsCalled: false, | ||
| expectListVersionsCalled: false, | ||
| expectListStoragesCalled: false, | ||
| expectListUserRolesCalled: false, | ||
| expectListDBCollationsCalled: false, | ||
| expectListDBCompatibilitiesCalled: false, | ||
| }, | ||
@@ -273,28 +378,100 @@ { | ||
| { | ||
| description: "list flavors fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listFlavorsFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: false, | ||
| expectListStoragesCalled: false, | ||
| description: "only user roles", | ||
| model: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.UserRoles = true | ||
| model.InstanceId = utils.Ptr(testInstanceId) | ||
| }), | ||
| isValid: true, | ||
| expectListUserRolesCalled: true, | ||
| }, | ||
| { | ||
| description: "list versions fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listVersionsFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: false, | ||
| description: "only db collations", | ||
| model: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.DBCollations = true | ||
| model.InstanceId = utils.Ptr(testInstanceId) | ||
| }), | ||
| isValid: true, | ||
| expectListDBCollationsCalled: true, | ||
| }, | ||
| { | ||
| description: "list storages fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listStoragesFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| description: "only db compatibilities", | ||
| model: fixtureInputModelAllFalse(func(model *inputModel) { | ||
| model.DBCompatibilities = true | ||
| model.InstanceId = utils.Ptr(testInstanceId) | ||
| }), | ||
| isValid: true, | ||
| expectListDBCompatibilitiesCalled: true, | ||
| }, | ||
| { | ||
| description: "list flavors fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listFlavorsFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: false, | ||
| expectListStoragesCalled: false, | ||
| expectListUserRolesCalled: false, | ||
| expectListDBCollationsCalled: false, | ||
| expectListDBCompatibilitiesCalled: false, | ||
| }, | ||
| { | ||
| description: "list versions fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listVersionsFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: false, | ||
| expectListUserRolesCalled: false, | ||
| expectListDBCollationsCalled: false, | ||
| expectListDBCompatibilitiesCalled: false, | ||
| }, | ||
| { | ||
| description: "list storages fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listStoragesFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| expectListUserRolesCalled: false, | ||
| expectListDBCollationsCalled: false, | ||
| expectListDBCompatibilitiesCalled: false, | ||
| }, | ||
| { | ||
| description: "list user roles fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listUserRolesFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| expectListUserRolesCalled: true, | ||
| expectListDBCollationsCalled: false, | ||
| expectListDBCompatibilitiesCalled: false, | ||
| }, | ||
| { | ||
| description: "list db collations fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listDBCollationsFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| expectListUserRolesCalled: true, | ||
| expectListDBCollationsCalled: true, | ||
| expectListDBCompatibilitiesCalled: false, | ||
| }, | ||
| { | ||
| description: "list db compatibilities fails", | ||
| model: fixtureInputModelAllTrue(), | ||
| isValid: false, | ||
| listDBCompatibilitiesFails: true, | ||
| expectListFlavorsCalled: true, | ||
| expectListVersionsCalled: true, | ||
| expectListStoragesCalled: true, | ||
| expectListUserRolesCalled: true, | ||
| expectListDBCollationsCalled: true, | ||
| expectListDBCompatibilitiesCalled: true, | ||
| }, | ||
| } | ||
@@ -304,9 +481,12 @@ | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := &print.Printer{} | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| p.Cmd = cmd | ||
| client := &sqlServerFlexClientMocked{ | ||
| listFlavorsFails: tt.listFlavorsFails, | ||
| listVersionsFails: tt.listVersionsFails, | ||
| listStoragesFails: tt.listStoragesFails, | ||
| listFlavorsFails: tt.listFlavorsFails, | ||
| listVersionsFails: tt.listVersionsFails, | ||
| listStoragesFails: tt.listStoragesFails, | ||
| listUserRolesFails: tt.listUserRolesFails, | ||
| listDBCollationsFails: tt.listDBCollationsFails, | ||
| listDBCompatibilitiesFails: tt.listDBCompatibilitiesFails, | ||
| } | ||
@@ -313,0 +493,0 @@ |
@@ -22,6 +22,11 @@ package options | ||
| const ( | ||
| flavorsFlag = "flavors" | ||
| versionsFlag = "versions" | ||
| storagesFlag = "storages" | ||
| flavorIdFlag = "flavor-id" | ||
| flavorsFlag = "flavors" | ||
| versionsFlag = "versions" | ||
| storagesFlag = "storages" | ||
| userRolesFlag = "user-roles" | ||
| dbCollationsFlag = "db-collations" | ||
| dbCompatibilitiesFlag = "db-compatibilities" | ||
| flavorIdFlag = "flavor-id" | ||
| instanceIdFlag = "instance-id" | ||
| ) | ||
@@ -32,12 +37,20 @@ | ||
| Flavors bool | ||
| Versions bool | ||
| Storages bool | ||
| FlavorId *string | ||
| Flavors bool | ||
| Versions bool | ||
| Storages bool | ||
| UserRoles bool | ||
| DBCollations bool | ||
| DBCompatibilities bool | ||
| FlavorId *string | ||
| InstanceId *string | ||
| } | ||
| type options struct { | ||
| Flavors *[]sqlserverflex.InstanceFlavorEntry `json:"flavors,omitempty"` | ||
| Versions *[]string `json:"versions,omitempty"` | ||
| Storages *flavorStorages `json:"flavorStorages,omitempty"` | ||
| Flavors *[]sqlserverflex.InstanceFlavorEntry `json:"flavors,omitempty"` | ||
| Versions *[]string `json:"versions,omitempty"` | ||
| Storages *flavorStorages `json:"flavorStorages,omitempty"` | ||
| UserRoles *instanceUserRoles `json:"userRoles,omitempty"` | ||
| DBCollations *instanceDBCollations `json:"dbCollations,omitempty"` | ||
| DBCompatibilities *instanceDBCompatibilities `json:"dbCompatibilities,omitempty"` | ||
| } | ||
@@ -50,2 +63,17 @@ | ||
| type instanceUserRoles struct { | ||
| InstanceId string `json:"instanceId"` | ||
| UserRoles []string `json:"userRoles"` | ||
| } | ||
| type instanceDBCollations struct { | ||
| InstanceId string `json:"instanceId"` | ||
| DBCollations []sqlserverflex.MssqlDatabaseCollation `json:"dbCollations"` | ||
| } | ||
| type instanceDBCompatibilities struct { | ||
| InstanceId string `json:"instanceId"` | ||
| DBCompatibilities []sqlserverflex.MssqlDatabaseCompatibility `json:"dbCompatibilities"` | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
@@ -60,9 +88,12 @@ cmd := &cobra.Command{ | ||
| `List SQL Server Flex flavors options`, | ||
| "$ stackit sqlserverflex options --flavors"), | ||
| "$ stackit beta sqlserverflex options --flavors"), | ||
| examples.NewExample( | ||
| `List SQL Server Flex available versions`, | ||
| "$ stackit sqlserverflex options --versions"), | ||
| "$ stackit beta sqlserverflex options --versions"), | ||
| examples.NewExample( | ||
| `List SQL Server Flex storage options for a given flavor. The flavor ID can be retrieved by running "$ stackit sqlserverflex options --flavors"`, | ||
| "$ stackit sqlserverflex options --storages --flavor-id <FLAVOR_ID>"), | ||
| `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 <FLAVOR_ID>"), | ||
| examples.NewExample( | ||
| `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 <INSTANCE_ID>"), | ||
| ), | ||
@@ -99,3 +130,7 @@ RunE: func(cmd *cobra.Command, args []string) error { | ||
| cmd.Flags().Bool(storagesFlag, false, "Lists supported storages for a given flavor") | ||
| cmd.Flags().Bool(userRolesFlag, false, "Lists supported user roles for a given instance") | ||
| cmd.Flags().Bool(dbCollationsFlag, false, "Lists supported database collations for a given instance") | ||
| cmd.Flags().Bool(dbCompatibilitiesFlag, false, "Lists supported database compatibilities for a given instance") | ||
| cmd.Flags().String(flavorIdFlag, "", `The flavor ID to show storages for. Only relevant when "--storages" is passed`) | ||
| 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`) | ||
| } | ||
@@ -105,8 +140,14 @@ | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| flavors := flags.FlagToBoolValue(p, cmd, flavorsFlag) | ||
| versions := flags.FlagToBoolValue(p, cmd, versionsFlag) | ||
| storages := flags.FlagToBoolValue(p, cmd, storagesFlag) | ||
| userRoles := flags.FlagToBoolValue(p, cmd, userRolesFlag) | ||
| dbCollations := flags.FlagToBoolValue(p, cmd, dbCollationsFlag) | ||
| dbCompatibilities := flags.FlagToBoolValue(p, cmd, dbCompatibilitiesFlag) | ||
| flavorId := flags.FlagToStringPointer(p, cmd, flavorIdFlag) | ||
| instanceId := flags.FlagToStringPointer(p, cmd, instanceIdFlag) | ||
| if !flavors && !versions && !storages { | ||
| if !flavors && !versions && !storages && !userRoles && !dbCollations && !dbCompatibilities { | ||
| return nil, fmt.Errorf("%s\n\n%s", | ||
@@ -121,11 +162,22 @@ "please specify at least one category for which to list the available options.", | ||
| "You can get the available flavor IDs by running:", | ||
| " $ stackit sqlserverflex options --flavors") | ||
| " $ stackit beta sqlserverflex options --flavors") | ||
| } | ||
| if (userRoles || dbCollations || dbCompatibilities) && instanceId == nil { | ||
| return nil, fmt.Errorf("%s\n\n%s\n%s", | ||
| `please specify an instance ID to show user roles, database collations or database compatibilities for by setting the flag "--instance-id <INSTANCE_ID>".`, | ||
| "You can get the available instances and their IDs by running:", | ||
| " $ stackit beta sqlserverflex instance list") | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Flavors: flavors, | ||
| Versions: versions, | ||
| Storages: storages, | ||
| FlavorId: flags.FlagToStringPointer(p, cmd, flavorIdFlag), | ||
| GlobalFlagModel: globalFlags, | ||
| Flavors: flavors, | ||
| Versions: versions, | ||
| Storages: storages, | ||
| UserRoles: userRoles, | ||
| DBCollations: dbCollations, | ||
| DBCompatibilities: dbCompatibilities, | ||
| FlavorId: flavorId, | ||
| InstanceId: instanceId, | ||
| } | ||
@@ -149,2 +201,5 @@ | ||
| ListStoragesExecute(ctx context.Context, projectId, flavorId string) (*sqlserverflex.ListStoragesResponse, error) | ||
| ListRolesExecute(ctx context.Context, projectId string, instanceId string) (*sqlserverflex.ListRolesResponse, error) | ||
| ListCollationsExecute(ctx context.Context, projectId string, instanceId string) (*sqlserverflex.ListCollationsResponse, error) | ||
| ListCompatibilityExecute(ctx context.Context, projectId string, instanceId string) (*sqlserverflex.ListCompatibilityResponse, error) | ||
| } | ||
@@ -156,2 +211,5 @@ | ||
| var storages *sqlserverflex.ListStoragesResponse | ||
| var userRoles *sqlserverflex.ListRolesResponse | ||
| var dbCollations *sqlserverflex.ListCollationsResponse | ||
| var dbCompatibilities *sqlserverflex.ListCompatibilityResponse | ||
| var err error | ||
@@ -177,7 +235,25 @@ | ||
| } | ||
| if model.UserRoles { | ||
| userRoles, err = apiClient.ListRolesExecute(ctx, model.ProjectId, *model.InstanceId) | ||
| if err != nil { | ||
| return fmt.Errorf("get SQL Server Flex user roles: %w", err) | ||
| } | ||
| } | ||
| if model.DBCollations { | ||
| dbCollations, err = apiClient.ListCollationsExecute(ctx, model.ProjectId, *model.InstanceId) | ||
| if err != nil { | ||
| return fmt.Errorf("get SQL Server Flex DB collations: %w", err) | ||
| } | ||
| } | ||
| if model.DBCompatibilities { | ||
| dbCompatibilities, err = apiClient.ListCompatibilityExecute(ctx, model.ProjectId, *model.InstanceId) | ||
| if err != nil { | ||
| return fmt.Errorf("get SQL Server Flex DB compatibilities: %w", err) | ||
| } | ||
| } | ||
| return outputResult(p, model, flavors, versions, storages) | ||
| return outputResult(p, model, flavors, versions, storages, userRoles, dbCollations, dbCompatibilities) | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, flavors *sqlserverflex.ListFlavorsResponse, versions *sqlserverflex.ListVersionsResponse, storages *sqlserverflex.ListStoragesResponse) error { | ||
| func outputResult(p *print.Printer, model *inputModel, flavors *sqlserverflex.ListFlavorsResponse, versions *sqlserverflex.ListVersionsResponse, storages *sqlserverflex.ListStoragesResponse, userRoles *sqlserverflex.ListRolesResponse, dbCollations *sqlserverflex.ListCollationsResponse, dbCompatibilities *sqlserverflex.ListCompatibilityResponse) error { | ||
| options := &options{} | ||
@@ -196,2 +272,20 @@ if flavors != nil { | ||
| } | ||
| if userRoles != nil && model.InstanceId != nil { | ||
| options.UserRoles = &instanceUserRoles{ | ||
| InstanceId: *model.InstanceId, | ||
| UserRoles: *userRoles.Roles, | ||
| } | ||
| } | ||
| if dbCollations != nil && model.InstanceId != nil { | ||
| options.DBCollations = &instanceDBCollations{ | ||
| InstanceId: *model.InstanceId, | ||
| DBCollations: *dbCollations.Collations, | ||
| } | ||
| } | ||
| if dbCompatibilities != nil && model.InstanceId != nil { | ||
| options.DBCompatibilities = &instanceDBCompatibilities{ | ||
| InstanceId: *model.InstanceId, | ||
| DBCompatibilities: *dbCompatibilities.Compatibilities, | ||
| } | ||
| } | ||
@@ -230,2 +324,12 @@ switch model.OutputFormat { | ||
| } | ||
| if model.UserRoles { | ||
| content += renderUserRoles(options.UserRoles) | ||
| } | ||
| if model.DBCompatibilities { | ||
| content += renderDBCompatibilities(options.DBCompatibilities) | ||
| } | ||
| // Rendered at last because table is very long | ||
| if model.DBCollations { | ||
| content += renderDBCollations(options.DBCollations) | ||
| } | ||
@@ -286,1 +390,43 @@ err := p.PagerDisplay(content) | ||
| } | ||
| func renderUserRoles(roles *instanceUserRoles) string { | ||
| if len(roles.UserRoles) == 0 { | ||
| return "" | ||
| } | ||
| table := tables.NewTable() | ||
| table.SetTitle("User Roles") | ||
| table.SetHeader("ROLE") | ||
| for i := range roles.UserRoles { | ||
| table.AddRow(roles.UserRoles[i]) | ||
| } | ||
| return table.Render() | ||
| } | ||
| func renderDBCollations(dbCollations *instanceDBCollations) string { | ||
| if len(dbCollations.DBCollations) == 0 { | ||
| return "" | ||
| } | ||
| table := tables.NewTable() | ||
| table.SetTitle("DB Collations") | ||
| table.SetHeader("NAME", "DESCRIPTION") | ||
| for i := range dbCollations.DBCollations { | ||
| table.AddRow(*dbCollations.DBCollations[i].CollationName, *dbCollations.DBCollations[i].Description) | ||
| } | ||
| return table.Render() | ||
| } | ||
| func renderDBCompatibilities(dbCompatibilities *instanceDBCompatibilities) string { | ||
| if len(dbCompatibilities.DBCompatibilities) == 0 { | ||
| return "" | ||
| } | ||
| table := tables.NewTable() | ||
| table.SetTitle("DB Compatibilities") | ||
| table.SetHeader("COMPATIBILITY LEVEL", "DESCRIPTION") | ||
| for i := range dbCompatibilities.DBCompatibilities { | ||
| table.AddRow(*dbCompatibilities.DBCompatibilities[i].CompatibilityLevel, *dbCompatibilities.DBCompatibilities[i].Description) | ||
| } | ||
| return table.Render() | ||
| } |
| package sqlserverflex | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex/database" | ||
| "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/pkg/args" | ||
@@ -26,4 +28,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(database.NewCmd(p)) | ||
| cmd.AddCommand(instance.NewCmd(p)) | ||
| cmd.AddCommand(options.NewCmd(p)) | ||
| cmd.AddCommand(user.NewCmd(p)) | ||
| } |
@@ -36,2 +36,3 @@ package set | ||
| secretsManagerCustomEndpointFlag = "secrets-manager-custom-endpoint" | ||
| serverBackupCustomEndpointFlag = "serverbackup-custom-endpoint" | ||
| serviceAccountCustomEndpointFlag = "service-account-custom-endpoint" | ||
@@ -143,2 +144,3 @@ skeCustomEndpointFlag = "ske-custom-endpoint" | ||
| cmd.Flags().String(serviceAccountCustomEndpointFlag, "", "Service Account API base URL, used in calls to this API") | ||
| cmd.Flags().String(serverBackupCustomEndpointFlag, "", "Server Backup API base URL, used in calls to this API") | ||
| cmd.Flags().String(skeCustomEndpointFlag, "", "SKE API base URL, used in calls to this API") | ||
@@ -175,2 +177,4 @@ cmd.Flags().String(sqlServerFlexCustomEndpointFlag, "", "SQLServer Flex API base URL, used in calls to this API") | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.ServerBackupCustomEndpointKey, cmd.Flags().Lookup(serverBackupCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.ServiceAccountCustomEndpointKey, cmd.Flags().Lookup(serviceAccountCustomEndpointFlag)) | ||
@@ -177,0 +181,0 @@ cobra.CheckErr(err) |
@@ -33,2 +33,3 @@ package unset | ||
| serviceAccountCustomEndpointFlag: true, | ||
| serverBackupCustomEndpointFlag: true, | ||
| skeCustomEndpointFlag: true, | ||
@@ -64,2 +65,3 @@ sqlServerFlexCustomEndpointFlag: true, | ||
| ServiceAccountCustomEndpoint: true, | ||
| ServerBackupCustomEndpoint: true, | ||
| SKECustomEndpoint: true, | ||
@@ -111,2 +113,3 @@ SQLServerFlexCustomEndpoint: true, | ||
| model.ServiceAccountCustomEndpoint = false | ||
| model.ServerBackupCustomEndpoint = false | ||
| model.SKECustomEndpoint = false | ||
@@ -196,2 +199,12 @@ model.SQLServerFlexCustomEndpoint = false | ||
| }, | ||
| { | ||
| description: "serverbackup custom endpoint empty", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]bool) { | ||
| flagValues[serverBackupCustomEndpointFlag] = false | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ServerBackupCustomEndpoint = false | ||
| }), | ||
| }, | ||
| } | ||
@@ -198,0 +211,0 @@ for _, tt := range tests { |
@@ -40,2 +40,3 @@ package unset | ||
| serviceAccountCustomEndpointFlag = "service-account-custom-endpoint" | ||
| serverBackupCustomEndpointFlag = "serverbackup-custom-endpoint" | ||
| skeCustomEndpointFlag = "ske-custom-endpoint" | ||
@@ -66,2 +67,3 @@ sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint" | ||
| SecretsManagerCustomEndpoint bool | ||
| ServerBackupCustomEndpoint bool | ||
| ServiceAccountCustomEndpoint bool | ||
@@ -153,2 +155,5 @@ SKECustomEndpoint bool | ||
| } | ||
| if model.ServerBackupCustomEndpoint { | ||
| viper.Set(config.ServerBackupCustomEndpointKey, "") | ||
| } | ||
| if model.SKECustomEndpoint { | ||
@@ -194,2 +199,3 @@ viper.Set(config.SKECustomEndpointKey, "") | ||
| cmd.Flags().Bool(serviceAccountCustomEndpointFlag, false, "SKE API base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(serverBackupCustomEndpointFlag, false, "Server Backup base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(skeCustomEndpointFlag, false, "SKE API base URL. If unset, uses the default base URL") | ||
@@ -222,2 +228,3 @@ cmd.Flags().Bool(sqlServerFlexCustomEndpointFlag, false, "SQLServer Flex API base URL. If unset, uses the default base URL") | ||
| ServiceAccountCustomEndpoint: flags.FlagToBoolValue(p, cmd, serviceAccountCustomEndpointFlag), | ||
| ServerBackupCustomEndpoint: flags.FlagToBoolValue(p, cmd, serverBackupCustomEndpointFlag), | ||
| SKECustomEndpoint: flags.FlagToBoolValue(p, cmd, skeCustomEndpointFlag), | ||
@@ -224,0 +231,0 @@ SQLServerFlexCustomEndpoint: flags.FlagToBoolValue(p, cmd, sqlServerFlexCustomEndpointFlag), |
@@ -48,6 +48,6 @@ package describe | ||
| `Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy"`, | ||
| "$ stackit mongodbflex user list xxx --instance-id yyy"), | ||
| "$ stackit mongodbflex user describe xxx --instance-id yyy"), | ||
| examples.NewExample( | ||
| `Get details of a MongoDB Flex user with ID "xxx" of instance with ID "yyy" in JSON format`, | ||
| "$ stackit mongodbflex user list xxx --instance-id yyy --output-format json"), | ||
| "$ stackit mongodbflex user describe xxx --instance-id yyy --output-format json"), | ||
| ), | ||
@@ -54,0 +54,0 @@ Args: args.SingleArg(userIdArg, utils.ValidateUUID), |
@@ -15,2 +15,3 @@ package create | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
@@ -64,2 +65,13 @@ | ||
| // Check if the project is enabled before trying to create | ||
| enabled, err := utils.ProjectEnabled(ctx, apiClient, model.ProjectId) | ||
| if err != nil { | ||
| return fmt.Errorf("check if Object Storage is enabled: %w", err) | ||
| } | ||
| if !enabled { | ||
| return &errors.ServiceDisabledError{ | ||
| Service: "object-storage", | ||
| } | ||
| } | ||
| // Call API | ||
@@ -66,0 +78,0 @@ req := buildRequest(ctx, model, apiClient) |
@@ -47,6 +47,6 @@ package describe | ||
| `Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy"`, | ||
| "$ stackit postgresflex user list xxx --instance-id yyy"), | ||
| "$ stackit postgresflex user describe xxx --instance-id yyy"), | ||
| examples.NewExample( | ||
| `Get details of a PostgreSQL Flex user with ID "xxx" of instance with ID "yyy" in JSON format`, | ||
| "$ stackit postgresflex user list xxx --instance-id yyy --output-format json"), | ||
| "$ stackit postgresflex user describe xxx --instance-id yyy --output-format json"), | ||
| ), | ||
@@ -53,0 +53,0 @@ Args: args.SingleArg(userIdArg, nil), |
@@ -61,3 +61,3 @@ package create | ||
| }), | ||
| Members: &[]resourcemanager.ProjectMember{ | ||
| Members: &[]resourcemanager.Member{ | ||
| { | ||
@@ -64,0 +64,0 @@ Role: utils.Ptr(ownerRole), |
@@ -181,3 +181,3 @@ package create | ||
| Labels: model.Labels, | ||
| Members: &[]resourcemanager.ProjectMember{ | ||
| Members: &[]resourcemanager.Member{ | ||
| { | ||
@@ -193,3 +193,3 @@ Role: utils.Ptr(ownerRole), | ||
| func outputResult(p *print.Printer, model *inputModel, resp *resourcemanager.ProjectResponse) error { | ||
| func outputResult(p *print.Printer, model *inputModel, resp *resourcemanager.Project) error { | ||
| switch model.OutputFormat { | ||
@@ -196,0 +196,0 @@ case print.JSONOutputFormat: |
@@ -121,3 +121,3 @@ package describe | ||
| func outputResult(p *print.Printer, outputFormat string, project *resourcemanager.ProjectResponseWithParents) error { | ||
| func outputResult(p *print.Printer, outputFormat string, project *resourcemanager.GetProjectResponse) error { | ||
| switch outputFormat { | ||
@@ -124,0 +124,0 @@ case print.JSONOutputFormat: |
@@ -454,4 +454,4 @@ package list | ||
| projects := make([]resourcemanager.ProjectResponse, numItemsToReturn) | ||
| mockedResp := resourcemanager.AllProjectsResponse{ | ||
| projects := make([]resourcemanager.Project, numItemsToReturn) | ||
| mockedResp := resourcemanager.ListProjectsResponse{ | ||
| Items: &projects, | ||
@@ -458,0 +458,0 @@ } |
@@ -185,3 +185,3 @@ package list | ||
| func fetchProjects(ctx context.Context, model *inputModel, apiClient resourceManagerClient) ([]resourcemanager.ProjectResponse, error) { | ||
| func fetchProjects(ctx context.Context, model *inputModel, apiClient resourceManagerClient) ([]resourcemanager.Project, error) { | ||
| if model.Limit != nil && *model.Limit < model.PageSize { | ||
@@ -192,3 +192,3 @@ model.PageSize = *model.Limit | ||
| offset := 0 | ||
| projects := []resourcemanager.ProjectResponse{} | ||
| projects := []resourcemanager.Project{} | ||
| for { | ||
@@ -224,3 +224,3 @@ // Call API | ||
| func outputResult(p *print.Printer, outputFormat string, projects []resourcemanager.ProjectResponse) error { | ||
| func outputResult(p *print.Printer, outputFormat string, projects []resourcemanager.Project) error { | ||
| switch outputFormat { | ||
@@ -227,0 +227,0 @@ case print.JSONOutputFormat: |
@@ -90,3 +90,3 @@ package create | ||
| // Check if SKE is enabled for this project | ||
| // Check if the project is enabled before trying to create | ||
| enabled, err := skeUtils.ProjectEnabled(ctx, apiClient, model.ProjectId) | ||
@@ -97,3 +97,5 @@ if err != nil { | ||
| if !enabled { | ||
| return fmt.Errorf("SKE isn't enabled for this project, please run 'stackit ske enable'") | ||
| return &errors.ServiceDisabledError{ | ||
| Service: "ske", | ||
| } | ||
| } | ||
@@ -100,0 +102,0 @@ |
@@ -35,2 +35,3 @@ package config | ||
| ServiceAccountCustomEndpointKey = "service_account_custom_endpoint" | ||
| ServerBackupCustomEndpointKey = "serverbackup_custom_endpoint" | ||
| SKECustomEndpointKey = "ske_custom_endpoint" | ||
@@ -80,2 +81,3 @@ SQLServerFlexCustomEndpointKey = "sqlserverflex_custom_endpoint" | ||
| ServiceAccountCustomEndpointKey, | ||
| ServerBackupCustomEndpointKey, | ||
| SKECustomEndpointKey, | ||
@@ -150,2 +152,3 @@ SQLServerFlexCustomEndpointKey, | ||
| viper.SetDefault(ServiceAccountCustomEndpointKey, "") | ||
| viper.SetDefault(ServerBackupCustomEndpointKey, "") | ||
| viper.SetDefault(SKECustomEndpointKey, "") | ||
@@ -152,0 +155,0 @@ viper.SetDefault(SQLServerFlexCustomEndpointKey, "") |
@@ -136,2 +136,7 @@ package errors | ||
| $ %s --help` | ||
| SERVICE_DISABLED = `This service isn't enabled for the current project. | ||
| To enable it, run: | ||
| $ stackit %s enable` | ||
| ) | ||
@@ -368,1 +373,9 @@ | ||
| } | ||
| type ServiceDisabledError struct { | ||
| Service string | ||
| } | ||
| func (e *ServiceDisabledError) Error() string { | ||
| return fmt.Sprintf(SERVICE_DISABLED, e.Service) | ||
| } |
@@ -6,2 +6,3 @@ package projectname | ||
| "fmt" | ||
| "os" | ||
@@ -45,3 +46,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/config" | ||
| // (So next time we can just pull it from there) | ||
| if !isProjectIdSetInFlags(p, cmd) { | ||
| if !(isProjectIdSetInFlags(p, cmd) || isProjectIdSetInEnvVar()) { | ||
| viper.Set(config.ProjectNameKey, projectName) | ||
@@ -62,3 +63,4 @@ err = config.Write() | ||
| // - Project name in the config file is not empty | ||
| projectIdSet := isProjectIdSetInFlags(p, cmd) | ||
| projectIdSetInFlags := isProjectIdSetInFlags(p, cmd) | ||
| projectIdSetInEnv := isProjectIdSetInEnvVar() | ||
| projectName := viper.GetString(config.ProjectNameKey) | ||
@@ -69,3 +71,3 @@ projectNameSet := false | ||
| } | ||
| return !projectIdSet && projectNameSet | ||
| return !projectIdSetInFlags && !projectIdSetInEnv && projectNameSet | ||
| } | ||
@@ -84,1 +86,7 @@ | ||
| } | ||
| func isProjectIdSetInEnvVar() bool { | ||
| // Reads the project Id from the environment variable PROJECT_ID | ||
| _, projectIdSetInEnv := os.LookupEnv("STACKIT_PROJECT_ID") | ||
| return projectIdSetInEnv | ||
| } |
@@ -23,2 +23,9 @@ package utils | ||
| type MongoDBFlexClient interface { | ||
| ListVersionsExecute(ctx context.Context, projectId string) (*mongodbflex.ListVersionsResponse, error) | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*mongodbflex.GetInstanceResponse, error) | ||
| GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*mongodbflex.GetUserResponse, error) | ||
| ListRestoreJobsExecute(ctx context.Context, projectId string, instanceId string) (*mongodbflex.ListRestoreJobsResponse, error) | ||
| } | ||
| func AvailableInstanceTypes() []string { | ||
@@ -119,9 +126,2 @@ instanceTypes := make([]string, len(instanceTypeToReplicas)) | ||
| type MongoDBFlexClient interface { | ||
| ListVersionsExecute(ctx context.Context, projectId string) (*mongodbflex.ListVersionsResponse, error) | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*mongodbflex.GetInstanceResponse, error) | ||
| GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*mongodbflex.GetUserResponse, error) | ||
| ListRestoreJobsExecute(ctx context.Context, projectId string, instanceId string) (*mongodbflex.ListRestoreJobsResponse, error) | ||
| } | ||
| func GetLatestMongoDBVersion(ctx context.Context, apiClient MongoDBFlexClient, projectId string) (string, error) { | ||
@@ -152,3 +152,3 @@ resp, err := apiClient.ListVersionsExecute(ctx, projectId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get MongoDBFlex instance: %w", err) | ||
| return "", fmt.Errorf("get MongoDB Flex instance: %w", err) | ||
| } | ||
@@ -161,3 +161,3 @@ return *resp.Item.Name, nil | ||
| if err != nil { | ||
| return "", fmt.Errorf("get MongoDBFlex user: %w", err) | ||
| return "", fmt.Errorf("get MongoDB Flex user: %w", err) | ||
| } | ||
@@ -164,0 +164,0 @@ return *resp.Item.Username, nil |
@@ -15,2 +15,3 @@ package utils | ||
| "github.com/stackitcloud/stackit-sdk-go/core/config" | ||
| "github.com/stackitcloud/stackit-sdk-go/core/oapierror" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" | ||
@@ -31,2 +32,4 @@ ) | ||
| type objectStorageClientMocked struct { | ||
| serviceDisabled bool | ||
| getServiceStatusFails bool | ||
| listCredentialsGroupsFails bool | ||
@@ -37,2 +40,12 @@ listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse | ||
| func (m *objectStorageClientMocked) GetServiceStatusExecute(_ context.Context, _ string) (*objectstorage.ProjectStatus, error) { | ||
| if m.getServiceStatusFails { | ||
| return nil, fmt.Errorf("could not get service status") | ||
| } | ||
| if m.serviceDisabled { | ||
| return nil, &oapierror.GenericOpenAPIError{StatusCode: 404} | ||
| } | ||
| return &objectstorage.ProjectStatus{}, nil | ||
| } | ||
| func (m *objectStorageClientMocked) ListCredentialsGroupsExecute(_ context.Context, _ string) (*objectstorage.ListCredentialsGroupsResponse, error) { | ||
@@ -49,2 +62,54 @@ if m.listCredentialsGroupsFails { | ||
| func TestProjectEnabled(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| serviceDisabled bool | ||
| getProjectFails bool | ||
| isValid bool | ||
| expectedOutput bool | ||
| }{ | ||
| { | ||
| description: "project enabled", | ||
| isValid: true, | ||
| expectedOutput: true, | ||
| }, | ||
| { | ||
| description: "project disabled (404)", | ||
| serviceDisabled: true, | ||
| isValid: true, | ||
| expectedOutput: false, | ||
| }, | ||
| { | ||
| description: "get project fails", | ||
| getProjectFails: true, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &objectStorageClientMocked{ | ||
| serviceDisabled: tt.serviceDisabled, | ||
| getServiceStatusFails: tt.getProjectFails, | ||
| } | ||
| output, err := ProjectEnabled(context.Background(), client, testProjectId) | ||
| if tt.isValid && err != nil { | ||
| fmt.Printf("failed on valid input: %v", err) | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Errorf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Errorf("expected output to be %t, got %t", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestGetCredentialsGroupName(t *testing.T) { | ||
@@ -51,0 +116,0 @@ tests := []struct { |
@@ -6,3 +6,5 @@ package utils | ||
| "fmt" | ||
| "net/http" | ||
| "github.com/stackitcloud/stackit-sdk-go/core/oapierror" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" | ||
@@ -12,2 +14,3 @@ ) | ||
| type ObjectStorageClient interface { | ||
| GetServiceStatusExecute(ctx context.Context, projectId string) (*objectstorage.ProjectStatus, error) | ||
| ListCredentialsGroupsExecute(ctx context.Context, projectId string) (*objectstorage.ListCredentialsGroupsResponse, error) | ||
@@ -17,2 +20,17 @@ ListAccessKeys(ctx context.Context, projectId string) objectstorage.ApiListAccessKeysRequest | ||
| func ProjectEnabled(ctx context.Context, apiClient ObjectStorageClient, projectId string) (bool, error) { | ||
| _, err := apiClient.GetServiceStatusExecute(ctx, projectId) | ||
| if err != nil { | ||
| oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped | ||
| if !ok { | ||
| return false, err | ||
| } | ||
| if oapiErr.StatusCode == http.StatusNotFound { | ||
| return false, nil | ||
| } | ||
| return false, err | ||
| } | ||
| return true, nil | ||
| } | ||
| func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId string) (string, error) { | ||
@@ -19,0 +37,0 @@ resp, err := apiClient.ListCredentialsGroupsExecute(ctx, projectId) |
@@ -21,2 +21,8 @@ package utils | ||
| type PostgresFlexClient interface { | ||
| ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error) | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*postgresflex.InstanceResponse, error) | ||
| GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) | ||
| } | ||
| func AvailableInstanceTypes() []string { | ||
@@ -117,8 +123,2 @@ instanceTypes := make([]string, len(instanceTypeToReplicas)) | ||
| type PostgresFlexClient interface { | ||
| ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error) | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*postgresflex.InstanceResponse, error) | ||
| GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) | ||
| } | ||
| func GetLatestPostgreSQLVersion(ctx context.Context, apiClient PostgresFlexClient, projectId string) (string, error) { | ||
@@ -125,0 +125,0 @@ resp, err := apiClient.ListVersionsExecute(ctx, projectId) |
@@ -14,2 +14,3 @@ package utils | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/core/oapierror" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/ske" | ||
@@ -27,2 +28,3 @@ ) | ||
| type skeClientMocked struct { | ||
| serviceDisabled bool | ||
| getServiceStatusFails bool | ||
@@ -40,2 +42,5 @@ getServiceStatusResp *ske.ProjectResponse | ||
| } | ||
| if m.serviceDisabled { | ||
| return nil, &oapierror.GenericOpenAPIError{StatusCode: 404} | ||
| } | ||
| return m.getServiceStatusResp, nil | ||
@@ -61,2 +66,3 @@ } | ||
| description string | ||
| serviceDisabled bool | ||
| getProjectFails bool | ||
@@ -74,2 +80,8 @@ getProjectResp *ske.ProjectResponse | ||
| { | ||
| description: "project disabled (404)", | ||
| serviceDisabled: true, | ||
| isValid: true, | ||
| expectedOutput: false, | ||
| }, | ||
| { | ||
| description: "project disabled 1", | ||
@@ -96,2 +108,3 @@ getProjectResp: &ske.ProjectResponse{State: ske.PROJECTSTATE_CREATING.Ptr()}, | ||
| client := &skeClientMocked{ | ||
| serviceDisabled: tt.serviceDisabled, | ||
| getServiceStatusFails: tt.getProjectFails, | ||
@@ -98,0 +111,0 @@ getServiceStatusResp: tt.getProjectResp, |
@@ -6,2 +6,3 @@ package utils | ||
| "fmt" | ||
| "net/http" | ||
| "os" | ||
@@ -13,2 +14,3 @@ "path/filepath" | ||
| "github.com/stackitcloud/stackit-sdk-go/core/oapierror" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/ske" | ||
@@ -42,3 +44,10 @@ "golang.org/x/mod/semver" | ||
| if err != nil { | ||
| return false, fmt.Errorf("get SKE status: %w", err) | ||
| oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped | ||
| if !ok { | ||
| return false, err | ||
| } | ||
| if oapiErr.StatusCode == http.StatusNotFound { | ||
| return false, nil | ||
| } | ||
| return false, err | ||
| } | ||
@@ -45,0 +54,0 @@ return *project.State == ske.PROJECTSTATE_CREATED, nil |
@@ -18,2 +18,3 @@ package utils | ||
| testInstanceId = uuid.NewString() | ||
| testUserId = uuid.NewString() | ||
| ) | ||
@@ -425,1 +426,51 @@ | ||
| } | ||
| func TestGetUserName(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| getUserFails bool | ||
| getUserResp *sqlserverflex.GetUserResponse | ||
| isValid bool | ||
| expectedOutput string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| getUserResp: &sqlserverflex.GetUserResponse{ | ||
| Item: &sqlserverflex.InstanceResponseUser{ | ||
| Username: utils.Ptr(testUserName), | ||
| }, | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: testUserName, | ||
| }, | ||
| { | ||
| description: "get user fails", | ||
| getUserFails: true, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &sqlServerFlexClientMocked{ | ||
| getUserFails: tt.getUserFails, | ||
| getUserResp: tt.getUserResp, | ||
| } | ||
| output, err := GetUserName(context.Background(), client, testProjectId, testInstanceId, testUserId) | ||
| if tt.isValid && err != nil { | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Errorf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } |
@@ -20,2 +20,3 @@ package utils | ||
| GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*sqlserverflex.GetInstanceResponse, error) | ||
| GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*sqlserverflex.GetUserResponse, error) | ||
| } | ||
@@ -95,1 +96,9 @@ | ||
| } | ||
| func GetUserName(ctx context.Context, apiClient SQLServerFlexClient, projectId, instanceId, userId string) (string, error) { | ||
| resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get SQLServer Flex user: %w", err) | ||
| } | ||
| return *resp.Item.Username, nil | ||
| } |
@@ -39,2 +39,8 @@ package spinner | ||
| func (s *Spinner) StopWithError() { | ||
| s.done <- true | ||
| close(s.done) | ||
| s.printer.Info("\r%s ✗ \n", s.message) | ||
| } | ||
| func (s *Spinner) animate() { | ||
@@ -41,0 +47,0 @@ i := 0 |
+4
-1
@@ -10,2 +10,5 @@ ROOT_DIR ?= $(shell git rev-parse --show-toplevel) | ||
| fmt: | ||
| @gofmt -s -w . | ||
| # Setup and tool initialization tasks | ||
@@ -37,2 +40,2 @@ project-help: | ||
| @echo "Generating docs..." | ||
| @go run $(SCRIPTS_BASE)/generate.go | ||
| @go run $(SCRIPTS_BASE)/generate.go |
+7
-5
@@ -1,11 +0,12 @@ | ||
| [](https://goreportcard.com/report/github.com/stackitcloud/stackit-cli)  [](https://www.apache.org/licenses/LICENSE-2.0) | ||
| <div align="center"> | ||
| <br> | ||
| <p align="center"> | ||
| <img src=".github/images/stackit-logo.png" alt="STACKIT logo" width="50%"/> | ||
| </p> | ||
| <img src=".github/images/stackit-logo.png" alt="STACKIT logo" width="50%"/> | ||
| <br> | ||
| <br> | ||
| </div> | ||
| # STACKIT CLI (BETA) | ||
| [](https://goreportcard.com/report/github.com/stackitcloud/stackit-cli)  [](https://www.apache.org/licenses/LICENSE-2.0) | ||
| Welcome to the [STACKIT](https://www.stackit.de/en) CLI, a command-line interface for the STACKIT services. | ||
@@ -76,2 +77,3 @@ | ||
| | Secrets Manager | `secrets-manager` | :white_check_mark: | | ||
| | Server Backup Management | `beta server backup` | :white_check_mark: | | ||
| | Service Account | `service-account` | :white_check_mark: | | ||
@@ -78,0 +80,0 @@ | SQLServer Flex | `beta sqlserverflex` | :white_check_mark: (beta) | |