From 34b107e062221ef902e7d5799f39c8cc86208351 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Mon, 7 Sep 2026 20:30:39 +0200 Subject: [PATCH 01/13] Added functionality for model experiments on Stackit CLI --- go.mod | 1 + go.sum | 2 + .../instance/create/create.go | 155 ++++++++++ .../instance/create/create_test.go | 178 +++++++++++ .../instance/delete/delete.go | 136 +++++++++ .../instance/delete/delete_test.go | 179 +++++++++++ .../cmd/modelexperiments/instance/get/get.go | 124 ++++++++ .../modelexperiments/instance/get/get_test.go | 181 +++++++++++ .../cmd/modelexperiments/instance/instance.go | 35 +++ .../modelexperiments/instance/list/list.go | 116 ++++++++ .../instance/list/list_test.go | 176 +++++++++++ .../modelexperiments/instance/patch/patch.go | 207 +++++++++++++ .../instance/patch/patch_test.go | 281 ++++++++++++++++++ .../cmd/modelexperiments/modelexperiments.go | 29 ++ .../modelexperiments/token/create/create.go | 106 +++++++ .../token/create/create_test.go | 100 +++++++ .../modelexperiments/token/delete/delete.go | 81 +++++ .../token/delete/delete_test.go | 63 ++++ .../cmd/modelexperiments/token/get/get.go | 85 ++++++ .../modelexperiments/token/get/get_test.go | 73 +++++ .../cmd/modelexperiments/token/list/list.go | 75 +++++ .../modelexperiments/token/list/list_test.go | 74 +++++ .../cmd/modelexperiments/token/patch/patch.go | 107 +++++++ .../token/patch/patch_test.go | 95 ++++++ internal/cmd/modelexperiments/token/token.go | 19 ++ internal/cmd/root.go | 2 + internal/pkg/config/config.go | 30 +- .../modelexperiments/client/client.go | 14 + .../services/modelexperiments/utils/utils.go | 36 +++ .../modelexperiments/utils/utils_test.go | 96 ++++++ 30 files changed, 2842 insertions(+), 14 deletions(-) create mode 100644 internal/cmd/modelexperiments/instance/create/create.go create mode 100644 internal/cmd/modelexperiments/instance/create/create_test.go create mode 100644 internal/cmd/modelexperiments/instance/delete/delete.go create mode 100644 internal/cmd/modelexperiments/instance/delete/delete_test.go create mode 100644 internal/cmd/modelexperiments/instance/get/get.go create mode 100644 internal/cmd/modelexperiments/instance/get/get_test.go create mode 100644 internal/cmd/modelexperiments/instance/instance.go create mode 100644 internal/cmd/modelexperiments/instance/list/list.go create mode 100644 internal/cmd/modelexperiments/instance/list/list_test.go create mode 100644 internal/cmd/modelexperiments/instance/patch/patch.go create mode 100644 internal/cmd/modelexperiments/instance/patch/patch_test.go create mode 100644 internal/cmd/modelexperiments/modelexperiments.go create mode 100644 internal/cmd/modelexperiments/token/create/create.go create mode 100644 internal/cmd/modelexperiments/token/create/create_test.go create mode 100644 internal/cmd/modelexperiments/token/delete/delete.go create mode 100644 internal/cmd/modelexperiments/token/delete/delete_test.go create mode 100644 internal/cmd/modelexperiments/token/get/get.go create mode 100644 internal/cmd/modelexperiments/token/get/get_test.go create mode 100644 internal/cmd/modelexperiments/token/list/list.go create mode 100644 internal/cmd/modelexperiments/token/list/list_test.go create mode 100644 internal/cmd/modelexperiments/token/patch/patch.go create mode 100644 internal/cmd/modelexperiments/token/patch/patch_test.go create mode 100644 internal/cmd/modelexperiments/token/token.go create mode 100644 internal/pkg/services/modelexperiments/client/client.go create mode 100644 internal/pkg/services/modelexperiments/utils/utils.go create mode 100644 internal/pkg/services/modelexperiments/utils/utils_test.go diff --git a/go.mod b/go.mod index 916453c27..db0195702 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/iaas v1.14.1 github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.1 github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.1 + github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1 github.com/stackitcloud/stackit-sdk-go/services/opensearch v1.3.0 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.13.0 diff --git a/go.sum b/go.sum index 854729b5f..9c6ef14e2 100644 --- a/go.sum +++ b/go.sum @@ -620,6 +620,8 @@ github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.1 h1:sWvLJF6/7Nh3hONp github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.1/go.mod h1:tvRejL8w5KpGBbLFPQ+dXOJURgZ3OMbZmwxlKQrGMuA= github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.0 h1:0JuNaVbyuIUEX5Cn88XoC5FVGU92cP1w3ZLAvCuuohQ= github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.0/go.mod h1:joa89Y1dyn0j22FstRcIKfW2ada3FDxNfttxSvq27uY= +github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 h1:g8AX62NqDLJy2dNPPTzfeaBoaR2K+EIwxF8sRiAESL0= +github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0/go.mod h1:TW2PYG0kSrfAos3yY8wUxDem0J9ZYSXulnbzBZ4BTaE= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1 h1:lLR6Ouu3H21jjyIcAZZc4f2SOBWgHTBEHbn/urtY+9M= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1/go.mod h1:0hHEPiOEMAA23EzEl42Rm3FlyKIzkW+LWLvDkuFTZ+Q= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.9.1 h1:9n1BrPj6gAuKnyJ1OmsN+MEl2DBg9KJHgUfkijN84Rs= diff --git a/internal/cmd/modelexperiments/instance/create/create.go b/internal/cmd/modelexperiments/instance/create/create.go new file mode 100644 index 000000000..9829833fc --- /dev/null +++ b/internal/cmd/modelexperiments/instance/create/create.go @@ -0,0 +1,155 @@ +package create + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "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/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + nameFlag = "name" + descriptionFlag = "description" + labelFlag = "label" + retentionFlag = "deleted-experiment-retention" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + Name string + Description *string + Labels *map[string]string + Retention *string + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates an AI Model Experiments instance", + Long: "Creates an AI Model Experiments (MLflow) instance in your STACKIT project.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create an AI Model Experiments instance with name "my-tracking"`, + `$ stackit ai-model-experiments instance create --name my-tracking`), + examples.NewExample( + `Create an instance with a description and labels`, + `$ stackit ai-model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + prompt := fmt.Sprintf("Are you sure you want to create an AI Model Experiments instance for project %q?", projectLabel) + if err := params.Printer.PromptForConfirmation(prompt); err != nil { + return err + } + + req := buildCreateInstanceRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("create AI Model Experiments instance: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, projectLabel, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().StringP(nameFlag, "n", "", "Instance name") + cmd.Flags().String(descriptionFlag, "", "Instance description") + cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + cmd.Flags().String(retentionFlag, "", `Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d)`) + err := flags.MarkFlagsRequired(cmd, nameFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + labels, err := cmd.Flags().GetStringToString(labelFlag) + if err != nil { + return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) + } + var labelsPtr *map[string]string + if len(labels) > 0 { + labelsPtr = &labels + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Name: flags.FlagToStringValue(p, cmd, nameFlag), + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Labels: labelsPtr, + Retention: flags.FlagToStringPointer(p, cmd, retentionFlag), + Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiCreateInstanceRequest { + req := apiClient.DefaultAPI.CreateInstance(ctx, model.ProjectId, model.Region) + + payload := modelexperiments.CreateInstancePayload{ + Name: model.Name, + } + if model.Description != nil { + payload.Description = model.Description + } + if model.Labels != nil { + payload.Labels = model.Labels + } + if model.Retention != nil { + payload.DeletedExperimentRetention = model.Retention + } + + return req.CreateInstancePayload(payload) +} + +func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *modelexperiments.CreateInstanceResponse) error { + if resp == nil { + return fmt.Errorf("response instance is nil") + } + + return p.OutputResult(outputFormat, resp.Instance, func() error { + p.Outputf("Creating AI Model Experiments instance for project %q. Instance ID: %s\n", projectLabel, resp.Instance.Id) + return nil + }) +} diff --git a/internal/cmd/modelexperiments/instance/create/create_test.go b/internal/cmd/modelexperiments/instance/create/create_test.go new file mode 100644 index 000000000..2bc86ef03 --- /dev/null +++ b/internal/cmd/modelexperiments/instance/create/create_test.go @@ -0,0 +1,178 @@ +package create + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &modelexperiments.APIClient{ + DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, +} +var testProjectId = uuid.NewString() + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + nameFlag: "example", + globalflags.RegionFlag: "eu01", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: "eu01", + Verbosity: globalflags.VerbosityDefault, + }, + Name: "example", + Region: "eu01", + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *modelexperiments.ApiCreateInstanceRequest)) modelexperiments.ApiCreateInstanceRequest { + request := testClient.DefaultAPI.CreateInstance(testCtx, testProjectId, "eu01") + request = request.CreateInstancePayload(modelexperiments.CreateInstancePayload{ + Name: "example", + }) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, nameFlag) + }), + isValid: false, + }, + { + description: "with description and retention", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[descriptionFlag] = "team tracking server" + flagValues[retentionFlag] = "30d" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Description = utils.Ptr("team tracking server") + model.Retention = utils.Ptr("30d") + }), + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildCreateInstanceRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest modelexperiments.ApiCreateInstanceRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "with description", + model: fixtureInputModel(func(model *inputModel) { + model.Description = utils.Ptr("team tracking server") + }), + expectedRequest: fixtureRequest(func(request *modelexperiments.ApiCreateInstanceRequest) { + payload := modelexperiments.CreateInstancePayload{ + Name: "example", + Description: utils.Ptr("team tracking server"), + } + *request = request.CreateInstancePayload(payload) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildCreateInstanceRequest(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) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + tests := []struct { + name string + resp *modelexperiments.CreateInstanceResponse + wantErr bool + }{ + { + name: "nil response", + resp: nil, + wantErr: true, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, "", "test-project", tt.resp); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/modelexperiments/instance/delete/delete.go b/internal/cmd/modelexperiments/instance/delete/delete.go new file mode 100644 index 000000000..4136d4984 --- /dev/null +++ b/internal/cmd/modelexperiments/instance/delete/delete.go @@ -0,0 +1,136 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + instanceIdArg = "INSTANCE_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceId string + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", instanceIdArg), + Short: "Deletes an AI Model Experiments instance", + Long: "Deletes an AI Model Experiments instance from a STACKIT project.", + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Delete an AI Model Experiments instance with ID "xxx"`, + `$ stackit ai-model-experiments instance delete xxx`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + prompt := fmt.Sprintf( + "Are you sure you want to delete AI Model Experiments instance %q?", + model.InstanceId, + ) + + if err := params.Printer.PromptForConfirmation(prompt); err != nil { + return err + } + + req := buildDeleteInstanceRequest(ctx, model, apiClient) + + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("delete AI Model Experiments instance: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + + configureFlags(cmd) + + return cmd +} + +func configureFlags(cmd *cobra.Command) { + _ = flags.MarkFlagsRequired(cmd, globalflags.RegionFlag) +} + +func parseInput( + p *print.Printer, + cmd *cobra.Command, + inputArgs []string, +) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: inputArgs[0], + Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), + } + + p.DebugInputModel(model) + + return &model, nil +} + +func buildDeleteInstanceRequest( + ctx context.Context, + model *inputModel, + apiClient *modelexperiments.APIClient, +) modelexperiments.ApiDeleteInstanceRequest { + return apiClient.DefaultAPI.DeleteInstance( + ctx, + model.ProjectId, + model.Region, + model.InstanceId, + ) +} + +func outputResult( + p *print.Printer, + outputFormat string, + resp *modelexperiments.DeleteInstanceResponse, +) error { + if resp == nil { + return fmt.Errorf("response is nil") + } + + return p.OutputResult(outputFormat, resp.Instance, func() error { + p.Outputf( + "Deleted AI Model Experiments instance.\n", + ) + return nil + }) +} diff --git a/internal/cmd/modelexperiments/instance/delete/delete_test.go b/internal/cmd/modelexperiments/instance/delete/delete_test.go new file mode 100644 index 000000000..fe21ee06c --- /dev/null +++ b/internal/cmd/modelexperiments/instance/delete/delete_test.go @@ -0,0 +1,179 @@ +package delete + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + +var testClient = &modelexperiments.APIClient{ + DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, +} + +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +func fixtureFlagValues( + mods ...func(*map[string]string), +) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + globalflags.RegionFlag: "eu01", + } + + for _, mod := range mods { + mod(&flagValues) + } + + return flagValues +} + +func fixtureInputModel( + mods ...func(*inputModel), +) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: "eu01", + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: testInstanceId, + Region: "eu01", + } + + for _, mod := range mods { + mod(model) + } + + return model +} + +func fixtureRequest( + mods ...func(*modelexperiments.ApiDeleteInstanceRequest), +) modelexperiments.ApiDeleteInstanceRequest { + request := testClient.DefaultAPI.DeleteInstance( + testCtx, + testProjectId, + "eu01", + testInstanceId, + ) + + for _, mod := range mods { + mod(&request) + } + + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id missing", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(func(flagValues *map[string]string) { + delete(*flagValues, projectIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput( + t, + NewCmd, + parseInput, + tt.expectedModel, + tt.argValues, + tt.flagValues, + tt.isValid, + ) + }) + } +} + +func TestBuildDeleteInstanceRequest(t *testing.T) { + request := buildDeleteInstanceRequest( + testCtx, + fixtureInputModel(), + testClient, + ) + + expectedRequest := fixtureRequest() + + diff := cmp.Diff( + request, + expectedRequest, + cmp.AllowUnexported(expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } +} + +func TestOutputResult(t *testing.T) { + tests := []struct { + name string + resp *modelexperiments.DeleteInstanceResponse + wantErr bool + }{ + { + name: "nil response", + resp: nil, + wantErr: true, + }, + { + name: "response", + resp: &modelexperiments.DeleteInstanceResponse{}, + wantErr: false, + }, + } + + params := testparams.NewTestParams() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult( + params.Printer, + "", + tt.resp, + ); (err != nil) != tt.wantErr { + t.Errorf( + "outputResult() error = %v, wantErr %v", + err, + tt.wantErr, + ) + } + }) + } +} diff --git a/internal/cmd/modelexperiments/instance/get/get.go b/internal/cmd/modelexperiments/instance/get/get.go new file mode 100644 index 000000000..3bd5a0ff6 --- /dev/null +++ b/internal/cmd/modelexperiments/instance/get/get.go @@ -0,0 +1,124 @@ +package get + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + instanceIdArg = "INSTANCE_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceId string + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("get %s", instanceIdArg), + Short: "Gets an AI Model Experiments instance", + Long: "Gets an AI Model Experiments instance in a STACKIT project.", + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Get an AI Model Experiments instance with ID "xxx"`, + `$ stackit ai-model-experiments instance get xxx`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + req := buildGetInstanceRequest(ctx, model, apiClient) + + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get AI Model Experiments instance: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + + configureFlags(cmd) + + return cmd +} + +func configureFlags(cmd *cobra.Command) { + _ = flags.MarkFlagsRequired(cmd, globalflags.RegionFlag) +} + +func parseInput( + p *print.Printer, + cmd *cobra.Command, + inputArgs []string, +) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: inputArgs[0], + Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), + } + + p.DebugInputModel(model) + + return &model, nil +} + +func buildGetInstanceRequest( + ctx context.Context, + model *inputModel, + apiClient *modelexperiments.APIClient, +) modelexperiments.ApiGetInstanceRequest { + return apiClient.DefaultAPI.GetInstance( + ctx, + model.ProjectId, + model.Region, + model.InstanceId, + ) +} + +func outputResult( + p *print.Printer, + outputFormat string, + resp *modelexperiments.GetInstanceResponse, +) error { + if resp == nil { + return fmt.Errorf("response instance is nil") + } + + return p.OutputResult(outputFormat, resp.Instance, func() error { + return nil + }) +} diff --git a/internal/cmd/modelexperiments/instance/get/get_test.go b/internal/cmd/modelexperiments/instance/get/get_test.go new file mode 100644 index 000000000..a15dcc374 --- /dev/null +++ b/internal/cmd/modelexperiments/instance/get/get_test.go @@ -0,0 +1,181 @@ +package get + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + +var testClient = &modelexperiments.APIClient{ + DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, +} + +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +func fixtureFlagValues( + mods ...func(*map[string]string), +) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + globalflags.RegionFlag: "eu01", + } + + for _, mod := range mods { + mod(&flagValues) + } + + return flagValues +} + +func fixtureInputModel( + mods ...func(*inputModel), +) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: "eu01", + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: testInstanceId, + Region: "eu01", + } + + for _, mod := range mods { + mod(model) + } + + return model +} + +func fixtureRequest( + mods ...func(*modelexperiments.ApiGetInstanceRequest), +) modelexperiments.ApiGetInstanceRequest { + request := testClient.DefaultAPI.GetInstance( + testCtx, + testProjectId, + "eu01", + testInstanceId, + ) + + for _, mod := range mods { + mod(&request) + } + + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id missing", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(func(flagValues *map[string]string) { + delete(*flagValues, projectIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput( + t, + NewCmd, + parseInput, + tt.expectedModel, + tt.argValues, + tt.flagValues, + tt.isValid, + ) + }) + } +} + +func TestBuildGetInstanceRequest(t *testing.T) { + request := buildGetInstanceRequest( + testCtx, + fixtureInputModel(), + testClient, + ) + + expectedRequest := fixtureRequest() + + diff := cmp.Diff( + request, + expectedRequest, + cmp.AllowUnexported(expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } +} + +func TestOutputResult(t *testing.T) { + tests := []struct { + name string + resp *modelexperiments.GetInstanceResponse + wantErr bool + }{ + { + name: "nil response", + resp: nil, + wantErr: true, + }, + { + name: "empty instance", + resp: &modelexperiments.GetInstanceResponse{ + Instance: modelexperiments.Instance{}, + }, + wantErr: false, + }, + } + + params := testparams.NewTestParams() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult( + params.Printer, + "", + tt.resp, + ); (err != nil) != tt.wantErr { + t.Errorf( + "outputResult() error = %v, wantErr %v", + err, + tt.wantErr, + ) + } + }) + } +} diff --git a/internal/cmd/modelexperiments/instance/instance.go b/internal/cmd/modelexperiments/instance/instance.go new file mode 100644 index 000000000..1cf6073c3 --- /dev/null +++ b/internal/cmd/modelexperiments/instance/instance.go @@ -0,0 +1,35 @@ +package instance + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/get" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/patch" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "instance", + Short: "Provides functionality for AI Model Experiments instances", + Long: "Provides functionality for AI Model Experiments instances.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + + addSubcommands(cmd, params) + + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(list.NewCmd(params)) + cmd.AddCommand(create.NewCmd(params)) + cmd.AddCommand(delete.NewCmd(params)) + cmd.AddCommand(get.NewCmd(params)) + cmd.AddCommand(patch.NewCmd(params)) +} diff --git a/internal/cmd/modelexperiments/instance/list/list.go b/internal/cmd/modelexperiments/instance/list/list.go new file mode 100644 index 000000000..0ba444e0e --- /dev/null +++ b/internal/cmd/modelexperiments/instance/list/list.go @@ -0,0 +1,116 @@ +package list + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists AI Model Experiments instances", + Long: "Lists AI Model Experiments instances in a STACKIT project.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List AI Model Experiments instances in a project`, + `$ stackit ai-model-experiments instance list --region eu01`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + req := buildListInstancesRequest(ctx, model, apiClient) + + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("list AI Model Experiments instances: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + + configureFlags(cmd) + + return cmd +} + +func configureFlags(cmd *cobra.Command) { + _ = flags.MarkFlagsRequired(cmd, globalflags.RegionFlag) +} + +func parseInput( + p *print.Printer, + cmd *cobra.Command, + _ []string, +) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), + } + + p.DebugInputModel(model) + + return &model, nil +} + +func buildListInstancesRequest( + ctx context.Context, + model *inputModel, + apiClient *modelexperiments.APIClient, +) modelexperiments.ApiListInstancesRequest { + return apiClient.DefaultAPI.ListInstances( + ctx, + model.ProjectId, + model.Region, + ) +} + +func outputResult( + p *print.Printer, + outputFormat string, + resp *modelexperiments.ListInstancesResponse, +) error { + if resp == nil { + return fmt.Errorf("response is nil") + } + + return p.OutputResult(outputFormat, resp.Instances, func() error { + return nil + }) +} diff --git a/internal/cmd/modelexperiments/instance/list/list_test.go b/internal/cmd/modelexperiments/instance/list/list_test.go new file mode 100644 index 000000000..ec288bad0 --- /dev/null +++ b/internal/cmd/modelexperiments/instance/list/list_test.go @@ -0,0 +1,176 @@ +package list + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + +var testClient = &modelexperiments.APIClient{ + DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, +} + +var testProjectId = uuid.NewString() + +func fixtureFlagValues( + mods ...func(*map[string]string), +) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + globalflags.RegionFlag: "eu01", + } + + for _, mod := range mods { + mod(&flagValues) + } + + return flagValues +} + +func fixtureInputModel( + mods ...func(*inputModel), +) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: "eu01", + Verbosity: globalflags.VerbosityDefault, + }, + Region: "eu01", + } + + for _, mod := range mods { + mod(model) + } + + return model +} + +func fixtureRequest( + mods ...func(*modelexperiments.ApiListInstancesRequest), +) modelexperiments.ApiListInstancesRequest { + request := testClient.DefaultAPI.ListInstances( + testCtx, + testProjectId, + "eu01", + ) + + for _, mod := range mods { + mod(&request) + } + + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues *map[string]string) { + delete(*flagValues, projectIdFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput( + t, + NewCmd, + parseInput, + tt.expectedModel, + tt.argValues, + tt.flagValues, + tt.isValid, + ) + }) + } +} + +func TestBuildListInstancesRequest(t *testing.T) { + request := buildListInstancesRequest( + testCtx, + fixtureInputModel(), + testClient, + ) + + expectedRequest := fixtureRequest() + + diff := cmp.Diff( + request, + expectedRequest, + cmp.AllowUnexported(expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } +} + +func TestOutputResult(t *testing.T) { + tests := []struct { + name string + resp *modelexperiments.ListInstancesResponse + wantErr bool + }{ + { + name: "nil response", + resp: nil, + wantErr: true, + }, + { + name: "instances", + resp: &modelexperiments.ListInstancesResponse{ + Instances: []modelexperiments.Instance{}, + }, + wantErr: false, + }, + } + + params := testparams.NewTestParams() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult( + params.Printer, + "", + tt.resp, + ); (err != nil) != tt.wantErr { + t.Errorf( + "outputResult() error = %v, wantErr %v", + err, + tt.wantErr, + ) + } + }) + } +} diff --git a/internal/cmd/modelexperiments/instance/patch/patch.go b/internal/cmd/modelexperiments/instance/patch/patch.go new file mode 100644 index 000000000..3d7082bfe --- /dev/null +++ b/internal/cmd/modelexperiments/instance/patch/patch.go @@ -0,0 +1,207 @@ +package patch + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + instanceIdArg = "INSTANCE_ID" + + nameFlag = "name" + descriptionFlag = "description" + labelFlag = "label" + retentionFlag = "deleted-experiment-retention" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + + InstanceId string + + Name *string + Description *string + Labels *map[string]*string + Retention *string + + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("patch %s", instanceIdArg), + Short: "Updates an AI Model Experiments instance", + Long: "Partially updates an AI Model Experiments instance in a STACKIT project.", + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Update the name of an AI Model Experiments instance with ID "xxx"`, + `$ stackit ai-model-experiments instance patch xxx --name my-new-name`, + ), + examples.NewExample( + `Update the description of an AI Model Experiments instance`, + `$ stackit ai-model-experiments instance patch xxx --description "team tracking server"`, + ), + examples.NewExample( + `Update labels on an AI Model Experiments instance`, + `$ stackit ai-model-experiments instance patch xxx --label env=prod`, + ), + examples.NewExample( + `Update multiple fields of an AI Model Experiments instance`, + `$ stackit ai-model-experiments instance patch xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d`, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + req := buildPatchInstanceRequest(ctx, model, apiClient) + + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("update AI Model Experiments instance: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + + configureFlags(cmd) + + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().StringP(nameFlag, "n", "", "Instance name") + cmd.Flags().String(descriptionFlag, "", "Instance description") + cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + cmd.Flags().String(retentionFlag, "", `Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d)`) +} + +func parseInput( + p *print.Printer, + cmd *cobra.Command, + inputArgs []string, +) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + + labels, err := cmd.Flags().GetStringToString(labelFlag) + if err != nil { + return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) + } + + var labelsPtr *map[string]*string + + if len(labels) > 0 { + convertedLabels := make(map[string]*string, len(labels)) + + for key, value := range labels { + valueCopy := value + convertedLabels[key] = &valueCopy + } + + labelsPtr = &convertedLabels + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + + InstanceId: inputArgs[0], + + Name: flags.FlagToStringPointer(p, cmd, nameFlag), + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Labels: labelsPtr, + Retention: flags.FlagToStringPointer(p, cmd, retentionFlag), + + Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), + } + + if model.Name == nil && + model.Description == nil && + model.Labels == nil && + model.Retention == nil { + return nil, fmt.Errorf("at least one update flag must be provided") + } + + p.DebugInputModel(model) + + return &model, nil +} + +func buildPatchInstanceRequest( + ctx context.Context, + model *inputModel, + apiClient *modelexperiments.APIClient, +) modelexperiments.ApiPartialUpdateInstanceRequest { + req := apiClient.DefaultAPI.PartialUpdateInstance( + ctx, + model.ProjectId, + model.Region, + model.InstanceId, + ) + + payload := modelexperiments.PartialUpdateInstancePayload{} + + if model.Name != nil { + payload.Name = model.Name + } + + if model.Description != nil { + payload.Description = model.Description + } + + if model.Labels != nil { + payload.Labels = model.Labels + } + + if model.Retention != nil { + payload.DeletedExperimentRetention = model.Retention + } + + return req.PartialUpdateInstancePayload(payload) +} + +func outputResult( + p *print.Printer, + outputFormat string, + resp *modelexperiments.PartialUpdateInstanceResponse, +) error { + if resp == nil { + return fmt.Errorf("response instance is nil") + } + + return p.OutputResult(outputFormat, resp.Instance, func() error { + p.Outputf( + "Updated AI Model Experiments instance. Instance ID: %s\n", + resp.Instance.Id, + ) + return nil + }) +} diff --git a/internal/cmd/modelexperiments/instance/patch/patch_test.go b/internal/cmd/modelexperiments/instance/patch/patch_test.go new file mode 100644 index 000000000..70407abee --- /dev/null +++ b/internal/cmd/modelexperiments/instance/patch/patch_test.go @@ -0,0 +1,281 @@ +package patch + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + +var testClient = &modelexperiments.APIClient{ + DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, +} + +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +func fixtureFlagValues( + mods ...func(*map[string]string), +) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + globalflags.RegionFlag: "eu01", + } + + for _, mod := range mods { + mod(&flagValues) + } + + return flagValues +} + +func fixtureInputModel( + mods ...func(*inputModel), +) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Region: "eu01", + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: testInstanceId, + Region: "eu01", + } + + for _, mod := range mods { + mod(model) + } + + return model +} + +func fixtureRequest( + mods ...func(*modelexperiments.ApiPartialUpdateInstanceRequest), +) modelexperiments.ApiPartialUpdateInstanceRequest { + request := testClient.DefaultAPI.PartialUpdateInstance( + testCtx, + testProjectId, + "eu01", + testInstanceId, + ) + + payload := modelexperiments.PartialUpdateInstancePayload{ + Name: utils.Ptr("example"), + } + + request = request.PartialUpdateInstancePayload(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: "name", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(func(flagValues *map[string]string) { + (*flagValues)[nameFlag] = "example" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Name = utils.Ptr("example") + }), + }, + { + description: "description", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(func(flagValues *map[string]string) { + (*flagValues)[descriptionFlag] = "team tracking server" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Description = utils.Ptr("team tracking server") + }), + }, + { + description: "retention", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(func(flagValues *map[string]string) { + (*flagValues)[retentionFlag] = "30d" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Retention = utils.Ptr("30d") + }), + }, + { + description: "no update flags", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "project id missing", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(func(flagValues *map[string]string) { + delete(*flagValues, projectIdFlag) + (*flagValues)[nameFlag] = "example" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput( + t, + NewCmd, + parseInput, + tt.expectedModel, + tt.argValues, + tt.flagValues, + tt.isValid, + ) + }) + } +} + +func TestBuildPatchInstanceRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest modelexperiments.ApiPartialUpdateInstanceRequest + }{ + { + description: "name", + model: fixtureInputModel(func(model *inputModel) { + model.Name = utils.Ptr("example") + }), + expectedRequest: fixtureRequest(), + }, + { + description: "description", + model: fixtureInputModel(func(model *inputModel) { + model.Description = utils.Ptr("team tracking server") + }), + expectedRequest: func() modelexperiments.ApiPartialUpdateInstanceRequest { + request := testClient.DefaultAPI.PartialUpdateInstance( + testCtx, + testProjectId, + "eu01", + testInstanceId, + ) + + request = request.PartialUpdateInstancePayload( + modelexperiments.PartialUpdateInstancePayload{ + Description: utils.Ptr("team tracking server"), + }, + ) + + return request + }(), + }, + { + description: "retention", + model: fixtureInputModel(func(model *inputModel) { + model.Retention = utils.Ptr("30d") + }), + expectedRequest: func() modelexperiments.ApiPartialUpdateInstanceRequest { + request := testClient.DefaultAPI.PartialUpdateInstance( + testCtx, + testProjectId, + "eu01", + testInstanceId, + ) + + request = request.PartialUpdateInstancePayload( + modelexperiments.PartialUpdateInstancePayload{ + DeletedExperimentRetention: utils.Ptr("30d"), + }, + ) + + return request + }(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildPatchInstanceRequest( + 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) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + tests := []struct { + name string + resp *modelexperiments.PartialUpdateInstanceResponse + wantErr bool + }{ + { + name: "nil response", + resp: nil, + wantErr: true, + }, + { + name: "empty instance", + resp: &modelexperiments.PartialUpdateInstanceResponse{ + Instance: modelexperiments.Instance{}, + }, + wantErr: false, + }, + } + + params := testparams.NewTestParams() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult( + params.Printer, + "", + tt.resp, + ); (err != nil) != tt.wantErr { + t.Errorf( + "outputResult() error = %v, wantErr %v", + err, + tt.wantErr, + ) + } + }) + } +} diff --git a/internal/cmd/modelexperiments/modelexperiments.go b/internal/cmd/modelexperiments/modelexperiments.go new file mode 100644 index 000000000..ae6804e09 --- /dev/null +++ b/internal/cmd/modelexperiments/modelexperiments.go @@ -0,0 +1,29 @@ +package modelexperiments + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "ai-model-experiments", + Short: "Provides functionality for AI Model Experiments", + Long: "Provides functionality for AI Model Experiments.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + + addSubcommands(cmd, params) + + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(instance.NewCmd(params)) + cmd.AddCommand(token.NewCmd(params)) +} diff --git a/internal/cmd/modelexperiments/token/create/create.go b/internal/cmd/modelexperiments/token/create/create.go new file mode 100644 index 000000000..ac6e5e1e9 --- /dev/null +++ b/internal/cmd/modelexperiments/token/create/create.go @@ -0,0 +1,106 @@ +package create + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + instanceIDFlag = "instance-id" + regionFlag = "region" + nameFlag = "name" + descriptionFlag = "description" + labelFlag = "label" + ttlDurationFlag = "ttl-duration" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceID string + Region string + Name string + Description *string + Labels *map[string]string + TTLDuration *string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{Use: "create", Short: "Creates an instance token", Long: "Creates an auth token for an AI Model Experiments instance.", Args: args.NoArgs, Example: examples.Build(examples.NewExample("Create an auth token", "$ stackit ai-model-experiments token create --instance-id xxx --name my-token")), RunE: func(cmd *cobra.Command, _ []string) error { + model, err := parseInput(params.Printer, cmd, nil) + if err != nil { + return err + } + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + resp, err := buildRequest(context.Background(), model, apiClient).Execute() + if err != nil { + return fmt.Errorf("create instance token: %w", err) + } + return outputResult(params.Printer, model.OutputFormat, resp) + }} + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") + cmd.Flags().String(nameFlag, "", "Token name") + cmd.Flags().String(descriptionFlag, "", "Token description") + cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + cmd.Flags().String(ttlDurationFlag, "", "Token time to live duration") + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag, nameFlag) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + labels, err := cmd.Flags().GetStringToString(labelFlag) + if err != nil { + return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) + } + var labelsPtr *map[string]string + if len(labels) > 0 { + labelsPtr = &labels + } + model := &inputModel{GlobalFlagModel: globalFlags, InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag), Name: flags.FlagToStringValue(p, cmd, nameFlag), Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr, TTLDuration: flags.FlagToStringPointer(p, cmd, ttlDurationFlag)} + if model.Name == "" { + return nil, fmt.Errorf("%s flag is required", nameFlag) + } + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiCreateInstanceTokenRequest { + payload := modelexperiments.CreateInstanceTokenPayload{Name: model.Name, Description: model.Description, Labels: model.Labels, TtlDuration: model.TTLDuration} + return apiClient.DefaultAPI.CreateInstanceToken(ctx, model.ProjectId, model.Region, model.InstanceID).CreateInstanceTokenPayload(payload) +} + +func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.CreateInstanceTokenResponse) error { + if resp == nil || resp.Token.Name == "" { + return fmt.Errorf("response token is nil") + } + return p.OutputResult(outputFormat, resp.Token, func() error { + p.Outputf( + "Created instance token. ID: %s\nToken: %s\n", + resp.Token.Id, + resp.Token.Content, + ) + return nil + }) +} diff --git a/internal/cmd/modelexperiments/token/create/create_test.go b/internal/cmd/modelexperiments/token/create/create_test.go new file mode 100644 index 000000000..cedf3b09e --- /dev/null +++ b/internal/cmd/modelexperiments/token/create/create_test.go @@ -0,0 +1,100 @@ +package create + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +var projectIDFlag = globalflags.ProjectIdFlag + +type testContextKey struct{} + +var testContext = context.WithValue(context.Background(), testContextKey{}, "token-create") +var testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} +var testProjectID = uuid.NewString() +var testInstanceID = uuid.NewString() + +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01", nameFlag: "example"} + for _, mod := range mods { + mod(values) + } + return values +} + +func fixtureInputModel(mods ...func(*inputModel)) *inputModel { + model := &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, InstanceID: testInstanceID, Region: "eu01", Name: "example"} + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(*modelexperiments.ApiCreateInstanceTokenRequest)) modelexperiments.ApiCreateInstanceTokenRequest { + request := testClient.DefaultAPI.CreateInstanceToken(testContext, testProjectID, "eu01", testInstanceID).CreateInstanceTokenPayload(modelexperiments.CreateInstanceTokenPayload{Name: "example"}) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + name string + flags map[string]string + valid bool + want *inputModel + }{ + {name: "base", flags: fixtureFlagValues(), valid: true, want: fixtureInputModel()}, + {name: "optional fields", flags: fixtureFlagValues(func(values map[string]string) { + values[descriptionFlag] = "service token" + values[labelFlag] = "env=prod" + values[ttlDurationFlag] = "5h" + }), valid: true, want: fixtureInputModel(func(model *inputModel) { + model.Description = utils.Ptr("service token") + model.Labels = &map[string]string{"env": "prod"} + model.TTLDuration = utils.Ptr("5h") + })}, + {name: "name missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, nameFlag) }), valid: false}, + {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { testutils.TestParseInput(t, NewCmd, parseInput, tt.want, nil, tt.flags, tt.valid) }) + } +} + +func TestBuildRequest(t *testing.T) { + model := fixtureInputModel(func(model *inputModel) { + model.Description = utils.Ptr("service token") + model.Labels = &map[string]string{"env": "prod"} + model.TTLDuration = utils.Ptr("5h") + }) + want := fixtureRequest(func(request *modelexperiments.ApiCreateInstanceTokenRequest) { + *request = request.CreateInstanceTokenPayload(modelexperiments.CreateInstanceTokenPayload{Name: "example", Description: utils.Ptr("service token"), Labels: &map[string]string{"env": "prod"}, TtlDuration: utils.Ptr("5h")}) + }) + got := buildRequest(testContext, model, testClient) + if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { + t.Fatalf("request mismatch (-got +want):\n%s", diff) + } +} + +func TestOutputResult(t *testing.T) { + params := testparams.NewTestParams() + if err := outputResult(params.Printer, "", nil); err == nil { + t.Fatal("expected nil response error") + } + if err := outputResult(params.Printer, "", &modelexperiments.CreateInstanceTokenResponse{}); err == nil { + t.Fatal("expected nil token error") + } +} diff --git a/internal/cmd/modelexperiments/token/delete/delete.go b/internal/cmd/modelexperiments/token/delete/delete.go new file mode 100644 index 000000000..902ff5081 --- /dev/null +++ b/internal/cmd/modelexperiments/token/delete/delete.go @@ -0,0 +1,81 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + tokenIDArg = "TOKEN_ID" + instanceIDFlag = "instance-id" + regionFlag = "region" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + TokenID string + InstanceID string + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", tokenIDArg), + Short: "Deletes an instance token", + Long: "Deletes an auth token from an AI Model Experiments instance.", + Args: args.SingleArg(tokenIDArg, utils.ValidateUUID), + Example: examples.Build(examples.NewExample(`Delete an auth token with ID "xxx"`, `$ stackit ai-model-experiments token delete xxx --instance-id yyy`)), + RunE: func(cmd *cobra.Command, inputArgs []string) error { + model, err := parseInput(params.Printer, cmd, inputArgs) + if err != nil { + return err + } + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + if err := params.Printer.PromptForConfirmation(fmt.Sprintf("Are you sure you want to delete instance token %q?", model.TokenID)); err != nil { + return err + } + if _, err := buildRequest(context.Background(), model, apiClient).Execute(); err != nil { + return fmt.Errorf("delete instance token: %w", err) + } + params.Printer.Outputf("Deleted instance token %q\n", model.TokenID) + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag)} + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiDeleteInstanceTokenRequest { + return apiClient.DefaultAPI.DeleteInstanceToken(ctx, model.ProjectId, model.Region, model.TokenID, model.InstanceID) +} diff --git a/internal/cmd/modelexperiments/token/delete/delete_test.go b/internal/cmd/modelexperiments/token/delete/delete_test.go new file mode 100644 index 000000000..94c013fe0 --- /dev/null +++ b/internal/cmd/modelexperiments/token/delete/delete_test.go @@ -0,0 +1,63 @@ +package delete + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var projectIDFlag = globalflags.ProjectIdFlag + +type testContextKey struct{} + +var testContext = context.WithValue(context.Background(), testContextKey{}, "token-delete") +var testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} +var testProjectID = uuid.NewString() +var testInstanceID = uuid.NewString() +var testTokenID = uuid.NewString() + +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + for _, mod := range mods { + mod(values) + } + return values +} +func fixtureInputModel() *inputModel { + return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID, Region: "eu01"} +} +func fixtureRequest() modelexperiments.ApiDeleteInstanceTokenRequest { + return testClient.DefaultAPI.DeleteInstanceToken(testContext, testProjectID, "eu01", testTokenID, testInstanceID) +} + +func TestParseInput(t *testing.T) { + tests := []struct { + name string + flags map[string]string + valid bool + }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var want *inputModel + if tt.valid { + want = fixtureInputModel() + } + testutils.TestParseInput(t, NewCmd, parseInput, want, []string{testTokenID}, tt.flags, tt.valid) + }) + } +} +func TestBuildRequest(t *testing.T) { + got := buildRequest(testContext, fixtureInputModel(), testClient) + want := fixtureRequest() + if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { + t.Fatalf("request mismatch (-got +want):\n%s", diff) + } +} diff --git a/internal/cmd/modelexperiments/token/get/get.go b/internal/cmd/modelexperiments/token/get/get.go new file mode 100644 index 000000000..5f8db6d6d --- /dev/null +++ b/internal/cmd/modelexperiments/token/get/get.go @@ -0,0 +1,85 @@ +package get + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + tokenIDArg = "TOKEN_ID" + instanceIDFlag = "instance-id" + regionFlag = "region" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + TokenID string + InstanceID string + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("get %s", tokenIDArg), + Short: "Gets an instance token", + Long: "Gets an auth token for an AI Model Experiments instance.", + Args: args.SingleArg(tokenIDArg, utils.ValidateUUID), + Example: examples.Build(examples.NewExample(`Get an auth token with ID "xxx"`, `$ stackit ai-model-experiments token get xxx --instance-id yyy`)), + RunE: func(cmd *cobra.Command, inputArgs []string) error { + model, err := parseInput(params.Printer, cmd, inputArgs) + if err != nil { + return err + } + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + resp, err := buildRequest(context.Background(), model, apiClient).Execute() + if err != nil { + return fmt.Errorf("get instance token: %w", err) + } + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag)} + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiGetInstanceTokenRequest { + return apiClient.DefaultAPI.GetInstanceToken(ctx, model.ProjectId, model.Region, model.TokenID, model.InstanceID) +} + +func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.GetInstanceTokenResponse) error { + if resp == nil || resp.Token.Name == "" { + return fmt.Errorf("response token is nil") + } + return p.OutputResult(outputFormat, resp.Token, func() error { return nil }) +} diff --git a/internal/cmd/modelexperiments/token/get/get_test.go b/internal/cmd/modelexperiments/token/get/get_test.go new file mode 100644 index 000000000..ec88ea99d --- /dev/null +++ b/internal/cmd/modelexperiments/token/get/get_test.go @@ -0,0 +1,73 @@ +package get + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var projectIDFlag = globalflags.ProjectIdFlag + +type testContextKey struct{} + +var testContext = context.WithValue(context.Background(), testContextKey{}, "token-get") +var testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} +var testProjectID = uuid.NewString() +var testInstanceID = uuid.NewString() +var testTokenID = uuid.NewString() + +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + for _, mod := range mods { + mod(values) + } + return values +} +func fixtureInputModel() *inputModel { + return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID, Region: "eu01"} +} +func fixtureRequest() modelexperiments.ApiGetInstanceTokenRequest { + return testClient.DefaultAPI.GetInstanceToken(testContext, testProjectID, "eu01", testTokenID, testInstanceID) +} + +func TestParseInput(t *testing.T) { + tests := []struct { + name string + flags map[string]string + valid bool + }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var want *inputModel + if tt.valid { + want = fixtureInputModel() + } + testutils.TestParseInput(t, NewCmd, parseInput, want, []string{testTokenID}, tt.flags, tt.valid) + }) + } +} +func TestBuildRequest(t *testing.T) { + got := buildRequest(testContext, fixtureInputModel(), testClient) + want := fixtureRequest() + if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { + t.Fatalf("request mismatch (-got +want):\n%s", diff) + } +} +func TestOutputResult(t *testing.T) { + params := testparams.NewTestParams() + if err := outputResult(params.Printer, "", nil); err == nil { + t.Fatal("expected nil response error") + } + if err := outputResult(params.Printer, "", &modelexperiments.GetInstanceTokenResponse{}); err == nil { + t.Fatal("expected nil token error") + } +} diff --git a/internal/cmd/modelexperiments/token/list/list.go b/internal/cmd/modelexperiments/token/list/list.go new file mode 100644 index 000000000..cf3da805f --- /dev/null +++ b/internal/cmd/modelexperiments/token/list/list.go @@ -0,0 +1,75 @@ +package list + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + instanceIDFlag = "instance-id" + regionFlag = "region" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceID string + Region string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{Use: "list", Short: "Lists instance tokens", Long: "Lists all auth tokens for an AI Model Experiments instance.", Args: args.NoArgs, Example: examples.Build(examples.NewExample("List all tokens for an instance", "$ stackit ai-model-experiments token list --instance-id xxx")), RunE: func(cmd *cobra.Command, _ []string) error { + model, err := parseInput(params.Printer, cmd, nil) + if err != nil { + return err + } + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + resp, err := buildRequest(context.Background(), model, apiClient).Execute() + if err != nil { + return fmt.Errorf("list instance tokens: %w", err) + } + return outputResult(params.Printer, model.OutputFormat, resp) + }} + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + model := &inputModel{GlobalFlagModel: globalFlags, InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag)} + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiListInstanceTokensRequest { + return apiClient.DefaultAPI.ListInstanceTokens(ctx, model.ProjectId, model.Region, model.InstanceID) +} + +func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.ListInstanceTokensResponse) error { + if resp == nil { + return fmt.Errorf("response is nil") + } + return p.OutputResult(outputFormat, resp.Tokens, func() error { return nil }) +} diff --git a/internal/cmd/modelexperiments/token/list/list_test.go b/internal/cmd/modelexperiments/token/list/list_test.go new file mode 100644 index 000000000..fbbd6352b --- /dev/null +++ b/internal/cmd/modelexperiments/token/list/list_test.go @@ -0,0 +1,74 @@ +package list + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +var projectIDFlag = globalflags.ProjectIdFlag + +type testContextKey struct{} + +var testContext = context.WithValue(context.Background(), testContextKey{}, "token-list") +var testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} +var testProjectID = uuid.NewString() +var testInstanceID = uuid.NewString() + +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + for _, mod := range mods { + mod(values) + } + return values +} +func fixtureInputModel() *inputModel { + return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, InstanceID: testInstanceID, Region: "eu01"} +} +func fixtureRequest() modelexperiments.ApiListInstanceTokensRequest { + return testClient.DefaultAPI.ListInstanceTokens(testContext, testProjectID, "eu01", testInstanceID) +} + +func TestParseInput(t *testing.T) { + tests := []struct { + name string + flags map[string]string + valid bool + }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var want *inputModel + if tt.valid { + want = fixtureInputModel() + } + testutils.TestParseInput(t, NewCmd, parseInput, want, nil, tt.flags, tt.valid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + got := buildRequest(testContext, fixtureInputModel(), testClient) + want := fixtureRequest() + if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { + t.Fatalf("request mismatch (-got +want):\n%s", diff) + } +} + +func TestOutputResult(t *testing.T) { + params := testparams.NewTestParams() + if err := outputResult(params.Printer, "", nil); err == nil { + t.Fatal("expected nil response error") + } + if err := outputResult(params.Printer, "", &modelexperiments.ListInstanceTokensResponse{}); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/internal/cmd/modelexperiments/token/patch/patch.go b/internal/cmd/modelexperiments/token/patch/patch.go new file mode 100644 index 000000000..4e3c91e00 --- /dev/null +++ b/internal/cmd/modelexperiments/token/patch/patch.go @@ -0,0 +1,107 @@ +package patch + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +const ( + tokenIDArg = "TOKEN_ID" + instanceIDFlag = "instance-id" + regionFlag = "region" + nameFlag = "name" + descriptionFlag = "description" + labelFlag = "label" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + TokenID string + InstanceID string + Region string + Name *string + Description *string + Labels *map[string]string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("patch %s", tokenIDArg), Short: "Updates an instance token", Long: "Partially updates an auth token for an AI Model Experiments instance.", Args: args.SingleArg(tokenIDArg, utils.ValidateUUID), + Example: examples.Build(examples.NewExample(`Update an auth token with ID "xxx"`, `$ stackit ai-model-experiments token patch xxx --instance-id yyy --name updated-token`)), + RunE: func(cmd *cobra.Command, inputArgs []string) error { + model, err := parseInput(params.Printer, cmd, inputArgs) + if err != nil { + return err + } + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + resp, err := buildRequest(context.Background(), model, apiClient).Execute() + if err != nil { + return fmt.Errorf("update instance token: %w", err) + } + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") + cmd.Flags().String(nameFlag, "", "Token name") + cmd.Flags().String(descriptionFlag, "", "Token description") + cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &cliErr.ProjectIdError{} + } + labels, err := cmd.Flags().GetStringToString(labelFlag) + if err != nil { + return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) + } + var labelsPtr *map[string]string + if len(labels) > 0 { + labelsPtr = &labels + } + model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag), Name: flags.FlagToStringPointer(p, cmd, nameFlag), Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr} + p.DebugInputModel(model) + return model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiPartialUpdateInstanceTokenRequest { + labels := map[string]*string(nil) + if model.Labels != nil { + labels = make(map[string]*string, len(*model.Labels)) + for key, value := range *model.Labels { + labels[key] = &value + } + } + payload := modelexperiments.PartialUpdateInstanceTokenPayload{Name: model.Name, Description: model.Description, Labels: &labels} + return apiClient.DefaultAPI.PartialUpdateInstanceToken(ctx, model.ProjectId, model.Region, model.TokenID, model.InstanceID).PartialUpdateInstanceTokenPayload(payload) +} + +func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.PartialUpdateInstanceTokenResponse) error { + if resp == nil || resp.Token.Name == "" { + return fmt.Errorf("response token is nil") + } + return p.OutputResult(outputFormat, resp.Token, func() error { return nil }) +} diff --git a/internal/cmd/modelexperiments/token/patch/patch_test.go b/internal/cmd/modelexperiments/token/patch/patch_test.go new file mode 100644 index 000000000..638932358 --- /dev/null +++ b/internal/cmd/modelexperiments/token/patch/patch_test.go @@ -0,0 +1,95 @@ +package patch + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +var projectIDFlag = globalflags.ProjectIdFlag + +type testContextKey struct{} + +var testContext = context.WithValue(context.Background(), testContextKey{}, "token-patch") +var testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} +var testProjectID = uuid.NewString() +var testInstanceID = uuid.NewString() +var testTokenID = uuid.NewString() + +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + for _, mod := range mods { + mod(values) + } + return values +} +func fixtureInputModel(mods ...func(*inputModel)) *inputModel { + model := &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID, Region: "eu01"} + for _, mod := range mods { + mod(model) + } + return model +} +func fixtureRequest(payload modelexperiments.PartialUpdateInstanceTokenPayload) modelexperiments.ApiPartialUpdateInstanceTokenRequest { + return testClient.DefaultAPI.PartialUpdateInstanceToken(testContext, testProjectID, "eu01", testTokenID, testInstanceID).PartialUpdateInstanceTokenPayload(payload) +} + +func TestParseInput(t *testing.T) { + tests := []struct { + name string + flags map[string]string + valid bool + want *inputModel + }{ + {name: "base", flags: fixtureFlagValues(), valid: true, want: fixtureInputModel()}, + {name: "optional fields", flags: fixtureFlagValues(func(values map[string]string) { + values[nameFlag] = "updated" + values[descriptionFlag] = "updated token" + values[labelFlag] = "env=prod" + }), valid: true, want: fixtureInputModel(func(model *inputModel) { + model.Name = utils.Ptr("updated") + model.Description = utils.Ptr("updated token") + model.Labels = &map[string]string{"env": "prod"} + })}, + {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.want, []string{testTokenID}, tt.flags, tt.valid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + model := fixtureInputModel(func(model *inputModel) { + model.Name = utils.Ptr("updated") + model.Description = utils.Ptr("updated token") + model.Labels = &map[string]string{"env": "prod"} + }) + got := buildRequest(testContext, model, testClient) + labels := map[string]*string{"env": utils.Ptr("prod")} + want := fixtureRequest(modelexperiments.PartialUpdateInstanceTokenPayload{Name: utils.Ptr("updated"), Description: utils.Ptr("updated token"), Labels: &labels}) + if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { + t.Fatalf("request mismatch (-got +want):\n%s", diff) + } +} + +func TestOutputResult(t *testing.T) { + params := testparams.NewTestParams() + if err := outputResult(params.Printer, "", nil); err == nil { + t.Fatal("expected nil response error") + } + if err := outputResult(params.Printer, "", &modelexperiments.PartialUpdateInstanceTokenResponse{}); err == nil { + t.Fatal("expected nil token error") + } +} diff --git a/internal/cmd/modelexperiments/token/token.go b/internal/cmd/modelexperiments/token/token.go new file mode 100644 index 000000000..67d8bced1 --- /dev/null +++ b/internal/cmd/modelexperiments/token/token.go @@ -0,0 +1,19 @@ +package token + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/get" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/patch" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{Use: "token", Short: "Provides functionality for AI Model Experiments instance tokens", Long: "Provides functionality for AI Model Experiments instance tokens.", Args: args.NoArgs, Run: utils.CmdHelp} + cmd.AddCommand(list.NewCmd(params), create.NewCmd(params), delete.NewCmd(params), get.NewCmd(params), patch.NewCmd(params)) + return cmd +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index bbb05aca3..1a432477c 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -22,6 +22,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/logme" "github.com/stackitcloud/stackit-cli/internal/cmd/logs" "github.com/stackitcloud/stackit-cli/internal/cmd/mariadb" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments" "github.com/stackitcloud/stackit-cli/internal/cmd/mongodbflex" "github.com/stackitcloud/stackit-cli/internal/cmd/network" networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/network-area" @@ -180,6 +181,7 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(logme.NewCmd(params)) cmd.AddCommand(logs.NewCmd(params)) cmd.AddCommand(mariadb.NewCmd(params)) + cmd.AddCommand(modelexperiments.NewCmd(params)) cmd.AddCommand(mongodbflex.NewCmd(params)) cmd.AddCommand(objectstorage.NewCmd(params)) cmd.AddCommand(observability.NewCmd(params)) diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 2365ed8b4..0c2663c47 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -23,20 +23,21 @@ const ( IdentityProviderCustomClientIdKey = "identity_provider_custom_client_id" AllowedUrlDomainKey = "allowed_url_domain" - AuthorizationCustomEndpointKey = "authorization_custom_endpoint" - AlbCustomEndpoint = "alb_custom _endpoint" - AlbWafCustomEndpointKey = "alb_waf_custom_endpoint" - DNSCustomEndpointKey = "dns_custom_endpoint" - EdgeCustomEndpointKey = "edge_custom_endpoint" - LoadBalancerCustomEndpointKey = "load_balancer_custom_endpoint" - LogMeCustomEndpointKey = "logme_custom_endpoint" - MariaDBCustomEndpointKey = "mariadb_custom_endpoint" - MongoDBFlexCustomEndpointKey = "mongodbflex_custom_endpoint" - ObjectStorageCustomEndpointKey = "object_storage_custom_endpoint" - ObservabilityCustomEndpointKey = "observability_custom_endpoint" - OpenSearchCustomEndpointKey = "opensearch_custom_endpoint" - PostgresFlexCustomEndpointKey = "postgresflex_custom_endpoint" - RabbitMQCustomEndpointKey = "rabbitmq_custom_endpoint" + AuthorizationCustomEndpointKey = "authorization_custom_endpoint" + AlbCustomEndpoint = "alb_custom _endpoint" + AlbWafCustomEndpointKey = "alb_waf_custom_endpoint" + DNSCustomEndpointKey = "dns_custom_endpoint" + EdgeCustomEndpointKey = "edge_custom_endpoint" + LoadBalancerCustomEndpointKey = "load_balancer_custom_endpoint" + LogMeCustomEndpointKey = "logme_custom_endpoint" + MariaDBCustomEndpointKey = "mariadb_custom_endpoint" + MongoDBFlexCustomEndpointKey = "mongodbflex_custom_endpoint" + ObjectStorageCustomEndpointKey = "object_storage_custom_endpoint" + ObservabilityCustomEndpointKey = "observability_custom_endpoint" + OpenSearchCustomEndpointKey = "opensearch_custom_endpoint" + PostgresFlexCustomEndpointKey = "postgresflex_custom_endpoint" + RabbitMQCustomEndpointKey = "rabbitmq_custom_endpoint" + AIModelExperimentsCustomEndpointKey = "aimodelexperiments_custom_endpoint" // Deprecated: Will be removed after 2027-08-31. RedisCustomEndpointKey = "redis_custom_endpoint" ResourceManagerEndpointKey = "resource_manager_custom_endpoint" @@ -114,6 +115,7 @@ var ConfigKeys = []string{ OpenSearchCustomEndpointKey, PostgresFlexCustomEndpointKey, RabbitMQCustomEndpointKey, + AIModelExperimentsCustomEndpointKey, RedisCustomEndpointKey, ResourceManagerEndpointKey, RunCommandCustomEndpointKey, diff --git a/internal/pkg/services/modelexperiments/client/client.go b/internal/pkg/services/modelexperiments/client/client.go new file mode 100644 index 000000000..9ab23f22d --- /dev/null +++ b/internal/pkg/services/modelexperiments/client/client.go @@ -0,0 +1,14 @@ +package client + +import ( + "github.com/stackitcloud/stackit-cli/internal/pkg/config" + genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + + "github.com/spf13/viper" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +func ConfigureClient(p *print.Printer, cliVersion string) (*modelexperiments.APIClient, error) { + return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.AIModelExperimentsCustomEndpointKey), true, genericclient.CreateApiClient[*modelexperiments.APIClient](modelexperiments.NewAPIClient)) +} diff --git a/internal/pkg/services/modelexperiments/utils/utils.go b/internal/pkg/services/modelexperiments/utils/utils.go new file mode 100644 index 000000000..ac0abfb71 --- /dev/null +++ b/internal/pkg/services/modelexperiments/utils/utils.go @@ -0,0 +1,36 @@ +package utils + +import ( + "context" + "errors" + "fmt" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" +) + +var ErrResponseNil = errors.New("response is nil") + +func GetInstanceName(ctx context.Context, apiClient modelexperiments.DefaultAPI, projectID, regionID, instanceID string) (string, error) { + resp, err := apiClient.GetInstance(ctx, projectID, regionID, instanceID).Execute() + if err != nil { + return "", fmt.Errorf("get AI Model Experiments instance: %w", err) + } + if resp == nil { + return "", ErrResponseNil + } + if resp.Instance.Name == "" { + return "", ErrResponseNil + } + return resp.Instance.Name, nil +} + +func GetTokenName(ctx context.Context, apiClient modelexperiments.DefaultAPI, projectID, regionID, instanceID, tokenID string) (string, error) { + resp, err := apiClient.GetInstanceToken(ctx, projectID, regionID, tokenID, instanceID).Execute() + if err != nil { + return "", fmt.Errorf("get AI Model Experiments token: %w", err) + } + if resp == nil || resp.Token.Name == "" { + return "", ErrResponseNil + } + return resp.Token.Name, nil +} diff --git a/internal/pkg/services/modelexperiments/utils/utils_test.go b/internal/pkg/services/modelexperiments/utils/utils_test.go new file mode 100644 index 000000000..3d364de8f --- /dev/null +++ b/internal/pkg/services/modelexperiments/utils/utils_test.go @@ -0,0 +1,96 @@ +package utils + +import ( + "context" + "fmt" + "testing" + + "github.com/google/uuid" + + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + + cliutils "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +var ( + testProjectID = uuid.NewString() + testRegion = "eu01" + testInstanceID = uuid.NewString() + testTokenID = uuid.NewString() +) + +type mockSettings struct { + instanceFails bool + instanceResp *modelexperiments.GetInstanceResponse + tokenFails bool + tokenResp *modelexperiments.GetInstanceTokenResponse +} + +func newAPIMock(settings mockSettings) modelexperiments.DefaultAPI { + return &modelexperiments.DefaultAPIServiceMock{ + GetInstanceExecuteMock: cliutils.Ptr(func(modelexperiments.ApiGetInstanceRequest) (*modelexperiments.GetInstanceResponse, error) { + if settings.instanceFails { + return nil, fmt.Errorf("could not get instance") + } + return settings.instanceResp, nil + }), + GetInstanceTokenExecuteMock: cliutils.Ptr(func(modelexperiments.ApiGetInstanceTokenRequest) (*modelexperiments.GetInstanceTokenResponse, error) { + if settings.tokenFails { + return nil, fmt.Errorf("could not get token") + } + return settings.tokenResp, nil + }), + } +} + +func TestGetInstanceName(t *testing.T) { + name := "instance" + tests := []struct { + name string + settings mockSettings + want string + wantErr bool + }{ + {name: "success", settings: mockSettings{instanceResp: &modelexperiments.GetInstanceResponse{Instance: modelexperiments.Instance{Name: name}}}, want: name}, + {name: "request error", settings: mockSettings{instanceFails: true}, wantErr: true}, + {name: "nil response", settings: mockSettings{}, wantErr: true}, + {name: "nil instance", settings: mockSettings{instanceResp: &modelexperiments.GetInstanceResponse{}}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetInstanceName(context.Background(), newAPIMock(tt.settings), testProjectID, testRegion, testInstanceID) + if (err != nil) != tt.wantErr { + t.Fatalf("error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && got != tt.want { + t.Fatalf("name = %q, want %q", got, tt.want) + } + }) + } +} + +func TestGetTokenName(t *testing.T) { + name := "token" + tests := []struct { + name string + settings mockSettings + want string + wantErr bool + }{ + {name: "success", settings: mockSettings{tokenResp: &modelexperiments.GetInstanceTokenResponse{Token: modelexperiments.TokenMetadata{Name: name}}}, want: name}, + {name: "request error", settings: mockSettings{tokenFails: true}, wantErr: true}, + {name: "nil response", settings: mockSettings{}, wantErr: true}, + {name: "zero token", settings: mockSettings{tokenResp: &modelexperiments.GetInstanceTokenResponse{}}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetTokenName(context.Background(), newAPIMock(tt.settings), testProjectID, testRegion, testInstanceID, testTokenID) + if (err != nil) != tt.wantErr { + t.Fatalf("error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && got != tt.want { + t.Fatalf("name = %q, want %q", got, tt.want) + } + }) + } +} From 2ca23eea9f4e3ef3f72a678aa8094a41fb66c2c0 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Tue, 8 Sep 2026 10:02:23 +0200 Subject: [PATCH 02/13] Generated docs, fixed formatting and lint passing --- docs/stackit.md | 1 + docs/stackit_ai-model-experiments.md | 35 ++++++++++++ docs/stackit_ai-model-experiments_instance.md | 38 +++++++++++++ ...it_ai-model-experiments_instance_create.md | 47 ++++++++++++++++ ...it_ai-model-experiments_instance_delete.md | 40 ++++++++++++++ ...ackit_ai-model-experiments_instance_get.md | 40 ++++++++++++++ ...ckit_ai-model-experiments_instance_list.md | 40 ++++++++++++++ ...kit_ai-model-experiments_instance_patch.md | 53 +++++++++++++++++++ docs/stackit_ai-model-experiments_token.md | 38 +++++++++++++ ...ackit_ai-model-experiments_token_create.md | 45 ++++++++++++++++ ...ackit_ai-model-experiments_token_delete.md | 41 ++++++++++++++ .../stackit_ai-model-experiments_token_get.md | 41 ++++++++++++++ ...stackit_ai-model-experiments_token_list.md | 41 ++++++++++++++ ...tackit_ai-model-experiments_token_patch.md | 44 +++++++++++++++ .../cmd/modelexperiments/instance/instance.go | 1 + .../cmd/modelexperiments/modelexperiments.go | 1 + .../modelexperiments/token/create/create.go | 1 + .../modelexperiments/token/delete/delete.go | 1 + .../cmd/modelexperiments/token/get/get.go | 1 + .../cmd/modelexperiments/token/list/list.go | 1 + .../cmd/modelexperiments/token/patch/patch.go | 1 + internal/cmd/modelexperiments/token/token.go | 1 + 22 files changed, 552 insertions(+) create mode 100644 docs/stackit_ai-model-experiments.md create mode 100644 docs/stackit_ai-model-experiments_instance.md create mode 100644 docs/stackit_ai-model-experiments_instance_create.md create mode 100644 docs/stackit_ai-model-experiments_instance_delete.md create mode 100644 docs/stackit_ai-model-experiments_instance_get.md create mode 100644 docs/stackit_ai-model-experiments_instance_list.md create mode 100644 docs/stackit_ai-model-experiments_instance_patch.md create mode 100644 docs/stackit_ai-model-experiments_token.md create mode 100644 docs/stackit_ai-model-experiments_token_create.md create mode 100644 docs/stackit_ai-model-experiments_token_delete.md create mode 100644 docs/stackit_ai-model-experiments_token_get.md create mode 100644 docs/stackit_ai-model-experiments_token_list.md create mode 100644 docs/stackit_ai-model-experiments_token_patch.md diff --git a/docs/stackit.md b/docs/stackit.md index 5f6b1107a..bcd1f9436 100644 --- a/docs/stackit.md +++ b/docs/stackit.md @@ -27,6 +27,7 @@ stackit [flags] ### SEE ALSO * [stackit affinity-group](./stackit_affinity-group.md) - Manage server affinity groups +* [stackit ai-model-experiments](./stackit_ai-model-experiments.md) - Provides functionality for AI Model Experiments * [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options diff --git a/docs/stackit_ai-model-experiments.md b/docs/stackit_ai-model-experiments.md new file mode 100644 index 000000000..95bc8397f --- /dev/null +++ b/docs/stackit_ai-model-experiments.md @@ -0,0 +1,35 @@ +## stackit ai-model-experiments + +Provides functionality for AI Model Experiments + +### Synopsis + +Provides functionality for AI Model Experiments. + +``` +stackit ai-model-experiments [flags] +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit](./stackit.md) - Manage STACKIT resources using the command line +* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens + diff --git a/docs/stackit_ai-model-experiments_instance.md b/docs/stackit_ai-model-experiments_instance.md new file mode 100644 index 000000000..413a32ae5 --- /dev/null +++ b/docs/stackit_ai-model-experiments_instance.md @@ -0,0 +1,38 @@ +## stackit ai-model-experiments instance + +Provides functionality for AI Model Experiments instances + +### Synopsis + +Provides functionality for AI Model Experiments instances. + +``` +stackit ai-model-experiments instance [flags] +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments 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 + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments](./stackit_ai-model-experiments.md) - Provides functionality for AI Model Experiments +* [stackit ai-model-experiments instance create](./stackit_ai-model-experiments_instance_create.md) - Creates an AI Model Experiments instance +* [stackit ai-model-experiments instance delete](./stackit_ai-model-experiments_instance_delete.md) - Deletes an AI Model Experiments instance +* [stackit ai-model-experiments instance get](./stackit_ai-model-experiments_instance_get.md) - Gets an AI Model Experiments instance +* [stackit ai-model-experiments instance list](./stackit_ai-model-experiments_instance_list.md) - Lists AI Model Experiments instances +* [stackit ai-model-experiments instance patch](./stackit_ai-model-experiments_instance_patch.md) - Updates an AI Model Experiments instance + diff --git a/docs/stackit_ai-model-experiments_instance_create.md b/docs/stackit_ai-model-experiments_instance_create.md new file mode 100644 index 000000000..5e32f4e22 --- /dev/null +++ b/docs/stackit_ai-model-experiments_instance_create.md @@ -0,0 +1,47 @@ +## stackit ai-model-experiments instance create + +Creates an AI Model Experiments instance + +### Synopsis + +Creates an AI Model Experiments (MLflow) instance in your STACKIT project. + +``` +stackit ai-model-experiments instance create [flags] +``` + +### Examples + +``` + Create an AI Model Experiments instance with name "my-tracking" + $ stackit ai-model-experiments instance create --name my-tracking + + Create an instance with a description and labels + $ stackit ai-model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod +``` + +### Options + +``` + --deleted-experiment-retention string Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d) + --description string Instance description + -h, --help Help for "stackit ai-model-experiments instance create" + --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) + -n, --name string Instance name +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances + diff --git a/docs/stackit_ai-model-experiments_instance_delete.md b/docs/stackit_ai-model-experiments_instance_delete.md new file mode 100644 index 000000000..bda6dab16 --- /dev/null +++ b/docs/stackit_ai-model-experiments_instance_delete.md @@ -0,0 +1,40 @@ +## stackit ai-model-experiments instance delete + +Deletes an AI Model Experiments instance + +### Synopsis + +Deletes an AI Model Experiments instance from a STACKIT project. + +``` +stackit ai-model-experiments instance delete INSTANCE_ID [flags] +``` + +### Examples + +``` + Delete an AI Model Experiments instance with ID "xxx" + $ stackit ai-model-experiments instance delete xxx +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments instance delete" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances + diff --git a/docs/stackit_ai-model-experiments_instance_get.md b/docs/stackit_ai-model-experiments_instance_get.md new file mode 100644 index 000000000..5ec18194d --- /dev/null +++ b/docs/stackit_ai-model-experiments_instance_get.md @@ -0,0 +1,40 @@ +## stackit ai-model-experiments instance get + +Gets an AI Model Experiments instance + +### Synopsis + +Gets an AI Model Experiments instance in a STACKIT project. + +``` +stackit ai-model-experiments instance get INSTANCE_ID [flags] +``` + +### Examples + +``` + Get an AI Model Experiments instance with ID "xxx" + $ stackit ai-model-experiments instance get xxx +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments instance get" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances + diff --git a/docs/stackit_ai-model-experiments_instance_list.md b/docs/stackit_ai-model-experiments_instance_list.md new file mode 100644 index 000000000..406482f54 --- /dev/null +++ b/docs/stackit_ai-model-experiments_instance_list.md @@ -0,0 +1,40 @@ +## stackit ai-model-experiments instance list + +Lists AI Model Experiments instances + +### Synopsis + +Lists AI Model Experiments instances in a STACKIT project. + +``` +stackit ai-model-experiments instance list [flags] +``` + +### Examples + +``` + List AI Model Experiments instances in a project + $ stackit ai-model-experiments instance list --region eu01 +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments instance list" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances + diff --git a/docs/stackit_ai-model-experiments_instance_patch.md b/docs/stackit_ai-model-experiments_instance_patch.md new file mode 100644 index 000000000..8ab0b25ea --- /dev/null +++ b/docs/stackit_ai-model-experiments_instance_patch.md @@ -0,0 +1,53 @@ +## stackit ai-model-experiments instance patch + +Updates an AI Model Experiments instance + +### Synopsis + +Partially updates an AI Model Experiments instance in a STACKIT project. + +``` +stackit ai-model-experiments instance patch INSTANCE_ID [flags] +``` + +### Examples + +``` + Update the name of an AI Model Experiments instance with ID "xxx" + $ stackit ai-model-experiments instance patch xxx --name my-new-name + + Update the description of an AI Model Experiments instance + $ stackit ai-model-experiments instance patch xxx --description "team tracking server" + + Update labels on an AI Model Experiments instance + $ stackit ai-model-experiments instance patch xxx --label env=prod + + Update multiple fields of an AI Model Experiments instance + $ stackit ai-model-experiments instance patch xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d +``` + +### Options + +``` + --deleted-experiment-retention string Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d) + --description string Instance description + -h, --help Help for "stackit ai-model-experiments instance patch" + --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) + -n, --name string Instance name +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances + diff --git a/docs/stackit_ai-model-experiments_token.md b/docs/stackit_ai-model-experiments_token.md new file mode 100644 index 000000000..d79f5eea2 --- /dev/null +++ b/docs/stackit_ai-model-experiments_token.md @@ -0,0 +1,38 @@ +## stackit ai-model-experiments token + +Provides functionality for AI Model Experiments instance tokens + +### Synopsis + +Provides functionality for AI Model Experiments instance tokens. + +``` +stackit ai-model-experiments token [flags] +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments token" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments](./stackit_ai-model-experiments.md) - Provides functionality for AI Model Experiments +* [stackit ai-model-experiments token create](./stackit_ai-model-experiments_token_create.md) - Creates an instance token +* [stackit ai-model-experiments token delete](./stackit_ai-model-experiments_token_delete.md) - Deletes an instance token +* [stackit ai-model-experiments token get](./stackit_ai-model-experiments_token_get.md) - Gets an instance token +* [stackit ai-model-experiments token list](./stackit_ai-model-experiments_token_list.md) - Lists instance tokens +* [stackit ai-model-experiments token patch](./stackit_ai-model-experiments_token_patch.md) - Updates an instance token + diff --git a/docs/stackit_ai-model-experiments_token_create.md b/docs/stackit_ai-model-experiments_token_create.md new file mode 100644 index 000000000..a4608e7f9 --- /dev/null +++ b/docs/stackit_ai-model-experiments_token_create.md @@ -0,0 +1,45 @@ +## stackit ai-model-experiments token create + +Creates an instance token + +### Synopsis + +Creates an auth token for an AI Model Experiments instance. + +``` +stackit ai-model-experiments token create [flags] +``` + +### Examples + +``` + Create an auth token + $ stackit ai-model-experiments token create --instance-id xxx --name my-token +``` + +### Options + +``` + --description string Token description + -h, --help Help for "stackit ai-model-experiments token create" + --instance-id string ID of the instance + --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) + --name string Token name + --ttl-duration string Token time to live duration +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens + diff --git a/docs/stackit_ai-model-experiments_token_delete.md b/docs/stackit_ai-model-experiments_token_delete.md new file mode 100644 index 000000000..862e16c35 --- /dev/null +++ b/docs/stackit_ai-model-experiments_token_delete.md @@ -0,0 +1,41 @@ +## stackit ai-model-experiments token delete + +Deletes an instance token + +### Synopsis + +Deletes an auth token from an AI Model Experiments instance. + +``` +stackit ai-model-experiments token delete TOKEN_ID [flags] +``` + +### Examples + +``` + Delete an auth token with ID "xxx" + $ stackit ai-model-experiments token delete xxx --instance-id yyy +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments token delete" + --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 + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens + diff --git a/docs/stackit_ai-model-experiments_token_get.md b/docs/stackit_ai-model-experiments_token_get.md new file mode 100644 index 000000000..aa28ce08b --- /dev/null +++ b/docs/stackit_ai-model-experiments_token_get.md @@ -0,0 +1,41 @@ +## stackit ai-model-experiments token get + +Gets an instance token + +### Synopsis + +Gets an auth token for an AI Model Experiments instance. + +``` +stackit ai-model-experiments token get TOKEN_ID [flags] +``` + +### Examples + +``` + Get an auth token with ID "xxx" + $ stackit ai-model-experiments token get xxx --instance-id yyy +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments token get" + --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 + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens + diff --git a/docs/stackit_ai-model-experiments_token_list.md b/docs/stackit_ai-model-experiments_token_list.md new file mode 100644 index 000000000..f51bd24a7 --- /dev/null +++ b/docs/stackit_ai-model-experiments_token_list.md @@ -0,0 +1,41 @@ +## stackit ai-model-experiments token list + +Lists instance tokens + +### Synopsis + +Lists all auth tokens for an AI Model Experiments instance. + +``` +stackit ai-model-experiments token list [flags] +``` + +### Examples + +``` + List all tokens for an instance + $ stackit ai-model-experiments token list --instance-id xxx +``` + +### Options + +``` + -h, --help Help for "stackit ai-model-experiments token list" + --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 + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens + diff --git a/docs/stackit_ai-model-experiments_token_patch.md b/docs/stackit_ai-model-experiments_token_patch.md new file mode 100644 index 000000000..390c9414c --- /dev/null +++ b/docs/stackit_ai-model-experiments_token_patch.md @@ -0,0 +1,44 @@ +## stackit ai-model-experiments token patch + +Updates an instance token + +### Synopsis + +Partially updates an auth token for an AI Model Experiments instance. + +``` +stackit ai-model-experiments token patch TOKEN_ID [flags] +``` + +### Examples + +``` + Update an auth token with ID "xxx" + $ stackit ai-model-experiments token patch xxx --instance-id yyy --name updated-token +``` + +### Options + +``` + --description string Token description + -h, --help Help for "stackit ai-model-experiments token patch" + --instance-id string ID of the instance + --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) + --name string Token name +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens + diff --git a/internal/cmd/modelexperiments/instance/instance.go b/internal/cmd/modelexperiments/instance/instance.go index 1cf6073c3..de3a56c00 100644 --- a/internal/cmd/modelexperiments/instance/instance.go +++ b/internal/cmd/modelexperiments/instance/instance.go @@ -2,6 +2,7 @@ package instance import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/create" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/get" diff --git a/internal/cmd/modelexperiments/modelexperiments.go b/internal/cmd/modelexperiments/modelexperiments.go index ae6804e09..ff4b96518 100644 --- a/internal/cmd/modelexperiments/modelexperiments.go +++ b/internal/cmd/modelexperiments/modelexperiments.go @@ -2,6 +2,7 @@ package modelexperiments import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token" "github.com/stackitcloud/stackit-cli/internal/pkg/args" diff --git a/internal/cmd/modelexperiments/token/create/create.go b/internal/cmd/modelexperiments/token/create/create.go index ac6e5e1e9..05e51794a 100644 --- a/internal/cmd/modelexperiments/token/create/create.go +++ b/internal/cmd/modelexperiments/token/create/create.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/spf13/cobra" + "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" diff --git a/internal/cmd/modelexperiments/token/delete/delete.go b/internal/cmd/modelexperiments/token/delete/delete.go index 902ff5081..0c3c2245f 100644 --- a/internal/cmd/modelexperiments/token/delete/delete.go +++ b/internal/cmd/modelexperiments/token/delete/delete.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/spf13/cobra" + "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" diff --git a/internal/cmd/modelexperiments/token/get/get.go b/internal/cmd/modelexperiments/token/get/get.go index 5f8db6d6d..56e5155f9 100644 --- a/internal/cmd/modelexperiments/token/get/get.go +++ b/internal/cmd/modelexperiments/token/get/get.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/spf13/cobra" + "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" diff --git a/internal/cmd/modelexperiments/token/list/list.go b/internal/cmd/modelexperiments/token/list/list.go index cf3da805f..84ad1a506 100644 --- a/internal/cmd/modelexperiments/token/list/list.go +++ b/internal/cmd/modelexperiments/token/list/list.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/spf13/cobra" + "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" diff --git a/internal/cmd/modelexperiments/token/patch/patch.go b/internal/cmd/modelexperiments/token/patch/patch.go index 4e3c91e00..38d63153e 100644 --- a/internal/cmd/modelexperiments/token/patch/patch.go +++ b/internal/cmd/modelexperiments/token/patch/patch.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/spf13/cobra" + "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" diff --git a/internal/cmd/modelexperiments/token/token.go b/internal/cmd/modelexperiments/token/token.go index 67d8bced1..3e796e95b 100644 --- a/internal/cmd/modelexperiments/token/token.go +++ b/internal/cmd/modelexperiments/token/token.go @@ -2,6 +2,7 @@ package token import ( "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/create" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/get" From dcc849902c966d8710c3d8c634ceb92b8de171e1 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Tue, 8 Sep 2026 12:00:48 +0200 Subject: [PATCH 03/13] Fixed region parsing --- internal/pkg/services/modelexperiments/client/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/services/modelexperiments/client/client.go b/internal/pkg/services/modelexperiments/client/client.go index 9ab23f22d..a6b12827c 100644 --- a/internal/pkg/services/modelexperiments/client/client.go +++ b/internal/pkg/services/modelexperiments/client/client.go @@ -10,5 +10,5 @@ import ( ) func ConfigureClient(p *print.Printer, cliVersion string) (*modelexperiments.APIClient, error) { - return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.AIModelExperimentsCustomEndpointKey), true, genericclient.CreateApiClient[*modelexperiments.APIClient](modelexperiments.NewAPIClient)) + return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.AIModelExperimentsCustomEndpointKey), false, genericclient.CreateApiClient[*modelexperiments.APIClient](modelexperiments.NewAPIClient)) } From 45d864011dd615edd80aa7ffc8ea66b36562730a Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Tue, 8 Sep 2026 17:26:24 +0200 Subject: [PATCH 04/13] Fixed region and output response, STACKITMLO-1718 --- .../instance/create/create.go | 4 +-- .../instance/create/create_test.go | 3 +-- .../instance/delete/delete.go | 6 +---- .../instance/delete/delete_test.go | 1 - .../cmd/modelexperiments/instance/get/get.go | 17 +++++++----- .../modelexperiments/instance/get/get_test.go | 1 - .../modelexperiments/instance/list/list.go | 26 ++++++++++++++----- .../instance/list/list_test.go | 1 - .../modelexperiments/instance/patch/patch.go | 6 +---- .../instance/patch/patch_test.go | 1 - .../cmd/modelexperiments/token/get/get.go | 5 +++- .../cmd/modelexperiments/token/list/list.go | 15 ++++++++++- .../cmd/modelexperiments/token/patch/patch.go | 5 +++- 13 files changed, 57 insertions(+), 34 deletions(-) diff --git a/internal/cmd/modelexperiments/instance/create/create.go b/internal/cmd/modelexperiments/instance/create/create.go index 9829833fc..173bdea48 100644 --- a/internal/cmd/modelexperiments/instance/create/create.go +++ b/internal/cmd/modelexperiments/instance/create/create.go @@ -33,7 +33,6 @@ type inputModel struct { Description *string Labels *map[string]string Retention *string - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -117,7 +116,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr, Retention: flags.FlagToStringPointer(p, cmd, retentionFlag), - Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), } p.DebugInputModel(model) @@ -125,7 +123,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiCreateInstanceRequest { - req := apiClient.DefaultAPI.CreateInstance(ctx, model.ProjectId, model.Region) + req := apiClient.DefaultAPI.CreateInstance(ctx, model.ProjectId, model.GlobalFlagModel.Region) payload := modelexperiments.CreateInstancePayload{ Name: model.Name, diff --git a/internal/cmd/modelexperiments/instance/create/create_test.go b/internal/cmd/modelexperiments/instance/create/create_test.go index 2bc86ef03..ba90c9eb2 100644 --- a/internal/cmd/modelexperiments/instance/create/create_test.go +++ b/internal/cmd/modelexperiments/instance/create/create_test.go @@ -45,8 +45,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { Region: "eu01", Verbosity: globalflags.VerbosityDefault, }, - Name: "example", - Region: "eu01", + Name: "example", } for _, mod := range mods { mod(model) diff --git a/internal/cmd/modelexperiments/instance/delete/delete.go b/internal/cmd/modelexperiments/instance/delete/delete.go index 4136d4984..69ca7fa04 100644 --- a/internal/cmd/modelexperiments/instance/delete/delete.go +++ b/internal/cmd/modelexperiments/instance/delete/delete.go @@ -9,7 +9,6 @@ import ( "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/modelexperiments/client" @@ -26,7 +25,6 @@ const ( type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -80,7 +78,6 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - _ = flags.MarkFlagsRequired(cmd, globalflags.RegionFlag) } func parseInput( @@ -97,7 +94,6 @@ func parseInput( model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: inputArgs[0], - Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), } p.DebugInputModel(model) @@ -113,7 +109,7 @@ func buildDeleteInstanceRequest( return apiClient.DefaultAPI.DeleteInstance( ctx, model.ProjectId, - model.Region, + model.GlobalFlagModel.Region, model.InstanceId, ) } diff --git a/internal/cmd/modelexperiments/instance/delete/delete_test.go b/internal/cmd/modelexperiments/instance/delete/delete_test.go index fe21ee06c..544845108 100644 --- a/internal/cmd/modelexperiments/instance/delete/delete_test.go +++ b/internal/cmd/modelexperiments/instance/delete/delete_test.go @@ -53,7 +53,6 @@ func fixtureInputModel( Verbosity: globalflags.VerbosityDefault, }, InstanceId: testInstanceId, - Region: "eu01", } for _, mod := range mods { diff --git a/internal/cmd/modelexperiments/instance/get/get.go b/internal/cmd/modelexperiments/instance/get/get.go index 3bd5a0ff6..69f613962 100644 --- a/internal/cmd/modelexperiments/instance/get/get.go +++ b/internal/cmd/modelexperiments/instance/get/get.go @@ -9,10 +9,10 @@ import ( "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -26,7 +26,6 @@ const ( type inputModel struct { *globalflags.GlobalFlagModel InstanceId string - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -71,7 +70,6 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - _ = flags.MarkFlagsRequired(cmd, globalflags.RegionFlag) } func parseInput( @@ -88,7 +86,6 @@ func parseInput( model := inputModel{ GlobalFlagModel: globalFlags, InstanceId: inputArgs[0], - Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), } p.DebugInputModel(model) @@ -104,7 +101,7 @@ func buildGetInstanceRequest( return apiClient.DefaultAPI.GetInstance( ctx, model.ProjectId, - model.Region, + model.GlobalFlagModel.Region, model.InstanceId, ) } @@ -119,6 +116,14 @@ func outputResult( } return p.OutputResult(outputFormat, resp.Instance, func() error { - return nil + table := tables.NewTable() + table.SetHeader("ID", "NAME", "REGION", "STATUS") + table.AddRow( + resp.Instance.Id, + resp.Instance.Name, + resp.Instance.Region, + resp.Instance.State, + ) + return table.Display(p) }) } diff --git a/internal/cmd/modelexperiments/instance/get/get_test.go b/internal/cmd/modelexperiments/instance/get/get_test.go index a15dcc374..1fdf6c814 100644 --- a/internal/cmd/modelexperiments/instance/get/get_test.go +++ b/internal/cmd/modelexperiments/instance/get/get_test.go @@ -53,7 +53,6 @@ func fixtureInputModel( Verbosity: globalflags.VerbosityDefault, }, InstanceId: testInstanceId, - Region: "eu01", } for _, mod := range mods { diff --git a/internal/cmd/modelexperiments/instance/list/list.go b/internal/cmd/modelexperiments/instance/list/list.go index 0ba444e0e..840973fc4 100644 --- a/internal/cmd/modelexperiments/instance/list/list.go +++ b/internal/cmd/modelexperiments/instance/list/list.go @@ -9,10 +9,10 @@ import ( "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/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" @@ -20,7 +20,6 @@ import ( type inputModel struct { *globalflags.GlobalFlagModel - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -65,7 +64,6 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - _ = flags.MarkFlagsRequired(cmd, globalflags.RegionFlag) } func parseInput( @@ -81,7 +79,6 @@ func parseInput( model := inputModel{ GlobalFlagModel: globalFlags, - Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), } p.DebugInputModel(model) @@ -97,7 +94,7 @@ func buildListInstancesRequest( return apiClient.DefaultAPI.ListInstances( ctx, model.ProjectId, - model.Region, + model.GlobalFlagModel.Region, ) } @@ -111,6 +108,23 @@ func outputResult( } return p.OutputResult(outputFormat, resp.Instances, func() error { - return nil + if len(resp.Instances) == 0 { + p.Outputf("No instances found\n") + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "NAME", "REGION", "STATUS") + + for _, instance := range resp.Instances { + table.AddRow( + instance.Id, + instance.Name, + instance.Region, + instance.State, + ) + } + + return table.Display(p) }) } diff --git a/internal/cmd/modelexperiments/instance/list/list_test.go b/internal/cmd/modelexperiments/instance/list/list_test.go index ec288bad0..0c536fd6a 100644 --- a/internal/cmd/modelexperiments/instance/list/list_test.go +++ b/internal/cmd/modelexperiments/instance/list/list_test.go @@ -51,7 +51,6 @@ func fixtureInputModel( Region: "eu01", Verbosity: globalflags.VerbosityDefault, }, - Region: "eu01", } for _, mod := range mods { diff --git a/internal/cmd/modelexperiments/instance/patch/patch.go b/internal/cmd/modelexperiments/instance/patch/patch.go index 3d7082bfe..1d1c77f94 100644 --- a/internal/cmd/modelexperiments/instance/patch/patch.go +++ b/internal/cmd/modelexperiments/instance/patch/patch.go @@ -37,8 +37,6 @@ type inputModel struct { Description *string Labels *map[string]*string Retention *string - - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -139,8 +137,6 @@ func parseInput( Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr, Retention: flags.FlagToStringPointer(p, cmd, retentionFlag), - - Region: flags.FlagToStringValue(p, cmd, globalflags.RegionFlag), } if model.Name == nil && @@ -163,7 +159,7 @@ func buildPatchInstanceRequest( req := apiClient.DefaultAPI.PartialUpdateInstance( ctx, model.ProjectId, - model.Region, + model.GlobalFlagModel.Region, model.InstanceId, ) diff --git a/internal/cmd/modelexperiments/instance/patch/patch_test.go b/internal/cmd/modelexperiments/instance/patch/patch_test.go index 70407abee..f1efb15dd 100644 --- a/internal/cmd/modelexperiments/instance/patch/patch_test.go +++ b/internal/cmd/modelexperiments/instance/patch/patch_test.go @@ -54,7 +54,6 @@ func fixtureInputModel( Verbosity: globalflags.VerbosityDefault, }, InstanceId: testInstanceId, - Region: "eu01", } for _, mod := range mods { diff --git a/internal/cmd/modelexperiments/token/get/get.go b/internal/cmd/modelexperiments/token/get/get.go index 56e5155f9..11010c898 100644 --- a/internal/cmd/modelexperiments/token/get/get.go +++ b/internal/cmd/modelexperiments/token/get/get.go @@ -82,5 +82,8 @@ func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments. if resp == nil || resp.Token.Name == "" { return fmt.Errorf("response token is nil") } - return p.OutputResult(outputFormat, resp.Token, func() error { return nil }) + return p.OutputResult(outputFormat, resp.Token, func() error { + p.Outputf("Instance token %q (ID: %s)\n", resp.Token.Name, resp.Token.Id) + return nil + }) } diff --git a/internal/cmd/modelexperiments/token/list/list.go b/internal/cmd/modelexperiments/token/list/list.go index 84ad1a506..78a3b7a28 100644 --- a/internal/cmd/modelexperiments/token/list/list.go +++ b/internal/cmd/modelexperiments/token/list/list.go @@ -13,6 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" @@ -72,5 +73,17 @@ func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments. if resp == nil { return fmt.Errorf("response is nil") } - return p.OutputResult(outputFormat, resp.Tokens, func() error { return nil }) + return p.OutputResult(outputFormat, resp.Tokens, func() error { + if len(resp.Tokens) == 0 { + p.Outputf("No instance tokens found\n") + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "NAME", "DESCRIPTION") + for _, token := range resp.Tokens { + table.AddRow(token.Id, token.Name, token.Description) + } + return table.Display(p) + }) } diff --git a/internal/cmd/modelexperiments/token/patch/patch.go b/internal/cmd/modelexperiments/token/patch/patch.go index 38d63153e..a33cdb520 100644 --- a/internal/cmd/modelexperiments/token/patch/patch.go +++ b/internal/cmd/modelexperiments/token/patch/patch.go @@ -104,5 +104,8 @@ func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments. if resp == nil || resp.Token.Name == "" { return fmt.Errorf("response token is nil") } - return p.OutputResult(outputFormat, resp.Token, func() error { return nil }) + return p.OutputResult(outputFormat, resp.Token, func() error { + p.Outputf("Updated instance token %q (ID: %s)\n", resp.Token.Name, resp.Token.Id) + return nil + }) } From b146f0323f587607927adfef6789cc9006000256 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Tue, 8 Sep 2026 22:30:45 +0200 Subject: [PATCH 05/13] Fix: model experiment list retrieval and response parsing, STACKITMLO-1718 --- internal/cmd/modelexperiments/instance/create/create.go | 2 +- internal/cmd/modelexperiments/instance/delete/delete.go | 7 +------ internal/cmd/modelexperiments/instance/get/get.go | 7 +------ internal/cmd/modelexperiments/instance/list/list.go | 7 +------ internal/cmd/modelexperiments/instance/patch/patch.go | 2 +- internal/cmd/modelexperiments/token/create/create.go | 8 +++----- internal/cmd/modelexperiments/token/create/create_test.go | 4 ++-- internal/cmd/modelexperiments/token/delete/delete.go | 6 ++---- internal/cmd/modelexperiments/token/delete/delete_test.go | 4 ++-- internal/cmd/modelexperiments/token/get/get.go | 6 ++---- internal/cmd/modelexperiments/token/get/get_test.go | 4 ++-- internal/cmd/modelexperiments/token/list/list.go | 6 ++---- internal/cmd/modelexperiments/token/list/list_test.go | 4 ++-- internal/cmd/modelexperiments/token/patch/patch.go | 8 +++----- internal/cmd/modelexperiments/token/patch/patch_test.go | 4 ++-- 15 files changed, 27 insertions(+), 52 deletions(-) diff --git a/internal/cmd/modelexperiments/instance/create/create.go b/internal/cmd/modelexperiments/instance/create/create.go index 173bdea48..d7d72d4f7 100644 --- a/internal/cmd/modelexperiments/instance/create/create.go +++ b/internal/cmd/modelexperiments/instance/create/create.go @@ -123,7 +123,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiCreateInstanceRequest { - req := apiClient.DefaultAPI.CreateInstance(ctx, model.ProjectId, model.GlobalFlagModel.Region) + req := apiClient.DefaultAPI.CreateInstance(ctx, model.ProjectId, model.Region) payload := modelexperiments.CreateInstancePayload{ Name: model.Name, diff --git a/internal/cmd/modelexperiments/instance/delete/delete.go b/internal/cmd/modelexperiments/instance/delete/delete.go index 69ca7fa04..33f92a286 100644 --- a/internal/cmd/modelexperiments/instance/delete/delete.go +++ b/internal/cmd/modelexperiments/instance/delete/delete.go @@ -72,14 +72,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { }, } - configureFlags(cmd) - return cmd } -func configureFlags(cmd *cobra.Command) { -} - func parseInput( p *print.Printer, cmd *cobra.Command, @@ -109,7 +104,7 @@ func buildDeleteInstanceRequest( return apiClient.DefaultAPI.DeleteInstance( ctx, model.ProjectId, - model.GlobalFlagModel.Region, + model.Region, model.InstanceId, ) } diff --git a/internal/cmd/modelexperiments/instance/get/get.go b/internal/cmd/modelexperiments/instance/get/get.go index 69f613962..b156a8d9b 100644 --- a/internal/cmd/modelexperiments/instance/get/get.go +++ b/internal/cmd/modelexperiments/instance/get/get.go @@ -64,14 +64,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { }, } - configureFlags(cmd) - return cmd } -func configureFlags(cmd *cobra.Command) { -} - func parseInput( p *print.Printer, cmd *cobra.Command, @@ -101,7 +96,7 @@ func buildGetInstanceRequest( return apiClient.DefaultAPI.GetInstance( ctx, model.ProjectId, - model.GlobalFlagModel.Region, + model.Region, model.InstanceId, ) } diff --git a/internal/cmd/modelexperiments/instance/list/list.go b/internal/cmd/modelexperiments/instance/list/list.go index 840973fc4..d1ee5b6a7 100644 --- a/internal/cmd/modelexperiments/instance/list/list.go +++ b/internal/cmd/modelexperiments/instance/list/list.go @@ -58,14 +58,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { }, } - configureFlags(cmd) - return cmd } -func configureFlags(cmd *cobra.Command) { -} - func parseInput( p *print.Printer, cmd *cobra.Command, @@ -94,7 +89,7 @@ func buildListInstancesRequest( return apiClient.DefaultAPI.ListInstances( ctx, model.ProjectId, - model.GlobalFlagModel.Region, + model.Region, ) } diff --git a/internal/cmd/modelexperiments/instance/patch/patch.go b/internal/cmd/modelexperiments/instance/patch/patch.go index 1d1c77f94..1b4e080f7 100644 --- a/internal/cmd/modelexperiments/instance/patch/patch.go +++ b/internal/cmd/modelexperiments/instance/patch/patch.go @@ -159,7 +159,7 @@ func buildPatchInstanceRequest( req := apiClient.DefaultAPI.PartialUpdateInstance( ctx, model.ProjectId, - model.GlobalFlagModel.Region, + model.Region, model.InstanceId, ) diff --git a/internal/cmd/modelexperiments/token/create/create.go b/internal/cmd/modelexperiments/token/create/create.go index 05e51794a..b9fa0f2a6 100644 --- a/internal/cmd/modelexperiments/token/create/create.go +++ b/internal/cmd/modelexperiments/token/create/create.go @@ -20,7 +20,6 @@ import ( const ( instanceIDFlag = "instance-id" - regionFlag = "region" nameFlag = "name" descriptionFlag = "description" labelFlag = "label" @@ -30,7 +29,6 @@ const ( type inputModel struct { *globalflags.GlobalFlagModel InstanceID string - Region string Name string Description *string Labels *map[string]string @@ -63,7 +61,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(descriptionFlag, "", "Token description") cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) cmd.Flags().String(ttlDurationFlag, "", "Token time to live duration") - _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag, nameFlag) + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, nameFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { @@ -79,7 +77,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, if len(labels) > 0 { labelsPtr = &labels } - model := &inputModel{GlobalFlagModel: globalFlags, InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag), Name: flags.FlagToStringValue(p, cmd, nameFlag), Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr, TTLDuration: flags.FlagToStringPointer(p, cmd, ttlDurationFlag)} + model := &inputModel{GlobalFlagModel: globalFlags, InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Name: flags.FlagToStringValue(p, cmd, nameFlag), Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr, TTLDuration: flags.FlagToStringPointer(p, cmd, ttlDurationFlag)} if model.Name == "" { return nil, fmt.Errorf("%s flag is required", nameFlag) } @@ -89,7 +87,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiCreateInstanceTokenRequest { payload := modelexperiments.CreateInstanceTokenPayload{Name: model.Name, Description: model.Description, Labels: model.Labels, TtlDuration: model.TTLDuration} - return apiClient.DefaultAPI.CreateInstanceToken(ctx, model.ProjectId, model.Region, model.InstanceID).CreateInstanceTokenPayload(payload) + return apiClient.DefaultAPI.CreateInstanceToken(ctx, model.ProjectId, model.GlobalFlagModel.Region, model.InstanceID).CreateInstanceTokenPayload(payload) } func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.CreateInstanceTokenResponse) error { diff --git a/internal/cmd/modelexperiments/token/create/create_test.go b/internal/cmd/modelexperiments/token/create/create_test.go index cedf3b09e..ad8fc2429 100644 --- a/internal/cmd/modelexperiments/token/create/create_test.go +++ b/internal/cmd/modelexperiments/token/create/create_test.go @@ -26,7 +26,7 @@ var testProjectID = uuid.NewString() var testInstanceID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01", nameFlag: "example"} + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01", nameFlag: "example"} for _, mod := range mods { mod(values) } @@ -34,7 +34,7 @@ func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { } func fixtureInputModel(mods ...func(*inputModel)) *inputModel { - model := &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, InstanceID: testInstanceID, Region: "eu01", Name: "example"} + model := &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, InstanceID: testInstanceID, Name: "example"} for _, mod := range mods { mod(model) } diff --git a/internal/cmd/modelexperiments/token/delete/delete.go b/internal/cmd/modelexperiments/token/delete/delete.go index 0c3c2245f..77106410f 100644 --- a/internal/cmd/modelexperiments/token/delete/delete.go +++ b/internal/cmd/modelexperiments/token/delete/delete.go @@ -22,14 +22,12 @@ import ( const ( tokenIDArg = "TOKEN_ID" instanceIDFlag = "instance-id" - regionFlag = "region" ) type inputModel struct { *globalflags.GlobalFlagModel TokenID string InstanceID string - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -64,7 +62,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") - _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -72,7 +70,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu if globalFlags.ProjectId == "" { return nil, &cliErr.ProjectIdError{} } - model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag)} + model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag)} p.DebugInputModel(model) return model, nil } diff --git a/internal/cmd/modelexperiments/token/delete/delete_test.go b/internal/cmd/modelexperiments/token/delete/delete_test.go index 94c013fe0..5909815ac 100644 --- a/internal/cmd/modelexperiments/token/delete/delete_test.go +++ b/internal/cmd/modelexperiments/token/delete/delete_test.go @@ -25,14 +25,14 @@ var testInstanceID = uuid.NewString() var testTokenID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } return values } func fixtureInputModel() *inputModel { - return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID, Region: "eu01"} + return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID} } func fixtureRequest() modelexperiments.ApiDeleteInstanceTokenRequest { return testClient.DefaultAPI.DeleteInstanceToken(testContext, testProjectID, "eu01", testTokenID, testInstanceID) diff --git a/internal/cmd/modelexperiments/token/get/get.go b/internal/cmd/modelexperiments/token/get/get.go index 11010c898..d0f4163e0 100644 --- a/internal/cmd/modelexperiments/token/get/get.go +++ b/internal/cmd/modelexperiments/token/get/get.go @@ -22,14 +22,12 @@ import ( const ( tokenIDArg = "TOKEN_ID" instanceIDFlag = "instance-id" - regionFlag = "region" ) type inputModel struct { *globalflags.GlobalFlagModel TokenID string InstanceID string - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -61,7 +59,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") - _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -69,7 +67,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu if globalFlags.ProjectId == "" { return nil, &cliErr.ProjectIdError{} } - model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag)} + model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag)} p.DebugInputModel(model) return model, nil } diff --git a/internal/cmd/modelexperiments/token/get/get_test.go b/internal/cmd/modelexperiments/token/get/get_test.go index ec88ea99d..4a7ab60a5 100644 --- a/internal/cmd/modelexperiments/token/get/get_test.go +++ b/internal/cmd/modelexperiments/token/get/get_test.go @@ -26,14 +26,14 @@ var testInstanceID = uuid.NewString() var testTokenID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } return values } func fixtureInputModel() *inputModel { - return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID, Region: "eu01"} + return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID} } func fixtureRequest() modelexperiments.ApiGetInstanceTokenRequest { return testClient.DefaultAPI.GetInstanceToken(testContext, testProjectID, "eu01", testTokenID, testInstanceID) diff --git a/internal/cmd/modelexperiments/token/list/list.go b/internal/cmd/modelexperiments/token/list/list.go index 78a3b7a28..194af8f4d 100644 --- a/internal/cmd/modelexperiments/token/list/list.go +++ b/internal/cmd/modelexperiments/token/list/list.go @@ -21,13 +21,11 @@ import ( const ( instanceIDFlag = "instance-id" - regionFlag = "region" ) type inputModel struct { *globalflags.GlobalFlagModel InstanceID string - Region string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -52,7 +50,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") - _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { @@ -60,7 +58,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, if globalFlags.ProjectId == "" { return nil, &cliErr.ProjectIdError{} } - model := &inputModel{GlobalFlagModel: globalFlags, InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag)} + model := &inputModel{GlobalFlagModel: globalFlags, InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag)} p.DebugInputModel(model) return model, nil } diff --git a/internal/cmd/modelexperiments/token/list/list_test.go b/internal/cmd/modelexperiments/token/list/list_test.go index fbbd6352b..9d7218969 100644 --- a/internal/cmd/modelexperiments/token/list/list_test.go +++ b/internal/cmd/modelexperiments/token/list/list_test.go @@ -25,14 +25,14 @@ var testProjectID = uuid.NewString() var testInstanceID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } return values } func fixtureInputModel() *inputModel { - return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, InstanceID: testInstanceID, Region: "eu01"} + return &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, InstanceID: testInstanceID} } func fixtureRequest() modelexperiments.ApiListInstanceTokensRequest { return testClient.DefaultAPI.ListInstanceTokens(testContext, testProjectID, "eu01", testInstanceID) diff --git a/internal/cmd/modelexperiments/token/patch/patch.go b/internal/cmd/modelexperiments/token/patch/patch.go index a33cdb520..904b28f36 100644 --- a/internal/cmd/modelexperiments/token/patch/patch.go +++ b/internal/cmd/modelexperiments/token/patch/patch.go @@ -22,7 +22,6 @@ import ( const ( tokenIDArg = "TOKEN_ID" instanceIDFlag = "instance-id" - regionFlag = "region" nameFlag = "name" descriptionFlag = "description" labelFlag = "label" @@ -32,7 +31,6 @@ type inputModel struct { *globalflags.GlobalFlagModel TokenID string InstanceID string - Region string Name *string Description *string Labels *map[string]string @@ -67,7 +65,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(nameFlag, "", "Token name") cmd.Flags().String(descriptionFlag, "", "Token description") cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) - _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, regionFlag) + _ = flags.MarkFlagsRequired(cmd, instanceIDFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -83,7 +81,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu if len(labels) > 0 { labelsPtr = &labels } - model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Region: flags.FlagToStringValue(p, cmd, regionFlag), Name: flags.FlagToStringPointer(p, cmd, nameFlag), Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr} + model := &inputModel{GlobalFlagModel: globalFlags, TokenID: inputArgs[0], InstanceID: flags.FlagToStringValue(p, cmd, instanceIDFlag), Name: flags.FlagToStringPointer(p, cmd, nameFlag), Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), Labels: labelsPtr} p.DebugInputModel(model) return model, nil } @@ -97,7 +95,7 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperi } } payload := modelexperiments.PartialUpdateInstanceTokenPayload{Name: model.Name, Description: model.Description, Labels: &labels} - return apiClient.DefaultAPI.PartialUpdateInstanceToken(ctx, model.ProjectId, model.Region, model.TokenID, model.InstanceID).PartialUpdateInstanceTokenPayload(payload) + return apiClient.DefaultAPI.PartialUpdateInstanceToken(ctx, model.ProjectId, model.GlobalFlagModel.Region, model.TokenID, model.InstanceID).PartialUpdateInstanceTokenPayload(payload) } func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.PartialUpdateInstanceTokenResponse) error { diff --git a/internal/cmd/modelexperiments/token/patch/patch_test.go b/internal/cmd/modelexperiments/token/patch/patch_test.go index 638932358..c846bb9b3 100644 --- a/internal/cmd/modelexperiments/token/patch/patch_test.go +++ b/internal/cmd/modelexperiments/token/patch/patch_test.go @@ -27,14 +27,14 @@ var testInstanceID = uuid.NewString() var testTokenID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, regionFlag: "eu01"} + values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } return values } func fixtureInputModel(mods ...func(*inputModel)) *inputModel { - model := &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID, Region: "eu01"} + model := &inputModel{GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectID, Region: "eu01", Verbosity: globalflags.VerbosityDefault}, TokenID: testTokenID, InstanceID: testInstanceID} for _, mod := range mods { mod(model) } From 69a2e31c91c862ababafa6025f0b0892d69afa46 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Fri, 11 Sep 2026 09:37:48 +0200 Subject: [PATCH 06/13] feat: fix output, STACKITMLO-1718 --- internal/cmd/modelexperiments/instance/list/list.go | 2 +- internal/cmd/modelexperiments/token/list/list.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/cmd/modelexperiments/instance/list/list.go b/internal/cmd/modelexperiments/instance/list/list.go index d1ee5b6a7..b810039f7 100644 --- a/internal/cmd/modelexperiments/instance/list/list.go +++ b/internal/cmd/modelexperiments/instance/list/list.go @@ -115,7 +115,7 @@ func outputResult( table.AddRow( instance.Id, instance.Name, - instance.Region, + *instance.Region, instance.State, ) } diff --git a/internal/cmd/modelexperiments/token/list/list.go b/internal/cmd/modelexperiments/token/list/list.go index 194af8f4d..51fe61e9f 100644 --- a/internal/cmd/modelexperiments/token/list/list.go +++ b/internal/cmd/modelexperiments/token/list/list.go @@ -80,7 +80,11 @@ func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments. table := tables.NewTable() table.SetHeader("ID", "NAME", "DESCRIPTION") for _, token := range resp.Tokens { - table.AddRow(token.Id, token.Name, token.Description) + description := "" + if token.Description != nil { + description = *token.Description + } + table.AddRow(token.Id, token.Name, description) } return table.Display(p) }) From 0d57a2d50de73647d1806ba337d9664a4952ec5b Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Tue, 22 Sep 2026 19:51:52 +0200 Subject: [PATCH 07/13] fix: internal tracking issues, STACKITMLO-1718 --- go.mod | 4 +--- go.sum | 16 ++-------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 3dcfb37c9..91f41ecda 100644 --- a/go.mod +++ b/go.mod @@ -24,11 +24,9 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/edge v0.13.2 github.com/stackitcloud/stackit-sdk-go/services/git v0.14.1 github.com/stackitcloud/stackit-sdk-go/services/iaas v1.14.1 - github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.1 - github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.1 - github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.2 github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.2 + github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1 github.com/stackitcloud/stackit-sdk-go/services/opensearch v1.3.1 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.15.0 diff --git a/go.sum b/go.sum index 3ebd31415..62b1c1061 100644 --- a/go.sum +++ b/go.sum @@ -626,20 +626,6 @@ github.com/stackitcloud/stackit-sdk-go/services/git v0.14.1 h1:7R5peSRer3Y83mfB6 github.com/stackitcloud/stackit-sdk-go/services/git v0.14.1/go.mod h1:kz2pl17t9MNQx0+fNXg10ZAVfjmvOtsHB7VlAzu/Io8= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.14.1 h1:P8XhOi8TLA7NbLHhxU8dCJjgQpha/65agos3pI59+vw= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.14.1/go.mod h1:/QpOsaCOQjwGgQqG8y9xXqryp25AfkorPjInX9xIeOU= -github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.1 h1:f9sKrmLxBK9g0zS9qGGDFVQx+6HjBALL18ibfPFOqPw= -github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.1/go.mod h1:UXhPs7JXjQvTp14d4ZffSLnFMQyB7y1cj100XWwQudI= -github.com/stackitcloud/stackit-sdk-go/services/kms v1.13.0 h1:4S9gcTlijtvlF8wYZR5anbQ98oIP2+fIaEsvBXluux4= -github.com/stackitcloud/stackit-sdk-go/services/kms v1.13.0/go.mod h1:pVaCmb1ZHAPGVRlSlBlVOjThp9Tb2sX9+nRX0M+d1KU= -github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.15.1 h1:+NT2okD80TlFNDTKSgaEvV/o7LevTrATqcDL0r4nuhU= -github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.15.1/go.mod h1:+Ld3dn648I+YKcBV3fEkYpDSr3fel421+LurJGywSBs= -github.com/stackitcloud/stackit-sdk-go/services/logme v1.3.0 h1:GR2hjluc/LDqj+QnNUh57XH1vp9Tlcf22TlIwYtRG4Q= -github.com/stackitcloud/stackit-sdk-go/services/logme v1.3.0/go.mod h1:JDOOYaGgcBts2x52nKPRMFgSZe7qqOFmfz1xIXCQgRY= -github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.1 h1:sWvLJF6/7Nh3hONpZDFWxTYT+QiqbypMbnWuh6qSQSI= -github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.1/go.mod h1:tvRejL8w5KpGBbLFPQ+dXOJURgZ3OMbZmwxlKQrGMuA= -github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.0 h1:0JuNaVbyuIUEX5Cn88XoC5FVGU92cP1w3ZLAvCuuohQ= -github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.0/go.mod h1:joa89Y1dyn0j22FstRcIKfW2ada3FDxNfttxSvq27uY= -github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 h1:g8AX62NqDLJy2dNPPTzfeaBoaR2K+EIwxF8sRiAESL0= -github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0/go.mod h1:TW2PYG0kSrfAos3yY8wUxDem0J9ZYSXulnbzBZ4BTaE= github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.2 h1:wC0J5fHffmbPLzsqjQ08P9Jb10/BbJUm45TSNfAm8jc= github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.2/go.mod h1:WIgF8d0xz2dIf95k1lMIGCBoyDhr2/AFJFEdnWB8tTo= github.com/stackitcloud/stackit-sdk-go/services/kms v1.13.1 h1:iEPerniBpji6dGiB31p1WAUxgBf9VBIL9gKocfbBqW8= @@ -652,6 +638,8 @@ github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.2 h1:ZVOAS95S445ZUvXL github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.2/go.mod h1:euyZx4EqA2N7lVrC/wWxdCKalZmnY5zjfrjouNkGycA= github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.1 h1:FZJLUhHVrlrfFnI5VqAx0dOEcTZpMi/vnk/MxkzTkJI= github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.1/go.mod h1:P1HFWSmuy2i1WA0PhcDfGsa0CRcbGTE+wDb1iEAL8/E= +github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 h1:g8AX62NqDLJy2dNPPTzfeaBoaR2K+EIwxF8sRiAESL0= +github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0/go.mod h1:TW2PYG0kSrfAos3yY8wUxDem0J9ZYSXulnbzBZ4BTaE= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1 h1:lLR6Ouu3H21jjyIcAZZc4f2SOBWgHTBEHbn/urtY+9M= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1/go.mod h1:0hHEPiOEMAA23EzEl42Rm3FlyKIzkW+LWLvDkuFTZ+Q= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.9.2 h1:FvT1SDs4Q8MTNwKERqMUgvXAyYwv/MQTAGfZAxcDfQo= From 429c2604b7de7b9a5e8808eb731d1d9f3f821610 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Wed, 7 Oct 2026 09:59:02 +0200 Subject: [PATCH 08/13] fix: internal tracking issues, STACKITMLO-1718 --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index bd85757af..7c749d19c 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/intake v0.11.2 github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.2 github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 - github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.2 github.com/stackitcloud/stackit-sdk-go/services/opensearch v1.3.1 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.15.0 diff --git a/go.sum b/go.sum index 5a1a3ea60..4b9de57b0 100644 --- a/go.sum +++ b/go.sum @@ -642,8 +642,6 @@ github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.1 h1:FZJLUhHVrlrfFn github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.3.1/go.mod h1:P1HFWSmuy2i1WA0PhcDfGsa0CRcbGTE+wDb1iEAL8/E= github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0 h1:g8AX62NqDLJy2dNPPTzfeaBoaR2K+EIwxF8sRiAESL0= github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.3.0/go.mod h1:TW2PYG0kSrfAos3yY8wUxDem0J9ZYSXulnbzBZ4BTaE= -github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1 h1:lLR6Ouu3H21jjyIcAZZc4f2SOBWgHTBEHbn/urtY+9M= -github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.1/go.mod h1:0hHEPiOEMAA23EzEl42Rm3FlyKIzkW+LWLvDkuFTZ+Q= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.2 h1:oXuBETy/UZ3T8XxnZM2rfKVPxOxiZhNdz5ojn8dqMFo= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.12.2/go.mod h1:/a74NLBtvdS0Z2jrC4AvZCa+rcJDIhOOBkjM5y37eO4= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.9.2 h1:FvT1SDs4Q8MTNwKERqMUgvXAyYwv/MQTAGfZAxcDfQo= From 329340de9aee2c18d4fdaf35cb4443c9619014e9 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Wed, 7 Oct 2026 10:18:01 +0200 Subject: [PATCH 09/13] fix: internal tracking issues, STACKITMLO-1718 --- internal/pkg/config/config.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index ad4935ed9..17333b05f 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -38,21 +38,7 @@ const ( PostgresFlexCustomEndpointKey = "postgresflex_custom_endpoint" RabbitMQCustomEndpointKey = "rabbitmq_custom_endpoint" AIModelExperimentsCustomEndpointKey = "aimodelexperiments_custom_endpoint" - AuthorizationCustomEndpointKey = "authorization_custom_endpoint" - AutomationCustomEndpointKey = "automation_custom_endpoint" - AlbCustomEndpoint = "alb_custom _endpoint" - AlbWafCustomEndpointKey = "alb_waf_custom_endpoint" - DNSCustomEndpointKey = "dns_custom_endpoint" - EdgeCustomEndpointKey = "edge_custom_endpoint" - LoadBalancerCustomEndpointKey = "load_balancer_custom_endpoint" - LogMeCustomEndpointKey = "logme_custom_endpoint" - MariaDBCustomEndpointKey = "mariadb_custom_endpoint" - MongoDBFlexCustomEndpointKey = "mongodbflex_custom_endpoint" - ObjectStorageCustomEndpointKey = "object_storage_custom_endpoint" - ObservabilityCustomEndpointKey = "observability_custom_endpoint" - OpenSearchCustomEndpointKey = "opensearch_custom_endpoint" - PostgresFlexCustomEndpointKey = "postgresflex_custom_endpoint" - RabbitMQCustomEndpointKey = "rabbitmq_custom_endpoint" + AutomationCustomEndpointKey = "automation_custom_endpoint" // Deprecated: Will be removed after 2027-08-31. RedisCustomEndpointKey = "redis_custom_endpoint" ResourceManagerEndpointKey = "resource_manager_custom_endpoint" From e8af823a920840bc9ff63e5ddc8ca1aa1cfd8d17 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Thu, 29 Oct 2026 09:34:15 +0100 Subject: [PATCH 10/13] feat: address pr review feedback, STACKITMLO-1718 --- docs/stackit.md | 2 +- docs/stackit_ai-model-experiments_instance.md | 38 ----- docs/stackit_ai-model-experiments_token.md | 38 ----- ...iments.md => stackit_model-experiments.md} | 10 +- docs/stackit_model-experiments_instance.md | 38 +++++ ...ckit_model-experiments_instance_create.md} | 12 +- ...ckit_model-experiments_instance_delete.md} | 10 +- ...it_model-experiments_instance_describe.md} | 16 +-- ...tackit_model-experiments_instance_list.md} | 10 +- ...ckit_model-experiments_instance_update.md} | 16 +-- docs/stackit_model-experiments_token.md | 38 +++++ ...stackit_model-experiments_token_create.md} | 10 +- ...stackit_model-experiments_token_delete.md} | 10 +- ...ackit_model-experiments_token_describe.md} | 16 +-- ...> stackit_model-experiments_token_list.md} | 10 +- ...stackit_model-experiments_token_update.md} | 10 +- internal/cmd/config/set/set.go | 4 + internal/cmd/config/unset/unset.go | 7 + internal/cmd/config/unset/unset_test.go | 133 ++++++++++-------- .../instance/create/create.go | 8 +- .../instance/create/create_test.go | 2 +- .../instance/delete/delete.go | 6 +- .../instance/delete/delete_test.go | 2 +- .../{get/get.go => describe/describe.go} | 24 ++-- .../get_test.go => describe/describe_test.go} | 6 +- .../cmd/modelexperiments/instance/instance.go | 8 +- .../modelexperiments/instance/list/list.go | 6 +- .../instance/list/list_test.go | 2 +- .../{patch/patch.go => update/update.go} | 16 +-- .../patch_test.go => update/update_test.go} | 6 +- .../cmd/modelexperiments/modelexperiments.go | 2 +- .../modelexperiments/token/create/create.go | 2 +- .../modelexperiments/token/delete/delete.go | 2 +- .../{get/get.go => describe/describe.go} | 12 +- .../get_test.go => describe/describe_test.go} | 4 +- .../cmd/modelexperiments/token/list/list.go | 2 +- internal/cmd/modelexperiments/token/token.go | 24 +++- .../{patch/patch.go => update/update.go} | 6 +- .../patch_test.go => update/update_test.go} | 4 +- internal/pkg/config/config.go | 35 ++--- .../modelexperiments/client/client.go | 2 +- .../services/modelexperiments/utils/utils.go | 5 +- .../modelexperiments/utils/utils_test.go | 81 ++++++++--- 43 files changed, 389 insertions(+), 306 deletions(-) delete mode 100644 docs/stackit_ai-model-experiments_instance.md delete mode 100644 docs/stackit_ai-model-experiments_token.md rename docs/{stackit_ai-model-experiments.md => stackit_model-experiments.md} (63%) create mode 100644 docs/stackit_model-experiments_instance.md rename docs/{stackit_ai-model-experiments_instance_create.md => stackit_model-experiments_instance_create.md} (72%) rename docs/{stackit_ai-model-experiments_instance_delete.md => stackit_model-experiments_instance_delete.md} (66%) rename docs/{stackit_ai-model-experiments_instance_get.md => stackit_model-experiments_instance_describe.md} (51%) rename docs/{stackit_ai-model-experiments_instance_list.md => stackit_model-experiments_instance_list.md} (66%) rename docs/{stackit_ai-model-experiments_instance_patch.md => stackit_model-experiments_instance_update.md} (67%) create mode 100644 docs/stackit_model-experiments_token.md rename docs/{stackit_ai-model-experiments_token_create.md => stackit_model-experiments_token_create.md} (70%) rename docs/{stackit_ai-model-experiments_token_delete.md => stackit_model-experiments_token_delete.md} (65%) rename docs/{stackit_ai-model-experiments_token_get.md => stackit_model-experiments_token_describe.md} (54%) rename docs/{stackit_ai-model-experiments_token_list.md => stackit_model-experiments_token_list.md} (66%) rename docs/{stackit_ai-model-experiments_token_patch.md => stackit_model-experiments_token_update.md} (69%) rename internal/cmd/modelexperiments/instance/{get/get.go => describe/describe.go} (78%) rename internal/cmd/modelexperiments/instance/{get/get_test.go => describe/describe_test.go} (97%) rename internal/cmd/modelexperiments/instance/{patch/patch.go => update/update.go} (89%) rename internal/cmd/modelexperiments/instance/{patch/patch_test.go => update/update_test.go} (98%) rename internal/cmd/modelexperiments/token/{get/get.go => describe/describe.go} (86%) rename internal/cmd/modelexperiments/token/{get/get_test.go => describe/describe_test.go} (97%) rename internal/cmd/modelexperiments/token/{patch/patch.go => update/update.go} (92%) rename internal/cmd/modelexperiments/token/{patch/patch_test.go => update/update_test.go} (98%) diff --git a/docs/stackit.md b/docs/stackit.md index b65d83a7b..faa15e7cb 100644 --- a/docs/stackit.md +++ b/docs/stackit.md @@ -27,7 +27,6 @@ stackit [flags] ### SEE ALSO * [stackit affinity-group](./stackit_affinity-group.md) - Manage server affinity groups -* [stackit ai-model-experiments](./stackit_ai-model-experiments.md) - Provides functionality for AI Model Experiments * [stackit alpha](./stackit_alpha.md) - Contains alpha STACKIT CLI commands * [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands @@ -42,6 +41,7 @@ stackit [flags] * [stackit logme](./stackit_logme.md) - Provides functionality for LogMe * [stackit logs](./stackit_logs.md) - Provides functionality for Logs * [stackit mariadb](./stackit_mariadb.md) - Provides functionality for MariaDB +* [stackit model-experiments](./stackit_model-experiments.md) - Provides functionality for AI Model Experiments * [stackit mongodbflex](./stackit_mongodbflex.md) - Provides functionality for MongoDB Flex * [stackit network](./stackit_network.md) - Provides functionality for networks * [stackit network-area](./stackit_network-area.md) - Provides functionality for STACKIT Network Area (SNA) diff --git a/docs/stackit_ai-model-experiments_instance.md b/docs/stackit_ai-model-experiments_instance.md deleted file mode 100644 index 413a32ae5..000000000 --- a/docs/stackit_ai-model-experiments_instance.md +++ /dev/null @@ -1,38 +0,0 @@ -## stackit ai-model-experiments instance - -Provides functionality for AI Model Experiments instances - -### Synopsis - -Provides functionality for AI Model Experiments instances. - -``` -stackit ai-model-experiments instance [flags] -``` - -### Options - -``` - -h, --help Help for "stackit ai-model-experiments 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 - --region string Target region for region-specific requests - --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") -``` - -### SEE ALSO - -* [stackit ai-model-experiments](./stackit_ai-model-experiments.md) - Provides functionality for AI Model Experiments -* [stackit ai-model-experiments instance create](./stackit_ai-model-experiments_instance_create.md) - Creates an AI Model Experiments instance -* [stackit ai-model-experiments instance delete](./stackit_ai-model-experiments_instance_delete.md) - Deletes an AI Model Experiments instance -* [stackit ai-model-experiments instance get](./stackit_ai-model-experiments_instance_get.md) - Gets an AI Model Experiments instance -* [stackit ai-model-experiments instance list](./stackit_ai-model-experiments_instance_list.md) - Lists AI Model Experiments instances -* [stackit ai-model-experiments instance patch](./stackit_ai-model-experiments_instance_patch.md) - Updates an AI Model Experiments instance - diff --git a/docs/stackit_ai-model-experiments_token.md b/docs/stackit_ai-model-experiments_token.md deleted file mode 100644 index d79f5eea2..000000000 --- a/docs/stackit_ai-model-experiments_token.md +++ /dev/null @@ -1,38 +0,0 @@ -## stackit ai-model-experiments token - -Provides functionality for AI Model Experiments instance tokens - -### Synopsis - -Provides functionality for AI Model Experiments instance tokens. - -``` -stackit ai-model-experiments token [flags] -``` - -### Options - -``` - -h, --help Help for "stackit ai-model-experiments token" -``` - -### Options inherited from parent commands - -``` - -y, --assume-yes If set, skips all confirmation prompts - --async If set, runs the command asynchronously - -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) - -p, --project-id string Project ID - --region string Target region for region-specific requests - --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") -``` - -### SEE ALSO - -* [stackit ai-model-experiments](./stackit_ai-model-experiments.md) - Provides functionality for AI Model Experiments -* [stackit ai-model-experiments token create](./stackit_ai-model-experiments_token_create.md) - Creates an instance token -* [stackit ai-model-experiments token delete](./stackit_ai-model-experiments_token_delete.md) - Deletes an instance token -* [stackit ai-model-experiments token get](./stackit_ai-model-experiments_token_get.md) - Gets an instance token -* [stackit ai-model-experiments token list](./stackit_ai-model-experiments_token_list.md) - Lists instance tokens -* [stackit ai-model-experiments token patch](./stackit_ai-model-experiments_token_patch.md) - Updates an instance token - diff --git a/docs/stackit_ai-model-experiments.md b/docs/stackit_model-experiments.md similarity index 63% rename from docs/stackit_ai-model-experiments.md rename to docs/stackit_model-experiments.md index 95bc8397f..0032ec8fd 100644 --- a/docs/stackit_ai-model-experiments.md +++ b/docs/stackit_model-experiments.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments +## stackit model-experiments Provides functionality for AI Model Experiments @@ -7,13 +7,13 @@ Provides functionality for AI Model Experiments Provides functionality for AI Model Experiments. ``` -stackit ai-model-experiments [flags] +stackit model-experiments [flags] ``` ### Options ``` - -h, --help Help for "stackit ai-model-experiments" + -h, --help Help for "stackit model-experiments" ``` ### Options inherited from parent commands @@ -30,6 +30,6 @@ stackit ai-model-experiments [flags] ### SEE ALSO * [stackit](./stackit.md) - Manage STACKIT resources using the command line -* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances -* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens +* [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +* [stackit model-experiments token](./stackit_model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens diff --git a/docs/stackit_model-experiments_instance.md b/docs/stackit_model-experiments_instance.md new file mode 100644 index 000000000..3377e86fd --- /dev/null +++ b/docs/stackit_model-experiments_instance.md @@ -0,0 +1,38 @@ +## stackit model-experiments instance + +Provides functionality for AI Model Experiments instances + +### Synopsis + +Provides functionality for AI Model Experiments instances. + +``` +stackit model-experiments instance [flags] +``` + +### Options + +``` + -h, --help Help for "stackit model-experiments 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 + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit model-experiments](./stackit_model-experiments.md) - Provides functionality for AI Model Experiments +* [stackit model-experiments instance create](./stackit_model-experiments_instance_create.md) - Creates an AI Model Experiments instance +* [stackit model-experiments instance delete](./stackit_model-experiments_instance_delete.md) - Deletes an AI Model Experiments instance +* [stackit model-experiments instance describe](./stackit_model-experiments_instance_describe.md) - Shows details of an AI Model Experiments instance +* [stackit model-experiments instance list](./stackit_model-experiments_instance_list.md) - Lists AI Model Experiments instances +* [stackit model-experiments instance update](./stackit_model-experiments_instance_update.md) - Updates an AI Model Experiments instance + diff --git a/docs/stackit_ai-model-experiments_instance_create.md b/docs/stackit_model-experiments_instance_create.md similarity index 72% rename from docs/stackit_ai-model-experiments_instance_create.md rename to docs/stackit_model-experiments_instance_create.md index 5e32f4e22..600cae9b3 100644 --- a/docs/stackit_ai-model-experiments_instance_create.md +++ b/docs/stackit_model-experiments_instance_create.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments instance create +## stackit model-experiments instance create Creates an AI Model Experiments instance @@ -7,17 +7,17 @@ Creates an AI Model Experiments instance Creates an AI Model Experiments (MLflow) instance in your STACKIT project. ``` -stackit ai-model-experiments instance create [flags] +stackit model-experiments instance create [flags] ``` ### Examples ``` Create an AI Model Experiments instance with name "my-tracking" - $ stackit ai-model-experiments instance create --name my-tracking + $ stackit model-experiments instance create --name my-tracking Create an instance with a description and labels - $ stackit ai-model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod + $ stackit model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod ``` ### Options @@ -25,7 +25,7 @@ stackit ai-model-experiments instance create [flags] ``` --deleted-experiment-retention string Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d) --description string Instance description - -h, --help Help for "stackit ai-model-experiments instance create" + -h, --help Help for "stackit model-experiments instance create" --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) -n, --name string Instance name ``` @@ -43,5 +43,5 @@ stackit ai-model-experiments instance create [flags] ### SEE ALSO -* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +* [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances diff --git a/docs/stackit_ai-model-experiments_instance_delete.md b/docs/stackit_model-experiments_instance_delete.md similarity index 66% rename from docs/stackit_ai-model-experiments_instance_delete.md rename to docs/stackit_model-experiments_instance_delete.md index bda6dab16..b5139f838 100644 --- a/docs/stackit_ai-model-experiments_instance_delete.md +++ b/docs/stackit_model-experiments_instance_delete.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments instance delete +## stackit model-experiments instance delete Deletes an AI Model Experiments instance @@ -7,20 +7,20 @@ Deletes an AI Model Experiments instance Deletes an AI Model Experiments instance from a STACKIT project. ``` -stackit ai-model-experiments instance delete INSTANCE_ID [flags] +stackit model-experiments instance delete INSTANCE_ID [flags] ``` ### Examples ``` Delete an AI Model Experiments instance with ID "xxx" - $ stackit ai-model-experiments instance delete xxx + $ stackit model-experiments instance delete xxx ``` ### Options ``` - -h, --help Help for "stackit ai-model-experiments instance delete" + -h, --help Help for "stackit model-experiments instance delete" ``` ### Options inherited from parent commands @@ -36,5 +36,5 @@ stackit ai-model-experiments instance delete INSTANCE_ID [flags] ### SEE ALSO -* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +* [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances diff --git a/docs/stackit_ai-model-experiments_instance_get.md b/docs/stackit_model-experiments_instance_describe.md similarity index 51% rename from docs/stackit_ai-model-experiments_instance_get.md rename to docs/stackit_model-experiments_instance_describe.md index 5ec18194d..9c847df63 100644 --- a/docs/stackit_ai-model-experiments_instance_get.md +++ b/docs/stackit_model-experiments_instance_describe.md @@ -1,26 +1,26 @@ -## stackit ai-model-experiments instance get +## stackit model-experiments instance describe -Gets an AI Model Experiments instance +Shows details of an AI Model Experiments instance ### Synopsis -Gets an AI Model Experiments instance in a STACKIT project. +Shows details of an AI Model Experiments instance in a STACKIT project. ``` -stackit ai-model-experiments instance get INSTANCE_ID [flags] +stackit model-experiments instance describe INSTANCE_ID [flags] ``` ### Examples ``` - Get an AI Model Experiments instance with ID "xxx" - $ stackit ai-model-experiments instance get xxx + Show details of an AI Model Experiments instance with ID "xxx" + $ stackit model-experiments instance describe xxx ``` ### Options ``` - -h, --help Help for "stackit ai-model-experiments instance get" + -h, --help Help for "stackit model-experiments instance describe" ``` ### Options inherited from parent commands @@ -36,5 +36,5 @@ stackit ai-model-experiments instance get INSTANCE_ID [flags] ### SEE ALSO -* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +* [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances diff --git a/docs/stackit_ai-model-experiments_instance_list.md b/docs/stackit_model-experiments_instance_list.md similarity index 66% rename from docs/stackit_ai-model-experiments_instance_list.md rename to docs/stackit_model-experiments_instance_list.md index 406482f54..3d51b4835 100644 --- a/docs/stackit_ai-model-experiments_instance_list.md +++ b/docs/stackit_model-experiments_instance_list.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments instance list +## stackit model-experiments instance list Lists AI Model Experiments instances @@ -7,20 +7,20 @@ Lists AI Model Experiments instances Lists AI Model Experiments instances in a STACKIT project. ``` -stackit ai-model-experiments instance list [flags] +stackit model-experiments instance list [flags] ``` ### Examples ``` List AI Model Experiments instances in a project - $ stackit ai-model-experiments instance list --region eu01 + $ stackit model-experiments instance list --region eu01 ``` ### Options ``` - -h, --help Help for "stackit ai-model-experiments instance list" + -h, --help Help for "stackit model-experiments instance list" ``` ### Options inherited from parent commands @@ -36,5 +36,5 @@ stackit ai-model-experiments instance list [flags] ### SEE ALSO -* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +* [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances diff --git a/docs/stackit_ai-model-experiments_instance_patch.md b/docs/stackit_model-experiments_instance_update.md similarity index 67% rename from docs/stackit_ai-model-experiments_instance_patch.md rename to docs/stackit_model-experiments_instance_update.md index 8ab0b25ea..6d9832062 100644 --- a/docs/stackit_ai-model-experiments_instance_patch.md +++ b/docs/stackit_model-experiments_instance_update.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments instance patch +## stackit model-experiments instance update Updates an AI Model Experiments instance @@ -7,23 +7,23 @@ Updates an AI Model Experiments instance Partially updates an AI Model Experiments instance in a STACKIT project. ``` -stackit ai-model-experiments instance patch INSTANCE_ID [flags] +stackit model-experiments instance update INSTANCE_ID [flags] ``` ### Examples ``` Update the name of an AI Model Experiments instance with ID "xxx" - $ stackit ai-model-experiments instance patch xxx --name my-new-name + $ stackit model-experiments instance update xxx --name my-new-name Update the description of an AI Model Experiments instance - $ stackit ai-model-experiments instance patch xxx --description "team tracking server" + $ stackit model-experiments instance update xxx --description "team tracking server" Update labels on an AI Model Experiments instance - $ stackit ai-model-experiments instance patch xxx --label env=prod + $ stackit model-experiments instance update xxx --label env=prod Update multiple fields of an AI Model Experiments instance - $ stackit ai-model-experiments instance patch xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d + $ stackit model-experiments instance update xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d ``` ### Options @@ -31,7 +31,7 @@ stackit ai-model-experiments instance patch INSTANCE_ID [flags] ``` --deleted-experiment-retention string Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d) --description string Instance description - -h, --help Help for "stackit ai-model-experiments instance patch" + -h, --help Help for "stackit model-experiments instance update" --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) -n, --name string Instance name ``` @@ -49,5 +49,5 @@ stackit ai-model-experiments instance patch INSTANCE_ID [flags] ### SEE ALSO -* [stackit ai-model-experiments instance](./stackit_ai-model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +* [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances diff --git a/docs/stackit_model-experiments_token.md b/docs/stackit_model-experiments_token.md new file mode 100644 index 000000000..f5156d738 --- /dev/null +++ b/docs/stackit_model-experiments_token.md @@ -0,0 +1,38 @@ +## stackit model-experiments token + +Provides functionality for AI Model Experiments instance tokens + +### Synopsis + +Provides functionality for AI Model Experiments instance tokens. + +``` +stackit model-experiments token [flags] +``` + +### Options + +``` + -h, --help Help for "stackit model-experiments token" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, yaml]) + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info") +``` + +### SEE ALSO + +* [stackit model-experiments](./stackit_model-experiments.md) - Provides functionality for AI Model Experiments +* [stackit model-experiments token create](./stackit_model-experiments_token_create.md) - Creates an instance token +* [stackit model-experiments token delete](./stackit_model-experiments_token_delete.md) - Deletes an instance token +* [stackit model-experiments token describe](./stackit_model-experiments_token_describe.md) - Shows details of an instance token +* [stackit model-experiments token list](./stackit_model-experiments_token_list.md) - Lists instance tokens +* [stackit model-experiments token update](./stackit_model-experiments_token_update.md) - Updates an instance token + diff --git a/docs/stackit_ai-model-experiments_token_create.md b/docs/stackit_model-experiments_token_create.md similarity index 70% rename from docs/stackit_ai-model-experiments_token_create.md rename to docs/stackit_model-experiments_token_create.md index a4608e7f9..3acb6dd66 100644 --- a/docs/stackit_ai-model-experiments_token_create.md +++ b/docs/stackit_model-experiments_token_create.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments token create +## stackit model-experiments token create Creates an instance token @@ -7,21 +7,21 @@ Creates an instance token Creates an auth token for an AI Model Experiments instance. ``` -stackit ai-model-experiments token create [flags] +stackit model-experiments token create [flags] ``` ### Examples ``` Create an auth token - $ stackit ai-model-experiments token create --instance-id xxx --name my-token + $ stackit model-experiments token create --instance-id xxx --name my-token ``` ### Options ``` --description string Token description - -h, --help Help for "stackit ai-model-experiments token create" + -h, --help Help for "stackit model-experiments token create" --instance-id string ID of the instance --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) --name string Token name @@ -41,5 +41,5 @@ stackit ai-model-experiments token create [flags] ### SEE ALSO -* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens +* [stackit model-experiments token](./stackit_model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens diff --git a/docs/stackit_ai-model-experiments_token_delete.md b/docs/stackit_model-experiments_token_delete.md similarity index 65% rename from docs/stackit_ai-model-experiments_token_delete.md rename to docs/stackit_model-experiments_token_delete.md index 862e16c35..616a4914c 100644 --- a/docs/stackit_ai-model-experiments_token_delete.md +++ b/docs/stackit_model-experiments_token_delete.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments token delete +## stackit model-experiments token delete Deletes an instance token @@ -7,20 +7,20 @@ Deletes an instance token Deletes an auth token from an AI Model Experiments instance. ``` -stackit ai-model-experiments token delete TOKEN_ID [flags] +stackit model-experiments token delete TOKEN_ID [flags] ``` ### Examples ``` Delete an auth token with ID "xxx" - $ stackit ai-model-experiments token delete xxx --instance-id yyy + $ stackit model-experiments token delete xxx --instance-id yyy ``` ### Options ``` - -h, --help Help for "stackit ai-model-experiments token delete" + -h, --help Help for "stackit model-experiments token delete" --instance-id string ID of the instance ``` @@ -37,5 +37,5 @@ stackit ai-model-experiments token delete TOKEN_ID [flags] ### SEE ALSO -* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens +* [stackit model-experiments token](./stackit_model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens diff --git a/docs/stackit_ai-model-experiments_token_get.md b/docs/stackit_model-experiments_token_describe.md similarity index 54% rename from docs/stackit_ai-model-experiments_token_get.md rename to docs/stackit_model-experiments_token_describe.md index aa28ce08b..081005b63 100644 --- a/docs/stackit_ai-model-experiments_token_get.md +++ b/docs/stackit_model-experiments_token_describe.md @@ -1,26 +1,26 @@ -## stackit ai-model-experiments token get +## stackit model-experiments token describe -Gets an instance token +Shows details of an instance token ### Synopsis -Gets an auth token for an AI Model Experiments instance. +Shows details of an auth token for an AI Model Experiments instance. ``` -stackit ai-model-experiments token get TOKEN_ID [flags] +stackit model-experiments token describe TOKEN_ID [flags] ``` ### Examples ``` - Get an auth token with ID "xxx" - $ stackit ai-model-experiments token get xxx --instance-id yyy + Show details of an auth token with ID "xxx" + $ stackit model-experiments token describe xxx --instance-id yyy ``` ### Options ``` - -h, --help Help for "stackit ai-model-experiments token get" + -h, --help Help for "stackit model-experiments token describe" --instance-id string ID of the instance ``` @@ -37,5 +37,5 @@ stackit ai-model-experiments token get TOKEN_ID [flags] ### SEE ALSO -* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens +* [stackit model-experiments token](./stackit_model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens diff --git a/docs/stackit_ai-model-experiments_token_list.md b/docs/stackit_model-experiments_token_list.md similarity index 66% rename from docs/stackit_ai-model-experiments_token_list.md rename to docs/stackit_model-experiments_token_list.md index f51bd24a7..661b55428 100644 --- a/docs/stackit_ai-model-experiments_token_list.md +++ b/docs/stackit_model-experiments_token_list.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments token list +## stackit model-experiments token list Lists instance tokens @@ -7,20 +7,20 @@ Lists instance tokens Lists all auth tokens for an AI Model Experiments instance. ``` -stackit ai-model-experiments token list [flags] +stackit model-experiments token list [flags] ``` ### Examples ``` List all tokens for an instance - $ stackit ai-model-experiments token list --instance-id xxx + $ stackit model-experiments token list --instance-id xxx ``` ### Options ``` - -h, --help Help for "stackit ai-model-experiments token list" + -h, --help Help for "stackit model-experiments token list" --instance-id string ID of the instance ``` @@ -37,5 +37,5 @@ stackit ai-model-experiments token list [flags] ### SEE ALSO -* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens +* [stackit model-experiments token](./stackit_model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens diff --git a/docs/stackit_ai-model-experiments_token_patch.md b/docs/stackit_model-experiments_token_update.md similarity index 69% rename from docs/stackit_ai-model-experiments_token_patch.md rename to docs/stackit_model-experiments_token_update.md index 390c9414c..c47372dd6 100644 --- a/docs/stackit_ai-model-experiments_token_patch.md +++ b/docs/stackit_model-experiments_token_update.md @@ -1,4 +1,4 @@ -## stackit ai-model-experiments token patch +## stackit model-experiments token update Updates an instance token @@ -7,21 +7,21 @@ Updates an instance token Partially updates an auth token for an AI Model Experiments instance. ``` -stackit ai-model-experiments token patch TOKEN_ID [flags] +stackit model-experiments token update TOKEN_ID [flags] ``` ### Examples ``` Update an auth token with ID "xxx" - $ stackit ai-model-experiments token patch xxx --instance-id yyy --name updated-token + $ stackit model-experiments token update xxx --instance-id yyy --name updated-token ``` ### Options ``` --description string Token description - -h, --help Help for "stackit ai-model-experiments token patch" + -h, --help Help for "stackit model-experiments token update" --instance-id string ID of the instance --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) --name string Token name @@ -40,5 +40,5 @@ stackit ai-model-experiments token patch TOKEN_ID [flags] ### SEE ALSO -* [stackit ai-model-experiments token](./stackit_ai-model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens +* [stackit model-experiments token](./stackit_model-experiments_token.md) - Provides functionality for AI Model Experiments instance tokens diff --git a/internal/cmd/config/set/set.go b/internal/cmd/config/set/set.go index 194291a3d..40a8bb15b 100644 --- a/internal/cmd/config/set/set.go +++ b/internal/cmd/config/set/set.go @@ -32,6 +32,7 @@ const ( loadBalancerCustomEndpointFlag = "load-balancer-custom-endpoint" logMeCustomEndpointFlag = "logme-custom-endpoint" mariaDBCustomEndpointFlag = "mariadb-custom-endpoint" + modelExperimentsCustomEndpointFlag = "model-experiments-custom-endpoint" mongoDBFlexCustomEndpointFlag = "mongodbflex-custom-endpoint" objectStorageCustomEndpointFlag = "object-storage-custom-endpoint" observabilityCustomEndpointFlag = "observability-custom-endpoint" @@ -156,6 +157,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(loadBalancerCustomEndpointFlag, "", "Load Balancer API base URL, used in calls to this API") cmd.Flags().String(logMeCustomEndpointFlag, "", "LogMe API base URL, used in calls to this API") cmd.Flags().String(mariaDBCustomEndpointFlag, "", "MariaDB API base URL, used in calls to this API") + cmd.Flags().String(modelExperimentsCustomEndpointFlag, "", "Model Experiments API base URL, used in calls to this API") cmd.Flags().String(mongoDBFlexCustomEndpointFlag, "", "MongoDB Flex API base URL, used in calls to this API") cmd.Flags().String(objectStorageCustomEndpointFlag, "", "Object Storage API base URL, used in calls to this API") cmd.Flags().String(openSearchCustomEndpointFlag, "", "OpenSearch API base URL, used in calls to this API") @@ -208,6 +210,8 @@ func configureFlags(cmd *cobra.Command) { cobra.CheckErr(err) err = viper.BindPFlag(config.MariaDBCustomEndpointKey, cmd.Flags().Lookup(mariaDBCustomEndpointFlag)) cobra.CheckErr(err) + err = viper.BindPFlag(config.ModelExperimentsCustomEndpointKey, cmd.Flags().Lookup(modelExperimentsCustomEndpointFlag)) + cobra.CheckErr(err) err = viper.BindPFlag(config.MongoDBFlexCustomEndpointKey, cmd.Flags().Lookup(mongoDBFlexCustomEndpointFlag)) cobra.CheckErr(err) err = viper.BindPFlag(config.ObjectStorageCustomEndpointKey, cmd.Flags().Lookup(objectStorageCustomEndpointFlag)) diff --git a/internal/cmd/config/unset/unset.go b/internal/cmd/config/unset/unset.go index 5aff526eb..3d2323d36 100644 --- a/internal/cmd/config/unset/unset.go +++ b/internal/cmd/config/unset/unset.go @@ -35,6 +35,7 @@ const ( loadBalancerCustomEndpointFlag = "load-balancer-custom-endpoint" logMeCustomEndpointFlag = "logme-custom-endpoint" mariaDBCustomEndpointFlag = "mariadb-custom-endpoint" + modelExperimentsCustomEndpointFlag = "model-experiments-custom-endpoint" mongoDBFlexCustomEndpointFlag = "mongodbflex-custom-endpoint" objectStorageCustomEndpointFlag = "object-storage-custom-endpoint" observabilityCustomEndpointFlag = "observability-custom-endpoint" @@ -88,6 +89,7 @@ type inputModel struct { LoadBalancerCustomEndpoint bool LogMeCustomEndpoint bool MariaDBCustomEndpoint bool + ModelExperimentsCustomEndpoint bool MongoDBFlexCustomEndpoint bool ObjectStorageCustomEndpoint bool ObservabilityCustomEndpoint bool @@ -194,6 +196,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if model.MariaDBCustomEndpoint { viper.Set(config.MariaDBCustomEndpointKey, "") } + if model.ModelExperimentsCustomEndpoint { + viper.Set(config.ModelExperimentsCustomEndpointKey, "") + } if model.MongoDBFlexCustomEndpoint { viper.Set(config.MongoDBFlexCustomEndpointKey, "") } @@ -300,6 +305,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(loadBalancerCustomEndpointFlag, false, "Load Balancer API base URL. If unset, uses the default base URL") cmd.Flags().Bool(logMeCustomEndpointFlag, false, "LogMe API base URL. If unset, uses the default base URL") cmd.Flags().Bool(mariaDBCustomEndpointFlag, false, "MariaDB API base URL. If unset, uses the default base URL") + cmd.Flags().Bool(modelExperimentsCustomEndpointFlag, false, "Model Experiments API base URL. If unset, uses the default base URL") cmd.Flags().Bool(mongoDBFlexCustomEndpointFlag, false, "MongoDB Flex API base URL. If unset, uses the default base URL") cmd.Flags().Bool(objectStorageCustomEndpointFlag, false, "Object Storage API base URL. If unset, uses the default base URL") cmd.Flags().Bool(openSearchCustomEndpointFlag, false, "OpenSearch API base URL. If unset, uses the default base URL") @@ -348,6 +354,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) *inputModel { LoadBalancerCustomEndpoint: flags.FlagToBoolValue(p, cmd, loadBalancerCustomEndpointFlag), LogMeCustomEndpoint: flags.FlagToBoolValue(p, cmd, logMeCustomEndpointFlag), MariaDBCustomEndpoint: flags.FlagToBoolValue(p, cmd, mariaDBCustomEndpointFlag), + ModelExperimentsCustomEndpoint: flags.FlagToBoolValue(p, cmd, modelExperimentsCustomEndpointFlag), MongoDBFlexCustomEndpoint: flags.FlagToBoolValue(p, cmd, mongoDBFlexCustomEndpointFlag), ObjectStorageCustomEndpoint: flags.FlagToBoolValue(p, cmd, objectStorageCustomEndpointFlag), ObservabilityCustomEndpoint: flags.FlagToBoolValue(p, cmd, observabilityCustomEndpointFlag), diff --git a/internal/cmd/config/unset/unset_test.go b/internal/cmd/config/unset/unset_test.go index 46a41ee21..d63a88944 100644 --- a/internal/cmd/config/unset/unset_test.go +++ b/internal/cmd/config/unset/unset_test.go @@ -22,36 +22,37 @@ func fixtureFlagValues(mods ...func(flagValues map[string]bool)) map[string]bool identityProviderCustomClientIdFlag: true, allowedUrlDomainFlag: true, - authorizationCustomEndpointFlag: true, - automationCustomEndpointFlag: true, - albWafCustomEndpointFlag: true, - dnsCustomEndpointFlag: true, - edgeCustomEndpointFlag: true, - loadBalancerCustomEndpointFlag: true, - logMeCustomEndpointFlag: true, - mariaDBCustomEndpointFlag: true, - objectStorageCustomEndpointFlag: true, - observabilityCustomEndpointFlag: true, - openSearchCustomEndpointFlag: true, - rabbitMQCustomEndpointFlag: true, - redisCustomEndpointFlag: true, - resourceManagerCustomEndpointFlag: true, - secretsManagerCustomEndpointFlag: true, - kmsCustomEndpointFlag: true, - serviceAccountCustomEndpointFlag: true, - serverBackupCustomEndpointFlag: true, - serverOsUpdateCustomEndpointFlag: true, - runCommandCustomEndpointFlag: true, - sfsCustomEndpointFlag: true, - skeCustomEndpointFlag: true, - sqlServerFlexCustomEndpointFlag: true, - iaasAlphaCustomEndpointFlag: true, - iaasCustomEndpointFlag: true, - tokenCustomEndpointFlag: true, - intakeCustomEndpointFlag: true, - logsCustomEndpointFlag: true, - cdnCustomEndpointFlag: true, - vpnCustomEndpointFlag: true, + authorizationCustomEndpointFlag: true, + automationCustomEndpointFlag: true, + albWafCustomEndpointFlag: true, + dnsCustomEndpointFlag: true, + edgeCustomEndpointFlag: true, + loadBalancerCustomEndpointFlag: true, + logMeCustomEndpointFlag: true, + mariaDBCustomEndpointFlag: true, + modelExperimentsCustomEndpointFlag: true, + objectStorageCustomEndpointFlag: true, + observabilityCustomEndpointFlag: true, + openSearchCustomEndpointFlag: true, + rabbitMQCustomEndpointFlag: true, + redisCustomEndpointFlag: true, + resourceManagerCustomEndpointFlag: true, + secretsManagerCustomEndpointFlag: true, + kmsCustomEndpointFlag: true, + serviceAccountCustomEndpointFlag: true, + serverBackupCustomEndpointFlag: true, + serverOsUpdateCustomEndpointFlag: true, + runCommandCustomEndpointFlag: true, + sfsCustomEndpointFlag: true, + skeCustomEndpointFlag: true, + sqlServerFlexCustomEndpointFlag: true, + iaasAlphaCustomEndpointFlag: true, + iaasCustomEndpointFlag: true, + tokenCustomEndpointFlag: true, + intakeCustomEndpointFlag: true, + logsCustomEndpointFlag: true, + cdnCustomEndpointFlag: true, + vpnCustomEndpointFlag: true, } for _, mod := range mods { mod(flagValues) @@ -72,36 +73,37 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { IdentityProviderCustomClientID: true, AllowedUrlDomain: true, - AuthorizationCustomEndpoint: true, - AutomationCustomEndpoint: true, - AlbWafCustomEndpoint: true, - DNSCustomEndpoint: true, - EdgeCustomEndpoint: true, - LoadBalancerCustomEndpoint: true, - LogMeCustomEndpoint: true, - MariaDBCustomEndpoint: true, - ObjectStorageCustomEndpoint: true, - ObservabilityCustomEndpoint: true, - OpenSearchCustomEndpoint: true, - RabbitMQCustomEndpoint: true, - RedisCustomEndpoint: true, - ResourceManagerCustomEndpoint: true, - SecretsManagerCustomEndpoint: true, - KMSCustomEndpoint: true, - ServiceAccountCustomEndpoint: true, - ServerBackupCustomEndpoint: true, - ServerOsUpdateCustomEndpoint: true, - RunCommandCustomEndpoint: true, - SfsCustomEndpoint: true, - SKECustomEndpoint: true, - SQLServerFlexCustomEndpoint: true, - IaaSAlphaCustomEndpoint: true, - IaaSCustomEndpoint: true, - TokenCustomEndpoint: true, - IntakeCustomEndpoint: true, - LogsCustomEndpoint: true, - CDNCustomEndpoint: true, - VpnCustomEndpoint: true, + AuthorizationCustomEndpoint: true, + AutomationCustomEndpoint: true, + AlbWafCustomEndpoint: true, + DNSCustomEndpoint: true, + EdgeCustomEndpoint: true, + LoadBalancerCustomEndpoint: true, + LogMeCustomEndpoint: true, + MariaDBCustomEndpoint: true, + ModelExperimentsCustomEndpoint: true, + ObjectStorageCustomEndpoint: true, + ObservabilityCustomEndpoint: true, + OpenSearchCustomEndpoint: true, + RabbitMQCustomEndpoint: true, + RedisCustomEndpoint: true, + ResourceManagerCustomEndpoint: true, + SecretsManagerCustomEndpoint: true, + KMSCustomEndpoint: true, + ServiceAccountCustomEndpoint: true, + ServerBackupCustomEndpoint: true, + ServerOsUpdateCustomEndpoint: true, + RunCommandCustomEndpoint: true, + SfsCustomEndpoint: true, + SKECustomEndpoint: true, + SQLServerFlexCustomEndpoint: true, + IaaSAlphaCustomEndpoint: true, + IaaSCustomEndpoint: true, + TokenCustomEndpoint: true, + IntakeCustomEndpoint: true, + LogsCustomEndpoint: true, + CDNCustomEndpoint: true, + VpnCustomEndpoint: true, } for _, mod := range mods { mod(model) @@ -146,6 +148,7 @@ func TestParseInput(t *testing.T) { model.LoadBalancerCustomEndpoint = false model.LogMeCustomEndpoint = false model.MariaDBCustomEndpoint = false + model.ModelExperimentsCustomEndpoint = false model.ObjectStorageCustomEndpoint = false model.ObservabilityCustomEndpoint = false model.OpenSearchCustomEndpoint = false @@ -300,6 +303,16 @@ func TestParseInput(t *testing.T) { model.ServiceAccountCustomEndpoint = false }), }, + { + description: "model experiments custom endpoint empty", + flagValues: fixtureFlagValues(func(flagValues map[string]bool) { + flagValues[modelExperimentsCustomEndpointFlag] = false + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ModelExperimentsCustomEndpoint = false + }), + }, { description: "sfs custom endpoint empty", flagValues: fixtureFlagValues(func(flagValues map[string]bool) { diff --git a/internal/cmd/modelexperiments/instance/create/create.go b/internal/cmd/modelexperiments/instance/create/create.go index d7d72d4f7..818b36bc5 100644 --- a/internal/cmd/modelexperiments/instance/create/create.go +++ b/internal/cmd/modelexperiments/instance/create/create.go @@ -44,10 +44,10 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Create an AI Model Experiments instance with name "my-tracking"`, - `$ stackit ai-model-experiments instance create --name my-tracking`), + `$ stackit model-experiments instance create --name my-tracking`), examples.NewExample( `Create an instance with a description and labels`, - `$ stackit ai-model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod`), + `$ stackit model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -73,7 +73,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - req := buildCreateInstanceRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { return fmt.Errorf("create AI Model Experiments instance: %w", err) @@ -122,7 +122,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, return &model, nil } -func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiCreateInstanceRequest { +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiCreateInstanceRequest { req := apiClient.DefaultAPI.CreateInstance(ctx, model.ProjectId, model.Region) payload := modelexperiments.CreateInstancePayload{ diff --git a/internal/cmd/modelexperiments/instance/create/create_test.go b/internal/cmd/modelexperiments/instance/create/create_test.go index ba90c9eb2..63fabf476 100644 --- a/internal/cmd/modelexperiments/instance/create/create_test.go +++ b/internal/cmd/modelexperiments/instance/create/create_test.go @@ -141,7 +141,7 @@ func TestBuildCreateInstanceRequest(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - request := buildCreateInstanceRequest(testCtx, tt.model, testClient) + request := buildRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), diff --git a/internal/cmd/modelexperiments/instance/delete/delete.go b/internal/cmd/modelexperiments/instance/delete/delete.go index 33f92a286..3051997ff 100644 --- a/internal/cmd/modelexperiments/instance/delete/delete.go +++ b/internal/cmd/modelexperiments/instance/delete/delete.go @@ -36,7 +36,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `Delete an AI Model Experiments instance with ID "xxx"`, - `$ stackit ai-model-experiments instance delete xxx`, + `$ stackit model-experiments instance delete xxx`, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -61,7 +61,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - req := buildDeleteInstanceRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { @@ -96,7 +96,7 @@ func parseInput( return &model, nil } -func buildDeleteInstanceRequest( +func buildRequest( ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient, diff --git a/internal/cmd/modelexperiments/instance/delete/delete_test.go b/internal/cmd/modelexperiments/instance/delete/delete_test.go index 544845108..22d61eace 100644 --- a/internal/cmd/modelexperiments/instance/delete/delete_test.go +++ b/internal/cmd/modelexperiments/instance/delete/delete_test.go @@ -120,7 +120,7 @@ func TestParseInput(t *testing.T) { } func TestBuildDeleteInstanceRequest(t *testing.T) { - request := buildDeleteInstanceRequest( + request := buildRequest( testCtx, fixtureInputModel(), testClient, diff --git a/internal/cmd/modelexperiments/instance/get/get.go b/internal/cmd/modelexperiments/instance/describe/describe.go similarity index 78% rename from internal/cmd/modelexperiments/instance/get/get.go rename to internal/cmd/modelexperiments/instance/describe/describe.go index b156a8d9b..44e20d9bb 100644 --- a/internal/cmd/modelexperiments/instance/get/get.go +++ b/internal/cmd/modelexperiments/instance/describe/describe.go @@ -1,4 +1,4 @@ -package get +package describe import ( "context" @@ -30,14 +30,18 @@ type inputModel struct { func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ - Use: fmt.Sprintf("get %s", instanceIdArg), - Short: "Gets an AI Model Experiments instance", - Long: "Gets an AI Model Experiments instance in a STACKIT project.", + Use: fmt.Sprintf("describe %s", instanceIdArg), + Short: "Shows details of an AI Model Experiments instance", + Long: "Shows details of an AI Model Experiments instance in a STACKIT project.", Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( examples.NewExample( - `Get an AI Model Experiments instance with ID "xxx"`, - `$ stackit ai-model-experiments instance get xxx`, + `Show details of an AI Model Experiments instance with ID "xxx"`, + `$ stackit model-experiments instance describe xxx`, + ), + examples.NewExample( + `Get details of an AI Model Experiments instance with ID "xxx" in JSON format`, + `$ stackit ai-model-experiments instance get xxx --output-format json`, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -53,11 +57,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - req := buildGetInstanceRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { - return fmt.Errorf("get AI Model Experiments instance: %w", err) + return fmt.Errorf("describe AI Model Experiments instance: %w", err) } return outputResult(params.Printer, model.OutputFormat, resp) @@ -88,7 +92,7 @@ func parseInput( return &model, nil } -func buildGetInstanceRequest( +func buildRequest( ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient, @@ -116,7 +120,7 @@ func outputResult( table.AddRow( resp.Instance.Id, resp.Instance.Name, - resp.Instance.Region, + utils.PtrString(resp.Instance.Region), resp.Instance.State, ) return table.Display(p) diff --git a/internal/cmd/modelexperiments/instance/get/get_test.go b/internal/cmd/modelexperiments/instance/describe/describe_test.go similarity index 97% rename from internal/cmd/modelexperiments/instance/get/get_test.go rename to internal/cmd/modelexperiments/instance/describe/describe_test.go index 1fdf6c814..afdb7ebfa 100644 --- a/internal/cmd/modelexperiments/instance/get/get_test.go +++ b/internal/cmd/modelexperiments/instance/describe/describe_test.go @@ -1,4 +1,4 @@ -package get +package describe import ( "context" @@ -119,8 +119,8 @@ func TestParseInput(t *testing.T) { } } -func TestBuildGetInstanceRequest(t *testing.T) { - request := buildGetInstanceRequest( +func TestBuildDescribeInstanceRequest(t *testing.T) { + request := buildRequest( testCtx, fixtureInputModel(), testClient, diff --git a/internal/cmd/modelexperiments/instance/instance.go b/internal/cmd/modelexperiments/instance/instance.go index de3a56c00..9b39d3704 100644 --- a/internal/cmd/modelexperiments/instance/instance.go +++ b/internal/cmd/modelexperiments/instance/instance.go @@ -5,9 +5,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/create" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/get" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/describe" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/patch" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/instance/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -31,6 +31,6 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(list.NewCmd(params)) cmd.AddCommand(create.NewCmd(params)) cmd.AddCommand(delete.NewCmd(params)) - cmd.AddCommand(get.NewCmd(params)) - cmd.AddCommand(patch.NewCmd(params)) + cmd.AddCommand(describe.NewCmd(params)) + cmd.AddCommand(update.NewCmd(params)) } diff --git a/internal/cmd/modelexperiments/instance/list/list.go b/internal/cmd/modelexperiments/instance/list/list.go index b810039f7..5dd56fa9a 100644 --- a/internal/cmd/modelexperiments/instance/list/list.go +++ b/internal/cmd/modelexperiments/instance/list/list.go @@ -31,7 +31,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Example: examples.Build( examples.NewExample( `List AI Model Experiments instances in a project`, - `$ stackit ai-model-experiments instance list --region eu01`, + `$ stackit model-experiments instance list --region eu01`, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -47,7 +47,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - req := buildListInstancesRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { @@ -81,7 +81,7 @@ func parseInput( return &model, nil } -func buildListInstancesRequest( +func buildRequest( ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient, diff --git a/internal/cmd/modelexperiments/instance/list/list_test.go b/internal/cmd/modelexperiments/instance/list/list_test.go index 0c536fd6a..6f40fa570 100644 --- a/internal/cmd/modelexperiments/instance/list/list_test.go +++ b/internal/cmd/modelexperiments/instance/list/list_test.go @@ -115,7 +115,7 @@ func TestParseInput(t *testing.T) { } func TestBuildListInstancesRequest(t *testing.T) { - request := buildListInstancesRequest( + request := buildRequest( testCtx, fixtureInputModel(), testClient, diff --git a/internal/cmd/modelexperiments/instance/patch/patch.go b/internal/cmd/modelexperiments/instance/update/update.go similarity index 89% rename from internal/cmd/modelexperiments/instance/patch/patch.go rename to internal/cmd/modelexperiments/instance/update/update.go index 1b4e080f7..fbe08c0dc 100644 --- a/internal/cmd/modelexperiments/instance/patch/patch.go +++ b/internal/cmd/modelexperiments/instance/update/update.go @@ -1,4 +1,4 @@ -package patch +package update import ( "context" @@ -41,26 +41,26 @@ type inputModel struct { func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ - Use: fmt.Sprintf("patch %s", instanceIdArg), + Use: fmt.Sprintf("update %s", instanceIdArg), Short: "Updates an AI Model Experiments instance", Long: "Partially updates an AI Model Experiments instance in a STACKIT project.", Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( examples.NewExample( `Update the name of an AI Model Experiments instance with ID "xxx"`, - `$ stackit ai-model-experiments instance patch xxx --name my-new-name`, + `$ stackit model-experiments instance update xxx --name my-new-name`, ), examples.NewExample( `Update the description of an AI Model Experiments instance`, - `$ stackit ai-model-experiments instance patch xxx --description "team tracking server"`, + `$ stackit model-experiments instance update xxx --description "team tracking server"`, ), examples.NewExample( `Update labels on an AI Model Experiments instance`, - `$ stackit ai-model-experiments instance patch xxx --label env=prod`, + `$ stackit model-experiments instance update xxx --label env=prod`, ), examples.NewExample( `Update multiple fields of an AI Model Experiments instance`, - `$ stackit ai-model-experiments instance patch xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d`, + `$ stackit model-experiments instance update xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d`, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -76,7 +76,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - req := buildPatchInstanceRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { @@ -151,7 +151,7 @@ func parseInput( return &model, nil } -func buildPatchInstanceRequest( +func buildRequest( ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient, diff --git a/internal/cmd/modelexperiments/instance/patch/patch_test.go b/internal/cmd/modelexperiments/instance/update/update_test.go similarity index 98% rename from internal/cmd/modelexperiments/instance/patch/patch_test.go rename to internal/cmd/modelexperiments/instance/update/update_test.go index f1efb15dd..13e1a1e9d 100644 --- a/internal/cmd/modelexperiments/instance/patch/patch_test.go +++ b/internal/cmd/modelexperiments/instance/update/update_test.go @@ -1,4 +1,4 @@ -package patch +package update import ( "context" @@ -159,7 +159,7 @@ func TestParseInput(t *testing.T) { } } -func TestBuildPatchInstanceRequest(t *testing.T) { +func TestBuildUpdateInstanceRequest(t *testing.T) { tests := []struct { description string model *inputModel @@ -220,7 +220,7 @@ func TestBuildPatchInstanceRequest(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - request := buildPatchInstanceRequest( + request := buildRequest( testCtx, tt.model, testClient, diff --git a/internal/cmd/modelexperiments/modelexperiments.go b/internal/cmd/modelexperiments/modelexperiments.go index ff4b96518..50b47ea84 100644 --- a/internal/cmd/modelexperiments/modelexperiments.go +++ b/internal/cmd/modelexperiments/modelexperiments.go @@ -12,7 +12,7 @@ import ( func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ - Use: "ai-model-experiments", + Use: "model-experiments", Short: "Provides functionality for AI Model Experiments", Long: "Provides functionality for AI Model Experiments.", Args: args.NoArgs, diff --git a/internal/cmd/modelexperiments/token/create/create.go b/internal/cmd/modelexperiments/token/create/create.go index b9fa0f2a6..606b3b7ce 100644 --- a/internal/cmd/modelexperiments/token/create/create.go +++ b/internal/cmd/modelexperiments/token/create/create.go @@ -36,7 +36,7 @@ type inputModel struct { } func NewCmd(params *types.CmdParams) *cobra.Command { - cmd := &cobra.Command{Use: "create", Short: "Creates an instance token", Long: "Creates an auth token for an AI Model Experiments instance.", Args: args.NoArgs, Example: examples.Build(examples.NewExample("Create an auth token", "$ stackit ai-model-experiments token create --instance-id xxx --name my-token")), RunE: func(cmd *cobra.Command, _ []string) error { + cmd := &cobra.Command{Use: "create", Short: "Creates an instance token", Long: "Creates an auth token for an AI Model Experiments instance.", Args: args.NoArgs, Example: examples.Build(examples.NewExample("Create an auth token", "$ stackit model-experiments token create --instance-id xxx --name my-token")), RunE: func(cmd *cobra.Command, _ []string) error { model, err := parseInput(params.Printer, cmd, nil) if err != nil { return err diff --git a/internal/cmd/modelexperiments/token/delete/delete.go b/internal/cmd/modelexperiments/token/delete/delete.go index 77106410f..b8d9ac716 100644 --- a/internal/cmd/modelexperiments/token/delete/delete.go +++ b/internal/cmd/modelexperiments/token/delete/delete.go @@ -36,7 +36,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { Short: "Deletes an instance token", Long: "Deletes an auth token from an AI Model Experiments instance.", Args: args.SingleArg(tokenIDArg, utils.ValidateUUID), - Example: examples.Build(examples.NewExample(`Delete an auth token with ID "xxx"`, `$ stackit ai-model-experiments token delete xxx --instance-id yyy`)), + Example: examples.Build(examples.NewExample(`Delete an auth token with ID "xxx"`, `$ stackit model-experiments token delete xxx --instance-id yyy`)), RunE: func(cmd *cobra.Command, inputArgs []string) error { model, err := parseInput(params.Printer, cmd, inputArgs) if err != nil { diff --git a/internal/cmd/modelexperiments/token/get/get.go b/internal/cmd/modelexperiments/token/describe/describe.go similarity index 86% rename from internal/cmd/modelexperiments/token/get/get.go rename to internal/cmd/modelexperiments/token/describe/describe.go index d0f4163e0..b67d227dd 100644 --- a/internal/cmd/modelexperiments/token/get/get.go +++ b/internal/cmd/modelexperiments/token/describe/describe.go @@ -1,4 +1,4 @@ -package get +package describe import ( "context" @@ -32,11 +32,11 @@ type inputModel struct { func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ - Use: fmt.Sprintf("get %s", tokenIDArg), - Short: "Gets an instance token", - Long: "Gets an auth token for an AI Model Experiments instance.", + Use: fmt.Sprintf("describe %s", tokenIDArg), + Short: "Shows details of an instance token", + Long: "Shows details of an auth token for an AI Model Experiments instance.", Args: args.SingleArg(tokenIDArg, utils.ValidateUUID), - Example: examples.Build(examples.NewExample(`Get an auth token with ID "xxx"`, `$ stackit ai-model-experiments token get xxx --instance-id yyy`)), + Example: examples.Build(examples.NewExample(`Show details of an auth token with ID "xxx"`, `$ stackit model-experiments token describe xxx --instance-id yyy`)), RunE: func(cmd *cobra.Command, inputArgs []string) error { model, err := parseInput(params.Printer, cmd, inputArgs) if err != nil { @@ -48,7 +48,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } resp, err := buildRequest(context.Background(), model, apiClient).Execute() if err != nil { - return fmt.Errorf("get instance token: %w", err) + return fmt.Errorf("describe instance token: %w", err) } return outputResult(params.Printer, model.OutputFormat, resp) }, diff --git a/internal/cmd/modelexperiments/token/get/get_test.go b/internal/cmd/modelexperiments/token/describe/describe_test.go similarity index 97% rename from internal/cmd/modelexperiments/token/get/get_test.go rename to internal/cmd/modelexperiments/token/describe/describe_test.go index 4a7ab60a5..f35b26485 100644 --- a/internal/cmd/modelexperiments/token/get/get_test.go +++ b/internal/cmd/modelexperiments/token/describe/describe_test.go @@ -1,4 +1,4 @@ -package get +package describe import ( "context" @@ -55,7 +55,7 @@ func TestParseInput(t *testing.T) { }) } } -func TestBuildRequest(t *testing.T) { +func TestBuildDescribeRequest(t *testing.T) { got := buildRequest(testContext, fixtureInputModel(), testClient) want := fixtureRequest() if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { diff --git a/internal/cmd/modelexperiments/token/list/list.go b/internal/cmd/modelexperiments/token/list/list.go index 51fe61e9f..a65ce6660 100644 --- a/internal/cmd/modelexperiments/token/list/list.go +++ b/internal/cmd/modelexperiments/token/list/list.go @@ -29,7 +29,7 @@ type inputModel struct { } func NewCmd(params *types.CmdParams) *cobra.Command { - cmd := &cobra.Command{Use: "list", Short: "Lists instance tokens", Long: "Lists all auth tokens for an AI Model Experiments instance.", Args: args.NoArgs, Example: examples.Build(examples.NewExample("List all tokens for an instance", "$ stackit ai-model-experiments token list --instance-id xxx")), RunE: func(cmd *cobra.Command, _ []string) error { + cmd := &cobra.Command{Use: "list", Short: "Lists instance tokens", Long: "Lists all auth tokens for an AI Model Experiments instance.", Args: args.NoArgs, Example: examples.Build(examples.NewExample("List all tokens for an instance", "$ stackit model-experiments token list --instance-id xxx")), RunE: func(cmd *cobra.Command, _ []string) error { model, err := parseInput(params.Printer, cmd, nil) if err != nil { return err diff --git a/internal/cmd/modelexperiments/token/token.go b/internal/cmd/modelexperiments/token/token.go index 3e796e95b..cd3868395 100644 --- a/internal/cmd/modelexperiments/token/token.go +++ b/internal/cmd/modelexperiments/token/token.go @@ -5,16 +5,30 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/create" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/get" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/describe" "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/patch" + "github.com/stackitcloud/stackit-cli/internal/cmd/modelexperiments/token/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) -func NewCmd(params *types.CmdParams) *cobra.Command { - cmd := &cobra.Command{Use: "token", Short: "Provides functionality for AI Model Experiments instance tokens", Long: "Provides functionality for AI Model Experiments instance tokens.", Args: args.NoArgs, Run: utils.CmdHelp} - cmd.AddCommand(list.NewCmd(params), create.NewCmd(params), delete.NewCmd(params), get.NewCmd(params), patch.NewCmd(params)) +func NewCmd( + params *types.CmdParams, +) *cobra.Command { + cmd := &cobra.Command{ + Use: "token", + Short: "Provides functionality for AI Model Experiments instance tokens", + Long: "Provides functionality for AI Model Experiments instance tokens.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + cmd.AddCommand( + list.NewCmd(params), + create.NewCmd(params), + delete.NewCmd(params), + describe.NewCmd(params), + update.NewCmd(params), + ) return cmd } diff --git a/internal/cmd/modelexperiments/token/patch/patch.go b/internal/cmd/modelexperiments/token/update/update.go similarity index 92% rename from internal/cmd/modelexperiments/token/patch/patch.go rename to internal/cmd/modelexperiments/token/update/update.go index 904b28f36..aa8f88ba5 100644 --- a/internal/cmd/modelexperiments/token/patch/patch.go +++ b/internal/cmd/modelexperiments/token/update/update.go @@ -1,4 +1,4 @@ -package patch +package update import ( "context" @@ -38,8 +38,8 @@ type inputModel struct { func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ - Use: fmt.Sprintf("patch %s", tokenIDArg), Short: "Updates an instance token", Long: "Partially updates an auth token for an AI Model Experiments instance.", Args: args.SingleArg(tokenIDArg, utils.ValidateUUID), - Example: examples.Build(examples.NewExample(`Update an auth token with ID "xxx"`, `$ stackit ai-model-experiments token patch xxx --instance-id yyy --name updated-token`)), + Use: fmt.Sprintf("update %s", tokenIDArg), Short: "Updates an instance token", Long: "Partially updates an auth token for an AI Model Experiments instance.", Args: args.SingleArg(tokenIDArg, utils.ValidateUUID), + Example: examples.Build(examples.NewExample(`Update an auth token with ID "xxx"`, `$ stackit model-experiments token update xxx --instance-id yyy --name updated-token`)), RunE: func(cmd *cobra.Command, inputArgs []string) error { model, err := parseInput(params.Printer, cmd, inputArgs) if err != nil { diff --git a/internal/cmd/modelexperiments/token/patch/patch_test.go b/internal/cmd/modelexperiments/token/update/update_test.go similarity index 98% rename from internal/cmd/modelexperiments/token/patch/patch_test.go rename to internal/cmd/modelexperiments/token/update/update_test.go index c846bb9b3..e332873ab 100644 --- a/internal/cmd/modelexperiments/token/patch/patch_test.go +++ b/internal/cmd/modelexperiments/token/update/update_test.go @@ -1,4 +1,4 @@ -package patch +package update import ( "context" @@ -70,7 +70,7 @@ func TestParseInput(t *testing.T) { } } -func TestBuildRequest(t *testing.T) { +func TestBuildUpdateRequest(t *testing.T) { model := fixtureInputModel(func(model *inputModel) { model.Name = utils.Ptr("updated") model.Description = utils.Ptr("updated token") diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index a1488c572..7b68d8a4f 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -23,22 +23,22 @@ const ( IdentityProviderCustomClientIdKey = "identity_provider_custom_client_id" AllowedUrlDomainKey = "allowed_url_domain" - AuthorizationCustomEndpointKey = "authorization_custom_endpoint" - AlbCustomEndpoint = "alb_custom _endpoint" - AlbWafCustomEndpointKey = "alb_waf_custom_endpoint" - DNSCustomEndpointKey = "dns_custom_endpoint" - EdgeCustomEndpointKey = "edge_custom_endpoint" - LoadBalancerCustomEndpointKey = "load_balancer_custom_endpoint" - LogMeCustomEndpointKey = "logme_custom_endpoint" - MariaDBCustomEndpointKey = "mariadb_custom_endpoint" - MongoDBFlexCustomEndpointKey = "mongodbflex_custom_endpoint" - ObjectStorageCustomEndpointKey = "object_storage_custom_endpoint" - ObservabilityCustomEndpointKey = "observability_custom_endpoint" - OpenSearchCustomEndpointKey = "opensearch_custom_endpoint" - PostgresFlexCustomEndpointKey = "postgresflex_custom_endpoint" - RabbitMQCustomEndpointKey = "rabbitmq_custom_endpoint" - AIModelExperimentsCustomEndpointKey = "aimodelexperiments_custom_endpoint" - AutomationCustomEndpointKey = "automation_custom_endpoint" + AuthorizationCustomEndpointKey = "authorization_custom_endpoint" + AlbCustomEndpoint = "alb_custom _endpoint" + AlbWafCustomEndpointKey = "alb_waf_custom_endpoint" + DNSCustomEndpointKey = "dns_custom_endpoint" + EdgeCustomEndpointKey = "edge_custom_endpoint" + LoadBalancerCustomEndpointKey = "load_balancer_custom_endpoint" + LogMeCustomEndpointKey = "logme_custom_endpoint" + MariaDBCustomEndpointKey = "mariadb_custom_endpoint" + MongoDBFlexCustomEndpointKey = "mongodbflex_custom_endpoint" + ObjectStorageCustomEndpointKey = "object_storage_custom_endpoint" + ObservabilityCustomEndpointKey = "observability_custom_endpoint" + OpenSearchCustomEndpointKey = "opensearch_custom_endpoint" + PostgresFlexCustomEndpointKey = "postgresflex_custom_endpoint" + RabbitMQCustomEndpointKey = "rabbitmq_custom_endpoint" + ModelExperimentsCustomEndpointKey = "modelexperiments_custom_endpoint" + AutomationCustomEndpointKey = "automation_custom_endpoint" // Deprecated: Will be removed after 2027-08-31. RedisCustomEndpointKey = "redis_custom_endpoint" ResourceManagerEndpointKey = "resource_manager_custom_endpoint" @@ -119,7 +119,7 @@ var ConfigKeys = []string{ OpenSearchCustomEndpointKey, PostgresFlexCustomEndpointKey, RabbitMQCustomEndpointKey, - AIModelExperimentsCustomEndpointKey, + ModelExperimentsCustomEndpointKey, RedisCustomEndpointKey, ResourceManagerEndpointKey, RunCommandCustomEndpointKey, @@ -230,6 +230,7 @@ func setConfigDefaults() { viper.SetDefault(CDNCustomEndpointKey, "") viper.SetDefault(ValkeyCustomEndpointKey, "") viper.SetDefault(VPNCustomEndpointKey, "") + viper.SetDefault(ModelExperimentsCustomEndpointKey, "") } func getConfigFilePath(configFolder string) string { diff --git a/internal/pkg/services/modelexperiments/client/client.go b/internal/pkg/services/modelexperiments/client/client.go index a6b12827c..3056f32a4 100644 --- a/internal/pkg/services/modelexperiments/client/client.go +++ b/internal/pkg/services/modelexperiments/client/client.go @@ -10,5 +10,5 @@ import ( ) func ConfigureClient(p *print.Printer, cliVersion string) (*modelexperiments.APIClient, error) { - return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.AIModelExperimentsCustomEndpointKey), false, genericclient.CreateApiClient[*modelexperiments.APIClient](modelexperiments.NewAPIClient)) + return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.ModelExperimentsCustomEndpointKey), false, modelexperiments.NewAPIClient) } diff --git a/internal/pkg/services/modelexperiments/utils/utils.go b/internal/pkg/services/modelexperiments/utils/utils.go index ac0abfb71..ad9de4945 100644 --- a/internal/pkg/services/modelexperiments/utils/utils.go +++ b/internal/pkg/services/modelexperiments/utils/utils.go @@ -15,10 +15,7 @@ func GetInstanceName(ctx context.Context, apiClient modelexperiments.DefaultAPI, if err != nil { return "", fmt.Errorf("get AI Model Experiments instance: %w", err) } - if resp == nil { - return "", ErrResponseNil - } - if resp.Instance.Name == "" { + if resp == nil || resp.Instance.Name == "" { return "", ErrResponseNil } return resp.Instance.Name, nil diff --git a/internal/pkg/services/modelexperiments/utils/utils_test.go b/internal/pkg/services/modelexperiments/utils/utils_test.go index 3d364de8f..f47c7a9ca 100644 --- a/internal/pkg/services/modelexperiments/utils/utils_test.go +++ b/internal/pkg/services/modelexperiments/utils/utils_test.go @@ -12,49 +12,73 @@ import ( cliutils "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) +const ( + testRegion = "eu01" + testInstanceName = "instance" + testTokenName = "token" +) + var ( testProjectID = uuid.NewString() - testRegion = "eu01" testInstanceID = uuid.NewString() testTokenID = uuid.NewString() ) type mockSettings struct { - instanceFails bool - instanceResp *modelexperiments.GetInstanceResponse - tokenFails bool - tokenResp *modelexperiments.GetInstanceTokenResponse + getInstanceFails bool + getInstanceResp *modelexperiments.GetInstanceResponse + getTokenFails bool + getTokenResp *modelexperiments.GetInstanceTokenResponse } func newAPIMock(settings mockSettings) modelexperiments.DefaultAPI { return &modelexperiments.DefaultAPIServiceMock{ GetInstanceExecuteMock: cliutils.Ptr(func(modelexperiments.ApiGetInstanceRequest) (*modelexperiments.GetInstanceResponse, error) { - if settings.instanceFails { + if settings.getInstanceFails { return nil, fmt.Errorf("could not get instance") } - return settings.instanceResp, nil + return settings.getInstanceResp, nil }), GetInstanceTokenExecuteMock: cliutils.Ptr(func(modelexperiments.ApiGetInstanceTokenRequest) (*modelexperiments.GetInstanceTokenResponse, error) { - if settings.tokenFails { + if settings.getTokenFails { return nil, fmt.Errorf("could not get token") } - return settings.tokenResp, nil + return settings.getTokenResp, nil }), } } func TestGetInstanceName(t *testing.T) { - name := "instance" tests := []struct { name string settings mockSettings want string wantErr bool }{ - {name: "success", settings: mockSettings{instanceResp: &modelexperiments.GetInstanceResponse{Instance: modelexperiments.Instance{Name: name}}}, want: name}, - {name: "request error", settings: mockSettings{instanceFails: true}, wantErr: true}, - {name: "nil response", settings: mockSettings{}, wantErr: true}, - {name: "nil instance", settings: mockSettings{instanceResp: &modelexperiments.GetInstanceResponse{}}, wantErr: true}, + { + name: "success", + settings: mockSettings{ + getInstanceResp: &modelexperiments.GetInstanceResponse{ + Instance: modelexperiments.Instance{Name: testInstanceName}, + }, + }, + want: testInstanceName, + }, + { + name: "request error", + settings: mockSettings{getInstanceFails: true}, + wantErr: true, + }, + { + name: "nil response", + settings: mockSettings{}, + wantErr: true, + }, + { + name: "nil instance", + settings: mockSettings{getInstanceResp: &modelexperiments.GetInstanceResponse{}}, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -70,17 +94,36 @@ func TestGetInstanceName(t *testing.T) { } func TestGetTokenName(t *testing.T) { - name := "token" tests := []struct { name string settings mockSettings want string wantErr bool }{ - {name: "success", settings: mockSettings{tokenResp: &modelexperiments.GetInstanceTokenResponse{Token: modelexperiments.TokenMetadata{Name: name}}}, want: name}, - {name: "request error", settings: mockSettings{tokenFails: true}, wantErr: true}, - {name: "nil response", settings: mockSettings{}, wantErr: true}, - {name: "zero token", settings: mockSettings{tokenResp: &modelexperiments.GetInstanceTokenResponse{}}, wantErr: true}, + { + name: "success", + settings: mockSettings{ + getTokenResp: &modelexperiments.GetInstanceTokenResponse{ + Token: modelexperiments.TokenMetadata{Name: testTokenName}, + }, + }, + want: testTokenName, + }, + { + name: "request error", + settings: mockSettings{getTokenFails: true}, + wantErr: true, + }, + { + name: "nil response", + settings: mockSettings{}, + wantErr: true, + }, + { + name: "empty token name", + settings: mockSettings{getTokenResp: &modelexperiments.GetInstanceTokenResponse{}}, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From e6905158326542c6a68d79a1cdcc857b6a2e52b5 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Fri, 30 Oct 2026 00:22:49 +0100 Subject: [PATCH 11/13] feat: address pr review feedback, STACKITMLO-1718 --- docs/stackit_config_set.md | 1 + docs/stackit_config_unset.md | 1 + docs/stackit_model-experiments_instance.md | 2 +- ...ackit_model-experiments_instance_create.md | 4 +- ...kit_model-experiments_instance_describe.md | 4 +- ...stackit_model-experiments_instance_list.md | 18 ++- ...ackit_model-experiments_instance_update.md | 7 +- .../stackit_model-experiments_token_create.md | 12 +- .../stackit_model-experiments_token_update.md | 10 +- .../instance/create/create.go | 57 ++++--- .../instance/create/create_test.go | 56 +++++-- .../instance/delete/delete.go | 47 +++--- .../instance/delete/delete_test.go | 67 ++++---- .../instance/describe/describe.go | 48 +++--- .../instance/describe/describe_test.go | 115 ++++++++----- .../modelexperiments/instance/list/list.go | 84 ++++++---- .../instance/list/list_test.go | 153 ++++++++++++++---- .../instance/update/update.go | 37 ++--- .../instance/update/update_test.go | 101 ++++++++---- .../modelexperiments/token/create/create.go | 8 +- .../token/create/create_test.go | 14 +- .../token/delete/delete_test.go | 6 +- .../token/describe/describe_test.go | 18 +-- .../modelexperiments/token/list/list_test.go | 6 +- internal/cmd/modelexperiments/token/token.go | 4 +- .../modelexperiments/token/update/update.go | 8 +- .../token/update/update_test.go | 14 +- 27 files changed, 554 insertions(+), 348 deletions(-) diff --git a/docs/stackit_config_set.md b/docs/stackit_config_set.md index 9f21f4095..d6467fd06 100644 --- a/docs/stackit_config_set.md +++ b/docs/stackit_config_set.md @@ -47,6 +47,7 @@ stackit config set [flags] --logme-custom-endpoint string LogMe API base URL, used in calls to this API --logs-custom-endpoint string Logs API base URL, used in calls to this API --mariadb-custom-endpoint string MariaDB API base URL, used in calls to this API + --model-experiments-custom-endpoint string Model Experiments API base URL, used in calls to this API --mongodbflex-custom-endpoint string MongoDB Flex API base URL, used in calls to this API --object-storage-custom-endpoint string Object Storage API base URL, used in calls to this API --observability-custom-endpoint string Observability API base URL, used in calls to this API diff --git a/docs/stackit_config_unset.md b/docs/stackit_config_unset.md index 5f64ad2e4..7aa78c027 100644 --- a/docs/stackit_config_unset.md +++ b/docs/stackit_config_unset.md @@ -46,6 +46,7 @@ stackit config unset [flags] --logme-custom-endpoint LogMe API base URL. If unset, uses the default base URL --logs-custom-endpoint Logs API base URL. If unset, uses the default base URL --mariadb-custom-endpoint MariaDB API base URL. If unset, uses the default base URL + --model-experiments-custom-endpoint Model Experiments API base URL. If unset, uses the default base URL --mongodbflex-custom-endpoint MongoDB Flex API base URL. If unset, uses the default base URL --object-storage-custom-endpoint Object Storage API base URL. If unset, uses the default base URL --observability-custom-endpoint Observability API base URL. If unset, uses the default base URL diff --git a/docs/stackit_model-experiments_instance.md b/docs/stackit_model-experiments_instance.md index 3377e86fd..9b6b762bb 100644 --- a/docs/stackit_model-experiments_instance.md +++ b/docs/stackit_model-experiments_instance.md @@ -33,6 +33,6 @@ stackit model-experiments instance [flags] * [stackit model-experiments instance create](./stackit_model-experiments_instance_create.md) - Creates an AI Model Experiments instance * [stackit model-experiments instance delete](./stackit_model-experiments_instance_delete.md) - Deletes an AI Model Experiments instance * [stackit model-experiments instance describe](./stackit_model-experiments_instance_describe.md) - Shows details of an AI Model Experiments instance -* [stackit model-experiments instance list](./stackit_model-experiments_instance_list.md) - Lists AI Model Experiments instances +* [stackit model-experiments instance list](./stackit_model-experiments_instance_list.md) - Lists all AI Model Experiments instances * [stackit model-experiments instance update](./stackit_model-experiments_instance_update.md) - Updates an AI Model Experiments instance diff --git a/docs/stackit_model-experiments_instance_create.md b/docs/stackit_model-experiments_instance_create.md index 600cae9b3..611da7c29 100644 --- a/docs/stackit_model-experiments_instance_create.md +++ b/docs/stackit_model-experiments_instance_create.md @@ -17,7 +17,7 @@ stackit model-experiments instance create [flags] $ stackit model-experiments instance create --name my-tracking Create an instance with a description and labels - $ stackit model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod + $ stackit model-experiments instance create --name my-tracking --description "team tracking server" --labels env=prod,team=ml ``` ### Options @@ -26,7 +26,7 @@ stackit model-experiments instance create [flags] --deleted-experiment-retention string Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d) --description string Instance description -h, --help Help for "stackit model-experiments instance create" - --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) + --labels stringToString Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml" (default []) -n, --name string Instance name ``` diff --git a/docs/stackit_model-experiments_instance_describe.md b/docs/stackit_model-experiments_instance_describe.md index 9c847df63..98a75ce69 100644 --- a/docs/stackit_model-experiments_instance_describe.md +++ b/docs/stackit_model-experiments_instance_describe.md @@ -15,6 +15,9 @@ stackit model-experiments instance describe INSTANCE_ID [flags] ``` Show details of an AI Model Experiments instance with ID "xxx" $ stackit model-experiments instance describe xxx + + Get details of an AI Model Experiments instance with ID "xxx" in JSON format + $ stackit model-experiments instance describe xxx --output-format json ``` ### Options @@ -37,4 +40,3 @@ stackit model-experiments instance describe INSTANCE_ID [flags] ### SEE ALSO * [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances - diff --git a/docs/stackit_model-experiments_instance_list.md b/docs/stackit_model-experiments_instance_list.md index 3d51b4835..6b22709fa 100644 --- a/docs/stackit_model-experiments_instance_list.md +++ b/docs/stackit_model-experiments_instance_list.md @@ -1,10 +1,10 @@ ## stackit model-experiments instance list -Lists AI Model Experiments instances +Lists all AI Model Experiments instances ### Synopsis -Lists AI Model Experiments instances in a STACKIT project. +Lists all AI Model Experiments instances in a STACKIT project. ``` stackit model-experiments instance list [flags] @@ -14,13 +14,23 @@ stackit model-experiments instance list [flags] ``` List AI Model Experiments instances in a project - $ stackit model-experiments instance list --region eu01 + $ stackit model-experiments instance list + + List AI Model Experiments instances in a project in region eu02 + $ stackit model-experiments instance list --region eu02 + + List AI Model Experiments instances in JSON format + $ stackit model-experiments instance list --output-format json + + List up to 10 AI Model Experiments instances + $ stackit model-experiments instance list --limit 10 ``` ### Options ``` - -h, --help Help for "stackit model-experiments instance list" + -h, --help Help for "stackit model-experiments instance list" + --limit int Maximum number of entries to list ``` ### Options inherited from parent commands diff --git a/docs/stackit_model-experiments_instance_update.md b/docs/stackit_model-experiments_instance_update.md index 6d9832062..350d463bb 100644 --- a/docs/stackit_model-experiments_instance_update.md +++ b/docs/stackit_model-experiments_instance_update.md @@ -20,10 +20,10 @@ stackit model-experiments instance update INSTANCE_ID [flags] $ stackit model-experiments instance update xxx --description "team tracking server" Update labels on an AI Model Experiments instance - $ stackit model-experiments instance update xxx --label env=prod + $ stackit model-experiments instance update xxx --labels env=prod,team=ml Update multiple fields of an AI Model Experiments instance - $ stackit model-experiments instance update xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d + $ stackit model-experiments instance update xxx --name my-new-name --description "team tracking server" --labels env=prod,team=ml --deleted-experiment-retention 30d ``` ### Options @@ -32,7 +32,7 @@ stackit model-experiments instance update INSTANCE_ID [flags] --deleted-experiment-retention string Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d) --description string Instance description -h, --help Help for "stackit model-experiments instance update" - --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) + --labels stringToString Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml" (default []) -n, --name string Instance name ``` @@ -50,4 +50,3 @@ stackit model-experiments instance update INSTANCE_ID [flags] ### SEE ALSO * [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances - diff --git a/docs/stackit_model-experiments_token_create.md b/docs/stackit_model-experiments_token_create.md index 3acb6dd66..db95d6c5e 100644 --- a/docs/stackit_model-experiments_token_create.md +++ b/docs/stackit_model-experiments_token_create.md @@ -20,12 +20,12 @@ stackit model-experiments token create [flags] ### Options ``` - --description string Token description - -h, --help Help for "stackit model-experiments token create" - --instance-id string ID of the instance - --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) - --name string Token name - --ttl-duration string Token time to live duration + --description string Token description + -h, --help Help for "stackit model-experiments token create" + --instance-id string ID of the instance + --labels stringToString Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml" (default []) + --name string Token name + --ttl-duration string Token time to live duration ``` ### Options inherited from parent commands diff --git a/docs/stackit_model-experiments_token_update.md b/docs/stackit_model-experiments_token_update.md index c47372dd6..311bef63c 100644 --- a/docs/stackit_model-experiments_token_update.md +++ b/docs/stackit_model-experiments_token_update.md @@ -20,11 +20,11 @@ stackit model-experiments token update TOKEN_ID [flags] ### Options ``` - --description string Token description - -h, --help Help for "stackit model-experiments token update" - --instance-id string ID of the instance - --label stringToString Labels as key-value pairs, e.g. "--label env=prod" (default []) - --name string Token name + --description string Token description + -h, --help Help for "stackit model-experiments token update" + --instance-id string ID of the instance + --labels stringToString Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml" (default []) + --name string Token name ``` ### Options inherited from parent commands diff --git a/internal/cmd/modelexperiments/instance/create/create.go b/internal/cmd/modelexperiments/instance/create/create.go index 818b36bc5..7e731c4b4 100644 --- a/internal/cmd/modelexperiments/instance/create/create.go +++ b/internal/cmd/modelexperiments/instance/create/create.go @@ -14,15 +14,17 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api/wait" ) const ( nameFlag = "name" descriptionFlag = "description" - labelFlag = "label" + labelsFlag = "labels" retentionFlag = "deleted-experiment-retention" ) @@ -47,12 +49,12 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `$ stackit model-experiments instance create --name my-tracking`), examples.NewExample( `Create an instance with a description and labels`, - `$ stackit model-experiments instance create --name my-tracking --description "team tracking server" --label env=prod`), + `$ stackit model-experiments instance create --name my-tracking --description "team tracking server" --labels env=prod,team=ml`), ), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() - model, err := parseInput(params.Printer, cmd, args) + model, err := parseInput(params.Printer, cmd, nil) if err != nil { return err } @@ -78,8 +80,21 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if err != nil { return fmt.Errorf("create AI Model Experiments instance: %w", err) } + if resp == nil { + return fmt.Errorf("create AI Model Experiments instance: response instance is nil") + } + + if !model.Async { + err := spinner.Run(params.Printer, "Creating AI Model Experiments instance", func() error { + _, err = wait.CreateInstanceWaitHandler(ctx, apiClient.DefaultAPI, model.Region, model.ProjectId, resp.Instance.Id).WaitWithContext(ctx) + return err + }) + if err != nil { + return fmt.Errorf("wait for AI Model Experiments instance creation: %w", err) + } + } - return outputResult(params.Printer, model.OutputFormat, projectLabel, resp) + return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp) }, } configureFlags(cmd) @@ -89,7 +104,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().StringP(nameFlag, "n", "", "Instance name") cmd.Flags().String(descriptionFlag, "", "Instance description") - cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + cmd.Flags().StringToString(labelsFlag, nil, `Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml"`) cmd.Flags().String(retentionFlag, "", `Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d)`) err := flags.MarkFlagsRequired(cmd, nameFlag) cobra.CheckErr(err) @@ -101,20 +116,11 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, return nil, &cliErr.ProjectIdError{} } - labels, err := cmd.Flags().GetStringToString(labelFlag) - if err != nil { - return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) - } - var labelsPtr *map[string]string - if len(labels) > 0 { - labelsPtr = &labels - } - model := inputModel{ GlobalFlagModel: globalFlags, Name: flags.FlagToStringValue(p, cmd, nameFlag), Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), - Labels: labelsPtr, + Labels: flags.FlagToStringToStringPointer(p, cmd, labelsFlag), Retention: flags.FlagToStringPointer(p, cmd, retentionFlag), } @@ -141,13 +147,22 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperi return req.CreateInstancePayload(payload) } -func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *modelexperiments.CreateInstanceResponse) error { - if resp == nil { - return fmt.Errorf("response instance is nil") +func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, resp *modelexperiments.CreateInstanceResponse) error { + var instance any + if resp != nil { + instance = resp.Instance } - return p.OutputResult(outputFormat, resp.Instance, func() error { - p.Outputf("Creating AI Model Experiments instance for project %q. Instance ID: %s\n", projectLabel, resp.Instance.Id) + return p.OutputResult(outputFormat, instance, func() error { + if resp == nil { + return fmt.Errorf("response instance is nil") + } + + operationState := "Created" + if async { + operationState = "Triggered creation of" + } + p.Outputf("%s AI Model Experiments instance for project %q. Instance ID: %s\n", operationState, projectLabel, resp.Instance.Id) return nil }) } diff --git a/internal/cmd/modelexperiments/instance/create/create_test.go b/internal/cmd/modelexperiments/instance/create/create_test.go index 63fabf476..92e4b28b5 100644 --- a/internal/cmd/modelexperiments/instance/create/create_test.go +++ b/internal/cmd/modelexperiments/instance/create/create_test.go @@ -16,21 +16,21 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} -var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &modelexperiments.APIClient{ - DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, -} -var testProjectId = uuid.NewString() +const testRegion = "eu01" + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} + testProjectId = uuid.NewString() +) func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - nameFlag: "example", - globalflags.RegionFlag: "eu01", + globalflags.ProjectIdFlag: testProjectId, + nameFlag: "example", + globalflags.RegionFlag: testRegion, } for _, mod := range mods { mod(flagValues) @@ -42,7 +42,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ ProjectId: testProjectId, - Region: "eu01", + Region: testRegion, Verbosity: globalflags.VerbosityDefault, }, Name: "example", @@ -54,7 +54,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { } func fixtureRequest(mods ...func(request *modelexperiments.ApiCreateInstanceRequest)) modelexperiments.ApiCreateInstanceRequest { - request := testClient.DefaultAPI.CreateInstance(testCtx, testProjectId, "eu01") + request := testClient.DefaultAPI.CreateInstance(testCtx, testProjectId, testRegion) request = request.CreateInstancePayload(modelexperiments.CreateInstancePayload{ Name: "example", }) @@ -97,10 +97,20 @@ func TestParseInput(t *testing.T) { model.Retention = utils.Ptr("30d") }), }, + { + description: "with labels", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[labelsFlag] = "env=prod,team=ml" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = &map[string]string{"env": "prod", "team": "ml"} + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, projectIdFlag) + delete(flagValues, globalflags.ProjectIdFlag) }), isValid: false, }, @@ -137,6 +147,19 @@ func TestBuildCreateInstanceRequest(t *testing.T) { *request = request.CreateInstancePayload(payload) }), }, + { + description: "with labels", + model: fixtureInputModel(func(model *inputModel) { + model.Labels = &map[string]string{"env": "prod", "team": "ml"} + }), + expectedRequest: fixtureRequest(func(request *modelexperiments.ApiCreateInstanceRequest) { + payload := modelexperiments.CreateInstancePayload{ + Name: "example", + Labels: &map[string]string{"env": "prod", "team": "ml"}, + } + *request = request.CreateInstancePayload(payload) + }), + }, } for _, tt := range tests { @@ -165,11 +188,16 @@ func TestOutputResult(t *testing.T) { resp: nil, wantErr: true, }, + { + name: "empty response", + resp: &modelexperiments.CreateInstanceResponse{}, + wantErr: false, + }, } params := testparams.NewTestParams() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResult(params.Printer, "", "test-project", tt.resp); (err != nil) != tt.wantErr { + if err := outputResult(params.Printer, "", false, "test-project", tt.resp); (err != nil) != tt.wantErr { t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/cmd/modelexperiments/instance/delete/delete.go b/internal/cmd/modelexperiments/instance/delete/delete.go index 3051997ff..5e1ca6647 100644 --- a/internal/cmd/modelexperiments/instance/delete/delete.go +++ b/internal/cmd/modelexperiments/instance/delete/delete.go @@ -12,10 +12,12 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/modelexperiments/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api/wait" ) const ( @@ -68,18 +70,24 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return fmt.Errorf("delete AI Model Experiments instance: %w", err) } - return outputResult(params.Printer, model.OutputFormat, resp) + if !model.Async { + err := spinner.Run(params.Printer, "Deleting AI Model Experiments instance", func() error { + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient.DefaultAPI, model.Region, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + return err + }) + if err != nil { + return fmt.Errorf("wait for AI Model Experiments instance deletion: %w", err) + } + } + + return outputResult(params.Printer, model.OutputFormat, model.Async, resp) }, } return cmd } -func parseInput( - p *print.Printer, - cmd *cobra.Command, - inputArgs []string, -) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { @@ -96,32 +104,21 @@ func parseInput( return &model, nil } -func buildRequest( - ctx context.Context, - model *inputModel, - apiClient *modelexperiments.APIClient, -) modelexperiments.ApiDeleteInstanceRequest { - return apiClient.DefaultAPI.DeleteInstance( - ctx, - model.ProjectId, - model.Region, - model.InstanceId, - ) +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiDeleteInstanceRequest { + return apiClient.DefaultAPI.DeleteInstance(ctx, model.ProjectId, model.Region, model.InstanceId) } -func outputResult( - p *print.Printer, - outputFormat string, - resp *modelexperiments.DeleteInstanceResponse, -) error { +func outputResult(p *print.Printer, outputFormat string, async bool, resp *modelexperiments.DeleteInstanceResponse) error { if resp == nil { return fmt.Errorf("response is nil") } return p.OutputResult(outputFormat, resp.Instance, func() error { - p.Outputf( - "Deleted AI Model Experiments instance.\n", - ) + operationState := "Deleted" + if async { + operationState = "Triggered deletion of" + } + p.Outputf("%s AI Model Experiments instance.\n", operationState) return nil }) } diff --git a/internal/cmd/modelexperiments/instance/delete/delete_test.go b/internal/cmd/modelexperiments/instance/delete/delete_test.go index 22d61eace..b8063b1d4 100644 --- a/internal/cmd/modelexperiments/instance/delete/delete_test.go +++ b/internal/cmd/modelexperiments/instance/delete/delete_test.go @@ -15,41 +15,35 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} -var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") - -var testClient = &modelexperiments.APIClient{ - DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, -} +const testRegion = "eu01" -var testProjectId = uuid.NewString() -var testInstanceId = uuid.NewString() +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} + testProjectId = uuid.NewString() + testInstanceId = uuid.NewString() +) -func fixtureFlagValues( - mods ...func(*map[string]string), -) map[string]string { +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - globalflags.RegionFlag: "eu01", + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, } for _, mod := range mods { - mod(&flagValues) + mod(flagValues) } return flagValues } -func fixtureInputModel( - mods ...func(*inputModel), -) *inputModel { +func fixtureInputModel(mods ...func(*inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ ProjectId: testProjectId, - Region: "eu01", + Region: testRegion, Verbosity: globalflags.VerbosityDefault, }, InstanceId: testInstanceId, @@ -62,13 +56,11 @@ func fixtureInputModel( return model } -func fixtureRequest( - mods ...func(*modelexperiments.ApiDeleteInstanceRequest), -) modelexperiments.ApiDeleteInstanceRequest { +func fixtureRequest(mods ...func(*modelexperiments.ApiDeleteInstanceRequest)) modelexperiments.ApiDeleteInstanceRequest { request := testClient.DefaultAPI.DeleteInstance( testCtx, testProjectId, - "eu01", + testRegion, testInstanceId, ) @@ -97,8 +89,8 @@ func TestParseInput(t *testing.T) { { description: "project id missing", argValues: []string{testInstanceId}, - flagValues: fixtureFlagValues(func(flagValues *map[string]string) { - delete(*flagValues, projectIdFlag) + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) }), isValid: false, }, @@ -142,9 +134,11 @@ func TestBuildDeleteInstanceRequest(t *testing.T) { func TestOutputResult(t *testing.T) { tests := []struct { - name string - resp *modelexperiments.DeleteInstanceResponse - wantErr bool + name string + resp *modelexperiments.DeleteInstanceResponse + async bool + wantErr bool + wantOutput string }{ { name: "nil response", @@ -152,9 +146,15 @@ func TestOutputResult(t *testing.T) { wantErr: true, }, { - name: "response", - resp: &modelexperiments.DeleteInstanceResponse{}, - wantErr: false, + name: "response", + resp: &modelexperiments.DeleteInstanceResponse{}, + wantOutput: "Deleted AI Model Experiments instance.\n", + }, + { + name: "async response", + resp: &modelexperiments.DeleteInstanceResponse{}, + async: true, + wantOutput: "Triggered deletion of AI Model Experiments instance.\n", }, } @@ -162,9 +162,11 @@ func TestOutputResult(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + params.Out.Reset() if err := outputResult( params.Printer, "", + tt.async, tt.resp, ); (err != nil) != tt.wantErr { t.Errorf( @@ -173,6 +175,9 @@ func TestOutputResult(t *testing.T) { tt.wantErr, ) } + if got := params.Out.String(); got != tt.wantOutput { + t.Errorf("outputResult() output = %q, want %q", got, tt.wantOutput) + } }) } } diff --git a/internal/cmd/modelexperiments/instance/describe/describe.go b/internal/cmd/modelexperiments/instance/describe/describe.go index 44e20d9bb..bcd6d3c4b 100644 --- a/internal/cmd/modelexperiments/instance/describe/describe.go +++ b/internal/cmd/modelexperiments/instance/describe/describe.go @@ -41,7 +41,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { ), examples.NewExample( `Get details of an AI Model Experiments instance with ID "xxx" in JSON format`, - `$ stackit ai-model-experiments instance get xxx --output-format json`, + `$ stackit model-experiments instance describe xxx --output-format json`, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -71,11 +71,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return cmd } -func parseInput( - p *print.Printer, - cmd *cobra.Command, - inputArgs []string, -) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { @@ -92,37 +88,29 @@ func parseInput( return &model, nil } -func buildRequest( - ctx context.Context, - model *inputModel, - apiClient *modelexperiments.APIClient, -) modelexperiments.ApiGetInstanceRequest { - return apiClient.DefaultAPI.GetInstance( - ctx, - model.ProjectId, - model.Region, - model.InstanceId, - ) +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiGetInstanceRequest { + return apiClient.DefaultAPI.GetInstance(ctx, model.ProjectId, model.Region, model.InstanceId) } -func outputResult( - p *print.Printer, - outputFormat string, - resp *modelexperiments.GetInstanceResponse, -) error { +func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.GetInstanceResponse) error { if resp == nil { return fmt.Errorf("response instance is nil") } return p.OutputResult(outputFormat, resp.Instance, func() error { table := tables.NewTable() - table.SetHeader("ID", "NAME", "REGION", "STATUS") - table.AddRow( - resp.Instance.Id, - resp.Instance.Name, - utils.PtrString(resp.Instance.Region), - resp.Instance.State, - ) - return table.Display(p) + table.AddRow("ID", resp.Instance.Id) + table.AddSeparator() + table.AddRow("NAME", resp.Instance.Name) + table.AddSeparator() + table.AddRow("REGION", utils.PtrString(resp.Instance.Region)) + table.AddSeparator() + table.AddRow("STATUS", resp.Instance.State) + table.AddSeparator() + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil }) } diff --git a/internal/cmd/modelexperiments/instance/describe/describe_test.go b/internal/cmd/modelexperiments/instance/describe/describe_test.go index afdb7ebfa..f8e184005 100644 --- a/internal/cmd/modelexperiments/instance/describe/describe_test.go +++ b/internal/cmd/modelexperiments/instance/describe/describe_test.go @@ -15,41 +15,47 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} -var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") - -var testClient = &modelexperiments.APIClient{ - DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, -} +const testRegion = "eu01" -var testProjectId = uuid.NewString() -var testInstanceId = uuid.NewString() +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} + testProjectId = uuid.NewString() + testInstanceId = uuid.NewString() +) -func fixtureFlagValues( - mods ...func(*map[string]string), -) map[string]string { +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - globalflags.RegionFlag: "eu01", + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, } for _, mod := range mods { - mod(&flagValues) + mod(flagValues) } return flagValues } -func fixtureInputModel( - mods ...func(*inputModel), -) *inputModel { +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testInstanceId, + } + + for _, mod := range mods { + mod(argValues) + } + + return argValues +} + +func fixtureInputModel(mods ...func(*inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ ProjectId: testProjectId, - Region: "eu01", + Region: testRegion, Verbosity: globalflags.VerbosityDefault, }, InstanceId: testInstanceId, @@ -62,13 +68,11 @@ func fixtureInputModel( return model } -func fixtureRequest( - mods ...func(*modelexperiments.ApiGetInstanceRequest), -) modelexperiments.ApiGetInstanceRequest { +func fixtureRequest(mods ...func(*modelexperiments.ApiGetInstanceRequest)) modelexperiments.ApiGetInstanceRequest { request := testClient.DefaultAPI.GetInstance( testCtx, testProjectId, - "eu01", + testRegion, testInstanceId, ) @@ -89,16 +93,34 @@ func TestParseInput(t *testing.T) { }{ { description: "base", - argValues: []string{testInstanceId}, + 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: []string{testInstanceId}, - flagValues: fixtureFlagValues(func(flagValues *map[string]string) { - delete(*flagValues, projectIdFlag) + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) }), isValid: false, }, @@ -120,23 +142,32 @@ func TestParseInput(t *testing.T) { } func TestBuildDescribeInstanceRequest(t *testing.T) { - request := buildRequest( - testCtx, - fixtureInputModel(), - testClient, - ) - - expectedRequest := fixtureRequest() + tests := []struct { + description string + model *inputModel + expectedRequest modelexperiments.ApiGetInstanceRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } - diff := cmp.Diff( - request, - expectedRequest, - cmp.AllowUnexported(expectedRequest), - cmpopts.EquateComparable(testCtx), - ) + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) + diff := cmp.Diff( + request, + tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) } } diff --git a/internal/cmd/modelexperiments/instance/list/list.go b/internal/cmd/modelexperiments/instance/list/list.go index 5dd56fa9a..6e1c1df21 100644 --- a/internal/cmd/modelexperiments/instance/list/list.go +++ b/internal/cmd/modelexperiments/instance/list/list.go @@ -9,29 +9,47 @@ import ( "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/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/modelexperiments/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" ) +const limitFlag = "limit" + type inputModel struct { *globalflags.GlobalFlagModel + Limit *int64 } func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "list", - Short: "Lists AI Model Experiments instances", - Long: "Lists AI Model Experiments instances in a STACKIT project.", + Short: "Lists all AI Model Experiments instances", + Long: "Lists all AI Model Experiments instances in a STACKIT project.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( `List AI Model Experiments instances in a project`, - `$ stackit model-experiments instance list --region eu01`, + `$ stackit model-experiments instance list`, + ), + examples.NewExample( + `List AI Model Experiments instances in a project in region eu02`, + `$ stackit model-experiments instance list --region eu02`, + ), + examples.NewExample( + `List AI Model Experiments instances in JSON format`, + `$ stackit model-experiments instance list --output-format json`, + ), + examples.NewExample( + `List up to 10 AI Model Experiments instances`, + `$ stackit model-experiments instance list --limit 10`, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -54,26 +72,41 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return fmt.Errorf("list AI Model Experiments instances: %w", err) } - return outputResult(params.Printer, model.OutputFormat, resp) + applyLimit(resp, model.Limit) + + projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + return outputResult(params.Printer, model.OutputFormat, projectLabel, resp) }, } + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + return cmd } -func parseInput( - p *print.Printer, - cmd *cobra.Command, - _ []string, -) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { return nil, &cliErr.ProjectIdError{} } + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &cliErr.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + model := inputModel{ GlobalFlagModel: globalFlags, + Limit: limit, } p.DebugInputModel(model) @@ -81,41 +114,38 @@ func parseInput( return &model, nil } -func buildRequest( - ctx context.Context, - model *inputModel, - apiClient *modelexperiments.APIClient, -) modelexperiments.ApiListInstancesRequest { - return apiClient.DefaultAPI.ListInstances( - ctx, - model.ProjectId, - model.Region, - ) +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiListInstancesRequest { + return apiClient.DefaultAPI.ListInstances(ctx, model.ProjectId, model.Region) +} + +func applyLimit(resp *modelexperiments.ListInstancesResponse, limit *int64) { + if resp == nil || limit == nil || int64(len(resp.Instances)) <= *limit { + return + } + + resp.Instances = resp.Instances[:int(*limit)] } -func outputResult( - p *print.Printer, - outputFormat string, - resp *modelexperiments.ListInstancesResponse, -) error { +func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *modelexperiments.ListInstancesResponse) error { if resp == nil { return fmt.Errorf("response is nil") } return p.OutputResult(outputFormat, resp.Instances, func() error { if len(resp.Instances) == 0 { - p.Outputf("No instances found\n") + p.Outputf("No instances found for project %q\n", projectLabel) return nil } table := tables.NewTable() - table.SetHeader("ID", "NAME", "REGION", "STATUS") + table.SetHeader("ID", "NAME", "ARTIFACT STORAGE NAME", "REGION", "STATUS") for _, instance := range resp.Instances { table.AddRow( instance.Id, instance.Name, - *instance.Region, + instance.GetBucketName(), + utils.PtrString(instance.Region), instance.State, ) } diff --git a/internal/cmd/modelexperiments/instance/list/list_test.go b/internal/cmd/modelexperiments/instance/list/list_test.go index 6f40fa570..72e5c7c5d 100644 --- a/internal/cmd/modelexperiments/instance/list/list_test.go +++ b/internal/cmd/modelexperiments/instance/list/list_test.go @@ -2,6 +2,7 @@ package list import ( "context" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -13,42 +14,37 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} -var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") - -var testClient = &modelexperiments.APIClient{ - DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, -} +const testRegion = "eu01" -var testProjectId = uuid.NewString() +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} + testProjectId = uuid.NewString() +) -func fixtureFlagValues( - mods ...func(*map[string]string), -) map[string]string { +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - globalflags.RegionFlag: "eu01", + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, } for _, mod := range mods { - mod(&flagValues) + mod(flagValues) } return flagValues } -func fixtureInputModel( - mods ...func(*inputModel), -) *inputModel { +func fixtureInputModel(mods ...func(*inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ ProjectId: testProjectId, - Region: "eu01", + Region: testRegion, Verbosity: globalflags.VerbosityDefault, }, } @@ -60,13 +56,11 @@ func fixtureInputModel( return model } -func fixtureRequest( - mods ...func(*modelexperiments.ApiListInstancesRequest), -) modelexperiments.ApiListInstancesRequest { +func fixtureRequest(mods ...func(*modelexperiments.ApiListInstancesRequest)) modelexperiments.ApiListInstancesRequest { request := testClient.DefaultAPI.ListInstances( testCtx, testProjectId, - "eu01", + testRegion, ) for _, mod := range mods { @@ -90,10 +84,27 @@ func TestParseInput(t *testing.T) { isValid: true, expectedModel: fixtureInputModel(), }, + { + description: "limit", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "10" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Limit = utils.Ptr(int64(10)) + }), + }, { description: "project id missing", - flagValues: fixtureFlagValues(func(flagValues *map[string]string) { - delete(*flagValues, projectIdFlag) + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "limit must be greater than zero", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" }), isValid: false, }, @@ -135,11 +146,70 @@ func TestBuildListInstancesRequest(t *testing.T) { } } +func TestApplyLimit(t *testing.T) { + tests := []struct { + name string + instances []modelexperiments.Instance + limit *int64 + nilResp bool + wantCount int + }{ + { + name: "no limit", + instances: make([]modelexperiments.Instance, 3), + wantCount: 3, + }, + { + name: "limit below result count", + instances: make([]modelexperiments.Instance, 3), + limit: utils.Ptr(int64(2)), + wantCount: 2, + }, + { + name: "limit above result count", + instances: make([]modelexperiments.Instance, 2), + limit: utils.Ptr(int64(3)), + wantCount: 2, + }, + { + name: "nil response", + limit: utils.Ptr(int64(1)), + nilResp: true, + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp := &modelexperiments.ListInstancesResponse{Instances: tt.instances} + if tt.nilResp { + resp = nil + } + + applyLimit(resp, tt.limit) + + if resp == nil { + if !tt.nilResp { + t.Fatal("applyLimit() unexpectedly returned a nil response") + } + return + } + if tt.nilResp { + t.Fatal("applyLimit() changed a nil response") + } + if got := len(resp.Instances); got != tt.wantCount { + t.Errorf("applyLimit() count = %d, want %d", got, tt.wantCount) + } + }) + } +} + func TestOutputResult(t *testing.T) { tests := []struct { - name string - resp *modelexperiments.ListInstancesResponse - wantErr bool + name string + resp *modelexperiments.ListInstancesResponse + wantErr bool + wantOutput []string }{ { name: "nil response", @@ -148,20 +218,37 @@ func TestOutputResult(t *testing.T) { }, { name: "instances", + resp: &modelexperiments.ListInstancesResponse{ + Instances: []modelexperiments.Instance{ + { + Id: "instance-id", + Name: "instance-name", + BucketName: utils.Ptr("model-experiments-bucket"), + Region: utils.Ptr(testRegion), + State: "READY", + }, + }, + }, + wantErr: false, + wantOutput: []string{"ID", "NAME", "instance-id", "instance-name", "model-experiments-bucket", "READY"}, + }, + { + name: "empty instances", resp: &modelexperiments.ListInstancesResponse{ Instances: []modelexperiments.Instance{}, }, - wantErr: false, + wantErr: false, + wantOutput: []string{`No instances found for project "test-project"`}, }, } - params := testparams.NewTestParams() - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + params := testparams.NewTestParams() if err := outputResult( params.Printer, "", + "test-project", tt.resp, ); (err != nil) != tt.wantErr { t.Errorf( @@ -170,6 +257,12 @@ func TestOutputResult(t *testing.T) { tt.wantErr, ) } + + for _, expected := range tt.wantOutput { + if !strings.Contains(params.Out.String(), expected) { + t.Errorf("outputResult() output = %q, want it to contain %q", params.Out.String(), expected) + } + } }) } } diff --git a/internal/cmd/modelexperiments/instance/update/update.go b/internal/cmd/modelexperiments/instance/update/update.go index fbe08c0dc..692b68085 100644 --- a/internal/cmd/modelexperiments/instance/update/update.go +++ b/internal/cmd/modelexperiments/instance/update/update.go @@ -24,7 +24,7 @@ const ( nameFlag = "name" descriptionFlag = "description" - labelFlag = "label" + labelsFlag = "labels" retentionFlag = "deleted-experiment-retention" ) @@ -56,11 +56,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command { ), examples.NewExample( `Update labels on an AI Model Experiments instance`, - `$ stackit model-experiments instance update xxx --label env=prod`, + `$ stackit model-experiments instance update xxx --labels env=prod,team=ml`, ), examples.NewExample( `Update multiple fields of an AI Model Experiments instance`, - `$ stackit model-experiments instance update xxx --name my-new-name --description "team tracking server" --label env=prod --deleted-experiment-retention 30d`, + `$ stackit model-experiments instance update xxx --name my-new-name --description "team tracking server" --labels env=prod,team=ml --deleted-experiment-retention 30d`, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -95,24 +95,20 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().StringP(nameFlag, "n", "", "Instance name") cmd.Flags().String(descriptionFlag, "", "Instance description") - cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + cmd.Flags().StringToString(labelsFlag, nil, `Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml"`) cmd.Flags().String(retentionFlag, "", `Retention period for deleted experiments before permanent purge, e.g. "30d" (min 1d, max 90d)`) } -func parseInput( - p *print.Printer, - cmd *cobra.Command, - inputArgs []string, -) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { return nil, &cliErr.ProjectIdError{} } - labels, err := cmd.Flags().GetStringToString(labelFlag) + labels, err := cmd.Flags().GetStringToString(labelsFlag) if err != nil { - return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) + return nil, fmt.Errorf("parse %q flag: %w", labelsFlag, err) } var labelsPtr *map[string]*string @@ -151,17 +147,8 @@ func parseInput( return &model, nil } -func buildRequest( - ctx context.Context, - model *inputModel, - apiClient *modelexperiments.APIClient, -) modelexperiments.ApiPartialUpdateInstanceRequest { - req := apiClient.DefaultAPI.PartialUpdateInstance( - ctx, - model.ProjectId, - model.Region, - model.InstanceId, - ) +func buildRequest(ctx context.Context, model *inputModel, apiClient *modelexperiments.APIClient) modelexperiments.ApiPartialUpdateInstanceRequest { + req := apiClient.DefaultAPI.PartialUpdateInstance(ctx, model.ProjectId, model.Region, model.InstanceId) payload := modelexperiments.PartialUpdateInstancePayload{} @@ -184,11 +171,7 @@ func buildRequest( return req.PartialUpdateInstancePayload(payload) } -func outputResult( - p *print.Printer, - outputFormat string, - resp *modelexperiments.PartialUpdateInstanceResponse, -) error { +func outputResult(p *print.Printer, outputFormat string, resp *modelexperiments.PartialUpdateInstanceResponse) error { if resp == nil { return fmt.Errorf("response instance is nil") } diff --git a/internal/cmd/modelexperiments/instance/update/update_test.go b/internal/cmd/modelexperiments/instance/update/update_test.go index 13e1a1e9d..8ccd47b78 100644 --- a/internal/cmd/modelexperiments/instance/update/update_test.go +++ b/internal/cmd/modelexperiments/instance/update/update_test.go @@ -16,41 +16,35 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} -var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") - -var testClient = &modelexperiments.APIClient{ - DefaultAPI: modelexperiments.DefaultAPIServiceMock{}, -} +const testRegion = "eu01" -var testProjectId = uuid.NewString() -var testInstanceId = uuid.NewString() +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} + testProjectId = uuid.NewString() + testInstanceId = uuid.NewString() +) -func fixtureFlagValues( - mods ...func(*map[string]string), -) map[string]string { +func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - globalflags.RegionFlag: "eu01", + globalflags.ProjectIdFlag: testProjectId, + globalflags.RegionFlag: testRegion, } for _, mod := range mods { - mod(&flagValues) + mod(flagValues) } return flagValues } -func fixtureInputModel( - mods ...func(*inputModel), -) *inputModel { +func fixtureInputModel(mods ...func(*inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ ProjectId: testProjectId, - Region: "eu01", + Region: testRegion, Verbosity: globalflags.VerbosityDefault, }, InstanceId: testInstanceId, @@ -63,13 +57,11 @@ func fixtureInputModel( return model } -func fixtureRequest( - mods ...func(*modelexperiments.ApiPartialUpdateInstanceRequest), -) modelexperiments.ApiPartialUpdateInstanceRequest { +func fixtureRequest(mods ...func(*modelexperiments.ApiPartialUpdateInstanceRequest)) modelexperiments.ApiPartialUpdateInstanceRequest { request := testClient.DefaultAPI.PartialUpdateInstance( testCtx, testProjectId, - "eu01", + testRegion, testInstanceId, ) @@ -97,8 +89,8 @@ func TestParseInput(t *testing.T) { { description: "name", argValues: []string{testInstanceId}, - flagValues: fixtureFlagValues(func(flagValues *map[string]string) { - (*flagValues)[nameFlag] = "example" + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[nameFlag] = "example" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { @@ -108,19 +100,34 @@ func TestParseInput(t *testing.T) { { description: "description", argValues: []string{testInstanceId}, - flagValues: fixtureFlagValues(func(flagValues *map[string]string) { - (*flagValues)[descriptionFlag] = "team tracking server" + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[descriptionFlag] = "team tracking server" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { model.Description = utils.Ptr("team tracking server") }), }, + { + description: "labels", + argValues: []string{testInstanceId}, + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[labelsFlag] = "env=prod,team=ml" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + labels := map[string]*string{ + "env": utils.Ptr("prod"), + "team": utils.Ptr("ml"), + } + model.Labels = &labels + }), + }, { description: "retention", argValues: []string{testInstanceId}, - flagValues: fixtureFlagValues(func(flagValues *map[string]string) { - (*flagValues)[retentionFlag] = "30d" + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[retentionFlag] = "30d" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { @@ -136,9 +143,9 @@ func TestParseInput(t *testing.T) { { description: "project id missing", argValues: []string{testInstanceId}, - flagValues: fixtureFlagValues(func(flagValues *map[string]string) { - delete(*flagValues, projectIdFlag) - (*flagValues)[nameFlag] = "example" + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + flagValues[nameFlag] = "example" }), isValid: false, }, @@ -181,7 +188,7 @@ func TestBuildUpdateInstanceRequest(t *testing.T) { request := testClient.DefaultAPI.PartialUpdateInstance( testCtx, testProjectId, - "eu01", + testRegion, testInstanceId, ) @@ -194,6 +201,32 @@ func TestBuildUpdateInstanceRequest(t *testing.T) { return request }(), }, + { + description: "labels", + model: fixtureInputModel(func(model *inputModel) { + labels := map[string]*string{ + "env": utils.Ptr("prod"), + "team": utils.Ptr("ml"), + } + model.Labels = &labels + }), + expectedRequest: func() modelexperiments.ApiPartialUpdateInstanceRequest { + request := testClient.DefaultAPI.PartialUpdateInstance( + testCtx, + testProjectId, + testRegion, + testInstanceId, + ) + + labels := map[string]*string{ + "env": utils.Ptr("prod"), + "team": utils.Ptr("ml"), + } + return request.PartialUpdateInstancePayload( + modelexperiments.PartialUpdateInstancePayload{Labels: &labels}, + ) + }(), + }, { description: "retention", model: fixtureInputModel(func(model *inputModel) { @@ -203,7 +236,7 @@ func TestBuildUpdateInstanceRequest(t *testing.T) { request := testClient.DefaultAPI.PartialUpdateInstance( testCtx, testProjectId, - "eu01", + testRegion, testInstanceId, ) diff --git a/internal/cmd/modelexperiments/token/create/create.go b/internal/cmd/modelexperiments/token/create/create.go index 606b3b7ce..6d11bb15e 100644 --- a/internal/cmd/modelexperiments/token/create/create.go +++ b/internal/cmd/modelexperiments/token/create/create.go @@ -22,7 +22,7 @@ const ( instanceIDFlag = "instance-id" nameFlag = "name" descriptionFlag = "description" - labelFlag = "label" + labelsFlag = "labels" ttlDurationFlag = "ttl-duration" ) @@ -59,7 +59,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") cmd.Flags().String(nameFlag, "", "Token name") cmd.Flags().String(descriptionFlag, "", "Token description") - cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + cmd.Flags().StringToString(labelsFlag, nil, `Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml"`) cmd.Flags().String(ttlDurationFlag, "", "Token time to live duration") _ = flags.MarkFlagsRequired(cmd, instanceIDFlag, nameFlag) } @@ -69,9 +69,9 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, if globalFlags.ProjectId == "" { return nil, &cliErr.ProjectIdError{} } - labels, err := cmd.Flags().GetStringToString(labelFlag) + labels, err := cmd.Flags().GetStringToString(labelsFlag) if err != nil { - return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) + return nil, fmt.Errorf("parse %q flag: %w", labelsFlag, err) } var labelsPtr *map[string]string if len(labels) > 0 { diff --git a/internal/cmd/modelexperiments/token/create/create_test.go b/internal/cmd/modelexperiments/token/create/create_test.go index ad8fc2429..7cd8fea27 100644 --- a/internal/cmd/modelexperiments/token/create/create_test.go +++ b/internal/cmd/modelexperiments/token/create/create_test.go @@ -16,8 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) -var projectIDFlag = globalflags.ProjectIdFlag - type testContextKey struct{} var testContext = context.WithValue(context.Background(), testContextKey{}, "token-create") @@ -26,7 +24,7 @@ var testProjectID = uuid.NewString() var testInstanceID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01", nameFlag: "example"} + values := map[string]string{globalflags.ProjectIdFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01", nameFlag: "example"} for _, mod := range mods { mod(values) } @@ -59,15 +57,15 @@ func TestParseInput(t *testing.T) { {name: "base", flags: fixtureFlagValues(), valid: true, want: fixtureInputModel()}, {name: "optional fields", flags: fixtureFlagValues(func(values map[string]string) { values[descriptionFlag] = "service token" - values[labelFlag] = "env=prod" + values[labelsFlag] = "env=prod,team=ml" values[ttlDurationFlag] = "5h" }), valid: true, want: fixtureInputModel(func(model *inputModel) { model.Description = utils.Ptr("service token") - model.Labels = &map[string]string{"env": "prod"} + model.Labels = &map[string]string{"env": "prod", "team": "ml"} model.TTLDuration = utils.Ptr("5h") })}, {name: "name missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, nameFlag) }), valid: false}, - {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}, + {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, globalflags.ProjectIdFlag) }), valid: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testutils.TestParseInput(t, NewCmd, parseInput, tt.want, nil, tt.flags, tt.valid) }) @@ -77,11 +75,11 @@ func TestParseInput(t *testing.T) { func TestBuildRequest(t *testing.T) { model := fixtureInputModel(func(model *inputModel) { model.Description = utils.Ptr("service token") - model.Labels = &map[string]string{"env": "prod"} + model.Labels = &map[string]string{"env": "prod", "team": "ml"} model.TTLDuration = utils.Ptr("5h") }) want := fixtureRequest(func(request *modelexperiments.ApiCreateInstanceTokenRequest) { - *request = request.CreateInstanceTokenPayload(modelexperiments.CreateInstanceTokenPayload{Name: "example", Description: utils.Ptr("service token"), Labels: &map[string]string{"env": "prod"}, TtlDuration: utils.Ptr("5h")}) + *request = request.CreateInstanceTokenPayload(modelexperiments.CreateInstanceTokenPayload{Name: "example", Description: utils.Ptr("service token"), Labels: &map[string]string{"env": "prod", "team": "ml"}, TtlDuration: utils.Ptr("5h")}) }) got := buildRequest(testContext, model, testClient) if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { diff --git a/internal/cmd/modelexperiments/token/delete/delete_test.go b/internal/cmd/modelexperiments/token/delete/delete_test.go index 5909815ac..d0b12acda 100644 --- a/internal/cmd/modelexperiments/token/delete/delete_test.go +++ b/internal/cmd/modelexperiments/token/delete/delete_test.go @@ -14,8 +14,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" ) -var projectIDFlag = globalflags.ProjectIdFlag - type testContextKey struct{} var testContext = context.WithValue(context.Background(), testContextKey{}, "token-delete") @@ -25,7 +23,7 @@ var testInstanceID = uuid.NewString() var testTokenID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} + values := map[string]string{globalflags.ProjectIdFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } @@ -43,7 +41,7 @@ func TestParseInput(t *testing.T) { name string flags map[string]string valid bool - }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}} + }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, globalflags.ProjectIdFlag) }), valid: false}} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var want *inputModel diff --git a/internal/cmd/modelexperiments/token/describe/describe_test.go b/internal/cmd/modelexperiments/token/describe/describe_test.go index f35b26485..08189dc89 100644 --- a/internal/cmd/modelexperiments/token/describe/describe_test.go +++ b/internal/cmd/modelexperiments/token/describe/describe_test.go @@ -15,18 +15,18 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" ) -var projectIDFlag = globalflags.ProjectIdFlag - type testContextKey struct{} -var testContext = context.WithValue(context.Background(), testContextKey{}, "token-get") -var testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} -var testProjectID = uuid.NewString() -var testInstanceID = uuid.NewString() -var testTokenID = uuid.NewString() +var ( + testContext = context.WithValue(context.Background(), testContextKey{}, "token-get") + testClient = &modelexperiments.APIClient{DefaultAPI: modelexperiments.DefaultAPIServiceMock{}} + testProjectID = uuid.NewString() + testInstanceID = uuid.NewString() + testTokenID = uuid.NewString() +) func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} + values := map[string]string{globalflags.ProjectIdFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } @@ -44,7 +44,7 @@ func TestParseInput(t *testing.T) { name string flags map[string]string valid bool - }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}} + }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, globalflags.ProjectIdFlag) }), valid: false}} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var want *inputModel diff --git a/internal/cmd/modelexperiments/token/list/list_test.go b/internal/cmd/modelexperiments/token/list/list_test.go index 9d7218969..64abbbc4a 100644 --- a/internal/cmd/modelexperiments/token/list/list_test.go +++ b/internal/cmd/modelexperiments/token/list/list_test.go @@ -15,8 +15,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" ) -var projectIDFlag = globalflags.ProjectIdFlag - type testContextKey struct{} var testContext = context.WithValue(context.Background(), testContextKey{}, "token-list") @@ -25,7 +23,7 @@ var testProjectID = uuid.NewString() var testInstanceID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} + values := map[string]string{globalflags.ProjectIdFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } @@ -43,7 +41,7 @@ func TestParseInput(t *testing.T) { name string flags map[string]string valid bool - }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}} + }{{name: "base", flags: fixtureFlagValues(), valid: true}, {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, globalflags.ProjectIdFlag) }), valid: false}} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var want *inputModel diff --git a/internal/cmd/modelexperiments/token/token.go b/internal/cmd/modelexperiments/token/token.go index cd3868395..8f056590a 100644 --- a/internal/cmd/modelexperiments/token/token.go +++ b/internal/cmd/modelexperiments/token/token.go @@ -13,9 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) -func NewCmd( - params *types.CmdParams, -) *cobra.Command { +func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "token", Short: "Provides functionality for AI Model Experiments instance tokens", diff --git a/internal/cmd/modelexperiments/token/update/update.go b/internal/cmd/modelexperiments/token/update/update.go index aa8f88ba5..5509689f6 100644 --- a/internal/cmd/modelexperiments/token/update/update.go +++ b/internal/cmd/modelexperiments/token/update/update.go @@ -24,7 +24,7 @@ const ( instanceIDFlag = "instance-id" nameFlag = "name" descriptionFlag = "description" - labelFlag = "label" + labelsFlag = "labels" ) type inputModel struct { @@ -64,7 +64,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIDFlag, "ID of the instance") cmd.Flags().String(nameFlag, "", "Token name") cmd.Flags().String(descriptionFlag, "", "Token description") - cmd.Flags().StringToString(labelFlag, nil, `Labels as key-value pairs, e.g. "--label env=prod"`) + cmd.Flags().StringToString(labelsFlag, nil, `Labels as comma-separated key-value pairs, e.g. "--labels env=prod,team=ml"`) _ = flags.MarkFlagsRequired(cmd, instanceIDFlag) } @@ -73,9 +73,9 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu if globalFlags.ProjectId == "" { return nil, &cliErr.ProjectIdError{} } - labels, err := cmd.Flags().GetStringToString(labelFlag) + labels, err := cmd.Flags().GetStringToString(labelsFlag) if err != nil { - return nil, fmt.Errorf("parse %q flag: %w", labelFlag, err) + return nil, fmt.Errorf("parse %q flag: %w", labelsFlag, err) } var labelsPtr *map[string]string if len(labels) > 0 { diff --git a/internal/cmd/modelexperiments/token/update/update_test.go b/internal/cmd/modelexperiments/token/update/update_test.go index e332873ab..5ca9be558 100644 --- a/internal/cmd/modelexperiments/token/update/update_test.go +++ b/internal/cmd/modelexperiments/token/update/update_test.go @@ -16,8 +16,6 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) -var projectIDFlag = globalflags.ProjectIdFlag - type testContextKey struct{} var testContext = context.WithValue(context.Background(), testContextKey{}, "token-patch") @@ -27,7 +25,7 @@ var testInstanceID = uuid.NewString() var testTokenID = uuid.NewString() func fixtureFlagValues(mods ...func(map[string]string)) map[string]string { - values := map[string]string{projectIDFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} + values := map[string]string{globalflags.ProjectIdFlag: testProjectID, instanceIDFlag: testInstanceID, globalflags.RegionFlag: "eu01"} for _, mod := range mods { mod(values) } @@ -55,13 +53,13 @@ func TestParseInput(t *testing.T) { {name: "optional fields", flags: fixtureFlagValues(func(values map[string]string) { values[nameFlag] = "updated" values[descriptionFlag] = "updated token" - values[labelFlag] = "env=prod" + values[labelsFlag] = "env=prod,team=ml" }), valid: true, want: fixtureInputModel(func(model *inputModel) { model.Name = utils.Ptr("updated") model.Description = utils.Ptr("updated token") - model.Labels = &map[string]string{"env": "prod"} + model.Labels = &map[string]string{"env": "prod", "team": "ml"} })}, - {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, projectIDFlag) }), valid: false}, + {name: "project missing", flags: fixtureFlagValues(func(values map[string]string) { delete(values, globalflags.ProjectIdFlag) }), valid: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -74,10 +72,10 @@ func TestBuildUpdateRequest(t *testing.T) { model := fixtureInputModel(func(model *inputModel) { model.Name = utils.Ptr("updated") model.Description = utils.Ptr("updated token") - model.Labels = &map[string]string{"env": "prod"} + model.Labels = &map[string]string{"env": "prod", "team": "ml"} }) got := buildRequest(testContext, model, testClient) - labels := map[string]*string{"env": utils.Ptr("prod")} + labels := map[string]*string{"env": utils.Ptr("prod"), "team": utils.Ptr("ml")} want := fixtureRequest(modelexperiments.PartialUpdateInstanceTokenPayload{Name: utils.Ptr("updated"), Description: utils.Ptr("updated token"), Labels: &labels}) if diff := cmp.Diff(got, want, cmp.AllowUnexported(want), cmpopts.EquateComparable(testContext)); diff != "" { t.Fatalf("request mismatch (-got +want):\n%s", diff) From 37fd73bddca028df1c80b483309543c0a39abd8f Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Fri, 30 Oct 2026 01:09:56 +0100 Subject: [PATCH 12/13] fix: linting issues, STACKITMLO-1718 --- internal/cmd/config/unset/unset_test.go | 6 +++--- internal/pkg/config/config.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cmd/config/unset/unset_test.go b/internal/cmd/config/unset/unset_test.go index 60d8b6ff1..1c5b5966b 100644 --- a/internal/cmd/config/unset/unset_test.go +++ b/internal/cmd/config/unset/unset_test.go @@ -30,7 +30,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]bool)) map[string]bool loadBalancerCustomEndpointFlag: true, logMeCustomEndpointFlag: true, mariaDBCustomEndpointFlag: true, - modelExperimentsCustomEndpointFlag: true, + modelExperimentsCustomEndpointFlag: true, objectStorageCustomEndpointFlag: true, observabilityCustomEndpointFlag: true, openSearchCustomEndpointFlag: true, @@ -82,7 +82,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { LoadBalancerCustomEndpoint: true, LogMeCustomEndpoint: true, MariaDBCustomEndpoint: true, - ModelExperimentsCustomEndpoint: true, + ModelExperimentsCustomEndpoint: true, ObjectStorageCustomEndpoint: true, ObservabilityCustomEndpoint: true, OpenSearchCustomEndpoint: true, @@ -314,7 +314,7 @@ func TestParseInput(t *testing.T) { isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { model.ModelExperimentsCustomEndpoint = false - }), + }), }, { description: "scf custom endpoint empty", diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 50810440a..d4d77269a 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -32,7 +32,7 @@ const ( LoadBalancerCustomEndpointKey = "load_balancer_custom_endpoint" LogMeCustomEndpointKey = "logme_custom_endpoint" MariaDBCustomEndpointKey = "mariadb_custom_endpoint" - ModelExperimentsCustomEndpointKey = "modelexperiments_custom_endpoint" + ModelExperimentsCustomEndpointKey = "modelexperiments_custom_endpoint" MongoDBFlexCustomEndpointKey = "mongodbflex_custom_endpoint" ObjectStorageCustomEndpointKey = "object_storage_custom_endpoint" ObservabilityCustomEndpointKey = "observability_custom_endpoint" From 5486db2cc2cb8a0e3caa43c42c67c3600ad2d8c9 Mon Sep 17 00:00:00 2001 From: Syeda Haya Fatima Date: Fri, 30 Oct 2026 09:36:36 +0100 Subject: [PATCH 13/13] fix: updated docs, STACKITMLO-1718 --- docs/stackit_model-experiments_instance_describe.md | 1 + docs/stackit_model-experiments_instance_update.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/stackit_model-experiments_instance_describe.md b/docs/stackit_model-experiments_instance_describe.md index 98a75ce69..c70200822 100644 --- a/docs/stackit_model-experiments_instance_describe.md +++ b/docs/stackit_model-experiments_instance_describe.md @@ -40,3 +40,4 @@ stackit model-experiments instance describe INSTANCE_ID [flags] ### SEE ALSO * [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances + diff --git a/docs/stackit_model-experiments_instance_update.md b/docs/stackit_model-experiments_instance_update.md index 350d463bb..8ad59a051 100644 --- a/docs/stackit_model-experiments_instance_update.md +++ b/docs/stackit_model-experiments_instance_update.md @@ -50,3 +50,4 @@ stackit model-experiments instance update INSTANCE_ID [flags] ### SEE ALSO * [stackit model-experiments instance](./stackit_model-experiments_instance.md) - Provides functionality for AI Model Experiments instances +