From e3e166783422c81dd505bd2c47cd5024b2182879 Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:04:17 +0200 Subject: [PATCH 01/14] JGC-481 - Add --format flag support to app-create --- .../commands/application/create_app_cmd.go | 82 +++++++++++++++++-- .../application/create_app_cmd_test.go | 74 +++++++++++++++-- .../applications/application_service.go | 11 ++- .../applications/application_service_test.go | 2 +- .../mocks/application_service_mock.go | 7 +- go.mod | 64 ++++++++------- go.sum | 67 +++++++++++++++ 7 files changed, 252 insertions(+), 55 deletions(-) diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index 9060fef..d2081ed 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -2,6 +2,10 @@ package application import ( "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" @@ -9,11 +13,14 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/model" "github.com/jfrog/jfrog-cli-application/apptrust/service" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -25,6 +32,7 @@ type createAppCommand struct { serverDetails *coreConfig.ServerDetails applicationService applications.ApplicationService requestBody *model.AppDescriptor + responseBody []byte } func (cac *createAppCommand) Run() error { @@ -33,7 +41,8 @@ func (cac *createAppCommand) Run() error { return err } - return cac.applicationService.CreateApplication(ctx, cac.requestBody) + cac.responseBody, err = cac.applicationService.CreateApplication(ctx, cac.requestBody) + return err } func (cac *createAppCommand) ServerDetails() (*coreConfig.ServerDetails, error) { @@ -127,7 +136,16 @@ func (cac *createAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return commonCLiCommands.Exec(cac) + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(cac); err != nil { + return err + } + + return printCreateAppResponse(cac.responseBody, outputFormat, os.Stdout) } func validateCreateAppContext(ctx *components.Context) error { @@ -161,15 +179,67 @@ func validateNoSpecAndFlagsTogether(ctx *components.Context) error { return nil } +// orderedCreateAppKeys defines the display order for app-create table output. +var orderedCreateAppKeys = []string{ + "application_key", + "application_name", + "project_key", + "description", + "criticality", + "maturity_level", +} + +// printCreateAppResponse formats and prints the create-application response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printCreateAppResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printCreateAppTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printCreateAppTable renders the create-application response as a FIELD/VALUE table. +func printCreateAppTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse application response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedCreateAppKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() +} + func GetCreateAppCommand(appContext app.Context) components.Command { cmd := &createAppCommand{ applicationService: appContext.GetApplicationService(), } return components.Command{ - Name: commands.AppCreate, - Description: "Create a new application.", - Category: common.CategoryApplication, - Aliases: []string{"ac"}, + Name: commands.AppCreate, + Description: "Create a new application.", + Category: common.CategoryApplication, + Aliases: []string{"ac"}, + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, Arguments: []components.Argument{ { Name: "application-key", diff --git a/apptrust/commands/application/create_app_cmd_test.go b/apptrust/commands/application/create_app_cmd_test.go index 0d11954..4f2b999 100644 --- a/apptrust/commands/application/create_app_cmd_test.go +++ b/apptrust/commands/application/create_app_cmd_test.go @@ -1,6 +1,8 @@ package application import ( + "bytes" + "encoding/json" "errors" "flag" "testing" @@ -9,6 +11,7 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/model" mockapps "github.com/jfrog/jfrog-cli-application/apptrust/service/applications/mocks" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -52,7 +55,7 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { } mockAppService := mockapps.NewMockApplicationService(ctrl) - mockAppService.EXPECT().CreateApplication(gomock.Any(), requestPayload).Return(nil).Times(1) + mockAppService.EXPECT().CreateApplication(gomock.Any(), requestPayload).Return(nil, nil).Times(1) cmd := &createAppCommand{ applicationService: mockAppService, @@ -75,7 +78,7 @@ func TestCreateAppCommand_Error(t *testing.T) { } mockAppService := mockapps.NewMockApplicationService(ctrl) - mockAppService.EXPECT().CreateApplication(gomock.Any(), requestPayload).Return(errors.New("failed to create an application. Status code: 500")).Times(1) + mockAppService.EXPECT().CreateApplication(gomock.Any(), requestPayload).Return(nil, errors.New("failed to create an application. Status code: 500")).Times(1) cmd := &createAppCommand{ applicationService: mockAppService, @@ -162,9 +165,9 @@ func TestCreateAppCommand_Run_FullSpecFile(t *testing.T) { var actualPayload *model.AppDescriptor mockAppService := mockapps.NewMockApplicationService(ctrl) mockAppService.EXPECT().CreateApplication(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.AppDescriptor) error { + DoAndReturn(func(_ interface{}, req *model.AppDescriptor) ([]byte, error) { actualPayload = req - return nil + return nil, nil }).Times(1) cmd := &createAppCommand{ @@ -243,9 +246,9 @@ func TestCreateAppCommand_Run_SpecFile(t *testing.T) { mockAppService := mockapps.NewMockApplicationService(ctrl) if !tt.expectsError { mockAppService.EXPECT().CreateApplication(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.AppDescriptor) error { + DoAndReturn(func(_ interface{}, req *model.AppDescriptor) ([]byte, error) { actualPayload = req - return nil + return nil, nil }).Times(1) } @@ -298,9 +301,9 @@ func TestCreateAppCommand_Run_SpecVars(t *testing.T) { var actualPayload *model.AppDescriptor mockAppService := mockapps.NewMockApplicationService(ctrl) mockAppService.EXPECT().CreateApplication(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.AppDescriptor) error { + DoAndReturn(func(_ interface{}, req *model.AppDescriptor) ([]byte, error) { actualPayload = req - return nil + return nil, nil }).Times(1) cmd := &createAppCommand{ @@ -334,3 +337,58 @@ func TestCreateAppCommand_Error_SpecAndFlags(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "the flag --project is not allowed when --spec is provided") } + +// --- printCreateAppResponse tests --- + +const sampleAppJSON = `{"application_key":"my-app","application_name":"My App","project_key":"proj1","criticality":"high","maturity_level":"production"}` + +func TestPrintCreateAppResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printCreateAppResponse([]byte(sampleAppJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleAppJSON), &parsed)) +} + +func TestPrintCreateAppResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printCreateAppResponse([]byte(sampleAppJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "application_name") + assert.Contains(t, output, "My App") + assert.Contains(t, output, "project_key") + assert.Contains(t, output, "proj1") +} + +func TestPrintCreateAppResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key is present — other fields must be absent from output. + payload := `{"application_key":"only-key"}` + var buf bytes.Buffer + err := printCreateAppResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "only-key") + assert.NotContains(t, output, "application_name") + assert.NotContains(t, output, "project_key") +} + +func TestPrintCreateAppResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printCreateAppResponse([]byte(sampleAppJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintCreateAppResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printCreateAppResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse application response") +} diff --git a/apptrust/service/applications/application_service.go b/apptrust/service/applications/application_service.go index 2490b9b..5695457 100644 --- a/apptrust/service/applications/application_service.go +++ b/apptrust/service/applications/application_service.go @@ -14,7 +14,7 @@ import ( ) type ApplicationService interface { - CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) error + CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) ([]byte, error) UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) error DeleteApplication(ctx service.Context, applicationKey string) error } @@ -25,20 +25,19 @@ func NewApplicationService() ApplicationService { return &applicationService{} } -func (as *applicationService) CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) error { +func (as *applicationService) CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) ([]byte, error) { response, responseBody, err := ctx.GetHttpClient().Post("/v1/applications", requestBody, nil) if err != nil { - return err + return nil, err } if response.StatusCode != http.StatusCreated { - return errorutils.CheckErrorf("failed to create an application. Status code: %d.\n%s", + return nil, errorutils.CheckErrorf("failed to create an application. Status code: %d.\n%s", response.StatusCode, responseBody) } log.Info(fmt.Sprintf("Application \"%s\" created successfully.", requestBody.ApplicationKey)) - log.Output(string(responseBody)) - return nil + return responseBody, nil } func (as *applicationService) UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) error { diff --git a/apptrust/service/applications/application_service_test.go b/apptrust/service/applications/application_service_test.go index 9e5a338..340ccc2 100644 --- a/apptrust/service/applications/application_service_test.go +++ b/apptrust/service/applications/application_service_test.go @@ -55,7 +55,7 @@ func TestApplicationService_CreateApplication(t *testing.T) { mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1) as := NewApplicationService() - err := as.CreateApplication(mockCtx, &model.AppDescriptor{ApplicationKey: "app-123"}) + _, err := as.CreateApplication(mockCtx, &model.AppDescriptor{ApplicationKey: "app-123"}) if tt.expectedError != "" { assert.EqualError(t, err, tt.expectedError) diff --git a/apptrust/service/applications/mocks/application_service_mock.go b/apptrust/service/applications/mocks/application_service_mock.go index 50af16c..8d9f9b1 100644 --- a/apptrust/service/applications/mocks/application_service_mock.go +++ b/apptrust/service/applications/mocks/application_service_mock.go @@ -42,11 +42,12 @@ func (m *MockApplicationService) EXPECT() *MockApplicationServiceMockRecorder { } // CreateApplication mocks base method. -func (m *MockApplicationService) CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) error { +func (m *MockApplicationService) CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateApplication", ctx, requestBody) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // CreateApplication indicates an expected call of CreateApplication. diff --git a/go.mod b/go.mod index d1fcf77..083d56b 100644 --- a/go.mod +++ b/go.mod @@ -1,53 +1,54 @@ module github.com/jfrog/jfrog-cli-application -go 1.24.6 +go 1.25.5 require ( - github.com/jfrog/jfrog-cli-core/v2 v2.59.5 - github.com/jfrog/jfrog-client-go v1.54.5 - github.com/stretchr/testify v1.10.0 - github.com/urfave/cli v1.22.16 + github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260504054219-ba16d20c7b0f + github.com/jfrog/jfrog-client-go v1.55.1-0.20251223101502-1a13a993b0c7 + github.com/stretchr/testify v1.11.1 + github.com/urfave/cli v1.22.17 go.uber.org/mock v0.5.2 ) require ( - dario.cat/mergo v1.0.1 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect - github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/CycloneDX/cyclonedx-go v0.9.3 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/andybalholm/brotli v1.1.1 // indirect - github.com/buger/jsonparser v1.1.1 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/buger/jsonparser v1.1.2 // indirect github.com/c-bata/go-prompt v0.2.6 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/forPelevin/gomoji v1.3.0 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/forPelevin/gomoji v1.4.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.14.0 // indirect + github.com/go-git/go-billy/v5 v5.8.0 // indirect + github.com/go-git/go-git/v5 v5.18.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gookit/color v1.5.4 // indirect + github.com/gookit/color v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jedib0t/go-pretty/v6 v6.6.5 // indirect + github.com/jedib0t/go-pretty/v6 v6.6.8 // indirect github.com/jfrog/archiver/v3 v3.6.1 // indirect - github.com/jfrog/build-info-go v1.10.16 // indirect + github.com/jfrog/build-info-go v1.13.1-0.20260429070557-93b98034d295 // indirect github.com/jfrog/gofrog v1.7.6 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/magiconair/properties v1.8.9 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -56,7 +57,7 @@ require ( github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nwaples/rardecode v1.1.3 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect @@ -64,23 +65,24 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect - 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/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.19.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/ulikunitz/xz v0.5.15 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect diff --git a/go.sum b/go.sum index 60f7a84..503228c 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,30 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= +github.com/CycloneDX/cyclonedx-go v0.9.3 h1:Pyk/lwavPz7AaZNvugKFkdWOm93MzaIyWmBwmBo3aUI= +github.com/CycloneDX/cyclonedx-go v0.9.3/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -23,6 +33,8 @@ github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oM github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -36,6 +48,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -54,20 +68,30 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/forPelevin/gomoji v1.3.0 h1:WPIOLWB1bvRYlKZnSSEevLt3IfKlLs+tK+YA9fFYlkE= github.com/forPelevin/gomoji v1.3.0/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ= +github.com/forPelevin/gomoji v1.4.1 h1:7U+Bl8o6RV/dOQz7coQFWj/jX6Ram6/cWFOuFDEPEUo= +github.com/forPelevin/gomoji v1.4.1/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ= 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.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +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/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= +github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= +github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= @@ -76,6 +100,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -83,30 +109,44 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= +github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo= github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs= +github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc= +github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= github.com/jfrog/build-info-go v1.10.16 h1:M77OfTXl3Ew9AG9SIIYLAG5ccIIaf6xjIZ5nomc2XAw= github.com/jfrog/build-info-go v1.10.16/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= +github.com/jfrog/build-info-go v1.13.1-0.20260429070557-93b98034d295 h1:EH0h86KwGvNHWyEBQoHoU9WfMMKy1GJ6jJQNmfy6E0U= +github.com/jfrog/build-info-go v1.13.1-0.20260429070557-93b98034d295/go.mod h1:+OCtMb22/D+u7Wne5lzkjJjaWr0LRZcHlDwTH86Mpwo= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= github.com/jfrog/jfrog-cli-core/v2 v2.59.5 h1:zPUOquY8gakWHJSdQifdcleFL0U23I0bdQdOYqxXAj4= github.com/jfrog/jfrog-cli-core/v2 v2.59.5/go.mod h1:+zJfCkkFRWDbRTGpHsoaLZw5ze1xzRwffaLKuXjrZKc= +github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260504054219-ba16d20c7b0f h1:l5BPLF8GYBSvXmNqurqAP291lVHr1iCo4nwc5xe7KNM= +github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260504054219-ba16d20c7b0f/go.mod h1:bjAkVD8c2W+jg4whqy10bSXDC/c+Se8/ll/GPp5F/+0= github.com/jfrog/jfrog-client-go v1.54.5 h1:WKd26zbJYPCgKTTFFflHnX/1QqtZt0IXh3czlikXsyY= github.com/jfrog/jfrog-client-go v1.54.5/go.mod h1:+pSVE7Co+ytwhOhmz84/Lpe5fSeTaWJXsP1qt+WVfw0= +github.com/jfrog/jfrog-client-go v1.55.1-0.20251223101502-1a13a993b0c7 h1:5JUiqmBV9ikFOZEH+ZgvJLHshT1aAuw08bfdJOLHbzQ= +github.com/jfrog/jfrog-client-go v1.55.1-0.20251223101502-1a13a993b0c7/go.mod h1:USb7bfWSE7bGKsJ4nR0lxGILvmtnCcR5OO4biSUItMs= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -118,6 +158,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -144,8 +186,11 @@ github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9l github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= @@ -168,6 +213,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -177,14 +224,24 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -197,6 +254,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo= @@ -208,12 +267,16 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= +github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= +github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/vbauerster/mpb/v8 v8.9.1 h1:LH5R3lXPfE2e3lIGxN7WNWv3Hl5nWO6LRi2B0L0ERHw= github.com/vbauerster/mpb/v8 v8.9.1/go.mod h1:4XMvznPh8nfe2NpnDo1QTPvW9MVkUhbG90mPWvmOzcQ= +github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= @@ -228,6 +291,8 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= @@ -237,6 +302,8 @@ golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= From e1e307ede7b009aae88abe9bd9aaac6ef04f9d2e Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:13:26 +0200 Subject: [PATCH 02/14] JGC-481 - Add --format flag support to app-update --- .../commands/application/update_app_cmd.go | 84 +++++++++++++++++-- .../application/update_app_cmd_test.go | 66 ++++++++++++++- .../applications/application_service.go | 11 ++- .../mocks/application_service_mock.go | 7 +- 4 files changed, 149 insertions(+), 19 deletions(-) diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index c7a5cbc..9ba6575 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -1,6 +1,12 @@ package application import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-application/apptrust/app" @@ -11,14 +17,18 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/service" "github.com/jfrog/jfrog-cli-application/apptrust/service/applications" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/log" ) type updateAppCommand struct { serverDetails *coreConfig.ServerDetails applicationService applications.ApplicationService requestBody *model.AppDescriptor + responseBody []byte } func (uac *updateAppCommand) Run() error { @@ -27,7 +37,8 @@ func (uac *updateAppCommand) Run() error { return err } - return uac.applicationService.UpdateApplication(ctx, uac.requestBody) + uac.responseBody, err = uac.applicationService.UpdateApplication(ctx, uac.requestBody) + return err } func (uac *updateAppCommand) ServerDetails() (*coreConfig.ServerDetails, error) { @@ -69,7 +80,67 @@ func (uac *updateAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return commonCLiCommands.Exec(uac) + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(uac); err != nil { + return err + } + + return printUpdateAppResponse(uac.responseBody, outputFormat, os.Stdout) +} + +// orderedUpdateAppKeys defines the display order for app-update table output. +var orderedUpdateAppKeys = []string{ + "application_key", + "application_name", + "project_key", + "description", + "criticality", + "maturity_level", +} + +// printUpdateAppResponse formats and prints the update-application response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printUpdateAppResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printUpdateAppTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printUpdateAppTable renders the update-application response as a FIELD/VALUE table. +func printUpdateAppTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse application response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedUpdateAppKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() } func GetUpdateAppCommand(appContext app.Context) components.Command { @@ -77,10 +148,11 @@ func GetUpdateAppCommand(appContext app.Context) components.Command { applicationService: appContext.GetApplicationService(), } return components.Command{ - Name: commands.AppUpdate, - Description: "Update an existing application", - Category: common.CategoryApplication, - Aliases: []string{"au"}, + Name: commands.AppUpdate, + Description: "Update an existing application", + Category: common.CategoryApplication, + Aliases: []string{"au"}, + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, Arguments: []components.Argument{ { Name: "application-key", diff --git a/apptrust/commands/application/update_app_cmd_test.go b/apptrust/commands/application/update_app_cmd_test.go index cc17d61..5740944 100644 --- a/apptrust/commands/application/update_app_cmd_test.go +++ b/apptrust/commands/application/update_app_cmd_test.go @@ -1,6 +1,8 @@ package application import ( + "bytes" + "encoding/json" "errors" "flag" "testing" @@ -10,6 +12,7 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" mockapps "github.com/jfrog/jfrog-cli-application/apptrust/service/applications/mocks" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -40,7 +43,7 @@ func TestUpdateAppCommand_Run(t *testing.T) { } mockAppService := mockapps.NewMockApplicationService(ctrl) - mockAppService.EXPECT().UpdateApplication(gomock.Any(), requestPayload).Return(nil).Times(1) + mockAppService.EXPECT().UpdateApplication(gomock.Any(), requestPayload).Return(nil, nil).Times(1) cmd := &updateAppCommand{ applicationService: mockAppService, @@ -76,7 +79,7 @@ func TestUpdateAppCommand_Run_Error(t *testing.T) { } mockAppService := mockapps.NewMockApplicationService(ctrl) - mockAppService.EXPECT().UpdateApplication(gomock.Any(), requestPayload).Return(errors.New("failed to update application. Status code: 500")).Times(1) + mockAppService.EXPECT().UpdateApplication(gomock.Any(), requestPayload).Return(nil, errors.New("failed to update application. Status code: 500")).Times(1) cmd := &updateAppCommand{ applicationService: mockAppService, @@ -338,9 +341,9 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { mockAppService := mockapps.NewMockApplicationService(ctrl) if !tt.expectsError { mockAppService.EXPECT().UpdateApplication(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.AppDescriptor) error { + DoAndReturn(func(_ interface{}, req *model.AppDescriptor) ([]byte, error) { actualPayload = req - return nil + return nil, nil }).Times(1) } @@ -365,3 +368,58 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { func stringPtr(s string) *string { return &s } + +// --- printUpdateAppResponse tests --- + +const sampleUpdateAppJSON = `{"application_key":"my-app","application_name":"My App","project_key":"proj1","criticality":"high","maturity_level":"production"}` + +func TestPrintUpdateAppResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppResponse([]byte(sampleUpdateAppJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleUpdateAppJSON), &parsed)) +} + +func TestPrintUpdateAppResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppResponse([]byte(sampleUpdateAppJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "application_name") + assert.Contains(t, output, "My App") + assert.Contains(t, output, "project_key") + assert.Contains(t, output, "proj1") +} + +func TestPrintUpdateAppResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key is present — other fields must be absent from output. + payload := `{"application_key":"only-key"}` + var buf bytes.Buffer + err := printUpdateAppResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "only-key") + assert.NotContains(t, output, "application_name") + assert.NotContains(t, output, "project_key") +} + +func TestPrintUpdateAppResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printUpdateAppResponse([]byte(sampleUpdateAppJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintUpdateAppResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse application response") +} diff --git a/apptrust/service/applications/application_service.go b/apptrust/service/applications/application_service.go index 5695457..d80777e 100644 --- a/apptrust/service/applications/application_service.go +++ b/apptrust/service/applications/application_service.go @@ -15,7 +15,7 @@ import ( type ApplicationService interface { CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) ([]byte, error) - UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) error + UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) ([]byte, error) DeleteApplication(ctx service.Context, applicationKey string) error } @@ -40,21 +40,20 @@ func (as *applicationService) CreateApplication(ctx service.Context, requestBody return responseBody, nil } -func (as *applicationService) UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) error { +func (as *applicationService) UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s", requestBody.ApplicationKey) response, responseBody, err := ctx.GetHttpClient().Patch(endpoint, requestBody, nil) if err != nil { - return err + return nil, err } if response.StatusCode != http.StatusOK { - return errorutils.CheckErrorf("failed to update application. Status code: %d.\n%s", + return nil, errorutils.CheckErrorf("failed to update application. Status code: %d.\n%s", response.StatusCode, responseBody) } log.Info(fmt.Sprintf("Application \"%s\" updated successfully.", requestBody.ApplicationKey)) - log.Output(string(responseBody)) - return nil + return responseBody, nil } func (as *applicationService) DeleteApplication(ctx service.Context, applicationKey string) error { diff --git a/apptrust/service/applications/mocks/application_service_mock.go b/apptrust/service/applications/mocks/application_service_mock.go index 8d9f9b1..733c943 100644 --- a/apptrust/service/applications/mocks/application_service_mock.go +++ b/apptrust/service/applications/mocks/application_service_mock.go @@ -71,11 +71,12 @@ func (mr *MockApplicationServiceMockRecorder) DeleteApplication(ctx, application } // UpdateApplication mocks base method. -func (m *MockApplicationService) UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) error { +func (m *MockApplicationService) UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateApplication", ctx, requestBody) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // UpdateApplication indicates an expected call of UpdateApplication. From 18d9964a3edd030ddce3b1537cf6f8a5f90d078a Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:22:00 +0200 Subject: [PATCH 03/14] JGC-481 - Add --format flag support to version-create --- .../version/create_app_version_cmd.go | 80 +++++++++++++++++- .../version/create_app_version_cmd_test.go | 81 +++++++++++++++++-- .../versions/mocks/version_service_mock.go | 7 +- apptrust/service/versions/version_service.go | 11 ++- .../service/versions/version_service_test.go | 2 +- 5 files changed, 159 insertions(+), 22 deletions(-) diff --git a/apptrust/commands/version/create_app_version_cmd.go b/apptrust/commands/version/create_app_version_cmd.go index 1952e5d..57d271a 100644 --- a/apptrust/commands/version/create_app_version_cmd.go +++ b/apptrust/commands/version/create_app_version_cmd.go @@ -1,6 +1,12 @@ package version import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + "github.com/jfrog/jfrog-cli-application/apptrust/service/versions" "github.com/jfrog/jfrog-cli-application/apptrust/app" @@ -10,10 +16,13 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/model" "github.com/jfrog/jfrog-cli-application/apptrust/service" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" ) type createAppVersionCommand struct { @@ -22,6 +31,7 @@ type createAppVersionCommand struct { requestPayload *model.CreateAppVersionRequest sync bool dryRun bool + responseBody []byte } func (cv *createAppVersionCommand) Run() error { @@ -30,7 +40,8 @@ func (cv *createAppVersionCommand) Run() error { return err } - return cv.versionService.CreateAppVersion(ctx, cv.requestPayload, cv.sync, cv.dryRun) + cv.responseBody, err = cv.versionService.CreateAppVersion(ctx, cv.requestPayload, cv.sync, cv.dryRun) + return err } func (cv *createAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) { @@ -56,7 +67,67 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } cv.dryRun = ctx.GetBoolFlagValue(commands.DryRunFlag) - return commonCLiCommands.Exec(cv) + + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(cv); err != nil { + return err + } + + return printCreateAppVersionResponse(cv.responseBody, outputFormat, os.Stdout) +} + +// orderedCreateAppVersionKeys defines the display order for version-create table output. +var orderedCreateAppVersionKeys = []string{ + "application_key", + "version", + "status", + "current_stage", + "tag", +} + +// printCreateAppVersionResponse formats and prints the create-app-version response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printCreateAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printCreateAppVersionTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printCreateAppVersionTable renders the create-app-version response as a FIELD/VALUE table. +func printCreateAppVersionTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse version response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedCreateAppVersionKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() } func (cv *createAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.CreateAppVersionRequest, error) { @@ -104,7 +175,8 @@ func GetCreateAppVersionCommand(appContext app.Context) components.Command { Optional: false, }, }, - Flags: commands.GetCommandFlags(commands.VersionCreate), - Action: cmd.prepareAndRunCommand, + Flags: commands.GetCommandFlags(commands.VersionCreate), + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/create_app_version_cmd_test.go b/apptrust/commands/version/create_app_version_cmd_test.go index 7a28bda..f19cda5 100644 --- a/apptrust/commands/version/create_app_version_cmd_test.go +++ b/apptrust/commands/version/create_app_version_cmd_test.go @@ -1,6 +1,8 @@ package version import ( + "bytes" + "encoding/json" "errors" "testing" @@ -9,6 +11,7 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -77,10 +80,10 @@ func TestCreateAppVersionCommand(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) if tt.shouldError { mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun). - Return(errors.New(tt.errorMessage)).Times(1) + Return(nil, errors.New(tt.errorMessage)).Times(1) } else { mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun). - Return(nil).Times(1) + Return(nil, nil).Times(1) } cmd := &createAppVersionCommand{ @@ -217,9 +220,9 @@ func TestCreateAppVersionCommand_FlagsSuite(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) if !tt.expectsError { mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, _ bool, _ bool) error { + DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, _ bool, _ bool) ([]byte, error) { actualPayload = req - return nil + return nil, nil }).Times(1) } @@ -828,10 +831,10 @@ func TestCreateAppVersionCommand_SpecFileSuite(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) if !tt.expectsError { mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) error { + DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) { actualPayload = req capturedSync = sync - return nil + return nil, nil }).Times(1) } @@ -897,9 +900,9 @@ func TestCreateAppVersionCommand_SyncFlagSuite(t *testing.T) { var capturedSync bool mockVersionService := mockversions.NewMockVersionService(ctrl) mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) error { + DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) { capturedSync = sync - return nil + return nil, nil }).Times(1) cmd := &createAppVersionCommand{ @@ -1667,3 +1670,65 @@ func TestBuildRequestPayload_Filters(t *testing.T) { }) } } + +// --- printCreateAppVersionResponse tests --- + +const sampleCreateAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"dev","tag":"release-tag"}` + +func TestPrintCreateAppVersionResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printCreateAppVersionResponse([]byte(sampleCreateAppVersionJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleCreateAppVersionJSON), &parsed)) +} + +func TestPrintCreateAppVersionResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printCreateAppVersionResponse([]byte(sampleCreateAppVersionJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.Contains(t, output, "status") + assert.Contains(t, output, "COMPLETED") + assert.Contains(t, output, "current_stage") + assert.Contains(t, output, "dev") + assert.Contains(t, output, "tag") + assert.Contains(t, output, "release-tag") +} + +func TestPrintCreateAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key and version are present — other fields must be absent from output. + payload := `{"application_key":"my-app","version":"1.0.0"}` + var buf bytes.Buffer + err := printCreateAppVersionResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.NotContains(t, output, "status") + assert.NotContains(t, output, "current_stage") + assert.NotContains(t, output, "tag") +} + +func TestPrintCreateAppVersionResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printCreateAppVersionResponse([]byte(sampleCreateAppVersionJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintCreateAppVersionResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printCreateAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse version response") +} diff --git a/apptrust/service/versions/mocks/version_service_mock.go b/apptrust/service/versions/mocks/version_service_mock.go index 3424137..091397f 100644 --- a/apptrust/service/versions/mocks/version_service_mock.go +++ b/apptrust/service/versions/mocks/version_service_mock.go @@ -42,11 +42,12 @@ func (m *MockVersionService) EXPECT() *MockVersionServiceMockRecorder { } // CreateAppVersion mocks base method. -func (m *MockVersionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) error { +func (m *MockVersionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateAppVersion", ctx, request, sync, dryRun) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // CreateAppVersion indicates an expected call of CreateAppVersion. diff --git a/apptrust/service/versions/version_service.go b/apptrust/service/versions/version_service.go index c126197..e256ae6 100644 --- a/apptrust/service/versions/version_service.go +++ b/apptrust/service/versions/version_service.go @@ -15,7 +15,7 @@ import ( ) type VersionService interface { - CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) error + CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) error ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) error RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) error @@ -30,22 +30,21 @@ func NewVersionService() VersionService { return &versionService{} } -func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) error { +func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s/versions/", request.ApplicationKey) response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{"async": strconv.FormatBool(!sync), "dry_run": strconv.FormatBool(dryRun)}) if err != nil { - return err + return nil, err } if !apphttp.IsSuccessStatusCode(response.StatusCode) { - return fmt.Errorf("failed to create app version. Status code: %d. \n%s", + return nil, fmt.Errorf("failed to create app version. Status code: %d. \n%s", response.StatusCode, responseBody) } logSuccessMessage(sync, request, dryRun) - log.Output(string(responseBody)) - return nil + return responseBody, nil } func (vs *versionService) PromoteAppVersion(ctx service.Context, applicationKey, version string, request *model.PromoteAppVersionRequest, sync bool) error { diff --git a/apptrust/service/versions/version_service_test.go b/apptrust/service/versions/version_service_test.go index 043d34c..5d8f15e 100644 --- a/apptrust/service/versions/version_service_test.go +++ b/apptrust/service/versions/version_service_test.go @@ -101,7 +101,7 @@ func TestCreateAppVersion(t *testing.T) { mockCtx := mockservice.NewMockContext(ctrl) mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1) - err := service.CreateAppVersion(mockCtx, tt.request, tt.sync, tt.dryRun) + _, err := service.CreateAppVersion(mockCtx, tt.request, tt.sync, tt.dryRun) if tt.expectedError == "" { assert.NoError(t, err) } else { From f239ca66e33f9688212b28752dbb81476a8df7c6 Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:29:18 +0200 Subject: [PATCH 04/14] JGC-481 - Add --format flag support to version-promote --- .../version/promote_app_version_cmd.go | 80 ++++++++++++++++++- .../version/promote_app_version_cmd_test.go | 68 +++++++++++++++- .../versions/mocks/version_service_mock.go | 7 +- apptrust/service/versions/version_service.go | 11 ++- .../service/versions/version_service_test.go | 2 +- 5 files changed, 152 insertions(+), 16 deletions(-) diff --git a/apptrust/commands/version/promote_app_version_cmd.go b/apptrust/commands/version/promote_app_version_cmd.go index b70e5f3..d480b63 100644 --- a/apptrust/commands/version/promote_app_version_cmd.go +++ b/apptrust/commands/version/promote_app_version_cmd.go @@ -3,6 +3,12 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -11,10 +17,13 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/service" "github.com/jfrog/jfrog-cli-application/apptrust/service/versions" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" ) type promoteAppVersionCommand struct { @@ -24,6 +33,7 @@ type promoteAppVersionCommand struct { version string requestPayload *model.PromoteAppVersionRequest sync bool + responseBody []byte } func (pv *promoteAppVersionCommand) Run() error { @@ -32,7 +42,8 @@ func (pv *promoteAppVersionCommand) Run() error { return err } - return pv.versionService.PromoteAppVersion(ctx, pv.applicationKey, pv.version, pv.requestPayload, pv.sync) + pv.responseBody, err = pv.versionService.PromoteAppVersion(ctx, pv.applicationKey, pv.version, pv.requestPayload, pv.sync) + return err } func (pv *promoteAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) { @@ -64,7 +75,67 @@ func (pv *promoteAppVersionCommand) prepareAndRunCommand(ctx *components.Context if errorutils.CheckError(err) != nil { return err } - return commonCLiCommands.Exec(pv) + + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(pv); err != nil { + return err + } + + return printPromoteAppVersionResponse(pv.responseBody, outputFormat, os.Stdout) +} + +// orderedPromoteAppVersionKeys defines the display order for version-promote table output. +var orderedPromoteAppVersionKeys = []string{ + "application_key", + "version", + "target_stage", + "status", + "current_stage", +} + +// printPromoteAppVersionResponse formats and prints the promote-app-version response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printPromoteAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printPromoteAppVersionTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printPromoteAppVersionTable renders the promote-app-version response as a FIELD/VALUE table. +func printPromoteAppVersionTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse promote response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedPromoteAppVersionKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() } func (pv *promoteAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.PromoteAppVersionRequest, error) { @@ -121,7 +192,8 @@ func GetPromoteAppVersionCommand(appContext app.Context) components.Command { Optional: false, }, }, - Flags: commands.GetCommandFlags(commands.VersionPromote), - Action: cmd.prepareAndRunCommand, + Flags: commands.GetCommandFlags(commands.VersionPromote), + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/promote_app_version_cmd_test.go b/apptrust/commands/version/promote_app_version_cmd_test.go index b93195c..c56e433 100644 --- a/apptrust/commands/version/promote_app_version_cmd_test.go +++ b/apptrust/commands/version/promote_app_version_cmd_test.go @@ -1,6 +1,8 @@ package version import ( + "bytes" + "encoding/json" "errors" "testing" @@ -8,6 +10,7 @@ import ( "go.uber.org/mock/gomock" "github.com/jfrog/jfrog-cli-application/apptrust/model" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" ) @@ -60,7 +63,7 @@ func TestPromoteAppVersionCommand_Run(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) mockVersionService.EXPECT().PromoteAppVersion(gomock.Any(), applicationKey, version, requestPayload, tt.sync). - Return(nil).Times(1) + Return(nil, nil).Times(1) cmd := &promoteAppVersionCommand{ versionService: mockVersionService, @@ -92,7 +95,7 @@ func TestPromoteAppVersionCommand_Run_Error(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) mockVersionService.EXPECT().PromoteAppVersion(gomock.Any(), applicationKey, version, requestPayload, sync). - Return(expectedError).Times(1) + Return(nil, expectedError).Times(1) cmd := &promoteAppVersionCommand{ versionService: mockVersionService, @@ -107,3 +110,64 @@ func TestPromoteAppVersionCommand_Run_Error(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "service error occurred") } + +// --- printPromoteAppVersionResponse tests --- + +const samplePromoteAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","target_stage":"prod","status":"COMPLETED","current_stage":"prod"}` + +func TestPrintPromoteAppVersionResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printPromoteAppVersionResponse([]byte(samplePromoteAppVersionJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(samplePromoteAppVersionJSON), &parsed)) +} + +func TestPrintPromoteAppVersionResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printPromoteAppVersionResponse([]byte(samplePromoteAppVersionJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.Contains(t, output, "target_stage") + assert.Contains(t, output, "prod") + assert.Contains(t, output, "status") + assert.Contains(t, output, "COMPLETED") + assert.Contains(t, output, "current_stage") +} + +func TestPrintPromoteAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key and version are present — other fields must be absent from output. + payload := `{"application_key":"my-app","version":"1.0.0"}` + var buf bytes.Buffer + err := printPromoteAppVersionResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.NotContains(t, output, "target_stage") + assert.NotContains(t, output, "status") + assert.NotContains(t, output, "current_stage") +} + +func TestPrintPromoteAppVersionResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printPromoteAppVersionResponse([]byte(samplePromoteAppVersionJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintPromoteAppVersionResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printPromoteAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse promote response") +} diff --git a/apptrust/service/versions/mocks/version_service_mock.go b/apptrust/service/versions/mocks/version_service_mock.go index 091397f..f83336a 100644 --- a/apptrust/service/versions/mocks/version_service_mock.go +++ b/apptrust/service/versions/mocks/version_service_mock.go @@ -71,11 +71,12 @@ func (mr *MockVersionServiceMockRecorder) DeleteAppVersion(ctx, applicationKey, } // PromoteAppVersion mocks base method. -func (m *MockVersionService) PromoteAppVersion(ctx service.Context, applicationKey, version string, payload *model.PromoteAppVersionRequest, sync bool) error { +func (m *MockVersionService) PromoteAppVersion(ctx service.Context, applicationKey, version string, payload *model.PromoteAppVersionRequest, sync bool) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PromoteAppVersion", ctx, applicationKey, version, payload, sync) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // PromoteAppVersion indicates an expected call of PromoteAppVersion. diff --git a/apptrust/service/versions/version_service.go b/apptrust/service/versions/version_service.go index e256ae6..15b8ad9 100644 --- a/apptrust/service/versions/version_service.go +++ b/apptrust/service/versions/version_service.go @@ -16,7 +16,7 @@ import ( type VersionService interface { CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) - PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) error + PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) ([]byte, error) ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) error RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) error DeleteAppVersion(ctx service.Context, applicationKey string, version string) error @@ -47,20 +47,19 @@ func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.C return responseBody, nil } -func (vs *versionService) PromoteAppVersion(ctx service.Context, applicationKey, version string, request *model.PromoteAppVersionRequest, sync bool) error { +func (vs *versionService) PromoteAppVersion(ctx service.Context, applicationKey, version string, request *model.PromoteAppVersionRequest, sync bool) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s/promote", applicationKey, version) response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{"async": strconv.FormatBool(!sync)}) if err != nil { - return err + return nil, err } if !apphttp.IsSuccessStatusCode(response.StatusCode) { - return fmt.Errorf("failed to promote app version. Status code: %d. \n%s", + return nil, fmt.Errorf("failed to promote app version. Status code: %d. \n%s", response.StatusCode, responseBody) } - log.Output(string(responseBody)) - return nil + return responseBody, nil } func (vs *versionService) ReleaseAppVersion(ctx service.Context, applicationKey, version string, request *model.ReleaseAppVersionRequest, sync bool) error { diff --git a/apptrust/service/versions/version_service_test.go b/apptrust/service/versions/version_service_test.go index 5d8f15e..f4d30aa 100644 --- a/apptrust/service/versions/version_service_test.go +++ b/apptrust/service/versions/version_service_test.go @@ -213,7 +213,7 @@ func TestPromoteAppVersion(t *testing.T) { mockCtx := mockservice.NewMockContext(ctrl) mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1) - err := service.PromoteAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload, tt.sync) + _, err := service.PromoteAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload, tt.sync) if tt.expectedError == "" { assert.NoError(t, err) } else { From 2c14c696285366374724b24dd86ea3bb78da7b0a Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:35:18 +0200 Subject: [PATCH 05/14] JGC-481 - Add --format flag support to version-release --- .../version/release_app_version_cmd.go | 79 ++++++++++++++++++- .../version/release_app_version_cmd_test.go | 66 +++++++++++++++- .../versions/mocks/version_service_mock.go | 7 +- apptrust/service/versions/version_service.go | 11 ++- .../service/versions/version_service_test.go | 2 +- 5 files changed, 149 insertions(+), 16 deletions(-) diff --git a/apptrust/commands/version/release_app_version_cmd.go b/apptrust/commands/version/release_app_version_cmd.go index 557682f..0430425 100644 --- a/apptrust/commands/version/release_app_version_cmd.go +++ b/apptrust/commands/version/release_app_version_cmd.go @@ -3,6 +3,12 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -11,10 +17,13 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/service" "github.com/jfrog/jfrog-cli-application/apptrust/service/versions" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" ) type releaseAppVersionCommand struct { @@ -24,6 +33,7 @@ type releaseAppVersionCommand struct { version string requestPayload *model.ReleaseAppVersionRequest sync bool + responseBody []byte } func (rv *releaseAppVersionCommand) Run() error { @@ -32,7 +42,8 @@ func (rv *releaseAppVersionCommand) Run() error { return err } - return rv.versionService.ReleaseAppVersion(ctx, rv.applicationKey, rv.version, rv.requestPayload, rv.sync) + rv.responseBody, err = rv.versionService.ReleaseAppVersion(ctx, rv.applicationKey, rv.version, rv.requestPayload, rv.sync) + return err } func (rv *releaseAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) { @@ -64,7 +75,66 @@ func (rv *releaseAppVersionCommand) prepareAndRunCommand(ctx *components.Context if errorutils.CheckError(err) != nil { return err } - return commonCLiCommands.Exec(rv) + + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(rv); err != nil { + return err + } + + return printReleaseAppVersionResponse(rv.responseBody, outputFormat, os.Stdout) +} + +// orderedReleaseAppVersionKeys defines the display order for version-release table output. +var orderedReleaseAppVersionKeys = []string{ + "application_key", + "version", + "status", + "current_stage", +} + +// printReleaseAppVersionResponse formats and prints the release-app-version response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printReleaseAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printReleaseAppVersionTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printReleaseAppVersionTable renders the release-app-version response as a FIELD/VALUE table. +func printReleaseAppVersionTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse release response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedReleaseAppVersionKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() } func (rv *releaseAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.ReleaseAppVersionRequest, error) { @@ -113,7 +183,8 @@ func GetReleaseAppVersionCommand(appContext app.Context) components.Command { Optional: false, }, }, - Flags: commands.GetCommandFlags(commands.VersionRelease), - Action: cmd.prepareAndRunCommand, + Flags: commands.GetCommandFlags(commands.VersionRelease), + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/release_app_version_cmd_test.go b/apptrust/commands/version/release_app_version_cmd_test.go index 9ea64b8..356fbda 100644 --- a/apptrust/commands/version/release_app_version_cmd_test.go +++ b/apptrust/commands/version/release_app_version_cmd_test.go @@ -1,6 +1,8 @@ package version import ( + "bytes" + "encoding/json" "errors" "testing" @@ -8,6 +10,7 @@ import ( "go.uber.org/mock/gomock" "github.com/jfrog/jfrog-cli-application/apptrust/model" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" ) @@ -61,7 +64,7 @@ func TestReleaseAppVersionCommand_Run(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) mockVersionService.EXPECT().ReleaseAppVersion(gomock.Any(), applicationKey, version, requestPayload, tt.sync). - Return(nil).Times(1) + Return(nil, nil).Times(1) cmd := &releaseAppVersionCommand{ versionService: mockVersionService, @@ -96,7 +99,7 @@ func TestReleaseAppVersionCommand_Run_Error(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) mockVersionService.EXPECT().ReleaseAppVersion(gomock.Any(), applicationKey, version, requestPayload, false). - Return(expectedError).Times(1) + Return(nil, expectedError).Times(1) cmd := &releaseAppVersionCommand{ versionService: mockVersionService, @@ -111,3 +114,62 @@ func TestReleaseAppVersionCommand_Run_Error(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "service error occurred") } + +// --- printReleaseAppVersionResponse tests --- + +const sampleReleaseAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"prod"}` + +func TestPrintReleaseAppVersionResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printReleaseAppVersionResponse([]byte(sampleReleaseAppVersionJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleReleaseAppVersionJSON), &parsed)) +} + +func TestPrintReleaseAppVersionResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printReleaseAppVersionResponse([]byte(sampleReleaseAppVersionJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.Contains(t, output, "status") + assert.Contains(t, output, "COMPLETED") + assert.Contains(t, output, "current_stage") + assert.Contains(t, output, "prod") +} + +func TestPrintReleaseAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key and version are present — other fields must be absent from output. + payload := `{"application_key":"my-app","version":"1.0.0"}` + var buf bytes.Buffer + err := printReleaseAppVersionResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.NotContains(t, output, "status") + assert.NotContains(t, output, "current_stage") +} + +func TestPrintReleaseAppVersionResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printReleaseAppVersionResponse([]byte(sampleReleaseAppVersionJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintReleaseAppVersionResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printReleaseAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse release response") +} diff --git a/apptrust/service/versions/mocks/version_service_mock.go b/apptrust/service/versions/mocks/version_service_mock.go index f83336a..26e575d 100644 --- a/apptrust/service/versions/mocks/version_service_mock.go +++ b/apptrust/service/versions/mocks/version_service_mock.go @@ -86,11 +86,12 @@ func (mr *MockVersionServiceMockRecorder) PromoteAppVersion(ctx, applicationKey, } // ReleaseAppVersion mocks base method. -func (m *MockVersionService) ReleaseAppVersion(ctx service.Context, applicationKey, version string, request *model.ReleaseAppVersionRequest, sync bool) error { +func (m *MockVersionService) ReleaseAppVersion(ctx service.Context, applicationKey, version string, request *model.ReleaseAppVersionRequest, sync bool) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReleaseAppVersion", ctx, applicationKey, version, request, sync) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // ReleaseAppVersion indicates an expected call of ReleaseAppVersion. diff --git a/apptrust/service/versions/version_service.go b/apptrust/service/versions/version_service.go index 15b8ad9..9fa0c83 100644 --- a/apptrust/service/versions/version_service.go +++ b/apptrust/service/versions/version_service.go @@ -17,7 +17,7 @@ import ( type VersionService interface { CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) ([]byte, error) - ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) error + ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) ([]byte, error) RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) error DeleteAppVersion(ctx service.Context, applicationKey string, version string) error UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) error @@ -62,20 +62,19 @@ func (vs *versionService) PromoteAppVersion(ctx service.Context, applicationKey, return responseBody, nil } -func (vs *versionService) ReleaseAppVersion(ctx service.Context, applicationKey, version string, request *model.ReleaseAppVersionRequest, sync bool) error { +func (vs *versionService) ReleaseAppVersion(ctx service.Context, applicationKey, version string, request *model.ReleaseAppVersionRequest, sync bool) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s/release", applicationKey, version) response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{"async": strconv.FormatBool(!sync)}) if err != nil { - return err + return nil, err } if !apphttp.IsSuccessStatusCode(response.StatusCode) { - return fmt.Errorf("failed to release app version. Status code: %d. \n%s", + return nil, fmt.Errorf("failed to release app version. Status code: %d. \n%s", response.StatusCode, responseBody) } - log.Output(string(responseBody)) - return nil + return responseBody, nil } func (vs *versionService) RollbackAppVersion(ctx service.Context, applicationKey, version string, request *model.RollbackAppVersionRequest, sync bool) error { diff --git a/apptrust/service/versions/version_service_test.go b/apptrust/service/versions/version_service_test.go index f4d30aa..7b1295b 100644 --- a/apptrust/service/versions/version_service_test.go +++ b/apptrust/service/versions/version_service_test.go @@ -325,7 +325,7 @@ func TestReleaseAppVersion(t *testing.T) { mockCtx := mockservice.NewMockContext(ctrl) mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1) - err := service.ReleaseAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload, tt.sync) + _, err := service.ReleaseAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload, tt.sync) if tt.expectedError == "" { assert.NoError(t, err) } else { From 32c8ea209670cf072039952b88446f88fc527d1f Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:41:40 +0200 Subject: [PATCH 06/14] JGC-481 - Add --format flag support to version-update --- .../version/update_app_version_cmd.go | 77 ++++++++++++++++++- .../version/update_app_version_cmd_test.go | 73 +++++++++++++++++- .../versions/mocks/version_service_mock.go | 7 +- apptrust/service/versions/version_service.go | 10 +-- .../service/versions/version_service_test.go | 2 +- 5 files changed, 152 insertions(+), 17 deletions(-) diff --git a/apptrust/commands/version/update_app_version_cmd.go b/apptrust/commands/version/update_app_version_cmd.go index d8916a2..fa1c414 100644 --- a/apptrust/commands/version/update_app_version_cmd.go +++ b/apptrust/commands/version/update_app_version_cmd.go @@ -3,6 +3,12 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -11,9 +17,11 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/service" "github.com/jfrog/jfrog-cli-application/apptrust/service/versions" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -24,6 +32,7 @@ type updateAppVersionCommand struct { applicationKey string version string requestPayload *model.UpdateAppVersionRequest + responseBody []byte } func (uv *updateAppVersionCommand) Run() error { @@ -33,7 +42,7 @@ func (uv *updateAppVersionCommand) Run() error { return err } - err = uv.versionService.UpdateAppVersion(ctx, uv.applicationKey, uv.version, uv.requestPayload) + uv.responseBody, err = uv.versionService.UpdateAppVersion(ctx, uv.applicationKey, uv.version, uv.requestPayload) if err != nil { log.Error("Failed to update application version:", err) return err @@ -65,7 +74,16 @@ func (uv *updateAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } - return commonCLiCommands.Exec(uv) + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(uv); err != nil { + return err + } + + return printUpdateAppVersionResponse(uv.responseBody, outputFormat, os.Stdout) } // parseFlagsAndSetFields parses CLI flags and sets struct fields accordingly. @@ -106,6 +124,56 @@ func (uv *updateAppVersionCommand) buildRequestPayload(ctx *components.Context) return request, nil } +// orderedUpdateAppVersionKeys defines the display order for version-update table output. +var orderedUpdateAppVersionKeys = []string{ + "application_key", + "version", + "status", + "current_stage", + "tag", +} + +// printUpdateAppVersionResponse formats and prints the update-app-version response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printUpdateAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printUpdateAppVersionTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printUpdateAppVersionTable renders the update-app-version response as a FIELD/VALUE table. +func printUpdateAppVersionTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse update response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedUpdateAppVersionKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() +} + func GetUpdateAppVersionCommand(appContext app.Context) components.Command { cmd := &updateAppVersionCommand{versionService: appContext.GetVersionService()} return components.Command{ @@ -125,7 +193,8 @@ func GetUpdateAppVersionCommand(appContext app.Context) components.Command { Optional: false, }, }, - Flags: commands.GetCommandFlags(commands.VersionUpdate), - Action: cmd.prepareAndRunCommand, + Flags: commands.GetCommandFlags(commands.VersionUpdate), + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/update_app_version_cmd_test.go b/apptrust/commands/version/update_app_version_cmd_test.go index 63e8657..4bfaf45 100644 --- a/apptrust/commands/version/update_app_version_cmd_test.go +++ b/apptrust/commands/version/update_app_version_cmd_test.go @@ -1,6 +1,8 @@ package version import ( + "bytes" + "encoding/json" "errors" "testing" @@ -9,6 +11,7 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -48,10 +51,10 @@ func TestUpdateAppVersionCommand_Run(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) if tt.shouldError { mockVersionService.EXPECT().UpdateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(errors.New(tt.errorMessage)).Times(1) + Return(nil, errors.New(tt.errorMessage)).Times(1) } else { mockVersionService.EXPECT().UpdateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(nil).Times(1) + Return(nil, nil).Times(1) } cmd := &updateAppVersionCommand{ @@ -209,9 +212,9 @@ func TestUpdateAppVersionCommand_FlagsSuite(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) if !tt.expectsError { mockVersionService.EXPECT().UpdateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, _ string, _ string, req *model.UpdateAppVersionRequest) error { + DoAndReturn(func(_ interface{}, _ string, _ string, req *model.UpdateAppVersionRequest) ([]byte, error) { actualPayload = req - return nil + return nil, nil }).Times(1) } @@ -232,3 +235,65 @@ func TestUpdateAppVersionCommand_FlagsSuite(t *testing.T) { }) } } + +// --- printUpdateAppVersionResponse tests --- + +const sampleUpdateAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"dev","tag":"release-tag"}` + +func TestPrintUpdateAppVersionResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppVersionResponse([]byte(sampleUpdateAppVersionJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleUpdateAppVersionJSON), &parsed)) +} + +func TestPrintUpdateAppVersionResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppVersionResponse([]byte(sampleUpdateAppVersionJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.Contains(t, output, "status") + assert.Contains(t, output, "COMPLETED") + assert.Contains(t, output, "current_stage") + assert.Contains(t, output, "dev") + assert.Contains(t, output, "tag") + assert.Contains(t, output, "release-tag") +} + +func TestPrintUpdateAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key and version are present — other fields must be absent from output. + payload := `{"application_key":"my-app","version":"1.0.0"}` + var buf bytes.Buffer + err := printUpdateAppVersionResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.NotContains(t, output, "status") + assert.NotContains(t, output, "current_stage") + assert.NotContains(t, output, "tag") +} + +func TestPrintUpdateAppVersionResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printUpdateAppVersionResponse([]byte(sampleUpdateAppVersionJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintUpdateAppVersionResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse update response") +} diff --git a/apptrust/service/versions/mocks/version_service_mock.go b/apptrust/service/versions/mocks/version_service_mock.go index 26e575d..48eab91 100644 --- a/apptrust/service/versions/mocks/version_service_mock.go +++ b/apptrust/service/versions/mocks/version_service_mock.go @@ -115,11 +115,12 @@ func (mr *MockVersionServiceMockRecorder) RollbackAppVersion(ctx, applicationKey } // UpdateAppVersion mocks base method. -func (m *MockVersionService) UpdateAppVersion(ctx service.Context, applicationKey, version string, request *model.UpdateAppVersionRequest) error { +func (m *MockVersionService) UpdateAppVersion(ctx service.Context, applicationKey, version string, request *model.UpdateAppVersionRequest) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateAppVersion", ctx, applicationKey, version, request) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // UpdateAppVersion indicates an expected call of UpdateAppVersion. diff --git a/apptrust/service/versions/version_service.go b/apptrust/service/versions/version_service.go index 9fa0c83..309e29d 100644 --- a/apptrust/service/versions/version_service.go +++ b/apptrust/service/versions/version_service.go @@ -20,7 +20,7 @@ type VersionService interface { ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) ([]byte, error) RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) error DeleteAppVersion(ctx service.Context, applicationKey string, version string) error - UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) error + UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) ([]byte, error) UpdateAppVersionSources(ctx service.Context, applicationKey string, version string, request *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) error } @@ -109,20 +109,20 @@ func (vs *versionService) DeleteAppVersion(ctx service.Context, applicationKey, return nil } -func (vs *versionService) UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) error { +func (vs *versionService) UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s", applicationKey, version) response, responseBody, err := ctx.GetHttpClient().Patch(endpoint, request, nil) if err != nil { - return err + return nil, err } if response.StatusCode != http.StatusOK { - return fmt.Errorf("failed to update app version. Status code: %d. \n%s", + return nil, fmt.Errorf("failed to update app version. Status code: %d. \n%s", response.StatusCode, responseBody) } log.Info("Application version updated successfully.") - return nil + return responseBody, nil } func (vs *versionService) UpdateAppVersionSources(ctx service.Context, applicationKey string, version string, request *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) error { diff --git a/apptrust/service/versions/version_service_test.go b/apptrust/service/versions/version_service_test.go index 7b1295b..26eda89 100644 --- a/apptrust/service/versions/version_service_test.go +++ b/apptrust/service/versions/version_service_test.go @@ -445,7 +445,7 @@ func TestUpdateAppVersion(t *testing.T) { mockCtx := mockservice.NewMockContext(ctrl) mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).AnyTimes() - err := service.UpdateAppVersion(mockCtx, "test-app", "1.0.0", tt.request) + _, err := service.UpdateAppVersion(mockCtx, "test-app", "1.0.0", tt.request) if tt.expectError { assert.Error(t, err) if tt.errorMsg != "" { From 17c583616fd5bc02a41b48deb361c13528cdef67 Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:46:54 +0200 Subject: [PATCH 07/14] JGC-481 - Add --format flag support to package-bind --- apptrust/commands/package/bind_package_cmd.go | 79 ++++++++++++++++++- .../commands/package/bind_package_cmd_test.go | 69 +++++++++++++++- .../packages/mocks/package_service_mock.go | 7 +- apptrust/service/packages/package_service.go | 11 ++- .../service/packages/package_service_test.go | 2 +- 5 files changed, 152 insertions(+), 16 deletions(-) diff --git a/apptrust/commands/package/bind_package_cmd.go b/apptrust/commands/package/bind_package_cmd.go index 75ff2e8..9c7d0a0 100644 --- a/apptrust/commands/package/bind_package_cmd.go +++ b/apptrust/commands/package/bind_package_cmd.go @@ -1,6 +1,12 @@ package packagecmds import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -9,9 +15,12 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/service" "github.com/jfrog/jfrog-cli-application/apptrust/service/packages" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/log" ) type bindPackageCommand struct { @@ -19,6 +28,7 @@ type bindPackageCommand struct { serverDetails *coreConfig.ServerDetails applicationKey string requestPayload *model.BindPackageRequest + responseBody []byte } func (bp *bindPackageCommand) Run() error { @@ -26,7 +36,8 @@ func (bp *bindPackageCommand) Run() error { if err != nil { return err } - return bp.packageService.BindPackage(ctx, bp.applicationKey, bp.requestPayload) + bp.responseBody, err = bp.packageService.BindPackage(ctx, bp.applicationKey, bp.requestPayload) + return err } func (bp *bindPackageCommand) ServerDetails() (*coreConfig.ServerDetails, error) { @@ -49,7 +60,16 @@ func (bp *bindPackageCommand) prepareAndRunCommand(ctx *components.Context) erro } bp.extractFromArgs(ctx) - return commonCLiCommands.Exec(bp) + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(bp); err != nil { + return err + } + + return printBindPackageResponse(bp.responseBody, outputFormat, os.Stdout) } func (bp *bindPackageCommand) extractFromArgs(ctx *components.Context) { @@ -65,6 +85,56 @@ func (bp *bindPackageCommand) extractFromArgs(ctx *components.Context) { } } +// orderedBindPackageKeys defines the display order for package-bind table output. +var orderedBindPackageKeys = []string{ + "application_key", + "package_type", + "package_name", + "package_version", + "status", +} + +// printBindPackageResponse formats and prints the bind-package response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printBindPackageResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printBindPackageTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printBindPackageTable renders the bind-package response as a FIELD/VALUE table. +func printBindPackageTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse bind-package response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedBindPackageKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() +} + func GetBindPackageCommand(appContext app.Context) components.Command { cmd := &bindPackageCommand{packageService: appContext.GetPackageService()} return components.Command{ @@ -90,7 +160,8 @@ func GetBindPackageCommand(appContext app.Context) components.Command { Description: "Package version.", }, }, - Flags: commands.GetCommandFlags(commands.PackageBind), - Action: cmd.prepareAndRunCommand, + Flags: commands.GetCommandFlags(commands.PackageBind), + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/package/bind_package_cmd_test.go b/apptrust/commands/package/bind_package_cmd_test.go index 3cbaa02..ec6582f 100644 --- a/apptrust/commands/package/bind_package_cmd_test.go +++ b/apptrust/commands/package/bind_package_cmd_test.go @@ -1,11 +1,14 @@ package packagecmds import ( + "bytes" + "encoding/json" "errors" "testing" "github.com/jfrog/jfrog-cli-application/apptrust/model" mockpackages "github.com/jfrog/jfrog-cli-application/apptrust/service/packages/mocks" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -25,7 +28,7 @@ func TestBindPackageCommand_Run(t *testing.T) { mockPackageService := mockpackages.NewMockPackageService(ctrl) mockPackageService.EXPECT().BindPackage(gomock.Any(), applicationKey, requestPayload). - Return(nil).Times(1) + Return(nil, nil).Times(1) cmd := &bindPackageCommand{ packageService: mockPackageService, @@ -52,7 +55,7 @@ func TestBindPackageCommand_Run_Error(t *testing.T) { mockPackageService := mockpackages.NewMockPackageService(ctrl) mockPackageService.EXPECT().BindPackage(gomock.Any(), applicationKey, requestPayload). - Return(errors.New("bind error")).Times(1) + Return(nil, errors.New("bind error")).Times(1) cmd := &bindPackageCommand{ packageService: mockPackageService, @@ -65,3 +68,65 @@ func TestBindPackageCommand_Run_Error(t *testing.T) { assert.Error(t, err) assert.Equal(t, "bind error", err.Error()) } + +// --- printBindPackageResponse tests --- + +const sampleBindPackageJSON = `{"application_key":"my-app","package_type":"npm","package_name":"my-package","package_version":"1.0.0","status":"bound"}` + +func TestPrintBindPackageResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printBindPackageResponse([]byte(sampleBindPackageJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleBindPackageJSON), &parsed)) +} + +func TestPrintBindPackageResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printBindPackageResponse([]byte(sampleBindPackageJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "package_type") + assert.Contains(t, output, "npm") + assert.Contains(t, output, "package_name") + assert.Contains(t, output, "my-package") + assert.Contains(t, output, "package_version") + assert.Contains(t, output, "1.0.0") + assert.Contains(t, output, "status") + assert.Contains(t, output, "bound") +} + +func TestPrintBindPackageResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key and package_type are present — other fields must be absent. + payload := `{"application_key":"my-app","package_type":"npm"}` + var buf bytes.Buffer + err := printBindPackageResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "package_type") + assert.Contains(t, output, "npm") + assert.NotContains(t, output, "package_name") + assert.NotContains(t, output, "package_version") + assert.NotContains(t, output, "status") +} + +func TestPrintBindPackageResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printBindPackageResponse([]byte(sampleBindPackageJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintBindPackageResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printBindPackageResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse bind-package response") +} diff --git a/apptrust/service/packages/mocks/package_service_mock.go b/apptrust/service/packages/mocks/package_service_mock.go index f7a28ed..29f8c43 100644 --- a/apptrust/service/packages/mocks/package_service_mock.go +++ b/apptrust/service/packages/mocks/package_service_mock.go @@ -42,11 +42,12 @@ func (m *MockPackageService) EXPECT() *MockPackageServiceMockRecorder { } // BindPackage mocks base method. -func (m *MockPackageService) BindPackage(ctx service.Context, applicationKey string, request *model.BindPackageRequest) error { +func (m *MockPackageService) BindPackage(ctx service.Context, applicationKey string, request *model.BindPackageRequest) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BindPackage", ctx, applicationKey, request) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // BindPackage indicates an expected call of BindPackage. diff --git a/apptrust/service/packages/package_service.go b/apptrust/service/packages/package_service.go index a8546e4..6d0093f 100644 --- a/apptrust/service/packages/package_service.go +++ b/apptrust/service/packages/package_service.go @@ -13,7 +13,7 @@ import ( ) type PackageService interface { - BindPackage(ctx service.Context, applicationKey string, request *model.BindPackageRequest) error + BindPackage(ctx service.Context, applicationKey string, request *model.BindPackageRequest) ([]byte, error) UnbindPackage(ctx service.Context, applicationKey, pkgType, pkgName, pkgVersion string) error } @@ -23,21 +23,20 @@ func NewPackageService() PackageService { return &packageService{} } -func (ps *packageService) BindPackage(ctx service.Context, applicationKey string, request *model.BindPackageRequest) error { +func (ps *packageService) BindPackage(ctx service.Context, applicationKey string, request *model.BindPackageRequest) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s/packages", applicationKey) response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, nil) if err != nil { - return err + return nil, err } if response.StatusCode != http.StatusCreated { - return fmt.Errorf("failed to bind package. Status code: %d.\n%s", + return nil, fmt.Errorf("failed to bind package. Status code: %d.\n%s", response.StatusCode, responseBody) } log.Info("Package bound successfully.") - log.Output(string(responseBody)) - return nil + return responseBody, nil } func (ps *packageService) UnbindPackage(ctx service.Context, applicationKey, pkgType, pkgName, pkgVersion string) error { diff --git a/apptrust/service/packages/package_service_test.go b/apptrust/service/packages/package_service_test.go index afa2bba..a67c825 100644 --- a/apptrust/service/packages/package_service_test.go +++ b/apptrust/service/packages/package_service_test.go @@ -73,7 +73,7 @@ func TestBindPackage(t *testing.T) { mockCtx := mockservice.NewMockContext(ctrl) mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1) - err := service.BindPackage(mockCtx, applicationKey, tt.request) + _, err := service.BindPackage(mockCtx, applicationKey, tt.request) if tt.expectedError == "" { assert.NoError(t, err) } else { From 6216fb9ea64bdb1d6e383dc8ab3f2e2f9cbd69bb Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:51:16 +0200 Subject: [PATCH 08/14] JGC-481 - Add --format flag support to version-update-sources --- .../version/update_app_version_sources_cmd.go | 77 ++++++++++++++++++- .../update_app_version_sources_cmd_test.go | 73 +++++++++++++++++- .../versions/mocks/version_service_mock.go | 7 +- apptrust/service/versions/version_service.go | 11 ++- .../service/versions/version_service_test.go | 2 +- 5 files changed, 152 insertions(+), 18 deletions(-) diff --git a/apptrust/commands/version/update_app_version_sources_cmd.go b/apptrust/commands/version/update_app_version_sources_cmd.go index 3a70187..9ac8fca 100644 --- a/apptrust/commands/version/update_app_version_sources_cmd.go +++ b/apptrust/commands/version/update_app_version_sources_cmd.go @@ -1,6 +1,12 @@ package version import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -9,9 +15,11 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/service" "github.com/jfrog/jfrog-cli-application/apptrust/service/versions" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -25,6 +33,7 @@ type updateAppVersionSourcesCommand struct { sync bool dryRun bool failFast bool + responseBody []byte } func (cmd *updateAppVersionSourcesCommand) Run() error { @@ -34,7 +43,7 @@ func (cmd *updateAppVersionSourcesCommand) Run() error { return err } - err = cmd.versionService.UpdateAppVersionSources(ctx, cmd.applicationKey, cmd.version, cmd.requestPayload, cmd.sync, cmd.dryRun, cmd.failFast) + cmd.responseBody, err = cmd.versionService.UpdateAppVersionSources(ctx, cmd.applicationKey, cmd.version, cmd.requestPayload, cmd.sync, cmd.dryRun, cmd.failFast) if err != nil { log.Error("Failed to update application version sources:", err) return err @@ -66,7 +75,16 @@ func (cmd *updateAppVersionSourcesCommand) prepareAndRunCommand(ctx *components. return err } - return commonCLiCommands.Exec(cmd) + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(cmd); err != nil { + return err + } + + return printUpdateAppVersionSourcesResponse(cmd.responseBody, outputFormat, os.Stdout) } func validateUpdateSourcesContext(ctx *components.Context) error { @@ -109,6 +127,56 @@ func (cmd *updateAppVersionSourcesCommand) buildRequestPayload(ctx *components.C }, nil } +// orderedUpdateAppVersionSourcesKeys defines the display order for version-update-sources table output. +var orderedUpdateAppVersionSourcesKeys = []string{ + "application_key", + "version", + "status", + "current_stage", + "tag", +} + +// printUpdateAppVersionSourcesResponse formats and prints the update-app-version-sources response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printUpdateAppVersionSourcesResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printUpdateAppVersionSourcesTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printUpdateAppVersionSourcesTable renders the update-app-version-sources response as a FIELD/VALUE table. +func printUpdateAppVersionSourcesTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse update sources response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedUpdateAppVersionSourcesKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() +} + func GetUpdateAppVersionSourcesCommand(appContext app.Context) components.Command { cmd := &updateAppVersionSourcesCommand{versionService: appContext.GetVersionService()} return components.Command{ @@ -128,7 +196,8 @@ func GetUpdateAppVersionSourcesCommand(appContext app.Context) components.Comman Optional: false, }, }, - Flags: commands.GetCommandFlags(commands.VersionUpdateSources), - Action: cmd.prepareAndRunCommand, + Flags: commands.GetCommandFlags(commands.VersionUpdateSources), + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/update_app_version_sources_cmd_test.go b/apptrust/commands/version/update_app_version_sources_cmd_test.go index a7116ce..cc3b9f2 100644 --- a/apptrust/commands/version/update_app_version_sources_cmd_test.go +++ b/apptrust/commands/version/update_app_version_sources_cmd_test.go @@ -1,6 +1,8 @@ package version import ( + "bytes" + "encoding/json" "errors" "testing" @@ -9,6 +11,7 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -53,10 +56,10 @@ func TestUpdateAppVersionSourcesCommand_Run(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) if tt.shouldError { mockVersionService.EXPECT().UpdateAppVersionSources(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(errors.New(tt.errorMessage)).Times(1) + Return(nil, errors.New(tt.errorMessage)).Times(1) } else { mockVersionService.EXPECT().UpdateAppVersionSources(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(nil).Times(1) + Return(nil, nil).Times(1) } cmd := &updateAppVersionSourcesCommand{ @@ -243,12 +246,12 @@ func TestUpdateAppVersionSourcesCommand_SourceFlagsSuite(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) if !tt.expectsError { mockVersionService.EXPECT().UpdateAppVersionSources(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, _ string, _ string, req *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) error { + DoAndReturn(func(_ interface{}, _ string, _ string, req *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) ([]byte, error) { actualPayload = req capturedSync = sync capturedDryRun = dryRun capturedFailFast = failFast - return nil + return nil, nil }).Times(1) } @@ -272,3 +275,65 @@ func TestUpdateAppVersionSourcesCommand_SourceFlagsSuite(t *testing.T) { }) } } + +// --- printUpdateAppVersionSourcesResponse tests --- + +const sampleUpdateAppVersionSourcesJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"dev","tag":"release-tag"}` + +func TestPrintUpdateAppVersionSourcesResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppVersionSourcesResponse([]byte(sampleUpdateAppVersionSourcesJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleUpdateAppVersionSourcesJSON), &parsed)) +} + +func TestPrintUpdateAppVersionSourcesResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppVersionSourcesResponse([]byte(sampleUpdateAppVersionSourcesJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.Contains(t, output, "status") + assert.Contains(t, output, "COMPLETED") + assert.Contains(t, output, "current_stage") + assert.Contains(t, output, "dev") + assert.Contains(t, output, "tag") + assert.Contains(t, output, "release-tag") +} + +func TestPrintUpdateAppVersionSourcesResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key and version are present — other fields must be absent from output. + payload := `{"application_key":"my-app","version":"1.0.0"}` + var buf bytes.Buffer + err := printUpdateAppVersionSourcesResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.0.0") + assert.NotContains(t, output, "status") + assert.NotContains(t, output, "current_stage") + assert.NotContains(t, output, "tag") +} + +func TestPrintUpdateAppVersionSourcesResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printUpdateAppVersionSourcesResponse([]byte(sampleUpdateAppVersionSourcesJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintUpdateAppVersionSourcesResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printUpdateAppVersionSourcesResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse update sources response") +} diff --git a/apptrust/service/versions/mocks/version_service_mock.go b/apptrust/service/versions/mocks/version_service_mock.go index 48eab91..0a7045d 100644 --- a/apptrust/service/versions/mocks/version_service_mock.go +++ b/apptrust/service/versions/mocks/version_service_mock.go @@ -130,11 +130,12 @@ func (mr *MockVersionServiceMockRecorder) UpdateAppVersion(ctx, applicationKey, } // UpdateAppVersionSources mocks base method. -func (m *MockVersionService) UpdateAppVersionSources(ctx service.Context, applicationKey, version string, request *model.UpdateVersionSourcesRequest, sync, dryRun, failFast bool) error { +func (m *MockVersionService) UpdateAppVersionSources(ctx service.Context, applicationKey, version string, request *model.UpdateVersionSourcesRequest, sync, dryRun, failFast bool) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateAppVersionSources", ctx, applicationKey, version, request, sync, dryRun, failFast) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // UpdateAppVersionSources indicates an expected call of UpdateAppVersionSources. diff --git a/apptrust/service/versions/version_service.go b/apptrust/service/versions/version_service.go index 309e29d..35186cc 100644 --- a/apptrust/service/versions/version_service.go +++ b/apptrust/service/versions/version_service.go @@ -21,7 +21,7 @@ type VersionService interface { RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) error DeleteAppVersion(ctx service.Context, applicationKey string, version string) error UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) ([]byte, error) - UpdateAppVersionSources(ctx service.Context, applicationKey string, version string, request *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) error + UpdateAppVersionSources(ctx service.Context, applicationKey string, version string, request *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) ([]byte, error) } type versionService struct{} @@ -125,7 +125,7 @@ func (vs *versionService) UpdateAppVersion(ctx service.Context, applicationKey s return responseBody, nil } -func (vs *versionService) UpdateAppVersionSources(ctx service.Context, applicationKey string, version string, request *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) error { +func (vs *versionService) UpdateAppVersionSources(ctx service.Context, applicationKey string, version string, request *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s", applicationKey, version) params := map[string]string{ @@ -136,7 +136,7 @@ func (vs *versionService) UpdateAppVersionSources(ctx service.Context, applicati response, responseBody, err := ctx.GetHttpClient().Patch(endpoint, request, params) if err != nil { - return err + return nil, err } expectedStatusCode := http.StatusOK @@ -145,13 +145,12 @@ func (vs *versionService) UpdateAppVersionSources(ctx service.Context, applicati } if response.StatusCode != expectedStatusCode { - return fmt.Errorf("failed to update app version sources. Status code: %d. \n%s", + return nil, fmt.Errorf("failed to update app version sources. Status code: %d. \n%s", response.StatusCode, responseBody) } log.Info("Application version sources updated successfully.") - log.Output(string(responseBody)) - return nil + return responseBody, nil } func logSuccessMessage(sync bool, request *model.CreateAppVersionRequest, dryRun bool) { diff --git a/apptrust/service/versions/version_service_test.go b/apptrust/service/versions/version_service_test.go index 26eda89..3480400 100644 --- a/apptrust/service/versions/version_service_test.go +++ b/apptrust/service/versions/version_service_test.go @@ -649,7 +649,7 @@ func TestUpdateAppVersionSources(t *testing.T) { mockCtx := mockservice.NewMockContext(ctrl) mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).AnyTimes() - err := service.UpdateAppVersionSources(mockCtx, "test-app", "1.0.0", tt.request, tt.sync, tt.dryRun, tt.failFast) + _, err := service.UpdateAppVersionSources(mockCtx, "test-app", "1.0.0", tt.request, tt.sync, tt.dryRun, tt.failFast) if tt.expectError { assert.Error(t, err) if tt.errorMsg != "" { From e9c98b348ea365f900e2e02690a8ad97de199ff9 Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 15:58:51 +0200 Subject: [PATCH 09/14] JGC-481 - Add --format flag support to version-rollback --- .../version/rollback_app_version_cmd.go | 79 ++++++++++++++++++- .../version/rollback_app_version_cmd_test.go | 67 +++++++++++++++- .../versions/mocks/version_service_mock.go | 7 +- apptrust/service/versions/version_service.go | 11 ++- .../service/versions/version_service_test.go | 2 +- 5 files changed, 151 insertions(+), 15 deletions(-) diff --git a/apptrust/commands/version/rollback_app_version_cmd.go b/apptrust/commands/version/rollback_app_version_cmd.go index 43859f8..6b89205 100644 --- a/apptrust/commands/version/rollback_app_version_cmd.go +++ b/apptrust/commands/version/rollback_app_version_cmd.go @@ -3,6 +3,12 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( + "encoding/json" + "fmt" + "io" + "os" + "text/tabwriter" + "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -11,9 +17,12 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/service" "github.com/jfrog/jfrog-cli-application/apptrust/service/versions" commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientUtils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/log" ) type rollbackAppVersionCommand struct { @@ -24,6 +33,7 @@ type rollbackAppVersionCommand struct { requestPayload *model.RollbackAppVersionRequest fromStage string sync bool + responseBody []byte } func (rv *rollbackAppVersionCommand) Run() error { @@ -32,7 +42,8 @@ func (rv *rollbackAppVersionCommand) Run() error { return err } - return rv.versionService.RollbackAppVersion(ctx, rv.applicationKey, rv.version, rv.requestPayload, rv.sync) + rv.responseBody, err = rv.versionService.RollbackAppVersion(ctx, rv.applicationKey, rv.version, rv.requestPayload, rv.sync) + return err } func (rv *rollbackAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) { @@ -61,7 +72,66 @@ func (rv *rollbackAppVersionCommand) prepareAndRunCommand(ctx *components.Contex rv.serverDetails = serverDetails rv.requestPayload = model.NewRollbackAppVersionRequest(rv.fromStage) - return commonCLiCommands.Exec(rv) + outputFormat, err := ctx.GetOutputFormat() + if err != nil { + return err + } + + if err = commonCLiCommands.Exec(rv); err != nil { + return err + } + + return printRollbackAppVersionResponse(rv.responseBody, outputFormat, os.Stdout) +} + +// orderedRollbackAppVersionKeys defines the display order for version-rollback table output. +var orderedRollbackAppVersionKeys = []string{ + "application_key", + "version", + "project_key", + "rollback_from_stage", + "rollback_to_stage", +} + +// printRollbackAppVersionResponse formats and prints the rollback-app-version response. +// When outputFormat is Table it renders a FIELD/VALUE table; when Json it +// pretty-prints the raw JSON; when None (flag absent) it falls back to the +// previous log.Output behaviour for backward-compatibility. +func printRollbackAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return printRollbackAppVersionTable(data, w) + default: + // No --format flag provided: preserve the original output behaviour. + log.Output(string(data)) + return nil + } +} + +// printRollbackAppVersionTable renders the rollback-app-version response as a FIELD/VALUE table. +func printRollbackAppVersionTable(data []byte, w io.Writer) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse rollback response: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedRollbackAppVersionKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() } func GetRollbackAppVersionCommand(appContext app.Context) components.Command { @@ -85,7 +155,8 @@ func GetRollbackAppVersionCommand(appContext app.Context) components.Command { Optional: false, }, }, - Flags: commands.GetCommandFlags(commands.VersionRollback), - Action: cmd.prepareAndRunCommand, + Flags: commands.GetCommandFlags(commands.VersionRollback), + SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/rollback_app_version_cmd_test.go b/apptrust/commands/version/rollback_app_version_cmd_test.go index 363e72a..993eda7 100644 --- a/apptrust/commands/version/rollback_app_version_cmd_test.go +++ b/apptrust/commands/version/rollback_app_version_cmd_test.go @@ -1,6 +1,8 @@ package version import ( + "bytes" + "encoding/json" "errors" "testing" @@ -8,6 +10,7 @@ import ( "go.uber.org/mock/gomock" "github.com/jfrog/jfrog-cli-application/apptrust/model" + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" ) @@ -63,7 +66,7 @@ func TestRollbackAppVersionCommand_Run(t *testing.T) { mockVersionService := mockversions.NewMockVersionService(ctrl) mockVersionService.EXPECT().RollbackAppVersion(gomock.Any(), tt.applicationKey, tt.version, requestPayload, tt.sync). - Return(tt.mockError).Times(1) + Return(nil, tt.mockError).Times(1) cmd := &rollbackAppVersionCommand{ versionService: mockVersionService, @@ -88,3 +91,65 @@ func TestRollbackAppVersionCommand_Run(t *testing.T) { }) } } + +// --- printRollbackAppVersionResponse tests --- + +const sampleRollbackAppVersionJSON = `{"application_key":"my-app","version":"1.5.0","project_key":"proj-1","rollback_from_stage":"prod","rollback_to_stage":"qa"}` + +func TestPrintRollbackAppVersionResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := printRollbackAppVersionResponse([]byte(sampleRollbackAppVersionJSON), coreformat.Json, &buf) + assert.NoError(t, err) + // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleRollbackAppVersionJSON), &parsed)) +} + +func TestPrintRollbackAppVersionResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := printRollbackAppVersionResponse([]byte(sampleRollbackAppVersionJSON), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.5.0") + assert.Contains(t, output, "project_key") + assert.Contains(t, output, "proj-1") + assert.Contains(t, output, "rollback_from_stage") + assert.Contains(t, output, "prod") + assert.Contains(t, output, "rollback_to_stage") + assert.Contains(t, output, "qa") +} + +func TestPrintRollbackAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { + // Only application_key and version are present — other fields must be absent from output. + payload := `{"application_key":"my-app","version":"1.5.0"}` + var buf bytes.Buffer + err := printRollbackAppVersionResponse([]byte(payload), coreformat.Table, &buf) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "application_key") + assert.Contains(t, output, "my-app") + assert.Contains(t, output, "version") + assert.Contains(t, output, "1.5.0") + assert.NotContains(t, output, "project_key") + assert.NotContains(t, output, "rollback_from_stage") + assert.NotContains(t, output, "rollback_to_stage") +} + +func TestPrintRollbackAppVersionResponse_None_BackwardCompat(t *testing.T) { + // When outputFormat is None (no flag set), the function must not error. + var buf bytes.Buffer + err := printRollbackAppVersionResponse([]byte(sampleRollbackAppVersionJSON), coreformat.None, &buf) + assert.NoError(t, err) +} + +func TestPrintRollbackAppVersionResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := printRollbackAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse rollback response") +} diff --git a/apptrust/service/versions/mocks/version_service_mock.go b/apptrust/service/versions/mocks/version_service_mock.go index 0a7045d..4cd178b 100644 --- a/apptrust/service/versions/mocks/version_service_mock.go +++ b/apptrust/service/versions/mocks/version_service_mock.go @@ -101,11 +101,12 @@ func (mr *MockVersionServiceMockRecorder) ReleaseAppVersion(ctx, applicationKey, } // RollbackAppVersion mocks base method. -func (m *MockVersionService) RollbackAppVersion(ctx service.Context, applicationKey, version string, request *model.RollbackAppVersionRequest, sync bool) error { +func (m *MockVersionService) RollbackAppVersion(ctx service.Context, applicationKey, version string, request *model.RollbackAppVersionRequest, sync bool) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RollbackAppVersion", ctx, applicationKey, version, request, sync) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 } // RollbackAppVersion indicates an expected call of RollbackAppVersion. diff --git a/apptrust/service/versions/version_service.go b/apptrust/service/versions/version_service.go index 35186cc..298544f 100644 --- a/apptrust/service/versions/version_service.go +++ b/apptrust/service/versions/version_service.go @@ -18,7 +18,7 @@ type VersionService interface { CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) ([]byte, error) ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) ([]byte, error) - RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) error + RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) ([]byte, error) DeleteAppVersion(ctx service.Context, applicationKey string, version string) error UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) ([]byte, error) UpdateAppVersionSources(ctx service.Context, applicationKey string, version string, request *model.UpdateVersionSourcesRequest, sync bool, dryRun bool, failFast bool) ([]byte, error) @@ -77,20 +77,19 @@ func (vs *versionService) ReleaseAppVersion(ctx service.Context, applicationKey, return responseBody, nil } -func (vs *versionService) RollbackAppVersion(ctx service.Context, applicationKey, version string, request *model.RollbackAppVersionRequest, sync bool) error { +func (vs *versionService) RollbackAppVersion(ctx service.Context, applicationKey, version string, request *model.RollbackAppVersionRequest, sync bool) ([]byte, error) { endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s/rollback", applicationKey, version) response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{"async": strconv.FormatBool(!sync)}) if err != nil { - return err + return nil, err } if !apphttp.IsSuccessStatusCode(response.StatusCode) { - return fmt.Errorf("failed to rollback app version. Status code: %d. \n%s", + return nil, fmt.Errorf("failed to rollback app version. Status code: %d. \n%s", response.StatusCode, responseBody) } - log.Output(string(responseBody)) - return nil + return responseBody, nil } func (vs *versionService) DeleteAppVersion(ctx service.Context, applicationKey, version string) error { diff --git a/apptrust/service/versions/version_service_test.go b/apptrust/service/versions/version_service_test.go index 3480400..634e69a 100644 --- a/apptrust/service/versions/version_service_test.go +++ b/apptrust/service/versions/version_service_test.go @@ -517,7 +517,7 @@ func TestRollbackAppVersion(t *testing.T) { Return(&http.Response{StatusCode: tt.expectedStatus}, []byte(""), nil) service := NewVersionService() - err := service.RollbackAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload, tt.sync) + _, err := service.RollbackAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload, tt.sync) if tt.expectedError { assert.Error(t, err) From f21dcf4899668418233a4c89d0608905719c0017 Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 4 May 2026 16:15:15 +0200 Subject: [PATCH 10/14] JGC-481 - Extract common PrintJsonOrTableResponse and PrintTable helpers --- .../commands/application/create_app_cmd.go | 47 +------- .../application/create_app_cmd_test.go | 58 ---------- .../commands/application/update_app_cmd.go | 48 +------- .../application/update_app_cmd_test.go | 58 ---------- apptrust/commands/package/bind_package_cmd.go | 48 +------- .../commands/package/bind_package_cmd_test.go | 65 ----------- .../version/create_app_version_cmd.go | 48 +------- .../version/create_app_version_cmd_test.go | 65 ----------- .../version/promote_app_version_cmd.go | 48 +------- .../version/promote_app_version_cmd_test.go | 64 ----------- .../version/release_app_version_cmd.go | 48 +------- .../version/release_app_version_cmd_test.go | 62 ---------- .../version/rollback_app_version_cmd.go | 48 +------- .../version/rollback_app_version_cmd_test.go | 65 ----------- .../version/update_app_version_cmd.go | 47 +------- .../version/update_app_version_cmd_test.go | 65 ----------- .../version/update_app_version_sources_cmd.go | 47 +------- .../update_app_version_sources_cmd_test.go | 65 ----------- apptrust/common/output.go | 51 +++++++++ apptrust/common/output_test.go | 60 ++++++++++ go.mod | 8 +- go.sum | 108 +----------------- 22 files changed, 126 insertions(+), 1097 deletions(-) create mode 100644 apptrust/common/output.go create mode 100644 apptrust/common/output_test.go diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index d2081ed..d90aa17 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -2,10 +2,7 @@ package application import ( "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" @@ -17,10 +14,8 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -145,7 +140,7 @@ func (cac *createAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return printCreateAppResponse(cac.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(cac.responseBody, outputFormat, os.Stdout, orderedCreateAppKeys) } func validateCreateAppContext(ctx *components.Context) error { @@ -189,46 +184,6 @@ var orderedCreateAppKeys = []string{ "maturity_level", } -// printCreateAppResponse formats and prints the create-application response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printCreateAppResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printCreateAppTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printCreateAppTable renders the create-application response as a FIELD/VALUE table. -func printCreateAppTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse application response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedCreateAppKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func GetCreateAppCommand(appContext app.Context) components.Command { cmd := &createAppCommand{ diff --git a/apptrust/commands/application/create_app_cmd_test.go b/apptrust/commands/application/create_app_cmd_test.go index 4f2b999..f8a1d74 100644 --- a/apptrust/commands/application/create_app_cmd_test.go +++ b/apptrust/commands/application/create_app_cmd_test.go @@ -1,8 +1,6 @@ package application import ( - "bytes" - "encoding/json" "errors" "flag" "testing" @@ -11,7 +9,6 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/model" mockapps "github.com/jfrog/jfrog-cli-application/apptrust/service/applications/mocks" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -337,58 +334,3 @@ func TestCreateAppCommand_Error_SpecAndFlags(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "the flag --project is not allowed when --spec is provided") } - -// --- printCreateAppResponse tests --- - -const sampleAppJSON = `{"application_key":"my-app","application_name":"My App","project_key":"proj1","criticality":"high","maturity_level":"production"}` - -func TestPrintCreateAppResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printCreateAppResponse([]byte(sampleAppJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleAppJSON), &parsed)) -} - -func TestPrintCreateAppResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printCreateAppResponse([]byte(sampleAppJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "application_name") - assert.Contains(t, output, "My App") - assert.Contains(t, output, "project_key") - assert.Contains(t, output, "proj1") -} - -func TestPrintCreateAppResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key is present — other fields must be absent from output. - payload := `{"application_key":"only-key"}` - var buf bytes.Buffer - err := printCreateAppResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "only-key") - assert.NotContains(t, output, "application_name") - assert.NotContains(t, output, "project_key") -} - -func TestPrintCreateAppResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printCreateAppResponse([]byte(sampleAppJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintCreateAppResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printCreateAppResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse application response") -} diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index 9ba6575..c5ef0ed 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -1,11 +1,7 @@ package application import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" @@ -20,8 +16,6 @@ import ( coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/log" ) type updateAppCommand struct { @@ -89,7 +83,7 @@ func (uac *updateAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return printUpdateAppResponse(uac.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(uac.responseBody, outputFormat, os.Stdout, orderedUpdateAppKeys) } // orderedUpdateAppKeys defines the display order for app-update table output. @@ -102,46 +96,6 @@ var orderedUpdateAppKeys = []string{ "maturity_level", } -// printUpdateAppResponse formats and prints the update-application response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printUpdateAppResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printUpdateAppTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printUpdateAppTable renders the update-application response as a FIELD/VALUE table. -func printUpdateAppTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse application response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedUpdateAppKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func GetUpdateAppCommand(appContext app.Context) components.Command { cmd := &updateAppCommand{ diff --git a/apptrust/commands/application/update_app_cmd_test.go b/apptrust/commands/application/update_app_cmd_test.go index 5740944..d12e26b 100644 --- a/apptrust/commands/application/update_app_cmd_test.go +++ b/apptrust/commands/application/update_app_cmd_test.go @@ -1,8 +1,6 @@ package application import ( - "bytes" - "encoding/json" "errors" "flag" "testing" @@ -12,7 +10,6 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" mockapps "github.com/jfrog/jfrog-cli-application/apptrust/service/applications/mocks" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -368,58 +365,3 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { func stringPtr(s string) *string { return &s } - -// --- printUpdateAppResponse tests --- - -const sampleUpdateAppJSON = `{"application_key":"my-app","application_name":"My App","project_key":"proj1","criticality":"high","maturity_level":"production"}` - -func TestPrintUpdateAppResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppResponse([]byte(sampleUpdateAppJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleUpdateAppJSON), &parsed)) -} - -func TestPrintUpdateAppResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppResponse([]byte(sampleUpdateAppJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "application_name") - assert.Contains(t, output, "My App") - assert.Contains(t, output, "project_key") - assert.Contains(t, output, "proj1") -} - -func TestPrintUpdateAppResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key is present — other fields must be absent from output. - payload := `{"application_key":"only-key"}` - var buf bytes.Buffer - err := printUpdateAppResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "only-key") - assert.NotContains(t, output, "application_name") - assert.NotContains(t, output, "project_key") -} - -func TestPrintUpdateAppResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printUpdateAppResponse([]byte(sampleUpdateAppJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintUpdateAppResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse application response") -} diff --git a/apptrust/commands/package/bind_package_cmd.go b/apptrust/commands/package/bind_package_cmd.go index 9c7d0a0..64d9ce8 100644 --- a/apptrust/commands/package/bind_package_cmd.go +++ b/apptrust/commands/package/bind_package_cmd.go @@ -1,11 +1,7 @@ package packagecmds import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -19,8 +15,6 @@ import ( pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/log" ) type bindPackageCommand struct { @@ -69,7 +63,7 @@ func (bp *bindPackageCommand) prepareAndRunCommand(ctx *components.Context) erro return err } - return printBindPackageResponse(bp.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(bp.responseBody, outputFormat, os.Stdout, orderedBindPackageKeys) } func (bp *bindPackageCommand) extractFromArgs(ctx *components.Context) { @@ -94,46 +88,6 @@ var orderedBindPackageKeys = []string{ "status", } -// printBindPackageResponse formats and prints the bind-package response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printBindPackageResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printBindPackageTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printBindPackageTable renders the bind-package response as a FIELD/VALUE table. -func printBindPackageTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse bind-package response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedBindPackageKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func GetBindPackageCommand(appContext app.Context) components.Command { cmd := &bindPackageCommand{packageService: appContext.GetPackageService()} diff --git a/apptrust/commands/package/bind_package_cmd_test.go b/apptrust/commands/package/bind_package_cmd_test.go index ec6582f..06b9ccd 100644 --- a/apptrust/commands/package/bind_package_cmd_test.go +++ b/apptrust/commands/package/bind_package_cmd_test.go @@ -1,14 +1,11 @@ package packagecmds import ( - "bytes" - "encoding/json" "errors" "testing" "github.com/jfrog/jfrog-cli-application/apptrust/model" mockpackages "github.com/jfrog/jfrog-cli-application/apptrust/service/packages/mocks" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -68,65 +65,3 @@ func TestBindPackageCommand_Run_Error(t *testing.T) { assert.Error(t, err) assert.Equal(t, "bind error", err.Error()) } - -// --- printBindPackageResponse tests --- - -const sampleBindPackageJSON = `{"application_key":"my-app","package_type":"npm","package_name":"my-package","package_version":"1.0.0","status":"bound"}` - -func TestPrintBindPackageResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printBindPackageResponse([]byte(sampleBindPackageJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleBindPackageJSON), &parsed)) -} - -func TestPrintBindPackageResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printBindPackageResponse([]byte(sampleBindPackageJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "package_type") - assert.Contains(t, output, "npm") - assert.Contains(t, output, "package_name") - assert.Contains(t, output, "my-package") - assert.Contains(t, output, "package_version") - assert.Contains(t, output, "1.0.0") - assert.Contains(t, output, "status") - assert.Contains(t, output, "bound") -} - -func TestPrintBindPackageResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key and package_type are present — other fields must be absent. - payload := `{"application_key":"my-app","package_type":"npm"}` - var buf bytes.Buffer - err := printBindPackageResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "package_type") - assert.Contains(t, output, "npm") - assert.NotContains(t, output, "package_name") - assert.NotContains(t, output, "package_version") - assert.NotContains(t, output, "status") -} - -func TestPrintBindPackageResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printBindPackageResponse([]byte(sampleBindPackageJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintBindPackageResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printBindPackageResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse bind-package response") -} diff --git a/apptrust/commands/version/create_app_version_cmd.go b/apptrust/commands/version/create_app_version_cmd.go index 57d271a..89d79f5 100644 --- a/apptrust/commands/version/create_app_version_cmd.go +++ b/apptrust/commands/version/create_app_version_cmd.go @@ -1,11 +1,7 @@ package version import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" "github.com/jfrog/jfrog-cli-application/apptrust/service/versions" @@ -20,9 +16,7 @@ import ( pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/log" ) type createAppVersionCommand struct { @@ -77,7 +71,7 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } - return printCreateAppVersionResponse(cv.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(cv.responseBody, outputFormat, os.Stdout, orderedCreateAppVersionKeys) } // orderedCreateAppVersionKeys defines the display order for version-create table output. @@ -89,46 +83,6 @@ var orderedCreateAppVersionKeys = []string{ "tag", } -// printCreateAppVersionResponse formats and prints the create-app-version response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printCreateAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printCreateAppVersionTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printCreateAppVersionTable renders the create-app-version response as a FIELD/VALUE table. -func printCreateAppVersionTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse version response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedCreateAppVersionKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func (cv *createAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.CreateAppVersionRequest, error) { sources, filters, err := buildSourcesAndFiltersFromContext(ctx) diff --git a/apptrust/commands/version/create_app_version_cmd_test.go b/apptrust/commands/version/create_app_version_cmd_test.go index f19cda5..42d91ac 100644 --- a/apptrust/commands/version/create_app_version_cmd_test.go +++ b/apptrust/commands/version/create_app_version_cmd_test.go @@ -1,8 +1,6 @@ package version import ( - "bytes" - "encoding/json" "errors" "testing" @@ -11,7 +9,6 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -1670,65 +1667,3 @@ func TestBuildRequestPayload_Filters(t *testing.T) { }) } } - -// --- printCreateAppVersionResponse tests --- - -const sampleCreateAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"dev","tag":"release-tag"}` - -func TestPrintCreateAppVersionResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printCreateAppVersionResponse([]byte(sampleCreateAppVersionJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleCreateAppVersionJSON), &parsed)) -} - -func TestPrintCreateAppVersionResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printCreateAppVersionResponse([]byte(sampleCreateAppVersionJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.Contains(t, output, "status") - assert.Contains(t, output, "COMPLETED") - assert.Contains(t, output, "current_stage") - assert.Contains(t, output, "dev") - assert.Contains(t, output, "tag") - assert.Contains(t, output, "release-tag") -} - -func TestPrintCreateAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key and version are present — other fields must be absent from output. - payload := `{"application_key":"my-app","version":"1.0.0"}` - var buf bytes.Buffer - err := printCreateAppVersionResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.NotContains(t, output, "status") - assert.NotContains(t, output, "current_stage") - assert.NotContains(t, output, "tag") -} - -func TestPrintCreateAppVersionResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printCreateAppVersionResponse([]byte(sampleCreateAppVersionJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintCreateAppVersionResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printCreateAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse version response") -} diff --git a/apptrust/commands/version/promote_app_version_cmd.go b/apptrust/commands/version/promote_app_version_cmd.go index d480b63..e5c311d 100644 --- a/apptrust/commands/version/promote_app_version_cmd.go +++ b/apptrust/commands/version/promote_app_version_cmd.go @@ -3,11 +3,7 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -21,9 +17,7 @@ import ( pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/log" ) type promoteAppVersionCommand struct { @@ -85,7 +79,7 @@ func (pv *promoteAppVersionCommand) prepareAndRunCommand(ctx *components.Context return err } - return printPromoteAppVersionResponse(pv.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(pv.responseBody, outputFormat, os.Stdout, orderedPromoteAppVersionKeys) } // orderedPromoteAppVersionKeys defines the display order for version-promote table output. @@ -97,46 +91,6 @@ var orderedPromoteAppVersionKeys = []string{ "current_stage", } -// printPromoteAppVersionResponse formats and prints the promote-app-version response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printPromoteAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printPromoteAppVersionTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printPromoteAppVersionTable renders the promote-app-version response as a FIELD/VALUE table. -func printPromoteAppVersionTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse promote response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedPromoteAppVersionKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func (pv *promoteAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.PromoteAppVersionRequest, error) { stage := ctx.Arguments[2] diff --git a/apptrust/commands/version/promote_app_version_cmd_test.go b/apptrust/commands/version/promote_app_version_cmd_test.go index c56e433..e61da85 100644 --- a/apptrust/commands/version/promote_app_version_cmd_test.go +++ b/apptrust/commands/version/promote_app_version_cmd_test.go @@ -1,8 +1,6 @@ package version import ( - "bytes" - "encoding/json" "errors" "testing" @@ -10,7 +8,6 @@ import ( "go.uber.org/mock/gomock" "github.com/jfrog/jfrog-cli-application/apptrust/model" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" ) @@ -110,64 +107,3 @@ func TestPromoteAppVersionCommand_Run_Error(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "service error occurred") } - -// --- printPromoteAppVersionResponse tests --- - -const samplePromoteAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","target_stage":"prod","status":"COMPLETED","current_stage":"prod"}` - -func TestPrintPromoteAppVersionResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printPromoteAppVersionResponse([]byte(samplePromoteAppVersionJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(samplePromoteAppVersionJSON), &parsed)) -} - -func TestPrintPromoteAppVersionResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printPromoteAppVersionResponse([]byte(samplePromoteAppVersionJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.Contains(t, output, "target_stage") - assert.Contains(t, output, "prod") - assert.Contains(t, output, "status") - assert.Contains(t, output, "COMPLETED") - assert.Contains(t, output, "current_stage") -} - -func TestPrintPromoteAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key and version are present — other fields must be absent from output. - payload := `{"application_key":"my-app","version":"1.0.0"}` - var buf bytes.Buffer - err := printPromoteAppVersionResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.NotContains(t, output, "target_stage") - assert.NotContains(t, output, "status") - assert.NotContains(t, output, "current_stage") -} - -func TestPrintPromoteAppVersionResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printPromoteAppVersionResponse([]byte(samplePromoteAppVersionJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintPromoteAppVersionResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printPromoteAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse promote response") -} diff --git a/apptrust/commands/version/release_app_version_cmd.go b/apptrust/commands/version/release_app_version_cmd.go index 0430425..310352d 100644 --- a/apptrust/commands/version/release_app_version_cmd.go +++ b/apptrust/commands/version/release_app_version_cmd.go @@ -3,11 +3,7 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -21,9 +17,7 @@ import ( pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/log" ) type releaseAppVersionCommand struct { @@ -85,7 +79,7 @@ func (rv *releaseAppVersionCommand) prepareAndRunCommand(ctx *components.Context return err } - return printReleaseAppVersionResponse(rv.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(rv.responseBody, outputFormat, os.Stdout, orderedReleaseAppVersionKeys) } // orderedReleaseAppVersionKeys defines the display order for version-release table output. @@ -96,46 +90,6 @@ var orderedReleaseAppVersionKeys = []string{ "current_stage", } -// printReleaseAppVersionResponse formats and prints the release-app-version response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printReleaseAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printReleaseAppVersionTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printReleaseAppVersionTable renders the release-app-version response as a FIELD/VALUE table. -func printReleaseAppVersionTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse release response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedReleaseAppVersionKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func (rv *releaseAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.ReleaseAppVersionRequest, error) { promotionType, includedRepos, excludedRepos, err := BuildPromotionParams(ctx) diff --git a/apptrust/commands/version/release_app_version_cmd_test.go b/apptrust/commands/version/release_app_version_cmd_test.go index 356fbda..87f25bd 100644 --- a/apptrust/commands/version/release_app_version_cmd_test.go +++ b/apptrust/commands/version/release_app_version_cmd_test.go @@ -1,8 +1,6 @@ package version import ( - "bytes" - "encoding/json" "errors" "testing" @@ -10,7 +8,6 @@ import ( "go.uber.org/mock/gomock" "github.com/jfrog/jfrog-cli-application/apptrust/model" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" ) @@ -114,62 +111,3 @@ func TestReleaseAppVersionCommand_Run_Error(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "service error occurred") } - -// --- printReleaseAppVersionResponse tests --- - -const sampleReleaseAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"prod"}` - -func TestPrintReleaseAppVersionResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printReleaseAppVersionResponse([]byte(sampleReleaseAppVersionJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleReleaseAppVersionJSON), &parsed)) -} - -func TestPrintReleaseAppVersionResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printReleaseAppVersionResponse([]byte(sampleReleaseAppVersionJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.Contains(t, output, "status") - assert.Contains(t, output, "COMPLETED") - assert.Contains(t, output, "current_stage") - assert.Contains(t, output, "prod") -} - -func TestPrintReleaseAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key and version are present — other fields must be absent from output. - payload := `{"application_key":"my-app","version":"1.0.0"}` - var buf bytes.Buffer - err := printReleaseAppVersionResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.NotContains(t, output, "status") - assert.NotContains(t, output, "current_stage") -} - -func TestPrintReleaseAppVersionResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printReleaseAppVersionResponse([]byte(sampleReleaseAppVersionJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintReleaseAppVersionResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printReleaseAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse release response") -} diff --git a/apptrust/commands/version/rollback_app_version_cmd.go b/apptrust/commands/version/rollback_app_version_cmd.go index 6b89205..79c9961 100644 --- a/apptrust/commands/version/rollback_app_version_cmd.go +++ b/apptrust/commands/version/rollback_app_version_cmd.go @@ -3,11 +3,7 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -21,8 +17,6 @@ import ( pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/log" ) type rollbackAppVersionCommand struct { @@ -81,7 +75,7 @@ func (rv *rollbackAppVersionCommand) prepareAndRunCommand(ctx *components.Contex return err } - return printRollbackAppVersionResponse(rv.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(rv.responseBody, outputFormat, os.Stdout, orderedRollbackAppVersionKeys) } // orderedRollbackAppVersionKeys defines the display order for version-rollback table output. @@ -93,46 +87,6 @@ var orderedRollbackAppVersionKeys = []string{ "rollback_to_stage", } -// printRollbackAppVersionResponse formats and prints the rollback-app-version response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printRollbackAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printRollbackAppVersionTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printRollbackAppVersionTable renders the rollback-app-version response as a FIELD/VALUE table. -func printRollbackAppVersionTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse rollback response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedRollbackAppVersionKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func GetRollbackAppVersionCommand(appContext app.Context) components.Command { cmd := &rollbackAppVersionCommand{ diff --git a/apptrust/commands/version/rollback_app_version_cmd_test.go b/apptrust/commands/version/rollback_app_version_cmd_test.go index 993eda7..ff80173 100644 --- a/apptrust/commands/version/rollback_app_version_cmd_test.go +++ b/apptrust/commands/version/rollback_app_version_cmd_test.go @@ -1,8 +1,6 @@ package version import ( - "bytes" - "encoding/json" "errors" "testing" @@ -10,7 +8,6 @@ import ( "go.uber.org/mock/gomock" "github.com/jfrog/jfrog-cli-application/apptrust/model" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" ) @@ -91,65 +88,3 @@ func TestRollbackAppVersionCommand_Run(t *testing.T) { }) } } - -// --- printRollbackAppVersionResponse tests --- - -const sampleRollbackAppVersionJSON = `{"application_key":"my-app","version":"1.5.0","project_key":"proj-1","rollback_from_stage":"prod","rollback_to_stage":"qa"}` - -func TestPrintRollbackAppVersionResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printRollbackAppVersionResponse([]byte(sampleRollbackAppVersionJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleRollbackAppVersionJSON), &parsed)) -} - -func TestPrintRollbackAppVersionResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printRollbackAppVersionResponse([]byte(sampleRollbackAppVersionJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.5.0") - assert.Contains(t, output, "project_key") - assert.Contains(t, output, "proj-1") - assert.Contains(t, output, "rollback_from_stage") - assert.Contains(t, output, "prod") - assert.Contains(t, output, "rollback_to_stage") - assert.Contains(t, output, "qa") -} - -func TestPrintRollbackAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key and version are present — other fields must be absent from output. - payload := `{"application_key":"my-app","version":"1.5.0"}` - var buf bytes.Buffer - err := printRollbackAppVersionResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.5.0") - assert.NotContains(t, output, "project_key") - assert.NotContains(t, output, "rollback_from_stage") - assert.NotContains(t, output, "rollback_to_stage") -} - -func TestPrintRollbackAppVersionResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printRollbackAppVersionResponse([]byte(sampleRollbackAppVersionJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintRollbackAppVersionResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printRollbackAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse rollback response") -} diff --git a/apptrust/commands/version/update_app_version_cmd.go b/apptrust/commands/version/update_app_version_cmd.go index fa1c414..25c6327 100644 --- a/apptrust/commands/version/update_app_version_cmd.go +++ b/apptrust/commands/version/update_app_version_cmd.go @@ -3,11 +3,7 @@ package version //go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE} import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -21,7 +17,6 @@ import ( pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -83,7 +78,7 @@ func (uv *updateAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } - return printUpdateAppVersionResponse(uv.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(uv.responseBody, outputFormat, os.Stdout, orderedUpdateAppVersionKeys) } // parseFlagsAndSetFields parses CLI flags and sets struct fields accordingly. @@ -133,46 +128,6 @@ var orderedUpdateAppVersionKeys = []string{ "tag", } -// printUpdateAppVersionResponse formats and prints the update-app-version response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printUpdateAppVersionResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printUpdateAppVersionTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printUpdateAppVersionTable renders the update-app-version response as a FIELD/VALUE table. -func printUpdateAppVersionTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse update response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedUpdateAppVersionKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func GetUpdateAppVersionCommand(appContext app.Context) components.Command { cmd := &updateAppVersionCommand{versionService: appContext.GetVersionService()} diff --git a/apptrust/commands/version/update_app_version_cmd_test.go b/apptrust/commands/version/update_app_version_cmd_test.go index 4bfaf45..0fec275 100644 --- a/apptrust/commands/version/update_app_version_cmd_test.go +++ b/apptrust/commands/version/update_app_version_cmd_test.go @@ -1,8 +1,6 @@ package version import ( - "bytes" - "encoding/json" "errors" "testing" @@ -11,7 +9,6 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -235,65 +232,3 @@ func TestUpdateAppVersionCommand_FlagsSuite(t *testing.T) { }) } } - -// --- printUpdateAppVersionResponse tests --- - -const sampleUpdateAppVersionJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"dev","tag":"release-tag"}` - -func TestPrintUpdateAppVersionResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppVersionResponse([]byte(sampleUpdateAppVersionJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleUpdateAppVersionJSON), &parsed)) -} - -func TestPrintUpdateAppVersionResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppVersionResponse([]byte(sampleUpdateAppVersionJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.Contains(t, output, "status") - assert.Contains(t, output, "COMPLETED") - assert.Contains(t, output, "current_stage") - assert.Contains(t, output, "dev") - assert.Contains(t, output, "tag") - assert.Contains(t, output, "release-tag") -} - -func TestPrintUpdateAppVersionResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key and version are present — other fields must be absent from output. - payload := `{"application_key":"my-app","version":"1.0.0"}` - var buf bytes.Buffer - err := printUpdateAppVersionResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.NotContains(t, output, "status") - assert.NotContains(t, output, "current_stage") - assert.NotContains(t, output, "tag") -} - -func TestPrintUpdateAppVersionResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printUpdateAppVersionResponse([]byte(sampleUpdateAppVersionJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintUpdateAppVersionResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppVersionResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse update response") -} diff --git a/apptrust/commands/version/update_app_version_sources_cmd.go b/apptrust/commands/version/update_app_version_sources_cmd.go index 9ac8fca..a56565a 100644 --- a/apptrust/commands/version/update_app_version_sources_cmd.go +++ b/apptrust/commands/version/update_app_version_sources_cmd.go @@ -1,11 +1,7 @@ package version import ( - "encoding/json" - "fmt" - "io" "os" - "text/tabwriter" "github.com/jfrog/jfrog-cli-application/apptrust/app" "github.com/jfrog/jfrog-cli-application/apptrust/commands" @@ -19,7 +15,6 @@ import ( pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -84,7 +79,7 @@ func (cmd *updateAppVersionSourcesCommand) prepareAndRunCommand(ctx *components. return err } - return printUpdateAppVersionSourcesResponse(cmd.responseBody, outputFormat, os.Stdout) + return common.PrintJsonOrTableResponse(cmd.responseBody, outputFormat, os.Stdout, orderedUpdateAppVersionSourcesKeys) } func validateUpdateSourcesContext(ctx *components.Context) error { @@ -136,46 +131,6 @@ var orderedUpdateAppVersionSourcesKeys = []string{ "tag", } -// printUpdateAppVersionSourcesResponse formats and prints the update-app-version-sources response. -// When outputFormat is Table it renders a FIELD/VALUE table; when Json it -// pretty-prints the raw JSON; when None (flag absent) it falls back to the -// previous log.Output behaviour for backward-compatibility. -func printUpdateAppVersionSourcesResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer) error { - switch outputFormat { - case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil - case coreformat.Table: - return printUpdateAppVersionSourcesTable(data, w) - default: - // No --format flag provided: preserve the original output behaviour. - log.Output(string(data)) - return nil - } -} - -// printUpdateAppVersionSourcesTable renders the update-app-version-sources response as a FIELD/VALUE table. -func printUpdateAppVersionSourcesTable(data []byte, w io.Writer) error { - var fields map[string]interface{} - if err := json.Unmarshal(data, &fields); err != nil { - return fmt.Errorf("failed to parse update sources response: %w", err) - } - - tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) - fmt.Fprintln(tw, "FIELD\tVALUE") - for _, key := range orderedUpdateAppVersionSourcesKeys { - val, ok := fields[key] - if !ok || val == nil { - continue - } - strVal := fmt.Sprintf("%v", val) - if strVal == "" { - continue - } - fmt.Fprintf(tw, "%s\t%s\n", key, strVal) - } - return tw.Flush() -} func GetUpdateAppVersionSourcesCommand(appContext app.Context) components.Command { cmd := &updateAppVersionSourcesCommand{versionService: appContext.GetVersionService()} diff --git a/apptrust/commands/version/update_app_version_sources_cmd_test.go b/apptrust/commands/version/update_app_version_sources_cmd_test.go index cc3b9f2..b8e75d0 100644 --- a/apptrust/commands/version/update_app_version_sources_cmd_test.go +++ b/apptrust/commands/version/update_app_version_sources_cmd_test.go @@ -1,8 +1,6 @@ package version import ( - "bytes" - "encoding/json" "errors" "testing" @@ -11,7 +9,6 @@ import ( "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" - coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/stretchr/testify/assert" @@ -275,65 +272,3 @@ func TestUpdateAppVersionSourcesCommand_SourceFlagsSuite(t *testing.T) { }) } } - -// --- printUpdateAppVersionSourcesResponse tests --- - -const sampleUpdateAppVersionSourcesJSON = `{"application_key":"my-app","version":"1.0.0","status":"COMPLETED","current_stage":"dev","tag":"release-tag"}` - -func TestPrintUpdateAppVersionSourcesResponse_JSON(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppVersionSourcesResponse([]byte(sampleUpdateAppVersionSourcesJSON), coreformat.Json, &buf) - assert.NoError(t, err) - // The JSON path goes through log.Output, not the writer — assert no error and valid JSON. - var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleUpdateAppVersionSourcesJSON), &parsed)) -} - -func TestPrintUpdateAppVersionSourcesResponse_Table(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppVersionSourcesResponse([]byte(sampleUpdateAppVersionSourcesJSON), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "FIELD") - assert.Contains(t, output, "VALUE") - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.Contains(t, output, "status") - assert.Contains(t, output, "COMPLETED") - assert.Contains(t, output, "current_stage") - assert.Contains(t, output, "dev") - assert.Contains(t, output, "tag") - assert.Contains(t, output, "release-tag") -} - -func TestPrintUpdateAppVersionSourcesResponse_Table_AbsentFieldsOmitted(t *testing.T) { - // Only application_key and version are present — other fields must be absent from output. - payload := `{"application_key":"my-app","version":"1.0.0"}` - var buf bytes.Buffer - err := printUpdateAppVersionSourcesResponse([]byte(payload), coreformat.Table, &buf) - assert.NoError(t, err) - output := buf.String() - assert.Contains(t, output, "application_key") - assert.Contains(t, output, "my-app") - assert.Contains(t, output, "version") - assert.Contains(t, output, "1.0.0") - assert.NotContains(t, output, "status") - assert.NotContains(t, output, "current_stage") - assert.NotContains(t, output, "tag") -} - -func TestPrintUpdateAppVersionSourcesResponse_None_BackwardCompat(t *testing.T) { - // When outputFormat is None (no flag set), the function must not error. - var buf bytes.Buffer - err := printUpdateAppVersionSourcesResponse([]byte(sampleUpdateAppVersionSourcesJSON), coreformat.None, &buf) - assert.NoError(t, err) -} - -func TestPrintUpdateAppVersionSourcesResponse_Table_InvalidJSON(t *testing.T) { - var buf bytes.Buffer - err := printUpdateAppVersionSourcesResponse([]byte("not-json"), coreformat.Table, &buf) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse update sources response") -} diff --git a/apptrust/common/output.go b/apptrust/common/output.go new file mode 100644 index 0000000..d80d93a --- /dev/null +++ b/apptrust/common/output.go @@ -0,0 +1,51 @@ +package common + +import ( + "encoding/json" + "fmt" + "io" + "text/tabwriter" + + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" + clientUtils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +// PrintJsonOrTableResponse formats and prints a JSON response body. +// Json: pretty-prints the JSON. Table: renders a FIELD/VALUE table using orderedKeys. +// None/default: falls back to raw string output for backward compatibility. +func PrintJsonOrTableResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer, orderedKeys []string) error { + switch outputFormat { + case coreformat.Json: + log.Output(clientUtils.IndentJson(data)) + return nil + case coreformat.Table: + return PrintTable(data, w, orderedKeys) + default: + log.Output(string(data)) + return nil + } +} + +// PrintTable renders data as a FIELD/VALUE table using orderedKeys for display order. +// Fields absent from data or with empty/nil values are omitted. +func PrintTable(data []byte, w io.Writer, orderedKeys []string) error { + var fields map[string]interface{} + if err := json.Unmarshal(data, &fields); err != nil { + return fmt.Errorf("failed to parse response: %w", err) + } + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "FIELD\tVALUE") + for _, key := range orderedKeys { + val, ok := fields[key] + if !ok || val == nil { + continue + } + strVal := fmt.Sprintf("%v", val) + if strVal == "" { + continue + } + fmt.Fprintf(tw, "%s\t%s\n", key, strVal) + } + return tw.Flush() +} diff --git a/apptrust/common/output_test.go b/apptrust/common/output_test.go new file mode 100644 index 0000000..6fde6e4 --- /dev/null +++ b/apptrust/common/output_test.go @@ -0,0 +1,60 @@ +package common + +import ( + "bytes" + "encoding/json" + "testing" + + coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" + "github.com/stretchr/testify/assert" +) + +const sampleOutputJSON = `{"key":"alpha","extra":"beta"}` + +var orderedOutputKeys = []string{"key", "extra", "missing"} + +func TestPrintJsonOrTableResponse_JSON(t *testing.T) { + var buf bytes.Buffer + err := PrintJsonOrTableResponse([]byte(sampleOutputJSON), coreformat.Json, &buf, orderedOutputKeys) + assert.NoError(t, err) + var parsed map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleOutputJSON), &parsed)) +} + +func TestPrintJsonOrTableResponse_Table(t *testing.T) { + var buf bytes.Buffer + err := PrintJsonOrTableResponse([]byte(sampleOutputJSON), coreformat.Table, &buf, orderedOutputKeys) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "FIELD") + assert.Contains(t, output, "VALUE") + assert.Contains(t, output, "key") + assert.Contains(t, output, "alpha") + assert.Contains(t, output, "extra") + assert.Contains(t, output, "beta") +} + +func TestPrintJsonOrTableResponse_Table_AbsentFieldsOmitted(t *testing.T) { + payload := `{"key":"only-key"}` + var buf bytes.Buffer + err := PrintJsonOrTableResponse([]byte(payload), coreformat.Table, &buf, orderedOutputKeys) + assert.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "key") + assert.Contains(t, output, "only-key") + assert.NotContains(t, output, "extra") + assert.NotContains(t, output, "missing") +} + +func TestPrintJsonOrTableResponse_None_BackwardCompat(t *testing.T) { + var buf bytes.Buffer + err := PrintJsonOrTableResponse([]byte(sampleOutputJSON), coreformat.None, &buf, orderedOutputKeys) + assert.NoError(t, err) +} + +func TestPrintJsonOrTableResponse_Table_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := PrintJsonOrTableResponse([]byte("not-json"), coreformat.Table, &buf, orderedOutputKeys) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse response") +} diff --git a/go.mod b/go.mod index 083d56b..1e40729 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/jfrog/jfrog-cli-application go 1.25.5 require ( + github.com/jfrog/build-info-go v1.13.1-0.20260429070557-93b98034d295 github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260504054219-ba16d20c7b0f github.com/jfrog/jfrog-client-go v1.55.1-0.20251223101502-1a13a993b0c7 github.com/stretchr/testify v1.11.1 @@ -38,24 +39,20 @@ require ( github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gookit/color v1.6.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedib0t/go-pretty/v6 v6.6.8 // indirect github.com/jfrog/archiver/v3 v3.6.1 // indirect - github.com/jfrog/build-info-go v1.13.1-0.20260429070557-93b98034d295 // indirect github.com/jfrog/gofrog v1.7.6 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/magiconair/properties v1.8.10 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-tty v0.0.7 // indirect github.com/minio/sha256-simd v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect @@ -66,7 +63,6 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect @@ -79,7 +75,6 @@ require ( github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect @@ -88,7 +83,6 @@ require ( golang.org/x/sys v0.38.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 503228c..5b30daa 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,18 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= -github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= github.com/CycloneDX/cyclonedx-go v0.9.3 h1:Pyk/lwavPz7AaZNvugKFkdWOm93MzaIyWmBwmBo3aUI= github.com/CycloneDX/cyclonedx-go v0.9.3/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -31,8 +21,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= @@ -46,11 +34,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= @@ -66,28 +51,20 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/forPelevin/gomoji v1.3.0 h1:WPIOLWB1bvRYlKZnSSEevLt3IfKlLs+tK+YA9fFYlkE= -github.com/forPelevin/gomoji v1.3.0/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ= github.com/forPelevin/gomoji v1.4.1 h1:7U+Bl8o6RV/dOQz7coQFWj/jX6Ram6/cWFOuFDEPEUo= github.com/forPelevin/gomoji v1.4.1/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ= 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.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= -github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -98,8 +75,6 @@ github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXe github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -107,44 +82,30 @@ 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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= -github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= +github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo= -github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs= github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc= github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= -github.com/jfrog/build-info-go v1.10.16 h1:M77OfTXl3Ew9AG9SIIYLAG5ccIIaf6xjIZ5nomc2XAw= -github.com/jfrog/build-info-go v1.10.16/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= github.com/jfrog/build-info-go v1.13.1-0.20260429070557-93b98034d295 h1:EH0h86KwGvNHWyEBQoHoU9WfMMKy1GJ6jJQNmfy6E0U= github.com/jfrog/build-info-go v1.13.1-0.20260429070557-93b98034d295/go.mod h1:+OCtMb22/D+u7Wne5lzkjJjaWr0LRZcHlDwTH86Mpwo= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= -github.com/jfrog/jfrog-cli-core/v2 v2.59.5 h1:zPUOquY8gakWHJSdQifdcleFL0U23I0bdQdOYqxXAj4= -github.com/jfrog/jfrog-cli-core/v2 v2.59.5/go.mod h1:+zJfCkkFRWDbRTGpHsoaLZw5ze1xzRwffaLKuXjrZKc= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260504054219-ba16d20c7b0f h1:l5BPLF8GYBSvXmNqurqAP291lVHr1iCo4nwc5xe7KNM= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260504054219-ba16d20c7b0f/go.mod h1:bjAkVD8c2W+jg4whqy10bSXDC/c+Se8/ll/GPp5F/+0= -github.com/jfrog/jfrog-client-go v1.54.5 h1:WKd26zbJYPCgKTTFFflHnX/1QqtZt0IXh3czlikXsyY= -github.com/jfrog/jfrog-client-go v1.54.5/go.mod h1:+pSVE7Co+ytwhOhmz84/Lpe5fSeTaWJXsP1qt+WVfw0= github.com/jfrog/jfrog-client-go v1.55.1-0.20251223101502-1a13a993b0c7 h1:5JUiqmBV9ikFOZEH+ZgvJLHshT1aAuw08bfdJOLHbzQ= github.com/jfrog/jfrog-client-go v1.55.1-0.20251223101502-1a13a993b0c7/go.mod h1:USb7bfWSE7bGKsJ4nR0lxGILvmtnCcR5OO4biSUItMs= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= @@ -156,8 +117,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= @@ -180,15 +139,10 @@ github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q= github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= @@ -211,35 +165,21 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -251,8 +191,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= @@ -261,22 +199,16 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo= github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= -github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= -github.com/vbauerster/mpb/v8 v8.9.1 h1:LH5R3lXPfE2e3lIGxN7WNWv3Hl5nWO6LRi2B0L0ERHw= -github.com/vbauerster/mpb/v8 v8.9.1/go.mod h1:4XMvznPh8nfe2NpnDo1QTPvW9MVkUhbG90mPWvmOzcQ= github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM= +github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= @@ -289,33 +221,17 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -335,24 +251,12 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -361,8 +265,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 6b0a70174fc2d6a3001a57d96c190dfe66a4d885 Mon Sep 17 00:00:00 2001 From: Esteguet Date: Thu, 7 May 2026 09:45:36 +0200 Subject: [PATCH 11/14] JGC-481 - Address review comments --- .../commands/application/create_app_cmd.go | 13 +---------- .../commands/application/update_app_cmd.go | 12 +--------- .../version/create_app_version_cmd.go | 12 +--------- .../version/update_app_version_cmd.go | 11 +--------- .../version/update_app_version_sources_cmd.go | 12 +--------- apptrust/common/keys.go | 22 +++++++++++++++++++ apptrust/common/output.go | 14 +++++++++++- apptrust/common/output_test.go | 13 ++++++++++- 8 files changed, 52 insertions(+), 57 deletions(-) create mode 100644 apptrust/common/keys.go diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index d90aa17..4688dbc 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -140,7 +140,7 @@ func (cac *createAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return common.PrintJsonOrTableResponse(cac.responseBody, outputFormat, os.Stdout, orderedCreateAppKeys) + return common.PrintJsonOrTableResponse(cac.responseBody, outputFormat, os.Stdout, common.OrderedAppKeys) } func validateCreateAppContext(ctx *components.Context) error { @@ -174,17 +174,6 @@ func validateNoSpecAndFlagsTogether(ctx *components.Context) error { return nil } -// orderedCreateAppKeys defines the display order for app-create table output. -var orderedCreateAppKeys = []string{ - "application_key", - "application_name", - "project_key", - "description", - "criticality", - "maturity_level", -} - - func GetCreateAppCommand(appContext app.Context) components.Command { cmd := &createAppCommand{ applicationService: appContext.GetApplicationService(), diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index c5ef0ed..85bd0eb 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -83,17 +83,7 @@ func (uac *updateAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return common.PrintJsonOrTableResponse(uac.responseBody, outputFormat, os.Stdout, orderedUpdateAppKeys) -} - -// orderedUpdateAppKeys defines the display order for app-update table output. -var orderedUpdateAppKeys = []string{ - "application_key", - "application_name", - "project_key", - "description", - "criticality", - "maturity_level", + return common.PrintJsonOrTableResponse(uac.responseBody, outputFormat, os.Stdout, common.OrderedAppKeys) } diff --git a/apptrust/commands/version/create_app_version_cmd.go b/apptrust/commands/version/create_app_version_cmd.go index 89d79f5..69753cd 100644 --- a/apptrust/commands/version/create_app_version_cmd.go +++ b/apptrust/commands/version/create_app_version_cmd.go @@ -71,19 +71,9 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } - return common.PrintJsonOrTableResponse(cv.responseBody, outputFormat, os.Stdout, orderedCreateAppVersionKeys) + return common.PrintJsonOrTableResponse(cv.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) } -// orderedCreateAppVersionKeys defines the display order for version-create table output. -var orderedCreateAppVersionKeys = []string{ - "application_key", - "version", - "status", - "current_stage", - "tag", -} - - func (cv *createAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.CreateAppVersionRequest, error) { sources, filters, err := buildSourcesAndFiltersFromContext(ctx) if err != nil { diff --git a/apptrust/commands/version/update_app_version_cmd.go b/apptrust/commands/version/update_app_version_cmd.go index 25c6327..9b606f9 100644 --- a/apptrust/commands/version/update_app_version_cmd.go +++ b/apptrust/commands/version/update_app_version_cmd.go @@ -78,7 +78,7 @@ func (uv *updateAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } - return common.PrintJsonOrTableResponse(uv.responseBody, outputFormat, os.Stdout, orderedUpdateAppVersionKeys) + return common.PrintJsonOrTableResponse(uv.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) } // parseFlagsAndSetFields parses CLI flags and sets struct fields accordingly. @@ -119,15 +119,6 @@ func (uv *updateAppVersionCommand) buildRequestPayload(ctx *components.Context) return request, nil } -// orderedUpdateAppVersionKeys defines the display order for version-update table output. -var orderedUpdateAppVersionKeys = []string{ - "application_key", - "version", - "status", - "current_stage", - "tag", -} - func GetUpdateAppVersionCommand(appContext app.Context) components.Command { cmd := &updateAppVersionCommand{versionService: appContext.GetVersionService()} diff --git a/apptrust/commands/version/update_app_version_sources_cmd.go b/apptrust/commands/version/update_app_version_sources_cmd.go index a56565a..ab22fe1 100644 --- a/apptrust/commands/version/update_app_version_sources_cmd.go +++ b/apptrust/commands/version/update_app_version_sources_cmd.go @@ -79,7 +79,7 @@ func (cmd *updateAppVersionSourcesCommand) prepareAndRunCommand(ctx *components. return err } - return common.PrintJsonOrTableResponse(cmd.responseBody, outputFormat, os.Stdout, orderedUpdateAppVersionSourcesKeys) + return common.PrintJsonOrTableResponse(cmd.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) } func validateUpdateSourcesContext(ctx *components.Context) error { @@ -122,16 +122,6 @@ func (cmd *updateAppVersionSourcesCommand) buildRequestPayload(ctx *components.C }, nil } -// orderedUpdateAppVersionSourcesKeys defines the display order for version-update-sources table output. -var orderedUpdateAppVersionSourcesKeys = []string{ - "application_key", - "version", - "status", - "current_stage", - "tag", -} - - func GetUpdateAppVersionSourcesCommand(appContext app.Context) components.Command { cmd := &updateAppVersionSourcesCommand{versionService: appContext.GetVersionService()} return components.Command{ diff --git a/apptrust/common/keys.go b/apptrust/common/keys.go new file mode 100644 index 0000000..0b03ed6 --- /dev/null +++ b/apptrust/common/keys.go @@ -0,0 +1,22 @@ +package common + +// OrderedAppVersionKeys defines the display order for application-version table output +// (shared by version-create, version-update, version-update-sources). +var OrderedAppVersionKeys = []string{ + "application_key", + "version", + "status", + "current_stage", + "tag", +} + +// OrderedAppKeys defines the display order for application table output +// (shared by app-create, app-update). +var OrderedAppKeys = []string{ + "application_key", + "application_name", + "project_key", + "description", + "criticality", + "maturity_level", +} diff --git a/apptrust/common/output.go b/apptrust/common/output.go index d80d93a..92b9c38 100644 --- a/apptrust/common/output.go +++ b/apptrust/common/output.go @@ -41,7 +41,19 @@ func PrintTable(data []byte, w io.Writer, orderedKeys []string) error { if !ok || val == nil { continue } - strVal := fmt.Sprintf("%v", val) + var strVal string + switch v := val.(type) { + case string: + strVal = v + case []interface{}, map[string]interface{}: + b, err := json.Marshal(v) + if err != nil { + return err + } + strVal = string(b) + default: + strVal = fmt.Sprintf("%v", v) + } if strVal == "" { continue } diff --git a/apptrust/common/output_test.go b/apptrust/common/output_test.go index 6fde6e4..9de1c57 100644 --- a/apptrust/common/output_test.go +++ b/apptrust/common/output_test.go @@ -6,7 +6,9 @@ import ( "testing" coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" + "github.com/jfrog/jfrog-client-go/utils/log" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const sampleOutputJSON = `{"key":"alpha","extra":"beta"}` @@ -14,11 +16,20 @@ const sampleOutputJSON = `{"key":"alpha","extra":"beta"}` var orderedOutputKeys = []string{"key", "extra", "missing"} func TestPrintJsonOrTableResponse_JSON(t *testing.T) { + var logBuf bytes.Buffer + prevLogger := log.GetLogger() + log.SetLogger(log.NewLogger(log.INFO, &logBuf)) + t.Cleanup(func() { log.SetLogger(prevLogger) }) + var buf bytes.Buffer err := PrintJsonOrTableResponse([]byte(sampleOutputJSON), coreformat.Json, &buf, orderedOutputKeys) assert.NoError(t, err) + assert.Empty(t, buf.String(), "Json branch must not write to the io.Writer") + var parsed map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleOutputJSON), &parsed)) + require.NoError(t, json.Unmarshal(logBuf.Bytes(), &parsed)) + assert.Equal(t, "alpha", parsed["key"]) + assert.Equal(t, "beta", parsed["extra"]) } func TestPrintJsonOrTableResponse_Table(t *testing.T) { From 561e21a7553719c9679d0dd7b177b1d02b8e518a Mon Sep 17 00:00:00 2001 From: Esteguet Date: Thu, 7 May 2026 11:44:00 +0200 Subject: [PATCH 12/14] JGC-481 - Apply review suggestion --- .../commands/application/create_app_cmd.go | 2 +- .../commands/application/update_app_cmd.go | 2 +- apptrust/commands/package/bind_package_cmd.go | 20 +++++----- .../version/create_app_version_cmd.go | 2 +- .../version/promote_app_version_cmd.go | 21 +++++------ .../version/release_app_version_cmd.go | 19 +++++----- .../version/rollback_app_version_cmd.go | 21 +++++------ .../version/update_app_version_cmd.go | 2 +- .../version/update_app_version_sources_cmd.go | 2 +- apptrust/common/output.go | 17 +++++---- apptrust/common/output_test.go | 37 ++++++++++--------- 11 files changed, 73 insertions(+), 72 deletions(-) diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index 4688dbc..6093bb3 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -140,7 +140,7 @@ func (cac *createAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return common.PrintJsonOrTableResponse(cac.responseBody, outputFormat, os.Stdout, common.OrderedAppKeys) + return common.PrintResponse(cac.responseBody, outputFormat, os.Stdout, common.OrderedAppKeys) } func validateCreateAppContext(ctx *components.Context) error { diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index 85bd0eb..47fa32b 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -83,7 +83,7 @@ func (uac *updateAppCommand) prepareAndRunCommand(ctx *components.Context) error return err } - return common.PrintJsonOrTableResponse(uac.responseBody, outputFormat, os.Stdout, common.OrderedAppKeys) + return common.PrintResponse(uac.responseBody, outputFormat, os.Stdout, common.OrderedAppKeys) } diff --git a/apptrust/commands/package/bind_package_cmd.go b/apptrust/commands/package/bind_package_cmd.go index 64d9ce8..c7ca1d1 100644 --- a/apptrust/commands/package/bind_package_cmd.go +++ b/apptrust/commands/package/bind_package_cmd.go @@ -17,6 +17,15 @@ import ( coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" ) +// orderedBindPackageKeys defines the display order for package-bind table output. +var orderedBindPackageKeys = []string{ + "application_key", + "package_type", + "package_name", + "package_version", + "status", +} + type bindPackageCommand struct { packageService packages.PackageService serverDetails *coreConfig.ServerDetails @@ -63,7 +72,7 @@ func (bp *bindPackageCommand) prepareAndRunCommand(ctx *components.Context) erro return err } - return common.PrintJsonOrTableResponse(bp.responseBody, outputFormat, os.Stdout, orderedBindPackageKeys) + return common.PrintResponse(bp.responseBody, outputFormat, os.Stdout, orderedBindPackageKeys) } func (bp *bindPackageCommand) extractFromArgs(ctx *components.Context) { @@ -79,15 +88,6 @@ func (bp *bindPackageCommand) extractFromArgs(ctx *components.Context) { } } -// orderedBindPackageKeys defines the display order for package-bind table output. -var orderedBindPackageKeys = []string{ - "application_key", - "package_type", - "package_name", - "package_version", - "status", -} - func GetBindPackageCommand(appContext app.Context) components.Command { cmd := &bindPackageCommand{packageService: appContext.GetPackageService()} diff --git a/apptrust/commands/version/create_app_version_cmd.go b/apptrust/commands/version/create_app_version_cmd.go index 69753cd..453f4ea 100644 --- a/apptrust/commands/version/create_app_version_cmd.go +++ b/apptrust/commands/version/create_app_version_cmd.go @@ -71,7 +71,7 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } - return common.PrintJsonOrTableResponse(cv.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) + return common.PrintResponse(cv.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) } func (cv *createAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.CreateAppVersionRequest, error) { diff --git a/apptrust/commands/version/promote_app_version_cmd.go b/apptrust/commands/version/promote_app_version_cmd.go index e5c311d..a2a909f 100644 --- a/apptrust/commands/version/promote_app_version_cmd.go +++ b/apptrust/commands/version/promote_app_version_cmd.go @@ -20,6 +20,15 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" ) +// orderedPromoteAppVersionKeys defines the display order for version-promote table output. +var orderedPromoteAppVersionKeys = []string{ + "application_key", + "version", + "target_stage", + "status", + "current_stage", +} + type promoteAppVersionCommand struct { versionService versions.VersionService serverDetails *coreConfig.ServerDetails @@ -79,19 +88,9 @@ func (pv *promoteAppVersionCommand) prepareAndRunCommand(ctx *components.Context return err } - return common.PrintJsonOrTableResponse(pv.responseBody, outputFormat, os.Stdout, orderedPromoteAppVersionKeys) + return common.PrintResponse(pv.responseBody, outputFormat, os.Stdout, orderedPromoteAppVersionKeys) } -// orderedPromoteAppVersionKeys defines the display order for version-promote table output. -var orderedPromoteAppVersionKeys = []string{ - "application_key", - "version", - "target_stage", - "status", - "current_stage", -} - - func (pv *promoteAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.PromoteAppVersionRequest, error) { stage := ctx.Arguments[2] diff --git a/apptrust/commands/version/release_app_version_cmd.go b/apptrust/commands/version/release_app_version_cmd.go index 310352d..694c711 100644 --- a/apptrust/commands/version/release_app_version_cmd.go +++ b/apptrust/commands/version/release_app_version_cmd.go @@ -20,6 +20,14 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" ) +// orderedReleaseAppVersionKeys defines the display order for version-release table output. +var orderedReleaseAppVersionKeys = []string{ + "application_key", + "version", + "status", + "current_stage", +} + type releaseAppVersionCommand struct { versionService versions.VersionService serverDetails *coreConfig.ServerDetails @@ -79,18 +87,9 @@ func (rv *releaseAppVersionCommand) prepareAndRunCommand(ctx *components.Context return err } - return common.PrintJsonOrTableResponse(rv.responseBody, outputFormat, os.Stdout, orderedReleaseAppVersionKeys) + return common.PrintResponse(rv.responseBody, outputFormat, os.Stdout, orderedReleaseAppVersionKeys) } -// orderedReleaseAppVersionKeys defines the display order for version-release table output. -var orderedReleaseAppVersionKeys = []string{ - "application_key", - "version", - "status", - "current_stage", -} - - func (rv *releaseAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.ReleaseAppVersionRequest, error) { promotionType, includedRepos, excludedRepos, err := BuildPromotionParams(ctx) if err != nil { diff --git a/apptrust/commands/version/rollback_app_version_cmd.go b/apptrust/commands/version/rollback_app_version_cmd.go index 79c9961..1bc5782 100644 --- a/apptrust/commands/version/rollback_app_version_cmd.go +++ b/apptrust/commands/version/rollback_app_version_cmd.go @@ -19,6 +19,15 @@ import ( coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" ) +// orderedRollbackAppVersionKeys defines the display order for version-rollback table output. +var orderedRollbackAppVersionKeys = []string{ + "application_key", + "version", + "project_key", + "rollback_from_stage", + "rollback_to_stage", +} + type rollbackAppVersionCommand struct { versionService versions.VersionService serverDetails *coreConfig.ServerDetails @@ -75,19 +84,9 @@ func (rv *rollbackAppVersionCommand) prepareAndRunCommand(ctx *components.Contex return err } - return common.PrintJsonOrTableResponse(rv.responseBody, outputFormat, os.Stdout, orderedRollbackAppVersionKeys) + return common.PrintResponse(rv.responseBody, outputFormat, os.Stdout, orderedRollbackAppVersionKeys) } -// orderedRollbackAppVersionKeys defines the display order for version-rollback table output. -var orderedRollbackAppVersionKeys = []string{ - "application_key", - "version", - "project_key", - "rollback_from_stage", - "rollback_to_stage", -} - - func GetRollbackAppVersionCommand(appContext app.Context) components.Command { cmd := &rollbackAppVersionCommand{ versionService: appContext.GetVersionService(), diff --git a/apptrust/commands/version/update_app_version_cmd.go b/apptrust/commands/version/update_app_version_cmd.go index 9b606f9..2e5880b 100644 --- a/apptrust/commands/version/update_app_version_cmd.go +++ b/apptrust/commands/version/update_app_version_cmd.go @@ -78,7 +78,7 @@ func (uv *updateAppVersionCommand) prepareAndRunCommand(ctx *components.Context) return err } - return common.PrintJsonOrTableResponse(uv.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) + return common.PrintResponse(uv.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) } // parseFlagsAndSetFields parses CLI flags and sets struct fields accordingly. diff --git a/apptrust/commands/version/update_app_version_sources_cmd.go b/apptrust/commands/version/update_app_version_sources_cmd.go index ab22fe1..c8ae6c8 100644 --- a/apptrust/commands/version/update_app_version_sources_cmd.go +++ b/apptrust/commands/version/update_app_version_sources_cmd.go @@ -79,7 +79,7 @@ func (cmd *updateAppVersionSourcesCommand) prepareAndRunCommand(ctx *components. return err } - return common.PrintJsonOrTableResponse(cmd.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) + return common.PrintResponse(cmd.responseBody, outputFormat, os.Stdout, common.OrderedAppVersionKeys) } func validateUpdateSourcesContext(ctx *components.Context) error { diff --git a/apptrust/common/output.go b/apptrust/common/output.go index 92b9c38..4216364 100644 --- a/apptrust/common/output.go +++ b/apptrust/common/output.go @@ -1,6 +1,7 @@ package common import ( + "bytes" "encoding/json" "fmt" "io" @@ -8,21 +9,20 @@ import ( coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" clientUtils "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/log" ) -// PrintJsonOrTableResponse formats and prints a JSON response body. +// PrintResponse formats and prints a JSON response body to w. // Json: pretty-prints the JSON. Table: renders a FIELD/VALUE table using orderedKeys. -// None/default: falls back to raw string output for backward compatibility. -func PrintJsonOrTableResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer, orderedKeys []string) error { +// None (no --format flag): no output, preserving pre-flag behavior where commands +// signal success only via their log.Info message. +func PrintResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer, orderedKeys []string) error { switch outputFormat { case coreformat.Json: - log.Output(clientUtils.IndentJson(data)) - return nil + _, err := fmt.Fprintln(w, clientUtils.IndentJson(data)) + return err case coreformat.Table: return PrintTable(data, w, orderedKeys) default: - log.Output(string(data)) return nil } } @@ -30,6 +30,9 @@ func PrintJsonOrTableResponse(data []byte, outputFormat coreformat.OutputFormat, // PrintTable renders data as a FIELD/VALUE table using orderedKeys for display order. // Fields absent from data or with empty/nil values are omitted. func PrintTable(data []byte, w io.Writer, orderedKeys []string) error { + if len(bytes.TrimSpace(data)) == 0 { + return nil + } var fields map[string]interface{} if err := json.Unmarshal(data, &fields); err != nil { return fmt.Errorf("failed to parse response: %w", err) diff --git a/apptrust/common/output_test.go b/apptrust/common/output_test.go index 9de1c57..3a2d96b 100644 --- a/apptrust/common/output_test.go +++ b/apptrust/common/output_test.go @@ -6,7 +6,6 @@ import ( "testing" coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format" - "github.com/jfrog/jfrog-client-go/utils/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -15,26 +14,20 @@ const sampleOutputJSON = `{"key":"alpha","extra":"beta"}` var orderedOutputKeys = []string{"key", "extra", "missing"} -func TestPrintJsonOrTableResponse_JSON(t *testing.T) { - var logBuf bytes.Buffer - prevLogger := log.GetLogger() - log.SetLogger(log.NewLogger(log.INFO, &logBuf)) - t.Cleanup(func() { log.SetLogger(prevLogger) }) - +func TestPrintResponse_JSON(t *testing.T) { var buf bytes.Buffer - err := PrintJsonOrTableResponse([]byte(sampleOutputJSON), coreformat.Json, &buf, orderedOutputKeys) + err := PrintResponse([]byte(sampleOutputJSON), coreformat.Json, &buf, orderedOutputKeys) assert.NoError(t, err) - assert.Empty(t, buf.String(), "Json branch must not write to the io.Writer") var parsed map[string]interface{} - require.NoError(t, json.Unmarshal(logBuf.Bytes(), &parsed)) + require.NoError(t, json.Unmarshal(buf.Bytes(), &parsed)) assert.Equal(t, "alpha", parsed["key"]) assert.Equal(t, "beta", parsed["extra"]) } -func TestPrintJsonOrTableResponse_Table(t *testing.T) { +func TestPrintResponse_Table(t *testing.T) { var buf bytes.Buffer - err := PrintJsonOrTableResponse([]byte(sampleOutputJSON), coreformat.Table, &buf, orderedOutputKeys) + err := PrintResponse([]byte(sampleOutputJSON), coreformat.Table, &buf, orderedOutputKeys) assert.NoError(t, err) output := buf.String() assert.Contains(t, output, "FIELD") @@ -45,10 +38,10 @@ func TestPrintJsonOrTableResponse_Table(t *testing.T) { assert.Contains(t, output, "beta") } -func TestPrintJsonOrTableResponse_Table_AbsentFieldsOmitted(t *testing.T) { +func TestPrintResponse_Table_AbsentFieldsOmitted(t *testing.T) { payload := `{"key":"only-key"}` var buf bytes.Buffer - err := PrintJsonOrTableResponse([]byte(payload), coreformat.Table, &buf, orderedOutputKeys) + err := PrintResponse([]byte(payload), coreformat.Table, &buf, orderedOutputKeys) assert.NoError(t, err) output := buf.String() assert.Contains(t, output, "key") @@ -57,15 +50,23 @@ func TestPrintJsonOrTableResponse_Table_AbsentFieldsOmitted(t *testing.T) { assert.NotContains(t, output, "missing") } -func TestPrintJsonOrTableResponse_None_BackwardCompat(t *testing.T) { +func TestPrintResponse_None_NoOutput(t *testing.T) { + var buf bytes.Buffer + err := PrintResponse([]byte(sampleOutputJSON), coreformat.None, &buf, orderedOutputKeys) + assert.NoError(t, err) + assert.Empty(t, buf.String(), "None format must produce no output") +} + +func TestPrintResponse_Table_EmptyData(t *testing.T) { var buf bytes.Buffer - err := PrintJsonOrTableResponse([]byte(sampleOutputJSON), coreformat.None, &buf, orderedOutputKeys) + err := PrintResponse([]byte(" \n"), coreformat.Table, &buf, orderedOutputKeys) assert.NoError(t, err) + assert.Empty(t, buf.String(), "empty data must produce no output") } -func TestPrintJsonOrTableResponse_Table_InvalidJSON(t *testing.T) { +func TestPrintResponse_Table_InvalidJSON(t *testing.T) { var buf bytes.Buffer - err := PrintJsonOrTableResponse([]byte("not-json"), coreformat.Table, &buf, orderedOutputKeys) + err := PrintResponse([]byte("not-json"), coreformat.Table, &buf, orderedOutputKeys) assert.Error(t, err) assert.Contains(t, err.Error(), "failed to parse response") } From e31a2f513ae2a3c36e0e3616ed6a3f0fda9e6a3e Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 11 May 2026 09:26:49 +0200 Subject: [PATCH 13/14] JGC-481 - Fix e2e --- apptrust/commands/version/create_app_version_cmd.go | 1 + 1 file changed, 1 insertion(+) diff --git a/apptrust/commands/version/create_app_version_cmd.go b/apptrust/commands/version/create_app_version_cmd.go index 453f4ea..4c5a93d 100644 --- a/apptrust/commands/version/create_app_version_cmd.go +++ b/apptrust/commands/version/create_app_version_cmd.go @@ -121,6 +121,7 @@ func GetCreateAppVersionCommand(appContext app.Context) components.Command { }, Flags: commands.GetCommandFlags(commands.VersionCreate), SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Action: cmd.prepareAndRunCommand, } } From 942b080a5a9852c9d0222fa8f013e8a545c1ed2f Mon Sep 17 00:00:00 2001 From: Esteguet Date: Mon, 11 May 2026 13:38:09 +0200 Subject: [PATCH 14/14] JGC-481 - Add default + e2e coverage --- .../commands/application/create_app_cmd.go | 1 + .../commands/application/update_app_cmd.go | 2 +- apptrust/commands/package/bind_package_cmd.go | 2 +- .../version/promote_app_version_cmd.go | 1 + .../version/release_app_version_cmd.go | 1 + .../version/rollback_app_version_cmd.go | 1 + .../version/update_app_version_cmd.go | 2 +- .../version/update_app_version_sources_cmd.go | 1 + apptrust/common/output.go | 4 +- e2e/format_test.go | 348 ++++++++++++++++++ e2e/utils/e2e_utils.go | 4 +- 11 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 e2e/format_test.go diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index 6093bb3..cce8fc2 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -184,6 +184,7 @@ func GetCreateAppCommand(appContext app.Context) components.Command { Category: common.CategoryApplication, Aliases: []string{"ac"}, SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Arguments: []components.Argument{ { Name: "application-key", diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index 47fa32b..ac4c7b3 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -86,7 +86,6 @@ func (uac *updateAppCommand) prepareAndRunCommand(ctx *components.Context) error return common.PrintResponse(uac.responseBody, outputFormat, os.Stdout, common.OrderedAppKeys) } - func GetUpdateAppCommand(appContext app.Context) components.Command { cmd := &updateAppCommand{ applicationService: appContext.GetApplicationService(), @@ -97,6 +96,7 @@ func GetUpdateAppCommand(appContext app.Context) components.Command { Category: common.CategoryApplication, Aliases: []string{"au"}, SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Arguments: []components.Argument{ { Name: "application-key", diff --git a/apptrust/commands/package/bind_package_cmd.go b/apptrust/commands/package/bind_package_cmd.go index c7ca1d1..1967756 100644 --- a/apptrust/commands/package/bind_package_cmd.go +++ b/apptrust/commands/package/bind_package_cmd.go @@ -88,7 +88,6 @@ func (bp *bindPackageCommand) extractFromArgs(ctx *components.Context) { } } - func GetBindPackageCommand(appContext app.Context) components.Command { cmd := &bindPackageCommand{packageService: appContext.GetPackageService()} return components.Command{ @@ -116,6 +115,7 @@ func GetBindPackageCommand(appContext app.Context) components.Command { }, Flags: commands.GetCommandFlags(commands.PackageBind), SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/promote_app_version_cmd.go b/apptrust/commands/version/promote_app_version_cmd.go index a2a909f..bc9000e 100644 --- a/apptrust/commands/version/promote_app_version_cmd.go +++ b/apptrust/commands/version/promote_app_version_cmd.go @@ -147,6 +147,7 @@ func GetPromoteAppVersionCommand(appContext app.Context) components.Command { }, Flags: commands.GetCommandFlags(commands.VersionPromote), SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/release_app_version_cmd.go b/apptrust/commands/version/release_app_version_cmd.go index 694c711..4e8f65a 100644 --- a/apptrust/commands/version/release_app_version_cmd.go +++ b/apptrust/commands/version/release_app_version_cmd.go @@ -138,6 +138,7 @@ func GetReleaseAppVersionCommand(appContext app.Context) components.Command { }, Flags: commands.GetCommandFlags(commands.VersionRelease), SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/rollback_app_version_cmd.go b/apptrust/commands/version/rollback_app_version_cmd.go index 1bc5782..44fcc63 100644 --- a/apptrust/commands/version/rollback_app_version_cmd.go +++ b/apptrust/commands/version/rollback_app_version_cmd.go @@ -110,6 +110,7 @@ func GetRollbackAppVersionCommand(appContext app.Context) components.Command { }, Flags: commands.GetCommandFlags(commands.VersionRollback), SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/update_app_version_cmd.go b/apptrust/commands/version/update_app_version_cmd.go index 2e5880b..bc0f981 100644 --- a/apptrust/commands/version/update_app_version_cmd.go +++ b/apptrust/commands/version/update_app_version_cmd.go @@ -119,7 +119,6 @@ func (uv *updateAppVersionCommand) buildRequestPayload(ctx *components.Context) return request, nil } - func GetUpdateAppVersionCommand(appContext app.Context) components.Command { cmd := &updateAppVersionCommand{versionService: appContext.GetVersionService()} return components.Command{ @@ -141,6 +140,7 @@ func GetUpdateAppVersionCommand(appContext app.Context) components.Command { }, Flags: commands.GetCommandFlags(commands.VersionUpdate), SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/commands/version/update_app_version_sources_cmd.go b/apptrust/commands/version/update_app_version_sources_cmd.go index c8ae6c8..b611543 100644 --- a/apptrust/commands/version/update_app_version_sources_cmd.go +++ b/apptrust/commands/version/update_app_version_sources_cmd.go @@ -143,6 +143,7 @@ func GetUpdateAppVersionSourcesCommand(appContext app.Context) components.Comman }, Flags: commands.GetCommandFlags(commands.VersionUpdateSources), SupportedFormats: []coreformat.OutputFormat{coreformat.Table, coreformat.Json}, + DefaultFormat: coreformat.Json, Action: cmd.prepareAndRunCommand, } } diff --git a/apptrust/common/output.go b/apptrust/common/output.go index 4216364..49f95f6 100644 --- a/apptrust/common/output.go +++ b/apptrust/common/output.go @@ -13,8 +13,8 @@ import ( // PrintResponse formats and prints a JSON response body to w. // Json: pretty-prints the JSON. Table: renders a FIELD/VALUE table using orderedKeys. -// None (no --format flag): no output, preserving pre-flag behavior where commands -// signal success only via their log.Info message. +// Commands set DefaultFormat: coreformat.Json so the default branch should not be reached; +// it is kept as a no-op safety net. func PrintResponse(data []byte, outputFormat coreformat.OutputFormat, w io.Writer, orderedKeys []string) error { switch outputFormat { case coreformat.Json: diff --git a/e2e/format_test.go b/e2e/format_test.go new file mode 100644 index 0000000..947637d --- /dev/null +++ b/e2e/format_test.go @@ -0,0 +1,348 @@ +//go:build e2e + +package e2e + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/jfrog/jfrog-cli-application/e2e/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// assertValidJSON parses output as a JSON object and returns the decoded map. +func assertValidJSON(t *testing.T, output string) map[string]interface{} { + t.Helper() + require.NotEmpty(t, strings.TrimSpace(output), "expected non-empty output") + var parsed map[string]interface{} + require.NoError(t, json.Unmarshal([]byte(output), &parsed), "expected valid JSON, got: %s", output) + return parsed +} + +// assertTableOutput verifies the output is a FIELD/VALUE table containing all expectedFields. +func assertTableOutput(t *testing.T, output string, expectedFields ...string) { + t.Helper() + require.Contains(t, output, "FIELD", "table output should have a FIELD column header") + require.Contains(t, output, "VALUE", "table output should have a VALUE column header") + for _, field := range expectedFields { + assert.Contains(t, output, field, "table output should contain field %q", field) + } +} + +func TestAppCreate_OutputFormat(t *testing.T) { + projectKey := utils.GetTestProjectKey(t) + + runAppCreate := func(appKey string, formatArgs ...string) string { + args := []string{"app-create", appKey, "--project=" + projectKey, "--application-name=" + appKey} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-create-fmt-default") + defer utils.DeleteApplication(t, appKey) + parsed := assertValidJSON(t, runAppCreate(appKey)) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("json", func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-create-fmt-json") + defer utils.DeleteApplication(t, appKey) + parsed := assertValidJSON(t, runAppCreate(appKey, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-create-fmt-table") + defer utils.DeleteApplication(t, appKey) + assertTableOutput(t, runAppCreate(appKey, "--format=table"), "application_key", "application_name", "project_key") + }) +} + +func TestAppUpdate_OutputFormat(t *testing.T) { + runAppUpdate := func(appKey string, formatArgs ...string) string { + args := []string{"app-update", appKey, "--application-name=Updated " + appKey} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-update-fmt-default") + utils.CreateBasicApplication(t, appKey) + defer utils.DeleteApplication(t, appKey) + parsed := assertValidJSON(t, runAppUpdate(appKey)) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("json", func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-update-fmt-json") + utils.CreateBasicApplication(t, appKey) + defer utils.DeleteApplication(t, appKey) + parsed := assertValidJSON(t, runAppUpdate(appKey, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-update-fmt-table") + utils.CreateBasicApplication(t, appKey) + defer utils.DeleteApplication(t, appKey) + assertTableOutput(t, runAppUpdate(appKey, "--format=table"), "application_key", "application_name") + }) +} + +func TestVersionUpdate_OutputFormat(t *testing.T) { + testPackage := utils.GetTestPackage(t) + + prepareVersion := func(t *testing.T, suffix string) (appKey, version string, cleanup func()) { + appKey = utils.GenerateUniqueKey("version-update-fmt-" + suffix) + utils.CreateBasicApplication(t, appKey) + version = "1.0.0" + packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s", + testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey) + require.NoError(t, utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag)) + return appKey, version, func() { + utils.DeleteApplicationVersion(t, appKey, version) + utils.DeleteApplication(t, appKey) + } + } + + runUpdate := func(appKey, version string, formatArgs ...string) string { + args := []string{"version-update", appKey, version, "--tag=fmt-tag"} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "default") + defer cleanup() + parsed := assertValidJSON(t, runUpdate(appKey, version)) + assert.Equal(t, appKey, parsed["application_key"]) + assert.Equal(t, version, parsed["version"]) + }) + + t.Run("json", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "json") + defer cleanup() + parsed := assertValidJSON(t, runUpdate(appKey, version, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "table") + defer cleanup() + assertTableOutput(t, runUpdate(appKey, version, "--format=table"), "application_key", "version") + }) +} + +func TestVersionUpdateSources_OutputFormat(t *testing.T) { + testPackage := utils.GetTestPackage(t) + artifactPath := utils.GetTestArtifact(t) + + prepareDraftVersion := func(t *testing.T, suffix string) (appKey, version string, cleanup func()) { + appKey = utils.GenerateUniqueKey("version-upd-src-fmt-" + suffix) + utils.CreateBasicApplication(t, appKey) + version = "1.0.0" + packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s", + testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey) + require.NoError(t, utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag, "--draft")) + return appKey, version, func() { + utils.DeleteApplicationVersion(t, appKey, version) + utils.DeleteApplication(t, appKey) + } + } + + runUpdateSources := func(appKey, version string, formatArgs ...string) string { + args := []string{"version-update-sources", appKey, version, "--source-type-artifacts=path=" + artifactPath} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey, version, cleanup := prepareDraftVersion(t, "default") + defer cleanup() + parsed := assertValidJSON(t, runUpdateSources(appKey, version)) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("json", func(t *testing.T) { + appKey, version, cleanup := prepareDraftVersion(t, "json") + defer cleanup() + parsed := assertValidJSON(t, runUpdateSources(appKey, version, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey, version, cleanup := prepareDraftVersion(t, "table") + defer cleanup() + assertTableOutput(t, runUpdateSources(appKey, version, "--format=table"), "application_key", "version") + }) +} + +func TestVersionPromote_OutputFormat(t *testing.T) { + testPackage := utils.GetTestPackage(t) + + prepareVersion := func(t *testing.T, suffix string) (appKey, version string, cleanup func()) { + appKey = utils.GenerateUniqueKey("version-promote-fmt-" + suffix) + utils.CreateBasicApplication(t, appKey) + version = "1.0.0" + packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s", + testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey) + require.NoError(t, utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag)) + return appKey, version, func() { + utils.DeleteApplicationVersion(t, appKey, version) + utils.DeleteApplication(t, appKey) + } + } + + runPromote := func(appKey, version string, formatArgs ...string) string { + args := []string{"version-promote", appKey, version, "DEV"} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "default") + defer cleanup() + parsed := assertValidJSON(t, runPromote(appKey, version)) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("json", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "json") + defer cleanup() + parsed := assertValidJSON(t, runPromote(appKey, version, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "table") + defer cleanup() + assertTableOutput(t, runPromote(appKey, version, "--format=table"), "application_key", "version", "target_stage") + }) +} + +func TestVersionRelease_OutputFormat(t *testing.T) { + testPackage := utils.GetTestPackage(t) + + prepareVersion := func(t *testing.T, suffix string) (appKey, version string, cleanup func()) { + appKey = utils.GenerateUniqueKey("version-release-fmt-" + suffix) + utils.CreateBasicApplication(t, appKey) + version = "1.0.0" + packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s", + testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey) + require.NoError(t, utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag)) + return appKey, version, func() { + utils.DeleteApplicationVersion(t, appKey, version) + utils.DeleteApplication(t, appKey) + } + } + + runRelease := func(appKey, version string, formatArgs ...string) string { + args := []string{"version-release", appKey, version} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "default") + defer cleanup() + parsed := assertValidJSON(t, runRelease(appKey, version)) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("json", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "json") + defer cleanup() + parsed := assertValidJSON(t, runRelease(appKey, version, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey, version, cleanup := prepareVersion(t, "table") + defer cleanup() + assertTableOutput(t, runRelease(appKey, version, "--format=table"), "application_key", "version") + }) +} + +func TestVersionRollback_OutputFormat(t *testing.T) { + testPackage := utils.GetTestPackage(t) + + preparePromoted := func(t *testing.T, suffix string) (appKey, version string, cleanup func()) { + appKey = utils.GenerateUniqueKey("version-rollback-fmt-" + suffix) + utils.CreateBasicApplication(t, appKey) + version = "1.0.0" + packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s", + testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey) + require.NoError(t, utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag)) + require.NoError(t, utils.AppTrustCli.Exec("version-promote", appKey, version, "DEV")) + return appKey, version, func() { + utils.DeleteApplicationVersion(t, appKey, version) + utils.DeleteApplication(t, appKey) + } + } + + runRollback := func(appKey, version string, formatArgs ...string) string { + args := []string{"version-rollback", appKey, version, "DEV"} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey, version, cleanup := preparePromoted(t, "default") + defer cleanup() + parsed := assertValidJSON(t, runRollback(appKey, version)) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("json", func(t *testing.T) { + appKey, version, cleanup := preparePromoted(t, "json") + defer cleanup() + parsed := assertValidJSON(t, runRollback(appKey, version, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey, version, cleanup := preparePromoted(t, "table") + defer cleanup() + assertTableOutput(t, runRollback(appKey, version, "--format=table"), "application_key", "version", "rollback_from_stage") + }) +} + +func TestPackageBind_OutputFormat(t *testing.T) { + testPackage := utils.GetTestPackage(t) + + prepareApp := func(t *testing.T, suffix string) (appKey string, cleanup func()) { + appKey = utils.GenerateUniqueKey("package-bind-fmt-" + suffix) + utils.CreateBasicApplication(t, appKey) + return appKey, func() { utils.DeleteApplication(t, appKey) } + } + + runBind := func(appKey string, formatArgs ...string) string { + args := []string{"package-bind", appKey, testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion} + args = append(args, formatArgs...) + return utils.AppTrustCli.RunCliCmdWithOutput(t, args...) + } + + t.Run("default", func(t *testing.T) { + appKey, cleanup := prepareApp(t, "default") + defer cleanup() + parsed := assertValidJSON(t, runBind(appKey)) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("json", func(t *testing.T) { + appKey, cleanup := prepareApp(t, "json") + defer cleanup() + parsed := assertValidJSON(t, runBind(appKey, "--format=json")) + assert.Equal(t, appKey, parsed["application_key"]) + }) + + t.Run("table", func(t *testing.T) { + appKey, cleanup := prepareApp(t, "table") + defer cleanup() + assertTableOutput(t, runBind(appKey, "--format=table"), "application_key", "package_type", "package_name", "package_version") + }) +} diff --git a/e2e/utils/e2e_utils.go b/e2e/utils/e2e_utils.go index dea23ee..13ad27f 100644 --- a/e2e/utils/e2e_utils.go +++ b/e2e/utils/e2e_utils.go @@ -37,8 +37,8 @@ var ( AppTrustCli *coreTests.JfrogCli - testProjectKey string - testPackageRes *TestPackageResources + testProjectKey string + testPackageRes *TestPackageResources testArtifactPath string )