Skip to content

feat(cli): onboard iaas volume backup api #773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

Benjosh95
Copy link

@Benjosh95 Benjosh95 commented Jun 1, 2025

Description

Onboarding the volume backup API https://docs.api.stackit.cloud/documentation/iaas/version/v1#tag/Backups to manage the backup resources via the cli.

Checklist

  • Issue was linked above
  • Code format was applied: make fmt
  • Examples were added / adjusted (see e.g. here)
  • Docs are up-to-date: make generate-docs (will be checked by CI)
  • Unit tests got implemented or updated
  • Unit tests are passing: make test (will be checked by CI)
  • No linter issues: make lint (will be checked by CI)

Testing-Instructions

  1. Create a test volume to have something to backup
    stackit volume create --name backup-test-vol --size 10 --performance-class storage_premium_perf0 --availability-zone eu01-1 --labels initial=true,purpose=backup-testing

  2. Create backup of the volume with name and labels
    stackit volume backup create --source-id <VOLUME_ID> --source-type volume --name first-backup --labels state=original,test=true

  3. List backups with limit and label selector
    stackit volume backup list --limit 5 --label-selector state=original

  4. Update backup with new name and labels
    stackit volume backup update <BACKUP_ID> --name updated-backup --labels state=modified,test=true

  5. Describe backup to verify changes
    stackit volume backup describe <BACKUP_ID>

  6. Restore backup
    stackit volume backup restore <BACKUP_ID>

  7. Clean up by deleting backup
    stackit volume backup delete <BACKUP_ID>

  8. Clean up by deleting volume
    stackit volume delete <VOLUME_ID>

args: args{},
wantErr: true,
},
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider adding a test case which tests an empty iaas backup in the slice

args: args{
	backups: []iaas.Backup{{}},
},

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, now you don't have a testcase for an empty slice anymore...

@rubenhoenle
Copy link
Member

@Benjosh95 please check the failing CI pipeline

@Benjosh95 Benjosh95 marked this pull request as ready for review June 2, 2025 17:14
@Benjosh95 Benjosh95 requested review from marceljk and Fyusel as code owners June 2, 2025 17:14
@Benjosh95
Copy link
Author

@Benjosh95 please check the failing CI pipeline

CI pipeline fails due to missing/undefined waiters that will be merged with this PR: stackitcloud/stackit-sdk-go#2669

