diff --git a/docs/common/env.go b/docs/common/env.go index 56ce2d408..2c4cd837b 100644 --- a/docs/common/env.go +++ b/docs/common/env.go @@ -41,6 +41,24 @@ const ( JfrogCliServerID = ` JFROG_CLI_SERVER_ID Server ID configured using the config command.` + JfrogCliUrl = ` JFROG_CLI_URL + [Default: None] + JFrog platform URL, used unless the --url command option is sent.` + + //#nosec G101 + JfrogCliAccessToken = ` JFROG_CLI_ACCESS_TOKEN + [Default: None] + JFrog access token, used unless the --access-token command option is sent.` + + JfrogCliUser = ` JFROG_CLI_USER + [Default: None] + JFrog username, used unless the --user command option is sent.` + + //#nosec G101 + JfrogCliPassword = ` JFROG_CLI_PASSWORD + [Default: None] + JFrog password, used unless the --password command option is sent.` + Ci = ` CI [Default: false] If true, disables interactive prompts and progress bar.` @@ -137,6 +155,10 @@ func GetGlobalEnvVars() string { JfrogCliBuildNumber, JfrogCliBuildProject, JfrogCliServerID, + JfrogCliUrl, + JfrogCliAccessToken, + JfrogCliUser, + JfrogCliPassword, Ci, JfrogCliPluginsServer, JfrogCliPluginsRepo, diff --git a/utils/cliutils/cli_consts.go b/utils/cliutils/cli_consts.go index 88d54e2d2..35d9745c2 100644 --- a/utils/cliutils/cli_consts.go +++ b/utils/cliutils/cli_consts.go @@ -28,6 +28,12 @@ const ( //#nosec G101 JfrogCliGithubToken = "JFROG_CLI_GITHUB_TOKEN" JfrogCliHideSurvey = "JFROG_CLI_HIDE_SURVEY" + JfrogCliUrl = "JFROG_CLI_URL" + //#nosec G101 + JfrogCliAccessToken = "JFROG_CLI_ACCESS_TOKEN" + JfrogCliUser = "JFROG_CLI_USER" + //#nosec G101 + JfrogCliPassword = "JFROG_CLI_PASSWORD" // JfrogCliErrorOutputFormat controls how HTTP response errors are surfaced. // Set to "json" to emit the structured response (status code + body) as JSON // on stderr instead of the default human-readable text. Unset or "text" keeps diff --git a/utils/cliutils/utils.go b/utils/cliutils/utils.go index 9ce02eecd..5ba0e133e 100644 --- a/utils/cliutils/utils.go +++ b/utils/cliutils/utils.go @@ -318,21 +318,27 @@ func getOrDefaultEnv(arg, envKey string) string { func CreateServerDetailsFromFlags(c *cli.Context) (details *coreConfig.ServerDetails, err error) { details = new(coreConfig.ServerDetails) - details.Url = clientutils.AddTrailingSlashIfNeeded(c.String(url)) + details.Url = clientutils.AddTrailingSlashIfNeeded(getOrDefaultEnv(c.String(url), JfrogCliUrl)) details.ArtifactoryUrl = clientutils.AddTrailingSlashIfNeeded(c.String(configRtUrl)) details.DistributionUrl = clientutils.AddTrailingSlashIfNeeded(c.String(configDistUrl)) details.XrayUrl = clientutils.AddTrailingSlashIfNeeded(c.String(configXrUrl)) details.MissionControlUrl = clientutils.AddTrailingSlashIfNeeded(c.String(configMcUrl)) details.PipelinesUrl = clientutils.AddTrailingSlashIfNeeded(c.String(configPlUrl)) - details.User = c.String(user) + details.User = getOrDefaultEnv(c.String(user), JfrogCliUser) details.Password, err = handleSecretInput(c, password, passwordStdin) if err != nil { return } + if details.Password == "" { + details.Password = os.Getenv(JfrogCliPassword) + } details.AccessToken, err = handleSecretInput(c, accessToken, accessTokenStdin) if err != nil { return } + if details.AccessToken == "" { + details.AccessToken = os.Getenv(JfrogCliAccessToken) + } details.SshKeyPath = c.String(sshKeyPath) details.SshPassphrase = c.String(sshPassphrase) details.ClientCertPath = c.String(ClientCertPath) diff --git a/utils/cliutils/utils_test.go b/utils/cliutils/utils_test.go index 335bc27e0..8498b6031 100644 --- a/utils/cliutils/utils_test.go +++ b/utils/cliutils/utils_test.go @@ -289,6 +289,100 @@ func TestGetFlagOrEnvValue(t *testing.T) { } } +func TestCreateServerDetailsFromFlagsEnvFallback(t *testing.T) { + testCases := []struct { + name string + flags map[string]string + env map[string]string + expectedUrl string + expectedUser string + expectedPassword string + expectedAccessToken string + }{ + { + name: "url falls back to env", + env: map[string]string{JfrogCliUrl: "https://acme.jfrog.io"}, + expectedUrl: "https://acme.jfrog.io/", + }, + { + name: "url flag wins over env", + flags: map[string]string{url: "https://flag.jfrog.io"}, + env: map[string]string{JfrogCliUrl: "https://env.jfrog.io"}, + expectedUrl: "https://flag.jfrog.io/", + }, + { + name: "access token falls back to env", + env: map[string]string{JfrogCliAccessToken: "env-token"}, + expectedAccessToken: "env-token", + }, + { + name: "access token flag wins over env", + flags: map[string]string{accessToken: "flag-token"}, + env: map[string]string{JfrogCliAccessToken: "env-token"}, + expectedAccessToken: "flag-token", + }, + { + name: "user and password fall back to env", + env: map[string]string{JfrogCliUser: "env-user", JfrogCliPassword: "env-password"}, + expectedUser: "env-user", + expectedPassword: "env-password", + }, + { + name: "user and password flags win over env", + flags: map[string]string{user: "flag-user", password: "flag-password"}, + env: map[string]string{JfrogCliUser: "env-user", JfrogCliPassword: "env-password"}, + expectedUser: "flag-user", + expectedPassword: "flag-password", + }, + { + name: "neither flags nor env keeps connection fields empty", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for _, envVarName := range []string{JfrogCliUrl, JfrogCliAccessToken, JfrogCliUser, JfrogCliPassword} { + t.Setenv(envVarName, "") + } + for envVarName, envVarValue := range tc.env { + t.Setenv(envVarName, envVarValue) + } + + flagValue := func(flagName string) string { + return tc.flags[flagName] + } + set := flag.NewFlagSet("test", 0) + set.String(url, flagValue(url), "") + set.String(configRtUrl, "", "") + set.String(configDistUrl, "", "") + set.String(configXrUrl, "", "") + set.String(configMcUrl, "", "") + set.String(configPlUrl, "", "") + set.String(user, flagValue(user), "") + set.String(password, flagValue(password), "") + set.Bool(passwordStdin, false, "") + set.String(accessToken, flagValue(accessToken), "") + set.Bool(accessTokenStdin, false, "") + set.String(sshKeyPath, "", "") + set.String(sshPassphrase, "", "") + set.String(ClientCertPath, "", "") + set.String(ClientCertKeyPath, "", "") + set.String(serverId, "", "") + set.Bool(InsecureTls, false, "") + set.Bool(disableTokenRefresh, false, "") + c := cli.NewContext(nil, set, nil) + + details, err := CreateServerDetailsFromFlags(c) + + assert.NoError(t, err) + assert.Equal(t, tc.expectedUrl, details.Url) + assert.Equal(t, tc.expectedUser, details.User) + assert.Equal(t, tc.expectedPassword, details.Password) + assert.Equal(t, tc.expectedAccessToken, details.AccessToken) + }) + } +} + // TestAuthorizationHeaderInCliVersionCheck tests that the HTTP request for checking new CLI versions // includes an authorization header when a GitHub token is provided. func TestAuthorizationHeaderInCliVersionCheck(t *testing.T) {