From 67d5569ab026edb5fb6071234699d073609b1c3b Mon Sep 17 00:00:00 2001 From: Pere Manent Date: Mon, 5 May 2025 13:37:11 +0200 Subject: [PATCH 1/5] Add stackit git commands: list, describe, create and delete. * Add stackit git list instances command * Add stackit git list instances command * Git. Move list package and add describe command * Stackit Git create instance command * Add delete command to the stackit git cli --- go.mod | 1 + go.sum | 2 + internal/cmd/git/create/create.go | 165 +++++++++++ internal/cmd/git/create/create_test.go | 258 ++++++++++++++++++ internal/cmd/git/delete/delete.go | 130 +++++++++ internal/cmd/git/delete/delete_test.go | 183 +++++++++++++ internal/cmd/git/describe/describe.go | 135 +++++++++ internal/cmd/git/describe/describe_test.go | 227 +++++++++++++++ internal/cmd/git/git.go | 34 +++ internal/cmd/git/list/list.go | 139 ++++++++++ internal/cmd/git/list/list_test.go | 208 ++++++++++++++ internal/cmd/root.go | 2 + internal/pkg/config/config.go | 3 + internal/pkg/services/git/client/client.go | 45 +++ internal/pkg/services/git/utils/utils.go | 23 ++ internal/pkg/services/git/utils/utils_test.go | 66 +++++ 16 files changed, 1621 insertions(+) create mode 100644 internal/cmd/git/create/create.go create mode 100644 internal/cmd/git/create/create_test.go create mode 100644 internal/cmd/git/delete/delete.go create mode 100644 internal/cmd/git/delete/delete_test.go create mode 100644 internal/cmd/git/describe/describe.go create mode 100644 internal/cmd/git/describe/describe_test.go create mode 100644 internal/cmd/git/git.go create mode 100644 internal/cmd/git/list/list.go create mode 100644 internal/cmd/git/list/list_test.go create mode 100644 internal/pkg/services/git/client/client.go create mode 100644 internal/pkg/services/git/utils/utils.go create mode 100644 internal/pkg/services/git/utils/utils_test.go diff --git a/go.mod b/go.mod index 4751511be..8e625319a 100644 --- a/go.mod +++ b/go.mod @@ -77,6 +77,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect + github.com/stackitcloud/stackit-sdk-go/services/git v0.3.1 github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.0.2 github.com/stackitcloud/stackit-sdk-go/services/logme v0.22.1 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.22.1 diff --git a/go.sum b/go.sum index 8ae569ae6..56f248214 100644 --- a/go.sum +++ b/go.sum @@ -119,6 +119,8 @@ github.com/stackitcloud/stackit-sdk-go/services/authorization v0.6.2 h1:erpN0BM7 github.com/stackitcloud/stackit-sdk-go/services/authorization v0.6.2/go.mod h1:dJ19ZwFjp2bfC5ZobXV3vUdSpE3quUw3GuoFSKLpHIo= github.com/stackitcloud/stackit-sdk-go/services/dns v0.13.2 h1:6rb3EM0yXuMIBd1U6WsJoMzEiVaHC3WQFWFvT23OE4Y= github.com/stackitcloud/stackit-sdk-go/services/dns v0.13.2/go.mod h1:PMHoavoIaRZpkI9BA0nsnRjGoHASVSBon45XB3QyhMA= +github.com/stackitcloud/stackit-sdk-go/services/git v0.3.1 h1:1QYUya5bstfd/XdHtQNQU2kHDp1Ikz/e2Zc9o3VSG48= +github.com/stackitcloud/stackit-sdk-go/services/git v0.3.1/go.mod h1:XhXHJpOVC9Rpwyf1G+EpMbprBafH9aZb8vWBdR+z0WM= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.22.1 h1:JXcLcbVesTtwVVb+jJjU3o0FmSpXBRnOw6PVETaeK+E= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.22.1/go.mod h1:QNH50Pq0Hu21lLDOwa02PIjRjTl0LfEdHoz5snGQRn8= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.0.2 h1:5rVt3n7kDJvJQxFCtxfx8uZI9PGkvJY9fVJ4yar10Uc= diff --git a/internal/cmd/git/create/create.go b/internal/cmd/git/create/create.go new file mode 100644 index 000000000..10a2eef14 --- /dev/null +++ b/internal/cmd/git/create/create.go @@ -0,0 +1,165 @@ +package create + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/goccy/go-yaml" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-sdk-go/services/git/wait" +) + +const ( + nameFlag = "name" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Id *string + Name string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates stackit git instance", + Long: "Create an stackit git instance by name.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create an instance with name 'my-new-instance'`, + `$ stackit git create --name my-new-instance`, + ), + ), + RunE: func(cmd *cobra.Command, _ []string) (err error) { + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to create the instance %q?", model.Name) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Call API + request := buildRequest(ctx, model, apiClient) + + result, err := request.Execute() + if err != nil { + return fmt.Errorf("create stackit git instance: %w", err) + } + model.Id = result.Id + + // Wait for async operation, if async mode not enabled + if !model.Async { + s := spinner.New(p) + s.Start("Creating stackit git instance") + _, err = wait.CreateGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, *model.Id).WaitWithContext(ctx) + if err != nil { + return fmt.Errorf("wait for stackit git Instance creation: %w", err) + } + s.Stop() + } + + return outputResult(p, model, result) + }, + } + + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(nameFlag, "", "The name of the instance.") + if err := flags.MarkFlagsRequired(cmd, nameFlag); err != nil { + cobra.CheckErr(err) + } +} + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + name := flags.FlagToStringValue(p, cmd, nameFlag) + + model := inputModel{ + GlobalFlagModel: globalFlags, + Name: name, + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *git.APIClient) git.ApiCreateInstanceRequest { + return apiClient.CreateInstance(ctx, model.ProjectId).CreateInstancePayload(createPayload(model)) +} + +func createPayload(model *inputModel) git.CreateInstancePayload { + return git.CreateInstancePayload{ + Name: &model.Name, + } +} + +func outputResult(p *print.Printer, model *inputModel, resp *git.Instance) error { + if model == nil { + return fmt.Errorf("input model is nil") + } + var outputFormat string + if model.GlobalFlagModel != nil { + outputFormat = model.OutputFormat + } + switch outputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return fmt.Errorf("marshal instance: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) + if err != nil { + return fmt.Errorf("marshal iminstanceage: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + p.Outputf("Created instance %q with id %s\n", model.Name, utils.PtrString(model.Id)) + return nil + } +} diff --git a/internal/cmd/git/create/create_test.go b/internal/cmd/git/create/create_test.go new file mode 100644 index 000000000..371b9c237 --- /dev/null +++ b/internal/cmd/git/create/create_test.go @@ -0,0 +1,258 @@ +package create + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &git.APIClient{} + testProjectId = uuid.NewString() + + testName = "test-instance" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + + nameFlag: testName, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, + Name: testName, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureCreatePayload(mods ...func(payload *git.CreateInstancePayload)) (payload git.CreateInstancePayload) { + payload = git.CreateInstancePayload{ + Name: &testName, + } + for _, mod := range mods { + mod(&payload) + } + return payload +} + +func fixtureRequest(mods ...func(request *git.ApiCreateInstanceRequest)) git.ApiCreateInstanceRequest { + request := testClient.CreateInstance(testCtx, testProjectId) + + request = request.CreateInstancePayload(fixtureCreatePayload()) + + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, nameFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + p := print.NewPrinter() + cmd := NewCmd(p) + if err := globalflags.Configure(cmd.Flags()); err != nil { + t.Errorf("cannot configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + if err := cmd.ValidateFlagGroups(); err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flag groups: %v", err) + } + + if err := cmd.ValidateRequiredFlags(); err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(p, cmd) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest git.ApiCreateInstanceRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "name flag", + model: fixtureInputModel(func(model *inputModel) { + model.Name = "new-name" + }), + expectedRequest: fixtureRequest(func(request *git.ApiCreateInstanceRequest) { + *request = request.CreateInstancePayload(fixtureCreatePayload(func(payload *git.CreateInstancePayload) { + payload.Name = utils.Ptr("new-name") + })) + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + cmp.AllowUnexported(git.NullableString{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + resp *git.Instance + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "nil", + args: args{ + model: nil, + resp: nil, + }, + wantErr: true, + }, + { + name: "empty input", + args: args{ + model: &inputModel{}, + resp: &git.Instance{}, + }, + wantErr: false, + }, + { + name: "output json", + args: args{ + model: &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + OutputFormat: print.JSONOutputFormat, + }, + }, + resp: nil, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(p) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.resp); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/git/delete/delete.go b/internal/cmd/git/delete/delete.go new file mode 100644 index 000000000..c31638160 --- /dev/null +++ b/internal/cmd/git/delete/delete.go @@ -0,0 +1,130 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/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/git/client" + gitUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/git" + "github.com/stackitcloud/stackit-sdk-go/services/git/wait" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceId string +} + +const instanceIdArg = "INSTANCE_ID" + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", instanceIdArg), + Short: "Deletes STACKIT Git instance", + Long: "Deletes an STACKIT Git instance by its internal ID.", + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample(`Delete an instance with ID "xxx"`, `$ stackit git delete xxx`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + projectName, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectName = model.ProjectId + } + + instanceName, err := gitUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId) + if err != nil { + p.Debug(print.ErrorLevel, "get stackit git intance name: %v", err) + instanceName = model.InstanceId + } else if instanceName == "" { + instanceName = model.InstanceId + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete the stackit git instance %q for %q?", instanceName, projectName) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Call API + request := buildRequest(ctx, model, apiClient) + + err = request.Execute() + if err != nil { + return fmt.Errorf("delete instance: %w", err) + } + + // Wait for async operation, if async mode not enabled + if !model.Async { + s := spinner.New(p) + s.Start("Deleting stackit git instance") + _, err = wait.DeleteGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + if err != nil { + return fmt.Errorf("wait for stackit git instance deletion: %w", err) + } + s.Stop() + } + + operationState := "Deleted" + if model.Async { + operationState = "Triggered deletion of" + } + p.Info("%s stackit git instance %s \n", operationState, model.InstanceId) + + return nil + }, + } + + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: cliArgs[0], + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *git.APIClient) git.ApiDeleteInstanceRequest { + return apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId) +} diff --git a/internal/cmd/git/delete/delete_test.go b/internal/cmd/git/delete/delete_test.go new file mode 100644 index 000000000..0f4bedfb7 --- /dev/null +++ b/internal/cmd/git/delete/delete_test.go @@ -0,0 +1,183 @@ +package delete + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &git.APIClient{} + testProjectId = uuid.NewString() + testInstanceId = uuid.NewString() +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, + InstanceId: testInstanceId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *git.ApiDeleteInstanceRequest)) git.ApiDeleteInstanceRequest { + request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + args []string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + args: []string{testInstanceId}, + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "no arguments", + flagValues: fixtureFlagValues(), + args: nil, + isValid: false, + }, + { + description: "multiple arguments", + flagValues: fixtureFlagValues(), + args: []string{"foo", "bar"}, + isValid: false, + }, + { + description: "invalid instance id", + flagValues: fixtureFlagValues(), + args: []string{"foo"}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + p := print.NewPrinter() + cmd := NewCmd(p) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + cmd.SetArgs(tt.args) + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + if err := cmd.ValidateArgs(tt.args); err != nil { + if !tt.isValid { + return + } + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(p, cmd, tt.args) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest git.ApiDeleteInstanceRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/git/describe/describe.go b/internal/cmd/git/describe/describe.go new file mode 100644 index 000000000..a5fdc8054 --- /dev/null +++ b/internal/cmd/git/describe/describe.go @@ -0,0 +1,135 @@ +package describe + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/goccy/go-yaml" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/git/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceId string +} + +const instanceIdArg = "INSTANCE_ID" + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("describe %s", instanceIdArg), + Short: "Describes STACKIT Git instance", + Long: "Describes an STACKIT Git instance by its internal ID.", + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample(`Describe instance "xxx"`, `$ stackit git describe xxx`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + // Call API + request := buildRequest(ctx, model, apiClient) + + instance, err := request.Execute() + if err != nil { + return fmt.Errorf("get instance: %w", err) + } + + if err := outputResult(p, model.OutputFormat, instance); err != nil { + return err + } + + return nil + }, + } + + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: cliArgs[0], + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *git.APIClient) git.ApiGetInstanceRequest { + return apiClient.GetInstance(ctx, model.ProjectId, model.InstanceId) +} + +func outputResult(p *print.Printer, outputFormat string, resp *git.Instance) error { + if resp == nil { + return fmt.Errorf("instance not found") + } + switch outputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return fmt.Errorf("marshal instance: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) + if err != nil { + return fmt.Errorf("marshal instance: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + table := tables.NewTable() + table.SetHeader("ID", "NAME", "URL", "VERSION", "STATE", "CREATED") + table.AddRow( + utils.PtrString(resp.Id), + utils.PtrString(resp.Name), + utils.PtrString(resp.Url), + utils.PtrString(resp.Version), + utils.PtrString(resp.State), + utils.PtrString(resp.Created), + ) + + if err := table.Display(p); err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + } +} diff --git a/internal/cmd/git/describe/describe_test.go b/internal/cmd/git/describe/describe_test.go new file mode 100644 index 000000000..509df0a73 --- /dev/null +++ b/internal/cmd/git/describe/describe_test.go @@ -0,0 +1,227 @@ +package describe + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &git.APIClient{} + testProjectId = uuid.NewString() + testInstanceId = []string{uuid.NewString()} +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, + InstanceId: testInstanceId[0], + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *git.ApiGetInstanceRequest)) git.ApiGetInstanceRequest { + request := testClient.GetInstance(testCtx, testProjectId, testInstanceId[0]) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + args []string + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + expectedModel: fixtureInputModel(), + args: testInstanceId, + isValid: true, + }, + { + description: "no values", + flagValues: map[string]string{}, + args: testInstanceId, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + args: testInstanceId, + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + args: testInstanceId, + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + args: testInstanceId, + isValid: false, + }, + { + description: "no instance id passed", + flagValues: fixtureFlagValues(), + args: nil, + isValid: false, + }, + { + description: "multiple instance ids passed", + flagValues: fixtureFlagValues(), + args: []string{uuid.NewString(), uuid.NewString()}, + isValid: false, + }, + { + description: "invalid instance id passed", + flagValues: fixtureFlagValues(), + args: []string{"foobar"}, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + p := print.NewPrinter() + cmd := NewCmd(p) + if err := globalflags.Configure(cmd.Flags()); err != nil { + t.Errorf("cannot configure global flags: %v", err) + } + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + if err := cmd.ValidateRequiredFlags(); err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + if err := cmd.ValidateArgs(tt.args); err != nil { + if !tt.isValid { + return + } + } + + model, err := parseInput(p, cmd, tt.args) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest git.ApiGetInstanceRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + resp *git.Instance + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{ + resp: &git.Instance{}, + }, + wantErr: false, + }, + { + name: "nil", + args: args{}, + wantErr: true, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(p) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.resp); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/git/git.go b/internal/cmd/git/git.go new file mode 100644 index 000000000..e18f8aad2 --- /dev/null +++ b/internal/cmd/git/git.go @@ -0,0 +1,34 @@ +package git + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/git/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/git/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/git/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/git/list" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "git", + Short: "Provides functionality for STACKIT Git", + Long: "Provides functionality for STACKIT Git.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, p) + return cmd +} + +func addSubcommands(cmd *cobra.Command, p *print.Printer) { + cmd.AddCommand( + list.NewCmd(p), + describe.NewCmd(p), + create.NewCmd(p), + delete.NewCmd(p), + ) +} diff --git a/internal/cmd/git/list/list.go b/internal/cmd/git/list/list.go new file mode 100644 index 000000000..1b8f79795 --- /dev/null +++ b/internal/cmd/git/list/list.go @@ -0,0 +1,139 @@ +package list + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/goccy/go-yaml" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/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/git/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all instances of STACKIT Git.", + Long: "Lists all instances of STACKIT Git current the current project.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List all STACKIT Git instances`, + "$ stackit git instance list"), + ), + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get STACKIT Git instances: %w", err) + } + instances := *resp.Instances + if len(instances) == 0 { + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + p.Info("No instances found for project %q\n", projectLabel) + return nil + } + + return outputResult(p, model.OutputFormat, instances) + }, + } + + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *git.APIClient) git.ApiListInstancesRequest { + return apiClient.ListInstances(ctx, model.ProjectId) +} + +func outputResult(p *print.Printer, outputFormat string, instances []git.Instance) error { + switch outputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(instances, "", " ") + if err != nil { + return fmt.Errorf("marshal Observability instance list: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(instances, yaml.IndentSequence(true), yaml.UseJSONMarshaler()) + if err != nil { + return fmt.Errorf("marshal Observability instance list: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + table := tables.NewTable() + table.SetHeader("ID", "NAME", "URL", "VERSION", "STATE", "CREATED") + for i := range instances { + instance := (instances)[i] + table.AddRow( + utils.PtrString(instance.Id), + utils.PtrString(instance.Name), + utils.PtrString(instance.Url), + utils.PtrString(instance.Version), + utils.PtrString(instance.State), + utils.PtrString(instance.Created), + ) + } + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + } +} diff --git a/internal/cmd/git/list/list_test.go b/internal/cmd/git/list/list_test.go new file mode 100644 index 000000000..21e593e59 --- /dev/null +++ b/internal/cmd/git/list/list_test.go @@ -0,0 +1,208 @@ +package list + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &git.APIClient{} +var testProjectId = uuid.NewString() + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *git.ApiListInstancesRequest)) git.ApiListInstancesRequest { + request := testClient.ListInstances(testCtx, testProjectId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + p := print.NewPrinter() + cmd := NewCmd(p) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(p, cmd) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest git.ApiListInstancesRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + instances []git.Instance + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "set empty instances slice", + args: args{ + instances: []git.Instance{}, + }, + wantErr: false, + }, + { + name: "set empty instances in instances slice", + args: args{ + instances: []git.Instance{{}}, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(p) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.instances); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 736c944f5..34565e7da 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -12,6 +12,7 @@ import ( configCmd "github.com/stackitcloud/stackit-cli/internal/cmd/config" "github.com/stackitcloud/stackit-cli/internal/cmd/curl" "github.com/stackitcloud/stackit-cli/internal/cmd/dns" + "github.com/stackitcloud/stackit-cli/internal/cmd/git" "github.com/stackitcloud/stackit-cli/internal/cmd/image" keypair "github.com/stackitcloud/stackit-cli/internal/cmd/key-pair" loadbalancer "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer" @@ -186,6 +187,7 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(image.NewCmd(p)) cmd.AddCommand(quota.NewCmd(p)) cmd.AddCommand(affinityGroups.NewCmd(p)) + cmd.AddCommand(git.NewCmd(p)) } // traverseCommands calls f for c and all of its children. diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index da282c335..957d7c475 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -45,6 +45,7 @@ const ( SQLServerFlexCustomEndpointKey = "sqlserverflex_custom_endpoint" IaaSCustomEndpointKey = "iaas_custom_endpoint" TokenCustomEndpointKey = "token_custom_endpoint" + GitCustomEndpointKey = "git_custom_endpoint" ProjectNameKey = "project_name" DefaultProfileName = "default" @@ -103,6 +104,7 @@ var ConfigKeys = []string{ SQLServerFlexCustomEndpointKey, IaaSCustomEndpointKey, TokenCustomEndpointKey, + GitCustomEndpointKey, } var defaultConfigFolderPath string @@ -187,6 +189,7 @@ func setConfigDefaults() { viper.SetDefault(SQLServerFlexCustomEndpointKey, "") viper.SetDefault(IaaSCustomEndpointKey, "") viper.SetDefault(TokenCustomEndpointKey, "") + viper.SetDefault(GitCustomEndpointKey, "") } func getConfigFilePath(configFolder string) string { diff --git a/internal/pkg/services/git/client/client.go b/internal/pkg/services/git/client/client.go new file mode 100644 index 000000000..dde3b7be6 --- /dev/null +++ b/internal/pkg/services/git/client/client.go @@ -0,0 +1,45 @@ +package client + +import ( + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + + "github.com/spf13/viper" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +func ConfigureClient(p *print.Printer) (*git.APIClient, error) { + var err error + var apiClient *git.APIClient + var cfgOptions []sdkConfig.ConfigurationOption + + authCfgOption, err := auth.AuthenticationConfig(p, auth.AuthorizeUser) + if err != nil { + p.Debug(print.ErrorLevel, "configure authentication: %v", err) + return nil, &errors.AuthError{} + } + cfgOptions = append(cfgOptions, authCfgOption) + + customEndpoint := viper.GetString(config.GitCustomEndpointKey) + + if customEndpoint != "" { + cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) + } + + if p.IsVerbosityDebug() { + cfgOptions = append(cfgOptions, + sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), + ) + } + + apiClient, err = git.NewAPIClient(cfgOptions...) + if err != nil { + p.Debug(print.ErrorLevel, "create new API client: %v", err) + return nil, &errors.AuthError{} + } + + return apiClient, nil +} diff --git a/internal/pkg/services/git/utils/utils.go b/internal/pkg/services/git/utils/utils.go new file mode 100644 index 000000000..3a875c920 --- /dev/null +++ b/internal/pkg/services/git/utils/utils.go @@ -0,0 +1,23 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +type GitClient interface { + GetInstanceExecute(ctx context.Context, projectId string, instanceId string) (*git.Instance, error) +} + +func GetInstanceName(ctx context.Context, apiClient GitClient, projectId, instanceId string) (string, error) { + resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId) + if err != nil { + return "", fmt.Errorf("get instance: %w", err) + } + if resp.Name == nil { + return "", nil + } + return *resp.Name, nil +} diff --git a/internal/pkg/services/git/utils/utils_test.go b/internal/pkg/services/git/utils/utils_test.go new file mode 100644 index 000000000..7ec5dc494 --- /dev/null +++ b/internal/pkg/services/git/utils/utils_test.go @@ -0,0 +1,66 @@ +package utils + +import ( + "context" + "fmt" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/git" +) + +type GitClientMocked struct { + GetInstanceFails bool + GetInstanceResp *git.Instance +} + +func (m *GitClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*git.Instance, error) { + if m.GetInstanceFails { + return nil, fmt.Errorf("could not get instance") + } + return m.GetInstanceResp, nil +} + +func TestGetinstanceName(t *testing.T) { + tests := []struct { + name string + instanceResp *git.Instance + instanceErr bool + want string + wantErr bool + }{ + { + name: "successful retrieval", + instanceResp: &git.Instance{Name: utils.Ptr("test-instance")}, + want: "test-instance", + wantErr: false, + }, + { + name: "error on retrieval", + instanceErr: true, + wantErr: true, + }, + { + name: "nil name", + instanceErr: false, + instanceResp: &git.Instance{}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := &GitClientMocked{ + GetInstanceFails: tt.instanceErr, + GetInstanceResp: tt.instanceResp, + } + got, err := GetInstanceName(context.Background(), client, "", "") + if (err != nil) != tt.wantErr { + t.Errorf("GetInstanceName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetInstanceName() = %v, want %v", got, tt.want) + } + }) + } +} From 61b9c73eb4866d308a3670eae6d1476cafc699eb Mon Sep 17 00:00:00 2001 From: Pere Manent Date: Mon, 5 May 2025 15:29:54 +0200 Subject: [PATCH 2/5] Stop using new variables for the globalflags.ProjectIdFlag and Docs generation * Stop using new variables for the globalflags.ProjectIdFlag and minor issues * Add docs of the stackit git instance command --- docs/stackit.md | 1 + docs/stackit_git.md | 37 +++++++++++++++++++ docs/stackit_git_create.md | 41 ++++++++++++++++++++++ docs/stackit_git_delete.md | 40 +++++++++++++++++++++ docs/stackit_git_describe.md | 40 +++++++++++++++++++++ docs/stackit_git_list.md | 40 +++++++++++++++++++++ internal/cmd/git/create/create.go | 4 +-- internal/cmd/git/create/create_test.go | 10 +++--- internal/cmd/git/delete/delete_test.go | 8 ++--- internal/cmd/git/describe/describe_test.go | 10 +++--- internal/cmd/git/list/list.go | 2 +- internal/cmd/git/list/list_test.go | 10 +++--- 12 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 docs/stackit_git.md create mode 100644 docs/stackit_git_create.md create mode 100644 docs/stackit_git_delete.md create mode 100644 docs/stackit_git_describe.md create mode 100644 docs/stackit_git_list.md diff --git a/docs/stackit.md b/docs/stackit.md index 2dc456b83..3cc35f2f9 100644 --- a/docs/stackit.md +++ b/docs/stackit.md @@ -33,6 +33,7 @@ stackit [flags] * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options * [stackit curl](./stackit_curl.md) - Executes an authenticated HTTP request to an endpoint * [stackit dns](./stackit_dns.md) - Provides functionality for DNS +* [stackit git](./stackit_git.md) - Provides functionality for STACKIT Git * [stackit image](./stackit_image.md) - Manage server images * [stackit key-pair](./stackit_key-pair.md) - Provides functionality for SSH key pairs * [stackit load-balancer](./stackit_load-balancer.md) - Provides functionality for Load Balancer diff --git a/docs/stackit_git.md b/docs/stackit_git.md new file mode 100644 index 000000000..8a959c7d2 --- /dev/null +++ b/docs/stackit_git.md @@ -0,0 +1,37 @@ +## stackit git + +Provides functionality for STACKIT Git + +### Synopsis + +Provides functionality for STACKIT Git. + +``` +stackit git [flags] +``` + +### Options + +``` + -h, --help Help for "stackit git" +``` + +### 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 git create](./stackit_git_create.md) - Creates STACKIT Git instance +* [stackit git delete](./stackit_git_delete.md) - Deletes STACKIT Git instance +* [stackit git describe](./stackit_git_describe.md) - Describes STACKIT Git instance +* [stackit git list](./stackit_git_list.md) - Lists all instances of STACKIT Git. + diff --git a/docs/stackit_git_create.md b/docs/stackit_git_create.md new file mode 100644 index 000000000..db00806cd --- /dev/null +++ b/docs/stackit_git_create.md @@ -0,0 +1,41 @@ +## stackit git create + +Creates STACKIT Git instance + +### Synopsis + +Create an STACKIT Git instance by name. + +``` +stackit git create [flags] +``` + +### Examples + +``` + Create an instance with name 'my-new-instance' + $ stackit git create --name my-new-instance +``` + +### Options + +``` + -h, --help Help for "stackit git create" + --name string The name 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 git](./stackit_git.md) - Provides functionality for STACKIT Git + diff --git a/docs/stackit_git_delete.md b/docs/stackit_git_delete.md new file mode 100644 index 000000000..c7bbc15e8 --- /dev/null +++ b/docs/stackit_git_delete.md @@ -0,0 +1,40 @@ +## stackit git delete + +Deletes STACKIT Git instance + +### Synopsis + +Deletes an STACKIT Git instance by its internal ID. + +``` +stackit git delete INSTANCE_ID [flags] +``` + +### Examples + +``` + Delete an instance with ID "xxx" + $ stackit git delete xxx +``` + +### Options + +``` + -h, --help Help for "stackit git 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 git](./stackit_git.md) - Provides functionality for STACKIT Git + diff --git a/docs/stackit_git_describe.md b/docs/stackit_git_describe.md new file mode 100644 index 000000000..0024eedcd --- /dev/null +++ b/docs/stackit_git_describe.md @@ -0,0 +1,40 @@ +## stackit git describe + +Describes STACKIT Git instance + +### Synopsis + +Describes an STACKIT Git instance by its internal ID. + +``` +stackit git describe INSTANCE_ID [flags] +``` + +### Examples + +``` + Describe instance "xxx" + $ stackit git describe xxx +``` + +### Options + +``` + -h, --help Help for "stackit git describe" +``` + +### 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 git](./stackit_git.md) - Provides functionality for STACKIT Git + diff --git a/docs/stackit_git_list.md b/docs/stackit_git_list.md new file mode 100644 index 000000000..6069929f6 --- /dev/null +++ b/docs/stackit_git_list.md @@ -0,0 +1,40 @@ +## stackit git list + +Lists all instances of STACKIT Git. + +### Synopsis + +Lists all instances of STACKIT Git for the current project. + +``` +stackit git list [flags] +``` + +### Examples + +``` + List all STACKIT Git instances + $ stackit git instance list +``` + +### Options + +``` + -h, --help Help for "stackit git 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 git](./stackit_git.md) - Provides functionality for STACKIT Git + diff --git a/internal/cmd/git/create/create.go b/internal/cmd/git/create/create.go index 10a2eef14..f43a64232 100644 --- a/internal/cmd/git/create/create.go +++ b/internal/cmd/git/create/create.go @@ -33,8 +33,8 @@ type inputModel struct { func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "create", - Short: "Creates stackit git instance", - Long: "Create an stackit git instance by name.", + Short: "Creates STACKIT Git instance", + Long: "Create an STACKIT Git instance by name.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( diff --git a/internal/cmd/git/create/create_test.go b/internal/cmd/git/create/create_test.go index 371b9c237..6da4384f2 100644 --- a/internal/cmd/git/create/create_test.go +++ b/internal/cmd/git/create/create_test.go @@ -13,8 +13,6 @@ import ( "github.com/stackitcloud/stackit-sdk-go/services/git" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} var ( @@ -27,7 +25,7 @@ var ( func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, + globalflags.ProjectIdFlag: testProjectId, nameFlag: testName, } @@ -90,21 +88,21 @@ func TestParseInput(t *testing.T) { { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, projectIdFlag) + delete(flagValues, globalflags.ProjectIdFlag) }), isValid: false, }, { description: "project id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "" + flagValues[globalflags.ProjectIdFlag] = "" }), isValid: false, }, { description: "project id invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "invalid-uuid" + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" }), isValid: false, }, diff --git a/internal/cmd/git/delete/delete_test.go b/internal/cmd/git/delete/delete_test.go index 0f4bedfb7..8ebf4bbe1 100644 --- a/internal/cmd/git/delete/delete_test.go +++ b/internal/cmd/git/delete/delete_test.go @@ -13,8 +13,6 @@ import ( "github.com/stackitcloud/stackit-sdk-go/services/git" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} var ( @@ -26,7 +24,7 @@ var ( func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, + globalflags.ProjectIdFlag: testProjectId, } for _, mod := range mods { mod(flagValues) @@ -71,14 +69,14 @@ func TestParseInput(t *testing.T) { { description: "project id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "" + flagValues[globalflags.ProjectIdFlag] = "" }), isValid: false, }, { description: "project id invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "invalid-uuid" + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" }), isValid: false, }, diff --git a/internal/cmd/git/describe/describe_test.go b/internal/cmd/git/describe/describe_test.go index 509df0a73..90be18002 100644 --- a/internal/cmd/git/describe/describe_test.go +++ b/internal/cmd/git/describe/describe_test.go @@ -12,8 +12,6 @@ import ( "github.com/stackitcloud/stackit-sdk-go/services/git" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} var ( @@ -25,7 +23,7 @@ var ( func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, + globalflags.ProjectIdFlag: testProjectId, } for _, mod := range mods { mod(flagValues) @@ -76,7 +74,7 @@ func TestParseInput(t *testing.T) { { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, projectIdFlag) + delete(flagValues, globalflags.ProjectIdFlag) }), args: testInstanceId, isValid: false, @@ -84,7 +82,7 @@ func TestParseInput(t *testing.T) { { description: "project id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "" + flagValues[globalflags.ProjectIdFlag] = "" }), args: testInstanceId, isValid: false, @@ -92,7 +90,7 @@ func TestParseInput(t *testing.T) { { description: "project id invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "invalid-uuid" + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" }), args: testInstanceId, isValid: false, diff --git a/internal/cmd/git/list/list.go b/internal/cmd/git/list/list.go index 1b8f79795..689eb932c 100644 --- a/internal/cmd/git/list/list.go +++ b/internal/cmd/git/list/list.go @@ -27,7 +27,7 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "list", Short: "Lists all instances of STACKIT Git.", - Long: "Lists all instances of STACKIT Git current the current project.", + Long: "Lists all instances of STACKIT Git for the current project.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( diff --git a/internal/cmd/git/list/list_test.go b/internal/cmd/git/list/list_test.go index 21e593e59..504c26e6f 100644 --- a/internal/cmd/git/list/list_test.go +++ b/internal/cmd/git/list/list_test.go @@ -12,8 +12,6 @@ import ( "github.com/stackitcloud/stackit-sdk-go/services/git" ) -var projectIdFlag = globalflags.ProjectIdFlag - type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") @@ -22,7 +20,7 @@ var testProjectId = uuid.NewString() func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, + globalflags.ProjectIdFlag: testProjectId, } for _, mod := range mods { mod(flagValues) @@ -72,21 +70,21 @@ func TestParseInput(t *testing.T) { { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, projectIdFlag) + delete(flagValues, globalflags.ProjectIdFlag) }), isValid: false, }, { description: "project id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "" + flagValues[globalflags.ProjectIdFlag] = "" }), isValid: false, }, { description: "project id invalid 2", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "invalid-uuid" + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" }), isValid: false, }, From 9399e8b66bf8b6fe06dc3b6001fdb15b3c5a9269 Mon Sep 17 00:00:00 2001 From: Pere Manent Date: Fri, 9 May 2025 08:40:43 +0200 Subject: [PATCH 3/5] Stackit git: add vertical table on describe and limit list command --- docs/stackit_git_create.md | 4 +-- docs/stackit_git_delete.md | 4 +-- docs/stackit_git_describe.md | 2 +- docs/stackit_git_list.md | 6 ++++- internal/cmd/git/create/create.go | 4 +-- internal/cmd/git/delete/delete.go | 4 +-- internal/cmd/git/describe/describe.go | 35 +++++++++++++++++++-------- internal/cmd/git/list/list.go | 26 ++++++++++++++++++-- internal/cmd/git/list/list_test.go | 30 +++++++++++++++++++++++ 9 files changed, 93 insertions(+), 22 deletions(-) diff --git a/docs/stackit_git_create.md b/docs/stackit_git_create.md index db00806cd..fa7a2a97d 100644 --- a/docs/stackit_git_create.md +++ b/docs/stackit_git_create.md @@ -4,7 +4,7 @@ Creates STACKIT Git instance ### Synopsis -Create an STACKIT Git instance by name. +Create a STACKIT Git instance by name. ``` stackit git create [flags] @@ -13,7 +13,7 @@ stackit git create [flags] ### Examples ``` - Create an instance with name 'my-new-instance' + Create a instance with name 'my-new-instance' $ stackit git create --name my-new-instance ``` diff --git a/docs/stackit_git_delete.md b/docs/stackit_git_delete.md index c7bbc15e8..6f863ee60 100644 --- a/docs/stackit_git_delete.md +++ b/docs/stackit_git_delete.md @@ -4,7 +4,7 @@ Deletes STACKIT Git instance ### Synopsis -Deletes an STACKIT Git instance by its internal ID. +Deletes a STACKIT Git instance by its internal ID. ``` stackit git delete INSTANCE_ID [flags] @@ -13,7 +13,7 @@ stackit git delete INSTANCE_ID [flags] ### Examples ``` - Delete an instance with ID "xxx" + Delete a instance with ID "xxx" $ stackit git delete xxx ``` diff --git a/docs/stackit_git_describe.md b/docs/stackit_git_describe.md index 0024eedcd..e2aedcc40 100644 --- a/docs/stackit_git_describe.md +++ b/docs/stackit_git_describe.md @@ -4,7 +4,7 @@ Describes STACKIT Git instance ### Synopsis -Describes an STACKIT Git instance by its internal ID. +Describes a STACKIT Git instance by its internal ID. ``` stackit git describe INSTANCE_ID [flags] diff --git a/docs/stackit_git_list.md b/docs/stackit_git_list.md index 6069929f6..c0f65927b 100644 --- a/docs/stackit_git_list.md +++ b/docs/stackit_git_list.md @@ -15,12 +15,16 @@ stackit git list [flags] ``` List all STACKIT Git instances $ stackit git instance list + + Lists up to 10 STACKIT Git instances + $ stackit git instance list --limit=10 ``` ### Options ``` - -h, --help Help for "stackit git list" + -h, --help Help for "stackit git list" + --limit int Limit the output to the first n elements ``` ### Options inherited from parent commands diff --git a/internal/cmd/git/create/create.go b/internal/cmd/git/create/create.go index f43a64232..f1b3b2888 100644 --- a/internal/cmd/git/create/create.go +++ b/internal/cmd/git/create/create.go @@ -34,11 +34,11 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "create", Short: "Creates STACKIT Git instance", - Long: "Create an STACKIT Git instance by name.", + Long: "Create a STACKIT Git instance by name.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `Create an instance with name 'my-new-instance'`, + `Create a instance with name 'my-new-instance'`, `$ stackit git create --name my-new-instance`, ), ), diff --git a/internal/cmd/git/delete/delete.go b/internal/cmd/git/delete/delete.go index c31638160..0701279e5 100644 --- a/internal/cmd/git/delete/delete.go +++ b/internal/cmd/git/delete/delete.go @@ -30,10 +30,10 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("delete %s", instanceIdArg), Short: "Deletes STACKIT Git instance", - Long: "Deletes an STACKIT Git instance by its internal ID.", + Long: "Deletes a STACKIT Git instance by its internal ID.", Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( - examples.NewExample(`Delete an instance with ID "xxx"`, `$ stackit git delete xxx`), + examples.NewExample(`Delete a instance with ID "xxx"`, `$ stackit git delete xxx`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() diff --git a/internal/cmd/git/describe/describe.go b/internal/cmd/git/describe/describe.go index a5fdc8054..f52e12855 100644 --- a/internal/cmd/git/describe/describe.go +++ b/internal/cmd/git/describe/describe.go @@ -29,7 +29,7 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("describe %s", instanceIdArg), Short: "Describes STACKIT Git instance", - Long: "Describes an STACKIT Git instance by its internal ID.", + Long: "Describes a STACKIT Git instance by its internal ID.", Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( examples.NewExample(`Describe instance "xxx"`, `$ stackit git describe xxx`), @@ -116,15 +116,30 @@ func outputResult(p *print.Printer, outputFormat string, resp *git.Instance) err return nil default: table := tables.NewTable() - table.SetHeader("ID", "NAME", "URL", "VERSION", "STATE", "CREATED") - table.AddRow( - utils.PtrString(resp.Id), - utils.PtrString(resp.Name), - utils.PtrString(resp.Url), - utils.PtrString(resp.Version), - utils.PtrString(resp.State), - utils.PtrString(resp.Created), - ) + if id := resp.Id; id != nil { + table.AddRow("ID", *id) + table.AddSeparator() + } + if name := resp.Name; name != nil { + table.AddRow("NAME", *name) + table.AddSeparator() + } + if url := resp.Url; url != nil { + table.AddRow("URL", *url) + table.AddSeparator() + } + if version := resp.Version; version != nil { + table.AddRow("VERSION", *version) + table.AddSeparator() + } + if state := resp.State; state != nil { + table.AddRow("STATE", *state) + table.AddSeparator() + } + if created := resp.Created; created != nil { + table.AddRow("CREATED", *created) + table.AddSeparator() + } if err := table.Display(p); err != nil { return fmt.Errorf("render table: %w", err) diff --git a/internal/cmd/git/list/list.go b/internal/cmd/git/list/list.go index 689eb932c..80e4beeb2 100644 --- a/internal/cmd/git/list/list.go +++ b/internal/cmd/git/list/list.go @@ -10,6 +10,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" @@ -21,8 +22,11 @@ import ( type inputModel struct { *globalflags.GlobalFlagModel + Limit *int64 } +const limitFlag = "limit" + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "list", @@ -33,6 +37,10 @@ func NewCmd(p *print.Printer) *cobra.Command { examples.NewExample( `List all STACKIT Git instances`, "$ stackit git instance list"), + examples.NewExample( + "Lists up to 10 STACKIT Git instances", + "$ stackit git instance list --limit=10", + ), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -62,23 +70,37 @@ func NewCmd(p *print.Printer) *cobra.Command { } p.Info("No instances found for project %q\n", projectLabel) return nil + } else if model.Limit != nil && len(instances) > int(*model.Limit) { + instances = (instances)[:*model.Limit] } - return outputResult(p, model.OutputFormat, instances) }, } - + configureFlags(cmd) return cmd } +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Limit the output to the first n elements") +} + func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + model := inputModel{ GlobalFlagModel: globalFlags, + Limit: limit, } if p.IsVerbosityDebug() { diff --git a/internal/cmd/git/list/list_test.go b/internal/cmd/git/list/list_test.go index 504c26e6f..741b62301 100644 --- a/internal/cmd/git/list/list_test.go +++ b/internal/cmd/git/list/list_test.go @@ -2,6 +2,7 @@ package list import ( "context" + "strconv" "testing" "github.com/google/go-cmp/cmp" @@ -9,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/git" ) @@ -18,6 +20,10 @@ var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &git.APIClient{} var testProjectId = uuid.NewString() +const ( + testLimit = 10 +) + func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ globalflags.ProjectIdFlag: testProjectId, @@ -88,6 +94,30 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, + { + description: "with limit flag", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["limit"] = strconv.Itoa(testLimit) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Limit = utils.Ptr(int64(testLimit)) + }), + }, + { + description: "with limit flag == 0", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["limit"] = strconv.Itoa(0) + }), + isValid: false, + }, + { + description: "with limit flag < 0", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["limit"] = strconv.Itoa(-1) + }), + isValid: false, + }, } for _, tt := range tests { From 9ad9ef458f2bee81c9b991bea3b994c926c79328 Mon Sep 17 00:00:00 2001 From: Pere Manent Date: Tue, 13 May 2025 10:52:11 +0200 Subject: [PATCH 4/5] Delete toolchain opetion from go.mod file --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index 7f69deaa8..45c36a6ea 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/stackitcloud/stackit-cli go 1.24.0 -toolchain go1.24.3 - require ( github.com/fatih/color v1.18.0 github.com/goccy/go-yaml v1.17.1 From fb1c1a667ee5625e3fd6dcaa8fe1ab26bb44b60a Mon Sep 17 00:00:00 2001 From: Pere Manent Date: Tue, 13 May 2025 11:55:56 +0200 Subject: [PATCH 5/5] Get go.mod and go.sum from main --- go.mod | 22 +++++++++++----------- go.sum | 45 ++++++++++++++++++++++----------------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 45c36a6ea..5e4de09de 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/stackitcloud/stackit-cli -go 1.24.0 +go 1.24 require ( github.com/fatih/color v1.18.0 @@ -19,6 +19,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/alb v0.2.3 github.com/stackitcloud/stackit-sdk-go/services/authorization v0.6.3 github.com/stackitcloud/stackit-sdk-go/services/dns v0.13.3 + github.com/stackitcloud/stackit-sdk-go/services/git v0.3.3 github.com/stackitcloud/stackit-sdk-go/services/iaas v0.22.2 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.0.1 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.21.2 @@ -37,8 +38,8 @@ require ( golang.org/x/oauth2 v0.30.0 golang.org/x/term v0.32.0 golang.org/x/text v0.25.0 - k8s.io/apimachinery v0.33.0 - k8s.io/client-go v0.33.0 + k8s.io/apimachinery v0.32.3 + k8s.io/client-go v0.32.3 ) require ( @@ -50,7 +51,7 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - al.essio.dev/pkg/shellescape v1.6.0 // indirect + al.essio.dev/pkg/shellescape v1.5.1 // indirect github.com/4meepo/tagalign v1.4.2 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.1.0 // indirect @@ -212,17 +213,17 @@ require ( honnef.co/go/tools v0.6.1 // indirect mvdan.cc/gofumpt v0.8.0 // indirect mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect - sigs.k8s.io/randfill v1.0.0 // indirect ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect @@ -231,11 +232,10 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.9.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.8.0 // indirect - github.com/stackitcloud/stackit-sdk-go/services/git v0.3.3 + github.com/spf13/cast v1.7.1 // indirect github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.0.3 github.com/stackitcloud/stackit-sdk-go/services/logme v0.22.2 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.22.2 @@ -247,11 +247,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.0 // indirect + k8s.io/api v0.32.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 6e553b8f3..d16afaad1 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= -al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= +al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -156,8 +156,8 @@ github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47A github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -271,8 +271,8 @@ github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNF github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -289,6 +289,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -518,8 +520,8 @@ github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= -github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= @@ -549,8 +551,8 @@ github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCp github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= -github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1072,16 +1074,16 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= -k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= -k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= -k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= -k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= -k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k= @@ -1093,10 +1095,7 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=