diff --git a/docs/stackit_volume_create.md b/docs/stackit_volume_create.md index d10e8e992..0b22962a3 100644 --- a/docs/stackit_volume_create.md +++ b/docs/stackit_volume_create.md @@ -24,20 +24,31 @@ stackit volume create [flags] Create a volume with availability zone "eu01-1", performance class "storage_premium_perf1" and size 64 GB $ stackit volume create --availability-zone eu01-1 --performance-class storage_premium_perf1 --size 64 + + Create an encrypted volume using a STACKIT KMS key + $ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com + + Create an encrypted volume with a predefined base64-encoded secret read from a file + $ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com --encryption-key-payload-base64 @./secret.txt ``` ### Options ``` - --availability-zone string Availability zone - --description string Volume description - -h, --help Help for "stackit volume create" - --labels stringToString Labels are key-value string pairs which can be attached to a volume. E.g. '--labels key1=value1,key2=value2,...' (default []) - -n, --name string Volume name - --performance-class string Performance class - --size int Volume size (GB). Either 'size' or the 'source-id' and 'source-type' flags must be given - --source-id string ID of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given - --source-type string Type of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given + --availability-zone string Availability zone + --description string Volume description + --encryption-kek-key-id string UUID of the STACKIT KMS key used for encryption + --encryption-kek-key-version int Version of the STACKIT KMS key used for encryption + --encryption-kek-keyring-id string UUID of the STACKIT KMS keyring containing the key + --encryption-key-payload-base64 secret Encryption-Key-Payload-Base64. Use @ to read from a file (example: @./secret.txt), or - to read from stdin (hidden input on a terminal). Literal values are not supported. Must be base64 encoded. If omitted, the API generates a random secret. + --encryption-service-account string Service account linked to the STACKIT KMS key + -h, --help Help for "stackit volume create" + --labels stringToString Labels are key-value string pairs which can be attached to a volume. E.g. '--labels key1=value1,key2=value2,...' (default []) + -n, --name string Volume name + --performance-class string Performance class + --size int Volume size (GB). Either 'size' or the 'source-id' and 'source-type' flags must be given + --source-id string ID of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given + --source-type string Type of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given ``` ### Options inherited from parent commands diff --git a/docs/stackit_volume_describe.md b/docs/stackit_volume_describe.md index f8df34be6..93246a88b 100644 --- a/docs/stackit_volume_describe.md +++ b/docs/stackit_volume_describe.md @@ -4,7 +4,7 @@ Shows details of a volume ### Synopsis -Shows details of a volume. +Shows details of a volume, including its encryption status. ``` stackit volume describe VOLUME_ID [flags] diff --git a/internal/cmd/volume/create/create.go b/internal/cmd/volume/create/create.go index 767f69e5f..3ca99cf8c 100644 --- a/internal/cmd/volume/create/create.go +++ b/internal/cmd/volume/create/create.go @@ -32,6 +32,12 @@ const ( sizeFlag = "size" sourceIdFlag = "source-id" sourceTypeFlag = "source-type" + + encryptionKekKeyIdFlag = "encryption-kek-key-id" + encryptionKekKeyringIdFlag = "encryption-kek-keyring-id" + encryptionKekKeyVersionFlag = "encryption-kek-key-version" + encryptionServiceAccountFlag = "encryption-service-account" + encryptionKeyPayloadFlag = "encryption-key-payload-base64" ) type inputModel struct { @@ -44,6 +50,12 @@ type inputModel struct { Size *int64 SourceId *string SourceType *string + + EncryptionKekKeyId *string + EncryptionKekKeyringId *string + EncryptionKekKeyVersion *int64 + EncryptionServiceAccount *string + EncryptionKeyPayload *string `json:"-"` } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -69,6 +81,14 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `Create a volume with availability zone "eu01-1", performance class "storage_premium_perf1" and size 64 GB`, `$ stackit volume create --availability-zone eu01-1 --performance-class storage_premium_perf1 --size 64`, ), + examples.NewExample( + `Create an encrypted volume using a STACKIT KMS key`, + `$ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com`, + ), + examples.NewExample( + `Create an encrypted volume with a predefined base64-encoded secret read from a file`, + `$ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com --encryption-key-payload-base64 @./secret.txt`, + ), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -96,7 +116,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } // Call API - req := buildRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient.DefaultAPI) resp, err := req.Execute() if err != nil { return fmt.Errorf("create volume : %w", err) @@ -117,11 +137,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return outputResult(params.Printer, model, projectLabel, resp) }, } - configureFlags(cmd) + configureFlags(cmd, params) return cmd } -func configureFlags(cmd *cobra.Command) { +func configureFlags(cmd *cobra.Command, params *types.CmdParams) { cmd.Flags().String(availabilityZoneFlag, "", "Availability zone") cmd.Flags().StringP(nameFlag, "n", "", "Volume name") cmd.Flags().String(descriptionFlag, "", "Volume description") @@ -131,6 +151,15 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(sourceIdFlag, "", "ID of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given") cmd.Flags().String(sourceTypeFlag, "", "Type of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given") + cmd.Flags().Var(flags.UUIDFlag(), encryptionKekKeyIdFlag, "UUID of the STACKIT KMS key used for encryption") + cmd.Flags().Var(flags.UUIDFlag(), encryptionKekKeyringIdFlag, "UUID of the STACKIT KMS keyring containing the key") + cmd.Flags().Int64(encryptionKekKeyVersionFlag, 0, "Version of the STACKIT KMS key used for encryption") + cmd.Flags().String(encryptionServiceAccountFlag, "", "Service account linked to the STACKIT KMS key") + + keyPayload := flags.SecretFlag(encryptionKeyPayloadFlag, params) + cmd.Flags().Var(keyPayload, encryptionKeyPayloadFlag, keyPayload.Usage()+" Must be base64 encoded. If omitted, the API generates a random secret.") + + cmd.MarkFlagsRequiredTogether(encryptionKekKeyIdFlag, encryptionKekKeyringIdFlag, encryptionKekKeyVersionFlag, encryptionServiceAccountFlag) err := flags.MarkFlagsRequired(cmd, availabilityZoneFlag) cobra.CheckErr(err) } @@ -151,14 +180,33 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, Size: flags.FlagToInt64Pointer(p, cmd, sizeFlag), SourceId: flags.FlagToStringPointer(p, cmd, sourceIdFlag), SourceType: flags.FlagToStringPointer(p, cmd, sourceTypeFlag), + + EncryptionKekKeyId: flags.FlagToStringPointer(p, cmd, encryptionKekKeyIdFlag), + EncryptionKekKeyringId: flags.FlagToStringPointer(p, cmd, encryptionKekKeyringIdFlag), + EncryptionKekKeyVersion: flags.FlagToInt64Pointer(p, cmd, encryptionKekKeyVersionFlag), + EncryptionServiceAccount: flags.FlagToStringPointer(p, cmd, encryptionServiceAccountFlag), } + // Cobra requires the encryption parameters together, so checking one is sufficient. + if cmd.Flags().Changed(encryptionKeyPayloadFlag) && model.EncryptionKekKeyId == nil { + return nil, &cliErr.FlagValidationError{ + Flag: encryptionKekKeyIdFlag, + Details: fmt.Sprintf("when using flag %q, flag %q is required", encryptionKeyPayloadFlag, encryptionKekKeyIdFlag), + } + } + + keyPayload, err := flags.ResolveSecretFlag(p, cmd, encryptionKeyPayloadFlag) + if err != nil { + return nil, err + } + model.EncryptionKeyPayload = keyPayload + p.DebugInputModel(model) return &model, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateVolumeRequest { - req := apiClient.DefaultAPI.CreateVolume(ctx, model.ProjectId, model.Region) +func buildRequest(ctx context.Context, model *inputModel, apiClient iaas.DefaultAPI) iaas.ApiCreateVolumeRequest { + req := apiClient.CreateVolume(ctx, model.ProjectId, model.Region) payload := iaas.CreateVolumePayload{ AvailabilityZone: model.AvailabilityZone, @@ -176,6 +224,16 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli } } + if model.EncryptionKekKeyId != nil && model.EncryptionKekKeyringId != nil && model.EncryptionKekKeyVersion != nil && model.EncryptionServiceAccount != nil { + payload.EncryptionParameters = &iaas.VolumeEncryptionParameter{ + KekKeyId: *model.EncryptionKekKeyId, + KekKeyringId: *model.EncryptionKekKeyringId, + KekKeyVersion: *model.EncryptionKekKeyVersion, + ServiceAccount: *model.EncryptionServiceAccount, + KeyPayload: model.EncryptionKeyPayload, + } + } + return req.CreateVolumePayload(payload) } diff --git a/internal/cmd/volume/create/create_test.go b/internal/cmd/volume/create/create_test.go index 3848a5e1f..fb84cbe11 100644 --- a/internal/cmd/volume/create/create_test.go +++ b/internal/cmd/volume/create/create_test.go @@ -2,16 +2,20 @@ package create import ( "context" + "strings" "testing" + "testing/fstest" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" + "github.com/spf13/cobra" iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) @@ -22,10 +26,49 @@ const ( type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} +var testClient = &iaas.DefaultAPIServiceMock{} var testProjectId = uuid.NewString() var testSourceId = uuid.NewString() +var testKeyId = uuid.NewString() +var testKeyringId = uuid.NewString() + +func fixtureEncryptionFlagValues(mods ...func(map[string]string)) map[string]string { + values := fixtureFlagValues() + values[encryptionKekKeyIdFlag] = testKeyId + values[encryptionKekKeyringIdFlag] = testKeyringId + values[encryptionKekKeyVersionFlag] = "1" + values[encryptionServiceAccountFlag] = "volume-kms@example.com" + for _, mod := range mods { + mod(values) + } + return values +} + +func fixtureEncryptedInputModel(mods ...func(*inputModel)) *inputModel { + model := fixtureInputModel() + model.EncryptionKekKeyId = new(testKeyId) + model.EncryptionKekKeyringId = new(testKeyringId) + model.EncryptionKekKeyVersion = new(int64(1)) + model.EncryptionServiceAccount = new("volume-kms@example.com") + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureEncryptionParameters(mods ...func(*iaas.VolumeEncryptionParameter)) *iaas.VolumeEncryptionParameter { + parameters := &iaas.VolumeEncryptionParameter{ + KekKeyId: testKeyId, + KekKeyringId: testKeyringId, + KekKeyVersion: 1, + ServiceAccount: "volume-kms@example.com", + } + for _, mod := range mods { + mod(parameters) + } + return parameters +} func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ @@ -72,7 +115,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *iaas.ApiCreateVolumeRequest)) iaas.ApiCreateVolumeRequest { - request := testClient.DefaultAPI.CreateVolume(testCtx, testProjectId, testRegion) + request := testClient.CreateVolume(testCtx, testProjectId, testRegion) request = request.CreateVolumePayload(fixturePayload()) for _, mod := range mods { mod(&request) @@ -81,7 +124,7 @@ func fixtureRequest(mods ...func(request *iaas.ApiCreateVolumeRequest)) iaas.Api } func fixtureRequiredRequest(mods ...func(request *iaas.ApiCreateVolumeRequest)) iaas.ApiCreateVolumeRequest { - request := testClient.DefaultAPI.CreateVolume(testCtx, testProjectId, testRegion) + request := testClient.CreateVolume(testCtx, testProjectId, testRegion) request = request.CreateVolumePayload(iaas.CreateVolumePayload{ AvailabilityZone: "eu01-1", }) @@ -113,9 +156,13 @@ func fixturePayload(mods ...func(payload *iaas.CreateVolumePayload)) iaas.Create } func TestParseInput(t *testing.T) { + const secretFile = "secret.txt" + // #nosec G101 // test-only secret value. + const secret = "c2VjcmV0" tests := []struct { description string argValues []string + stdin string flagValues map[string]string isValid bool expectedModel *inputModel @@ -126,6 +173,64 @@ func TestParseInput(t *testing.T) { isValid: true, expectedModel: fixtureInputModel(), }, + { + description: "encryption with generated secret", + flagValues: fixtureEncryptionFlagValues(), + isValid: true, + expectedModel: fixtureEncryptedInputModel(), + }, + { + description: "encryption with predefined secret from file", + flagValues: fixtureEncryptionFlagValues(func(values map[string]string) { + values[encryptionKeyPayloadFlag] = "@" + secretFile + }), + isValid: true, + expectedModel: fixtureEncryptedInputModel(func(model *inputModel) { + model.EncryptionKeyPayload = new(secret) + }), + }, + { + description: "missing key id", + flagValues: fixtureEncryptionFlagValues(func(values map[string]string) { + delete(values, encryptionKekKeyIdFlag) + }), + }, + { + description: "missing keyring id", + flagValues: fixtureEncryptionFlagValues(func(values map[string]string) { + delete(values, encryptionKekKeyringIdFlag) + }), + }, + { + description: "missing key version", + flagValues: fixtureEncryptionFlagValues(func(values map[string]string) { + delete(values, encryptionKekKeyVersionFlag) + }), + }, + { + description: "missing service account", + flagValues: fixtureEncryptionFlagValues(func(values map[string]string) { + delete(values, encryptionServiceAccountFlag) + }), + }, + { + description: "invalid key id", + flagValues: fixtureEncryptionFlagValues(func(values map[string]string) { + values[encryptionKekKeyIdFlag] = "invalid" + }), + }, + { + description: "invalid keyring id", + flagValues: fixtureEncryptionFlagValues(func(values map[string]string) { + values[encryptionKekKeyringIdFlag] = "invalid" + }), + }, + { + description: "payload without encryption parameters", + flagValues: fixtureFlagValues(func(values map[string]string) { + values[encryptionKeyPayloadFlag] = "@secret.txt" + }), + }, { description: "required only", flagValues: fixtureFlagValues(func(flagValues map[string]string) { @@ -189,8 +294,8 @@ func TestParseInput(t *testing.T) { }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.PerformanceClass = utils.Ptr("example-perf-class") - model.Size = utils.Ptr(int64(5)) + model.PerformanceClass = new("example-perf-class") + model.Size = new(int64(5)) }), }, { @@ -202,14 +307,20 @@ func TestParseInput(t *testing.T) { isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { model.SourceId = &testSourceId - model.SourceType = utils.Ptr("example-source-type") + model.SourceType = new("example-source-type") }), }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + testutils.TestParseInput(t, func(params *types.CmdParams) *cobra.Command { + params.Fs = fstest.MapFS{ + secretFile: {Data: []byte(secret)}, + } + params.Printer.StdIn = strings.NewReader(tt.stdin) + return NewCmd(params) + }, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) }) } } @@ -225,6 +336,28 @@ func TestBuildRequest(t *testing.T) { model: fixtureInputModel(), expectedRequest: fixtureRequest(), }, + { + description: "encryption with generated secret", + model: fixtureEncryptedInputModel(), + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateVolumeRequest) { + *request = request.CreateVolumePayload(fixturePayload(func(payload *iaas.CreateVolumePayload) { + payload.EncryptionParameters = fixtureEncryptionParameters() + })) + }), + }, + { + description: "encryption with predefined secret", + model: fixtureEncryptedInputModel(func(model *inputModel) { + model.EncryptionKeyPayload = new("c2VjcmV0") + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateVolumeRequest) { + *request = request.CreateVolumePayload(fixturePayload(func(payload *iaas.CreateVolumePayload) { + payload.EncryptionParameters = fixtureEncryptionParameters(func(parameters *iaas.VolumeEncryptionParameter) { + parameters.KeyPayload = new("c2VjcmV0") + }) + })) + }), + }, { description: "only availability zone in payload", model: &inputModel{ @@ -245,7 +378,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), - cmpopts.EquateComparable(testCtx, iaas.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx, testClient), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/volume/describe/describe.go b/internal/cmd/volume/describe/describe.go index 1ac849084..37942307a 100644 --- a/internal/cmd/volume/describe/describe.go +++ b/internal/cmd/volume/describe/describe.go @@ -34,7 +34,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("describe %s", volumeIdArg), Short: "Shows details of a volume", - Long: "Shows details of a volume.", + Long: "Shows details of a volume, including its encryption status.", Args: args.SingleArg(volumeIdArg, utils.ValidateUUID), Example: examples.Build( examples.NewExample( @@ -60,7 +60,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } // Call API - req := buildRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient.DefaultAPI) resp, err := req.Execute() if err != nil { return fmt.Errorf("read volume: %w", err) @@ -89,8 +89,8 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return &model, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetVolumeRequest { - return apiClient.DefaultAPI.GetVolume(ctx, model.ProjectId, model.Region, model.VolumeId) +func buildRequest(ctx context.Context, model *inputModel, apiClient iaas.DefaultAPI) iaas.ApiGetVolumeRequest { + return apiClient.GetVolume(ctx, model.ProjectId, model.Region, model.VolumeId) } func outputResult(p *print.Printer, outputFormat string, volume *iaas.Volume) error { @@ -112,6 +112,9 @@ func outputResult(p *print.Printer, outputFormat string, volume *iaas.Volume) er table.AddRow("AVAILABILITY ZONE", volume.AvailabilityZone) table.AddSeparator() + table.AddRow("ENCRYPTED", utils.PtrString(volume.Encrypted)) + table.AddSeparator() + if volume.Source != nil { sourceId := volume.Source.Id table.AddRow("SOURCE", sourceId) diff --git a/internal/cmd/volume/describe/describe_test.go b/internal/cmd/volume/describe/describe_test.go index 70ba621f0..27b6b07a5 100644 --- a/internal/cmd/volume/describe/describe_test.go +++ b/internal/cmd/volume/describe/describe_test.go @@ -21,7 +21,7 @@ const ( type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} +var testClient = &iaas.DefaultAPIServiceMock{} var testProjectId = uuid.NewString() var testVolumeId = uuid.NewString() @@ -62,7 +62,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *iaas.ApiGetVolumeRequest)) iaas.ApiGetVolumeRequest { - request := testClient.DefaultAPI.GetVolume(testCtx, testProjectId, testRegion, testVolumeId) + request := testClient.GetVolume(testCtx, testProjectId, testRegion, testVolumeId) for _, mod := range mods { mod(&request) } @@ -166,7 +166,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), - cmpopts.EquateComparable(testCtx, iaas.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx, testClient), ) if diff != "" { t.Fatalf("Data does not match: %s", diff) diff --git a/internal/cmd/volume/list/list.go b/internal/cmd/volume/list/list.go index cf2cb500c..aff17423e 100644 --- a/internal/cmd/volume/list/list.go +++ b/internal/cmd/volume/list/list.go @@ -70,7 +70,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } // Call API - req := buildRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient.DefaultAPI) resp, err := req.Execute() if err != nil { return fmt.Errorf("list volumes: %w", err) @@ -125,8 +125,8 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, return &model, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListVolumesRequest { - req := apiClient.DefaultAPI.ListVolumes(ctx, model.ProjectId, model.Region) +func buildRequest(ctx context.Context, model *inputModel, apiClient iaas.DefaultAPI) iaas.ApiListVolumesRequest { + req := apiClient.ListVolumes(ctx, model.ProjectId, model.Region) if model.LabelSelector != nil { req = req.LabelSelector(*model.LabelSelector) } @@ -141,7 +141,7 @@ func outputResult(p *print.Printer, outputFormat, projectLabel string, volumes [ return nil } table := tables.NewTable() - table.SetHeader("ID", "Name", "Status", "Server", "Availability Zone", "Size (GB)") + table.SetHeader("ID", "Name", "Status", "Server", "Availability Zone", "Size (GB)", "Encrypted") for i := range volumes { volume := volumes[i] @@ -152,6 +152,7 @@ func outputResult(p *print.Printer, outputFormat, projectLabel string, volumes [ utils.PtrString(volume.ServerId), volume.AvailabilityZone, utils.PtrString(volume.Size), + utils.PtrString(volume.Encrypted), ) table.AddSeparator() } diff --git a/internal/cmd/volume/list/list_test.go b/internal/cmd/volume/list/list_test.go index 65667f341..0fb0e0712 100644 --- a/internal/cmd/volume/list/list_test.go +++ b/internal/cmd/volume/list/list_test.go @@ -22,7 +22,7 @@ const ( type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} +var testClient = &iaas.DefaultAPIServiceMock{} var testProjectId = uuid.NewString() var testLabelSelector = "label" @@ -57,7 +57,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *iaas.ApiListVolumesRequest)) iaas.ApiListVolumesRequest { - request := testClient.DefaultAPI.ListVolumes(testCtx, testProjectId, testRegion) + request := testClient.ListVolumes(testCtx, testProjectId, testRegion) request = request.LabelSelector(testLabelSelector) for _, mod := range mods { mod(&request) @@ -162,7 +162,7 @@ func TestBuildRequest(t *testing.T) { diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), - cmpopts.EquateComparable(testCtx, iaas.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx, testClient), ) if diff != "" { t.Fatalf("Data does not match: %s", diff)