Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions buildtools/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,9 +707,12 @@ func MvnCmd(c *cli.Context) (err error) {
if !xrayScan && format != "" {
return cliutils.PrintHelpAndReturnError("The --format option can be sent only with the --scan option", c)
}
scanOutputFormat, err := outputFormat.GetOutputFormat(format)
if err != nil {
return err
scanOutputFormat := outputFormat.Table
if format != "" {
scanOutputFormat, err = outputFormat.ParseOutputFormat(format, outputFormat.All)
if err != nil {
return err
}
}
mvnCmd := mvn.NewMvnCommand().SetConfiguration(buildConfiguration).SetConfigPath(configFilePath).SetGoals(filteredMavenArgs).SetThreads(threads).SetInsecureTls(insecureTls).SetDetailedSummary(detailedSummary || printDeploymentView).SetXrayScan(xrayScan).SetScanOutputFormat(scanOutputFormat)
err = commands.Exec(mvnCmd)
Expand Down Expand Up @@ -813,9 +816,14 @@ func GradleCmd(c *cli.Context) (err error) {
if !xrayScan && format != "" {
return cliutils.PrintHelpAndReturnError("The --format option can be sent only with the --scan option", c)
}
scanOutputFormat, err := outputFormat.GetOutputFormat(format)
if err != nil {
return err
var scanOutputFormat outputFormat.OutputFormat
if format == "" {
scanOutputFormat = outputFormat.Table
} else {
scanOutputFormat, err = outputFormat.ParseOutputFormat(format, outputFormat.All)
if err != nil {
return err
}
}
printDeploymentView := log.IsStdErrTerminal()
gradleCmd := gradle.NewGradleCommand().SetConfiguration(buildConfiguration).SetTasks(filteredGradleArgs).SetConfigPath(configFilePath).SetThreads(threads).SetDetailedSummary(detailedSummary || printDeploymentView).SetXrayScan(xrayScan).SetScanOutputFormat(scanOutputFormat)
Expand Down
130 changes: 125 additions & 5 deletions config/cli.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
package config

import (
"encoding/json"
"fmt"
"io"
"os"
"slices"
"strings"
"text/tabwriter"

"github.com/jfrog/jfrog-cli-core/v2/general/token"

"github.com/jfrog/jfrog-client-go/auth/cert"

"github.com/jfrog/jfrog-cli-core/v2/common/commands"
coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format"
corecommon "github.com/jfrog/jfrog-cli-core/v2/docs/common"
commonCliUtils "github.com/jfrog/jfrog-cli-core/v2/common/cliutils"
coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli/docs/config/add"
"github.com/jfrog/jfrog-cli/docs/config/edit"
"github.com/jfrog/jfrog-cli/docs/config/remove"
"github.com/jfrog/jfrog-cli/docs/config/use"
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/urfave/cli"

"github.com/jfrog/jfrog-cli/docs/config/exportcmd"
Expand Down Expand Up @@ -48,6 +57,7 @@ func GetCommands() []cli.Command {
Name: "show",
Aliases: []string{"s"},
Usage: show.GetDescription(),
Flags: cliutils.GetCommandFlags(cliutils.ConfigShow),
HelpName: corecommon.CreateUsage("c show", show.GetDescription(), show.Usage),
BashComplete: corecommon.CreateBashCompletionFunc(commands.GetAllServerIds()...),
Action: showCmd,
Expand Down Expand Up @@ -178,7 +188,119 @@ func showCmd(c *cli.Context) error {
if c.NArg() == 1 {
serverId = c.Args()[0]
}
return commands.ShowConfig(serverId)

if !c.IsSet(cliutils.Format) {
// No format flag — use existing behavior unchanged.
return commands.ShowConfig(serverId)
}

outputFormat, err := commonCliUtils.GetOutputFormat(c, coreformat.Table)
if err != nil {
return err
}

// Read config directly so we can render it.
var configs []*coreconfig.ServerDetails
if serverId != "" {
single, err := coreconfig.GetSpecificConfig(serverId, false, false)
if err != nil {
return err
}
configs = []*coreconfig.ServerDetails{single}
} else {
configs, err = coreconfig.GetAllServersConfigs()
if err != nil {
return err
}
}
return printConfigShowResponse(configs, outputFormat, os.Stdout)
}

func printConfigShowResponse(configs []*coreconfig.ServerDetails, outputFormat coreformat.OutputFormat, w io.Writer) error {
switch outputFormat {
case coreformat.Json:
return printConfigShowJSON(configs)
Comment on lines +221 to +222

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a way to make this a common function so sub command creator dont worry about it ?

case coreformat.Table:
return printConfigShowTable(configs, w)
default:
return errorutils.CheckErrorf("unsupported format '%s' for config show. Accepted values: table, json", outputFormat)
}
}

// sanitizeServerDetails returns a copy with sensitive fields masked.
func sanitizeServerDetails(d *coreconfig.ServerDetails) coreconfig.ServerDetails {
s := *d
if s.Password != "" {
s.Password = "***"
}
if s.SshPassphrase != "" {
s.SshPassphrase = "***"
}
if s.AccessToken != "" {
s.AccessToken = "***"
}
if s.RefreshToken != "" {
s.RefreshToken = "***"
}
if s.ArtifactoryRefreshToken != "" {
s.ArtifactoryRefreshToken = "***"
}
return s
}

func printConfigShowJSON(configs []*coreconfig.ServerDetails) error {
sanitized := make([]coreconfig.ServerDetails, len(configs))
for i, c := range configs {
sanitized[i] = sanitizeServerDetails(c)
}
data, err := json.Marshal(sanitized) // #nosec G117
if err != nil {
return errorutils.CheckErrorf("failed to marshal config: %s", err.Error())
}
log.Output(clientUtils.IndentJson(data))
return nil
}

func printConfigShowTable(configs []*coreconfig.ServerDetails, w io.Writer) error {
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
for i, d := range configs {
if i > 0 {
_, _ = fmt.Fprintln(tw, "---")
}
_, _ = fmt.Fprintln(tw, "FIELD\tVALUE")
printConfigRow := func(field, value string) {
if value != "" {
_, _ = fmt.Fprintf(tw, "%s\t%s\n", field, value)
}
}
printConfigRow("server_id", d.ServerId)
printConfigRow("url", d.Url)
printConfigRow("artifactory_url", d.ArtifactoryUrl)
printConfigRow("distribution_url", d.DistributionUrl)
printConfigRow("xray_url", d.XrayUrl)
printConfigRow("mission_control_url", d.MissionControlUrl)
printConfigRow("pipelines_url", d.PipelinesUrl)
printConfigRow("user", d.User)
if d.Password != "" {
printConfigRow("password", "***")
}
if d.AccessToken != "" {
printConfigRow("access_token", "***")
}
if d.RefreshToken != "" {
printConfigRow("refresh_token", "***")
}
printConfigRow("ssh_key_path", d.SshKeyPath)
if d.SshPassphrase != "" {
printConfigRow("ssh_passphrase", "***")
}
printConfigRow("client_cert_path", d.ClientCertPath)
printConfigRow("client_cert_key_path", d.ClientCertKeyPath)
if d.IsDefault {
printConfigRow("is_default", "true")
}
}
return tw.Flush()
}

func deleteCmd(c *cli.Context) error {
Expand Down Expand Up @@ -243,10 +365,8 @@ func CreateConfigCommandConfiguration(c *cli.Context) (configCommandConfiguratio

func ValidateServerId(serverId string) error {
reservedIds := []string{"delete", "use", "show", "clear"}
for _, reservedId := range reservedIds {
if serverId == reservedId {
return fmt.Errorf("server can't have one of the following IDs: %s\n%s", strings.Join(reservedIds, ", "), cliutils.GetDocumentationMessage())
}
if slices.Contains(reservedIds, serverId) {
return fmt.Errorf("server can't have one of the following IDs: %s\n%s", strings.Join(reservedIds, ", "), cliutils.GetDocumentationMessage())
}
return nil
}
Expand Down
142 changes: 142 additions & 0 deletions config/cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package config

import (
"bytes"
"testing"

coreformat "github.com/jfrog/jfrog-cli-core/v2/common/format"
coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// ---------------------------------------------------------------------------
// printConfigShowTable
// ---------------------------------------------------------------------------

func TestPrintConfigShowTable_Basic(t *testing.T) {
configs := []*coreconfig.ServerDetails{
{ServerId: "my-server", ArtifactoryUrl: "https://acme.jfrog.io/artifactory"},
}
var buf bytes.Buffer
err := printConfigShowTable(configs, &buf)
require.NoError(t, err)
out := buf.String()
assert.Contains(t, out, "FIELD")
assert.Contains(t, out, "VALUE")
assert.Contains(t, out, "server_id")
assert.Contains(t, out, "my-server")
assert.Contains(t, out, "artifactory_url")
assert.Contains(t, out, "https://acme.jfrog.io/artifactory")
}

func TestPrintConfigShowTable_MasksSensitiveFields(t *testing.T) {
configs := []*coreconfig.ServerDetails{ // #nosec G101
{ // #nosec G101
ServerId: "masked",
Password: "secret-password",
AccessToken: "secret-token",
RefreshToken: "secret-refresh",
SshPassphrase: "secret-passphrase",
ArtifactoryRefreshToken: "secret-art-refresh",
},
}
var buf bytes.Buffer
err := printConfigShowTable(configs, &buf)
require.NoError(t, err)
out := buf.String()
assert.Contains(t, out, "***")
assert.NotContains(t, out, "secret-password")
assert.NotContains(t, out, "secret-token")
assert.NotContains(t, out, "secret-refresh")
assert.NotContains(t, out, "secret-passphrase")
}

func TestPrintConfigShowTable_OmitsEmptyFields(t *testing.T) {
configs := []*coreconfig.ServerDetails{
{ServerId: "only-id"},
}
var buf bytes.Buffer
err := printConfigShowTable(configs, &buf)
require.NoError(t, err)
out := buf.String()
// Fields that are not set should not appear.
assert.NotContains(t, out, "artifactory_url")
assert.NotContains(t, out, "xray_url")
assert.NotContains(t, out, "user")
assert.NotContains(t, out, "password")
assert.NotContains(t, out, "access_token")
assert.Contains(t, out, "only-id")
}

func TestPrintConfigShowTable_MultipleConfigs(t *testing.T) {
configs := []*coreconfig.ServerDetails{
{ServerId: "server-one"},
{ServerId: "server-two"},
}
var buf bytes.Buffer
err := printConfigShowTable(configs, &buf)
require.NoError(t, err)
out := buf.String()
assert.Contains(t, out, "---")
assert.Contains(t, out, "server-one")
assert.Contains(t, out, "server-two")
}

// ---------------------------------------------------------------------------
// printConfigShowJSON
// ---------------------------------------------------------------------------

func TestPrintConfigShowJSON_NoError(t *testing.T) {
configs := []*coreconfig.ServerDetails{
{ServerId: "json-server", ArtifactoryUrl: "https://acme.jfrog.io/artifactory"},
{ServerId: "json-server-2"},
}
// JSON is written to log.Output, not to w — just verify no error is returned.
err := printConfigShowJSON(configs)
assert.NoError(t, err)
}

// ---------------------------------------------------------------------------
// printConfigShowResponse
// ---------------------------------------------------------------------------

func TestPrintConfigShowResponse_UnsupportedFormat(t *testing.T) {
configs := []*coreconfig.ServerDetails{{ServerId: "test"}}
var buf bytes.Buffer
err := printConfigShowResponse(configs, coreformat.Sarif, &buf)
require.Error(t, err)
assert.Contains(t, err.Error(), "unsupported format")
}

// ---------------------------------------------------------------------------
// sanitizeServerDetails
// ---------------------------------------------------------------------------

func TestSanitizeServerDetails(t *testing.T) {
original := &coreconfig.ServerDetails{
ServerId: "my-server",
Password: "real-password",
SshPassphrase: "real-passphrase",
AccessToken: "real-access-token",
RefreshToken: "real-refresh-token",
ArtifactoryRefreshToken: "real-art-refresh-token",
User: "admin",
}
sanitized := sanitizeServerDetails(original)

// Sensitive fields must be masked.
assert.Equal(t, "***", sanitized.Password)
assert.Equal(t, "***", sanitized.SshPassphrase)
assert.Equal(t, "***", sanitized.AccessToken)
assert.Equal(t, "***", sanitized.RefreshToken)
assert.Equal(t, "***", sanitized.ArtifactoryRefreshToken)

// Non-sensitive fields must be preserved.
assert.Equal(t, "my-server", sanitized.ServerId)
assert.Equal(t, "admin", sanitized.User)

// Original must be unchanged.
assert.Equal(t, "real-password", original.Password)
assert.Equal(t, "real-access-token", original.AccessToken)
}
2 changes: 2 additions & 0 deletions docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ func buildBuilderImage(t *testing.T, workspace, dockerfile, containerName string
// The way we build the fat manifest and push it to Artifactory is not important.
// Therefore, this test runs only with docker.
func TestPushFatManifestImage(t *testing.T) {
t.Skip("Skipping until JGC-483 is fixed")
if !*tests.TestDocker {
t.Skip("Skipping test. To run it, add the '-test.docker=true' option.")
}
Expand Down Expand Up @@ -489,6 +490,7 @@ CMD ["echo", "Hello from nested path"]`, baseImage)
// TestDockerBuildPushWithNestedPath tests docker build --push with nested paths like repo/myorg/image.
// This validates that layer fetching works correctly for single platform images with nested paths.
func TestDockerBuildPushWithNestedPath(t *testing.T) {
t.Skip("Skipping until JGC-483 is fixed")
cleanup := initDockerBuildTest(t)
defer cleanup()
runNestedPathDockerBuildTest(t, "docker-build-nested", "test-single-nested", "myorg", "")
Expand Down
Loading
Loading