diff --git a/docs/stackit_mongodbflex_instance_create.md b/docs/stackit_mongodbflex_instance_create.md index a3cb372a..e8303cc0 100644 --- a/docs/stackit_mongodbflex_instance_create.md +++ b/docs/stackit_mongodbflex_instance_create.md @@ -36,7 +36,7 @@ stackit mongodbflex instance create [flags] --storage-class string Storage class (default "premium-perf2-mongodb") --storage-size int Storage size (in GB) (default 10) --type string Instance type, one of ["Replica" "Sharded" "Single"] (default "Replica") - --version string Version (default "6.0") + --version string MongoDB version. Defaults to the latest version available ``` ### Options inherited from parent commands diff --git a/internal/cmd/mongodbflex/instance/create/create.go b/internal/cmd/mongodbflex/instance/create/create.go index 1c96c251..e51b4353 100644 --- a/internal/cmd/mongodbflex/instance/create/create.go +++ b/internal/cmd/mongodbflex/instance/create/create.go @@ -38,7 +38,6 @@ const ( defaultStorageClass = "premium-perf2-mongodb" defaultStorageSize = 10 defaultType = "Replica" - defaultVersion = "6.0" ) type inputModel struct { @@ -100,6 +99,15 @@ func NewCmd() *cobra.Command { } } + // Fill in version, if needed + if model.Version == nil { + version, err := mongodbflexUtils.GetLatestMongoDBVersion(ctx, apiClient, model.ProjectId) + if err != nil { + return fmt.Errorf("get latest MongoDB version: %w", err) + } + model.Version = &version + } + // Call API req, err := buildRequest(ctx, model, apiClient) if err != nil { @@ -145,7 +153,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(ramFlag, 0, "Amount of RAM (in GB)") cmd.Flags().String(storageClassFlag, defaultStorageClass, "Storage class") cmd.Flags().Int64(storageSizeFlag, defaultStorageSize, "Storage size (in GB)") - cmd.Flags().String(versionFlag, defaultVersion, "Version") + cmd.Flags().String(versionFlag, "", "MongoDB version. Defaults to the latest version available") cmd.Flags().Var(flags.EnumFlag(false, defaultType, typeFlagOptions...), typeFlag, fmt.Sprintf("Instance type, one of %q", typeFlagOptions)) err := flags.MarkFlagsRequired(cmd, instanceNameFlag, aclFlag) @@ -187,7 +195,7 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { RAM: ram, StorageClass: utils.Ptr(flags.FlagWithDefaultToStringValue(cmd, storageClassFlag)), StorageSize: &storageSize, - Version: utils.Ptr(flags.FlagWithDefaultToStringValue(cmd, versionFlag)), + Version: flags.FlagToStringPointer(cmd, versionFlag), Type: utils.Ptr(flags.FlagWithDefaultToStringValue(cmd, typeFlag)), }, nil } diff --git a/internal/cmd/mongodbflex/instance/create/create_test.go b/internal/cmd/mongodbflex/instance/create/create_test.go index d9ae6a3d..44fbaa2d 100644 --- a/internal/cmd/mongodbflex/instance/create/create_test.go +++ b/internal/cmd/mongodbflex/instance/create/create_test.go @@ -136,7 +136,6 @@ func TestParseInput(t *testing.T) { description: "with defaults", flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, backupScheduleFlag) - delete(flagValues, versionFlag) delete(flagValues, typeFlag) }), isValid: true, @@ -204,6 +203,16 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, + { + description: "no version", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, versionFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Version = nil + }), + }, { description: "repeated acl flags", flagValues: fixtureFlagValues(), diff --git a/internal/cmd/postgresflex/instance/create/create.go b/internal/cmd/postgresflex/instance/create/create.go index 5650e949..848ea4f0 100644 --- a/internal/cmd/postgresflex/instance/create/create.go +++ b/internal/cmd/postgresflex/instance/create/create.go @@ -99,7 +99,7 @@ func NewCmd() *cobra.Command { } } - // Fill in defautl version, if needed + // Fill in version, if needed if model.Version == nil { version, err := postgresflexUtils.GetLatestPostgreSQLVersion(ctx, apiClient, model.ProjectId) if err != nil { diff --git a/internal/pkg/services/mongodbflex/utils/utils.go b/internal/pkg/services/mongodbflex/utils/utils.go index 7c1c574a..0536a724 100644 --- a/internal/pkg/services/mongodbflex/utils/utils.go +++ b/internal/pkg/services/mongodbflex/utils/utils.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "golang.org/x/mod/semver" "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" ) @@ -17,14 +18,6 @@ var instanceTypeToReplicas = map[string]int64{ "Sharded": 9, } -func GetUserName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId, userId string) (string, error) { - resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) - if err != nil { - return "", fmt.Errorf("get MongoDBFlex user: %w", err) - } - return *resp.Item.Username, nil -} - func AvailableInstanceTypes() []string { instanceTypes := make([]string, len(instanceTypeToReplicas)) i := 0 @@ -118,10 +111,33 @@ func LoadFlavorId(cpu, ram int64, flavors *[]mongodbflex.HandlersInfraFlavor) (* } 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) } +func GetLatestMongoDBVersion(ctx context.Context, apiClient MongoDBFlexClient, projectId string) (string, error) { + resp, err := apiClient.ListVersionsExecute(ctx, projectId) + if err != nil { + return "", fmt.Errorf("get MongoDB versions: %w", err) + } + versions := *resp.Versions + + latestVersion := "0" + for i := range versions { + oldSemVer := fmt.Sprintf("v%s", latestVersion) + newSemVer := fmt.Sprintf("v%s", versions[i]) + if semver.Compare(newSemVer, oldSemVer) != 1 { + continue + } + latestVersion = versions[i] + } + if latestVersion == "0" { + return "", fmt.Errorf("no MongoDB versions found") + } + return latestVersion, nil +} + func GetInstanceName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId string) (string, error) { resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId) if err != nil { @@ -129,3 +145,11 @@ func GetInstanceName(ctx context.Context, apiClient MongoDBFlexClient, projectId } return *resp.Item.Name, nil } + +func GetUserName(ctx context.Context, apiClient MongoDBFlexClient, projectId, instanceId, userId string) (string, error) { + resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) + if err != nil { + return "", fmt.Errorf("get MongoDBFlex user: %w", err) + } + return *resp.Item.Username, nil +} diff --git a/internal/pkg/services/mongodbflex/utils/utils_test.go b/internal/pkg/services/mongodbflex/utils/utils_test.go index 604a8fe8..c8339e82 100644 --- a/internal/pkg/services/mongodbflex/utils/utils_test.go +++ b/internal/pkg/services/mongodbflex/utils/utils_test.go @@ -24,10 +24,19 @@ const ( ) type mongoDBFlexClientMocked struct { - getInstanceFails bool - getInstanceResp *mongodbflex.GetInstanceResponse - getUserFails bool - getUserResp *mongodbflex.GetUserResponse + listVersionsFails bool + listVersionsResp *mongodbflex.ListVersionsResponse + getInstanceFails bool + getInstanceResp *mongodbflex.GetInstanceResponse + getUserFails bool + getUserResp *mongodbflex.GetUserResponse +} + +func (m *mongoDBFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*mongodbflex.ListVersionsResponse, error) { + if m.listVersionsFails { + return nil, fmt.Errorf("could not list versions") + } + return m.listVersionsResp, nil } func (m *mongoDBFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*mongodbflex.GetInstanceResponse, error) { @@ -355,6 +364,61 @@ func TestLoadFlavorId(t *testing.T) { } } +func TestGetLatestPostgreSQLVersion(t *testing.T) { + tests := []struct { + description string + listVersionsFails bool + listVersionsResp *mongodbflex.ListVersionsResponse + isValid bool + expectedOutput string + }{ + { + description: "base", + listVersionsResp: &mongodbflex.ListVersionsResponse{ + Versions: &[]string{"8", "10", "9"}, + }, + isValid: true, + expectedOutput: "10", + }, + { + description: "get instance fails", + listVersionsFails: true, + isValid: false, + }, + { + description: "no versions", + listVersionsResp: &mongodbflex.ListVersionsResponse{ + Versions: &[]string{}, + }, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &mongoDBFlexClientMocked{ + listVersionsFails: tt.listVersionsFails, + listVersionsResp: tt.listVersionsResp, + } + + output, err := GetLatestMongoDBVersion(context.Background(), client, testProjectId) + + 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) + } + }) + } +} + func TestGetInstanceName(t *testing.T) { tests := []struct { description string