func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateBackupRequest {
req := apiClient.CreateBackup(ctx, model.ProjectId)

// Convert map[string]string to map[string]interface{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is duplicated already 20 times across the codebase: https://github.com/search?q=repo%3Astackitcloud%2Fstackit-cli%20%2F%2F%20Convert%20map%5Bstring%5Dstring%20to%20map%5Bstring%5Dinterface%7B%7D&type=code

Please create a util function for it.

You can also create a Jira ticket in the backlog instead if you prefer. But we shouldn't continue to work like that.

wantErr bool
}{
{
name: "empty backup",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: "empty backup",
name: "backup is nil",

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty backup is another testcase which you should add IMO


default:
if async {
p.Outputf("Triggered backup of %s in %s. Backup ID: %s\n", sourceLabel, projectLabel, *resp.Id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will panic if resp.Id is nil, see my comment below about the missing testcase

table := tables.NewTable()
table.SetHeader("ID", "NAME", "SIZE", "STATUS", "SNAPSHOT ID", "VOLUME ID", "AVAILABILITY ZONE", "LABELS", "CREATED AT", "UPDATED AT")

for i := range backups {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for i := range backups {
for _, backup := range backups {

or did I miss something? 😄

if backup != nil && backup.VolumeId != nil {
volume, err := apiClient.GetVolume(ctx, model.ProjectId, *backup.VolumeId).Execute()
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get volume details: %v", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the line sourceLabel = *backup.VolumeId is the same as in the else branch.
Just put this line before the whole if/else if/ else block.

Then you can save yourself the duplication and the else block :)

s.Stop()
}

projectLabel := model.ProjectId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the point in creating a new variable here

Comment on lines +48 to +57
examples.NewExample(
`Create a backup from a volume`,
"$ stackit volume backup create --source-id xxx --source-type volume --project-id xxx"),
examples.NewExample(
`Create a backup from a snapshot with a name`,
"$ stackit volume backup create --source-id xxx --source-type snapshot --name my-backup --project-id xxx"),
examples.NewExample(
`Create a backup with labels`,
"$ stackit volume backup create --source-id xxx --source-type volume --labels key1=value1,key2=value2 --project-id xxx"),
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI has also an option to configure the project-id global for all commands. The project-id can be overwritten in the commands with the flag, but for the examples I wouldn't add the flag to stay consistent with the other commands and their examples

Suggested change
examples.NewExample(
`Create a backup from a volume`,
"$ stackit volume backup create --source-id xxx --source-type volume --project-id xxx"),
examples.NewExample(
`Create a backup from a snapshot with a name`,
"$ stackit volume backup create --source-id xxx --source-type snapshot --name my-backup --project-id xxx"),
examples.NewExample(
`Create a backup with labels`,
"$ stackit volume backup create --source-id xxx --source-type volume --labels key1=value1,key2=value2 --project-id xxx"),
),
examples.NewExample(
`Create a backup from a volume`,
"$ stackit volume backup create --source-id xxx --source-type volume"),
examples.NewExample(
`Create a backup from a snapshot with a name`,
"$ stackit volume backup create --source-id xxx --source-type snapshot --name my-backup"),
examples.NewExample(
`Create a backup with labels`,
"$ stackit volume backup create --source-id xxx --source-type volume --labels key1=value1,key2=value2"),
),


func configureFlags(cmd *cobra.Command) {
cmd.Flags().String(sourceIdFlag, "", "ID of the source from which a backup should be created")
cmd.Flags().String(sourceTypeFlag, "", "Source type of the backup (volume or snapshot)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be configured with flags.EnumFlag(). See also:

cmd.Flags().Var(flags.EnumFlag(false, "", typeFlagOptions...), typeFlag, fmt.Sprintf("Instance type, one of %q", typeFlagOptions))

This as the advantage, that you can define the allowed values in a slice and reuse it also for the validation and description. And in the future it can easily be extended by just adding a value to the slice

Comment on lines +39 to +40
`Delete a backup`,
"$ stackit volume backup delete xxx-xxx-xxx"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nitpick to stay consistent with the existing commands

Suggested change
`Delete a backup`,
"$ stackit volume backup delete xxx-xxx-xxx"),
`Delete a backup with ID "xxx"`,
"$ stackit volume backup delete xxx"),

Comment on lines +42 to +43
`Delete a backup and wait for deletion to be completed`,
"$ stackit volume backup delete xxx-xxx-xxx --async=false"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async flag can be globally configured like the projectId and is on default already false. So compared to the previous example there is no difference.
I think it's fine to have just one example for the delete command. For other delete commands we also have only one example

Comment on lines +40 to +41
`Get details of a backup`,
"$ stackit volume backup describe xxx-xxx-xxx"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`Get details of a backup`,
"$ stackit volume backup describe xxx-xxx-xxx"),
`Get details of a backup with ID "xxx"`,
"$ stackit volume backup describe xxx"),

Comment on lines +39 to +40
`Restore a backup`,
"$ stackit volume backup restore xxx-xxx-xxx"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`Restore a backup`,
"$ stackit volume backup restore xxx-xxx-xxx"),
`Restore a backup with ID "xxx"`,
"$ stackit volume backup restore xxx"),

Comment on lines +59 to +62
backup, err := apiClient.GetBackup(ctx, model.ProjectId, model.BackupId).Execute()
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get backup details: %v", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move this into a utils function. You can reuse it then also for the update functions, to prompt if the wants to update the backup with the name "yyy"

s.Stop()
}

projectLabel := model.ProjectId
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line isn't required. You can just use model.ProjectId in the params.Printer.Info() calls

Comment on lines +44 to +45
`Update a backup name`,
"$ stackit volume backup update xxx-xxx-xxx --name new-name"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`Update a backup name`,
"$ stackit volume backup update xxx-xxx-xxx --name new-name"),
`Update the name of a backup with ID "xxx"`,
"$ stackit volume backup update xxx --name new-name"),

Comment on lines +47 to +48
`Update backup labels`,
"$ stackit volume backup update xxx-xxx-xxx --labels key1=value1,key2=value2"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`Update backup labels`,
"$ stackit volume backup update xxx-xxx-xxx --labels key1=value1,key2=value2"),
`Update the labels of a backup with ID "xxx"`,
"$ stackit volume backup update xxx --labels key1=value1,key2=value2"),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants