Skip to content

Commit 0f89a04

Browse files
Merge pull request #1107 from Checkmarx/feature/saraChen/customConfigFileP
Add support on custom config file path (AST-88673)
2 parents ce7ffa4 + fb9755b commit 0f89a04

File tree

9 files changed

+248
-22
lines changed

9 files changed

+248
-22
lines changed

cmd/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ func main() {
2727
var err error
2828
bindProxy()
2929
bindKeysToEnvAndDefault()
30-
configuration.LoadConfiguration()
30+
err = configuration.LoadConfiguration()
31+
exitIfError(err)
3132
scans := viper.GetString(params.ScansPathKey)
3233
groups := viper.GetString(params.GroupsPathKey)
3334
logs := viper.GetString(params.LogsPathKey)

internal/commands/util/configuration_test.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package util
22

33
import (
4+
"github.com/checkmarx/ast-cli/internal/params"
5+
"github.com/spf13/viper"
46
"os"
57
"strings"
68
"testing"
@@ -47,9 +49,11 @@ func TestGetConfigFilePath_CheckmarxConfigFileExists_Success(t *testing.T) {
4749
}
4850

4951
func TestWriteSingleConfigKeyToExistingFile_ChangeAscaPortToZero_Success(t *testing.T) {
50-
configuration.LoadConfiguration()
52+
err := configuration.LoadConfiguration()
53+
assert.NilError(t, err)
54+
5155
configFilePath, _ := configuration.GetConfigFilePath()
52-
err := configuration.SafeWriteSingleConfigKey(configFilePath, cxAscaPort, 0)
56+
err = configuration.SafeWriteSingleConfigKey(configFilePath, cxAscaPort, 0)
5357
assert.NilError(t, err)
5458

5559
config, err := configuration.LoadConfig(configFilePath)
@@ -78,7 +82,9 @@ func TestWriteSingleConfigKeyNonExistingFile_CreatingTheFileAndWritesTheKey_Succ
7882
}
7983

8084
func TestChangedOnlyAscaPortInConfigFile_ConfigFileExistsWithDefaultValues_OnlyAscaPortChangedSuccess(t *testing.T) {
81-
configuration.LoadConfiguration()
85+
err := configuration.LoadConfiguration()
86+
assert.NilError(t, err)
87+
8288
configFilePath, _ := configuration.GetConfigFilePath()
8389

8490
oldConfig, err := configuration.LoadConfig(configFilePath)
@@ -100,9 +106,11 @@ func TestChangedOnlyAscaPortInConfigFile_ConfigFileExistsWithDefaultValues_OnlyA
100106
}
101107

102108
func TestWriteSingleConfigKeyStringToExistingFile_UpdateScsScanOverviewPath_Success(t *testing.T) {
103-
configuration.LoadConfiguration()
109+
err := configuration.LoadConfiguration()
110+
assert.NilError(t, err)
111+
104112
configFilePath, _ := configuration.GetConfigFilePath()
105-
err := configuration.SafeWriteSingleConfigKeyString(configFilePath, cxScsScanOverviewPath, defaultScsScanOverviewPath)
113+
err = configuration.SafeWriteSingleConfigKeyString(configFilePath, cxScsScanOverviewPath, defaultScsScanOverviewPath)
106114
assert.NilError(t, err)
107115

108116
config, err := configuration.LoadConfig(configFilePath)
@@ -131,7 +139,9 @@ func TestWriteSingleConfigKeyStringNonExistingFile_CreatingTheFileAndWritesTheKe
131139
}
132140

133141
func TestChangedOnlyScsScanOverviewPathInConfigFile_ConfigFileExistsWithDefaultValues_OnlyScsScanOverviewPathChangedSuccess(t *testing.T) {
134-
configuration.LoadConfiguration()
142+
err := configuration.LoadConfiguration()
143+
assert.NilError(t, err)
144+
135145
configFilePath, _ := configuration.GetConfigFilePath()
136146

137147
oldConfig, err := configuration.LoadConfig(configFilePath)
@@ -151,3 +161,12 @@ func TestChangedOnlyScsScanOverviewPathInConfigFile_ConfigFileExistsWithDefaultV
151161
}
152162
}
153163
}
164+
165+
func TestGetConfigFilePath_CustomFile(t *testing.T) {
166+
expectedPath := "/custom/path/checkmarxcli.yaml"
167+
viper.Set(params.ConfigFilePathKey, expectedPath)
168+
169+
actualPath, err := configuration.GetConfigFilePath()
170+
assert.NilError(t, err)
171+
assert.Equal(t, actualPath, expectedPath, "Expected path to match the set value in viper")
172+
}

internal/params/binds.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,5 @@ var EnvVarsBinds = []struct {
7474
{ASCAPortKey, ASCAPortEnv, ""},
7575
{ScsRepoTokenKey, ScsRepoTokenEnv, ""},
7676
{RiskManagementPathKey, RiskManagementPathEnv, "api/risk-management/projects/%s/results"},
77+
{ConfigFilePathKey, ConfigFilePathEnv, ""},
7778
}

internal/params/envs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,5 @@ const (
7373
ASCAPortEnv = "CX_ASCA_PORT"
7474
ScsRepoTokenEnv = "SCS_REPO_TOKEN"
7575
RiskManagementPathEnv = "CX_RISK_MANAGEMENT_PATH"
76+
ConfigFilePathEnv = "CX_CONFIG_FILE_PATH"
7677
)

internal/params/keys.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,5 @@ var (
7373
ASCAPortKey = strings.ToLower(ASCAPortEnv)
7474
ScsRepoTokenKey = strings.ToLower(ScsRepoTokenEnv)
7575
RiskManagementPathKey = strings.ToLower(RiskManagementPathEnv)
76+
ConfigFilePathKey = strings.ToLower(ConfigFilePathEnv)
7677
)

internal/wrappers/configuration/configuration.go

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ func setConfigPropertyQuiet(propName, propValue string) {
108108
// SafeWriteConfig() will not update files but it will create them, combined
109109
// this code will successfully update files.
110110
if viperErr := viper.SafeWriteConfig(); viperErr != nil {
111-
_ = viper.WriteConfig()
111+
err := viper.WriteConfig()
112+
if err != nil {
113+
fmt.Println("Error writing config file", err)
114+
}
112115
}
113116
}
114117

@@ -117,18 +120,54 @@ func SetConfigProperty(propName, propValue string) {
117120
setConfigPropertyQuiet(propName, propValue)
118121
}
119122

120-
func LoadConfiguration() {
121-
usr, err := user.Current()
123+
func LoadConfiguration() error {
124+
configFilePath := viper.GetString(params.ConfigFilePathKey)
125+
126+
if configFilePath != "" {
127+
err := validateConfigFile(configFilePath)
128+
if err != nil {
129+
return err
130+
}
131+
viper.SetConfigFile(configFilePath)
132+
if err = viper.ReadInConfig(); err != nil {
133+
return errors.New("An error occurred while accessing the file or environment variable. Please verify the CLI configuration file")
134+
}
135+
} else {
136+
usr, err := user.Current()
137+
if err != nil {
138+
log.Fatal("Cannot file home directory.", err)
139+
}
140+
fullPath := usr.HomeDir + configDirName
141+
verifyConfigDir(fullPath)
142+
viper.AddConfigPath(fullPath)
143+
configFile := "checkmarxcli"
144+
viper.SetConfigName(configFile)
145+
viper.SetConfigType("yaml")
146+
_ = viper.ReadInConfig()
147+
}
148+
return nil
149+
}
150+
151+
func validateConfigFile(configFilePath string) error {
152+
info, err := os.Stat(configFilePath)
122153
if err != nil {
123-
log.Fatal("Cannot file home directory.", err)
154+
if os.IsNotExist(err) {
155+
return fmt.Errorf("The specified file does not exist. Please check the path and ensure the CLI configuration file is available.")
156+
}
157+
return fmt.Errorf("An error occurred while accessing the file or environment variable. Please verify the CLI configuration file")
158+
}
159+
160+
if info.IsDir() {
161+
return fmt.Errorf("The specified path points to a directory, not a file. Please provide a valid CLI configuration file path.")
162+
}
163+
164+
file, err := os.OpenFile(configFilePath, os.O_RDONLY, 0644)
165+
if err != nil {
166+
return fmt.Errorf("Access to the specified file is restricted. Please ensure you have the necessary permissions to access the CLI configuration file")
124167
}
125-
fullPath := usr.HomeDir + configDirName
126-
verifyConfigDir(fullPath)
127-
viper.AddConfigPath(fullPath)
128-
configFile := "checkmarxcli"
129-
viper.SetConfigName(configFile)
130-
viper.SetConfigType("yaml")
131-
_ = viper.ReadInConfig()
168+
defer file.Close()
169+
170+
return nil
132171
}
133172

134173
func SafeWriteSingleConfigKey(configFilePath, key string, value int) error {
@@ -231,11 +270,16 @@ func SaveConfig(path string, config map[string]interface{}) error {
231270
}
232271

233272
func GetConfigFilePath() (string, error) {
234-
usr, err := user.Current()
235-
if err != nil {
236-
return "", fmt.Errorf("error getting current user: %w", err)
273+
configFilePath := viper.GetString(params.ConfigFilePathKey)
274+
275+
if configFilePath == "" {
276+
usr, err := user.Current()
277+
if err != nil {
278+
return "", fmt.Errorf("error getting current user: %w", err)
279+
}
280+
configFilePath = usr.HomeDir + configDirName + "/checkmarxcli.yaml"
237281
}
238-
return usr.HomeDir + configDirName + "/checkmarxcli.yaml", nil
282+
return configFilePath, nil
239283
}
240284

241285
func verifyConfigDir(fullPath string) {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//go:build integration
2+
3+
package integration
4+
5+
import (
6+
"github.com/checkmarx/ast-cli/internal/wrappers/configuration"
7+
"github.com/spf13/viper"
8+
"gotest.tools/assert"
9+
"os"
10+
"strings"
11+
"testing"
12+
)
13+
14+
const filePath = "data/config.yaml"
15+
16+
func TestLoadConfiguration_EnvVarConfigFilePath(t *testing.T) {
17+
os.Setenv("CX_CONFIG_FILE_PATH", filePath)
18+
defer os.Unsetenv("CX_CONFIG_FILE_PATH")
19+
20+
_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
21+
err := configuration.LoadConfiguration()
22+
assert.NilError(t, err)
23+
}
24+
25+
func TestLoadConfiguration_FileNotFound(t *testing.T) {
26+
os.Setenv("CX_CONFIG_FILE_PATH", "data/nonexistent_config.yaml")
27+
defer os.Unsetenv("CX_CONFIG_FILE_PATH")
28+
29+
_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
30+
var err error
31+
err = configuration.LoadConfiguration()
32+
assert.ErrorContains(t, err, "The specified file does not exist")
33+
}
34+
func TestLoadConfiguration_ValidDirectory(t *testing.T) {
35+
validDirPath := "data"
36+
os.Setenv("CX_CONFIG_FILE_PATH", validDirPath)
37+
defer os.Unsetenv("CX_CONFIG_FILE_PATH")
38+
39+
_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
40+
err := configuration.LoadConfiguration()
41+
assert.ErrorContains(t, err, "The specified path points to a directory")
42+
}
43+
func TestLoadConfiguration_FileWithoutPermission_UsingConfigFile(t *testing.T) {
44+
if err := os.Chmod(filePath, 0000); err != nil {
45+
t.Fatalf("failed to set file permissions: %v", err)
46+
}
47+
defer os.Chmod(filePath, 0644)
48+
49+
os.Setenv("CX_CONFIG_FILE_PATH", filePath)
50+
defer os.Unsetenv("CX_CONFIG_FILE_PATH")
51+
52+
_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
53+
err := configuration.LoadConfiguration()
54+
55+
assert.ErrorContains(t, err, "Access to the specified file is restricted")
56+
}
57+
58+
func TestSetConfigProperty_EnvVarConfigFilePath(t *testing.T) {
59+
os.Setenv("CX_CONFIG_FILE_PATH", filePath)
60+
defer os.Unsetenv("CX_CONFIG_FILE_PATH")
61+
62+
_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
63+
err := configuration.LoadConfiguration()
64+
assert.NilError(t, err)
65+
66+
err, _ = executeCommand(t, "configure", "set", "--prop-name", "cx_client_id", "--prop-value", "dummy-client_id")
67+
assert.NilError(t, err)
68+
69+
content, err := os.ReadFile(filePath)
70+
assert.NilError(t, err)
71+
assert.Assert(t, strings.Contains(string(content), "dummy-client_id"))
72+
73+
err, _ = executeCommand(t, "configure", "set", "--prop-name", "cx_client_id", "--prop-value", "example_client_id")
74+
assert.NilError(t, err)
75+
}

test/integration/data/config.yaml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
apikey-override: false
2+
cx_access_management_path: api/access-management
3+
cx_agent_name: ASTCLI
4+
cx_aiproxy_azureai_route: api/ai-proxy/redirect/externalAzure
5+
cx_aiproxy_checkmarxai_route: api/ai-proxy/redirect/azure
6+
cx_api_key: example_api_key
7+
cx_apikey: ""
8+
cx_applications_path: api/applications
9+
cx_asca_port: ""
10+
cx_ast_keycloak_web_app_health_check_path: auth
11+
cx_ast_role: SCA_AGENT
12+
cx_ast_web_app_health_check_path: '#/projects'
13+
cx_base_auth_uri: https://auth.example.com
14+
cx_base_uri: https://example.com
15+
cx_bfl_path: api/bfl
16+
cx_branch: ""
17+
cx_byor_path: api/byor
18+
cx_client_id: example_client_id
19+
cx_client_secret: example_client_secret
20+
cx_codebashing_path: api/codebashing/lessons
21+
cx_config_file_path: data/config.yaml
22+
cx_create_oath2_client_path: auth/realms/organization/pip/clients
23+
cx_custom_states_path: api/custom-states
24+
cx_descriptions_path: api/queries/descriptions
25+
cx_export_path: api/sca/export
26+
cx_feature_flags_path: api/flags
27+
cx_groups_path: auth/realms/organization/pip/groups
28+
cx_healthcheck_db_path: logging
29+
cx_healthcheck_in_memory_db_path: in-memory-db
30+
cx_healthcheck_message_queue_path: message-queue
31+
cx_healthcheck_object_store_path: object-store
32+
cx_healthcheck_path: api/healthcheck
33+
cx_healthcheck_sast_engines_path: sast-engines
34+
cx_healthcheck_scan_flow_path: scan-flow
35+
cx_ignore_proxy: ""
36+
cx_kics_results_path: api/kics-results
37+
cx_kics_results_predicates_path: api/kics-results-predicates
38+
cx_logs_engine_log_path: /%s/%s
39+
cx_logs_path: api/logs
40+
cx_origin: CLI
41+
cx_policy_evaluation_path: api/policy_management_service_uri/evaluation
42+
cx_pr_decoration_azure_path: api/flow-publisher/pr/azure
43+
cx_pr_decoration_bitbucket_cloud_path: api/flow-publisher/pr/bitbucket
44+
cx_pr_decoration_bitbucket_server_path: api/flow-publisher/pr/bitbucket-server
45+
cx_pr_decoration_github_path: api/flow-publisher/pr/github
46+
cx_pr_decoration_gitlab_path: api/flow-publisher/pr/gitlab
47+
cx_projects_path: api/projects
48+
cx_proxy: http://proxy.example.com
49+
cx_proxy_auth_type: basic
50+
cx_proxy_ntlm_domain: ""
51+
cx_queries_clone_path: clone
52+
cx_queries_path: api/queries
53+
cx_results_path: api/results
54+
cx_results_pdf_report_path: api/reports
55+
cx_risk_management_path: api/risk-management/projects/%s/results
56+
cx_risks_overview_path: api/apisec/static/api/scan/%s/risks-overview
57+
cx_sast_results_path: api/sast-results
58+
cx_sast_results_predicates_path: api/sast-results-predicates
59+
cx_sast_rm_path: api/sast-rm
60+
cx_sast_scan_inc_metrics_path: '%s/metrics'
61+
cx_sast_scan_inc_path: api/sast-metadata
62+
cx_scan_summary_path: api/scan-summary
63+
cx_scans_path: api/scans
64+
cx_scs_results_predicates_read_path: api/micro-engines/read/predicates
65+
cx_scs_results_predicates_write_path: api/micro-engines/write/predicates
66+
cx_scs_scan_overview_path: api/micro-engines/read/scans/%s/scan-overview
67+
cx_tenant: example_tenant
68+
cx_tenant_configuration_path: api/configuration/tenant
69+
cx_timeout: "30"
70+
cx_token_expiry_seconds: 2
71+
cx_uploads_path: api/uploads
72+
debug: false
73+
http_proxy: ""
74+
insecure: false
75+
kics-container-name: cli-kics-realtime-b0ddfae9-fb90-4571-8b81-85cbf08fd4b3
76+
retry: "3"
77+
retry-delay: "5"
78+
sca_resolver: ./ScaResolver
79+
scs_repo_token: ""
80+
token: ""
81+
url: https://api.github.com
82+
url-gitlab: https://gitlab.com

test/integration/util_command.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/checkmarx/ast-cli/internal/commands"
1515
"github.com/checkmarx/ast-cli/internal/params"
1616
"github.com/checkmarx/ast-cli/internal/wrappers"
17+
"github.com/checkmarx/ast-cli/internal/wrappers/configuration"
1718
"github.com/spf13/cobra"
1819
"github.com/spf13/viper"
1920
"gotest.tools/assert"
@@ -56,6 +57,7 @@ func bindProxy(t *testing.T) {
5657
func createASTIntegrationTestCommand(t *testing.T) *cobra.Command {
5758
bindProxy(t)
5859
bindKeysToEnvAndDefault(t)
60+
configuration.LoadConfiguration()
5961
_ = viper.BindEnv(pat)
6062
viper.AutomaticEnv()
6163
viper.Set("CX_TOKEN_EXPIRY_SECONDS", 2)

0 commit comments

Comments
 (0)