Skip to content

Commit f3e7930

Browse files
Merge branch 'main' into feature/saraChen/addTelemetryCmd
2 parents 6b840ce + 55cb135 commit f3e7930

File tree

6 files changed

+180
-5
lines changed

6 files changed

+180
-5
lines changed

internal/commands/root.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package commands
22

33
import (
44
"fmt"
5+
"io"
56
"log"
67
"os"
8+
"path/filepath"
79
"strings"
810

911
"github.com/MakeNowJust/heredoc"
@@ -103,12 +105,21 @@ func NewAstCLI(
103105
rootCmd.PersistentFlags().Bool(params.ApikeyOverrideFlag, false, "")
104106

105107
_ = rootCmd.PersistentFlags().MarkHidden(params.ApikeyOverrideFlag)
108+
rootCmd.PersistentFlags().String(params.LogFileFlag, "", params.LogFileUsage)
109+
rootCmd.PersistentFlags().String(params.LogFileConsoleFlag, "", params.LogFileConsoleUsage)
106110

107111
// This monitors and traps situations where "extra/garbage" commands
108112
// are passed to Cobra.
109113
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
114+
err := customLogConfiguration(rootCmd)
115+
if err != nil {
116+
return err
117+
}
110118
PrintConfiguration()
111-
err := configuration.LoadConfiguration()
119+
err = configuration.LoadConfiguration()
120+
if err != nil {
121+
return err
122+
}
112123
// Need to check the __complete command to allow correct behavior of the autocomplete
113124
if len(args) > 0 && cmd.Name() != params.Help && cmd.Name() != "__complete" {
114125
_ = cmd.Help()
@@ -137,7 +148,8 @@ func NewAstCLI(
137148
_ = viper.BindPFlag(params.RetryFlag, rootCmd.PersistentFlags().Lookup(params.RetryFlag))
138149
_ = viper.BindPFlag(params.RetryDelayFlag, rootCmd.PersistentFlags().Lookup(params.RetryDelayFlag))
139150
_ = viper.BindPFlag(params.ApikeyOverrideFlag, rootCmd.PersistentFlags().Lookup(params.ApikeyOverrideFlag))
140-
151+
_ = viper.BindPFlag(params.LogFileFlag, rootCmd.PersistentFlags().Lookup(params.LogFileFlag))
152+
_ = viper.BindPFlag(params.LogFileConsoleFlag, rootCmd.PersistentFlags().Lookup(params.LogFileConsoleFlag))
141153
// Set help func
142154
rootCmd.SetHelpFunc(
143155
func(command *cobra.Command, args []string) {
@@ -336,3 +348,61 @@ func printByScanInfoFormat(cmd *cobra.Command, view interface{}) error {
336348
f, _ := cmd.Flags().GetString(params.ScanInfoFormatFlag)
337349
return printer.Print(cmd.OutOrStdout(), view, f)
338350
}
351+
352+
func customLogConfiguration(cmd *cobra.Command) error {
353+
if cmd.PersistentFlags().Changed(params.LogFileFlag) {
354+
if err := setLogOutputFromFlag(params.LogFileFlag, viper.GetString(params.LogFileFlag)); err != nil {
355+
return err
356+
}
357+
}
358+
if cmd.PersistentFlags().Changed(params.LogFileConsoleFlag) {
359+
if err := setLogOutputFromFlag(params.LogFileConsoleFlag, viper.GetString(params.LogFileConsoleFlag)); err != nil {
360+
return err
361+
}
362+
}
363+
return nil
364+
}
365+
366+
func setLogOutputFromFlag(flag, dirPath string) error {
367+
if strings.TrimSpace(dirPath) == "" {
368+
return errors.New("flag needs an argument: --" + flag)
369+
}
370+
371+
// Confirm it’s a directory
372+
info, err := os.Stat(dirPath)
373+
if err != nil {
374+
if os.IsNotExist(err) {
375+
return fmt.Errorf("the specified directory path does not exist. Please check the path: %s", dirPath)
376+
}
377+
return fmt.Errorf("an error occurred while accessing the directory path. Please check the path: %s", dirPath)
378+
}
379+
if !info.IsDir() {
380+
return fmt.Errorf("expected a directory path but got a file: %s", dirPath)
381+
}
382+
383+
// Create full path for the log file
384+
logFilePath := filepath.Join(dirPath, "ast-cli.log")
385+
386+
const defaultFilePermissions = 0666
387+
// open the log file with write and append permissions
388+
// If file doesn't exist, it will be created. If permission is denied for directory path, return an error.
389+
file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, defaultFilePermissions)
390+
if err != nil {
391+
if os.IsPermission(err) {
392+
return fmt.Errorf("permission denied: cannot write to directory %s", dirPath)
393+
}
394+
return fmt.Errorf("unable to open log file %s: %v", logFilePath, err)
395+
}
396+
397+
// Configure the logger to write to the log file and optionally to stdout.
398+
// If the flag indicates stdout logging is enabled, log output is duplicated to both file and console.
399+
// Otherwise, logs are written only to the file.
400+
var multiWriter io.Writer
401+
if flag == params.LogFileConsoleFlag {
402+
multiWriter = io.MultiWriter(file, os.Stdout)
403+
} else {
404+
multiWriter = io.MultiWriter(file)
405+
}
406+
log.SetOutput(multiWriter)
407+
return nil
408+
}

internal/commands/root_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"strings"
1111
"testing"
1212

13+
"github.com/checkmarx/ast-cli/internal/params"
14+
1315
"github.com/checkmarx/ast-cli/internal/wrappers"
1416
"github.com/checkmarx/ast-cli/internal/wrappers/mock"
1517
"github.com/spf13/viper"
@@ -254,3 +256,55 @@ func Test_stateExclude_not_exploitableRepalceForAllStatesExceptNot_exploitable(t
254256
})
255257
}
256258
}
259+
func TestSetLogOutputFromFlag_InvalidDir1(t *testing.T) {
260+
err := setLogOutputFromFlag(params.LogFileFlag, "/custom/path")
261+
assert.ErrorContains(t, err, "the specified directory path does not exist.")
262+
}
263+
264+
func TestSetLogOutputFromFlag_EmptyDirPath(t *testing.T) {
265+
err := setLogOutputFromFlag(params.LogFileFlag, "")
266+
assert.ErrorContains(t, err, "flag needs an argument")
267+
}
268+
269+
func TestSetLogOutputFromFlag_DirPathIsFilePath(t *testing.T) {
270+
tempFile, _ := os.CreateTemp("", "ast-cli.txt")
271+
defer func() {
272+
if err := os.RemoveAll(tempFile.Name()); err != nil {
273+
fmt.Printf("Warning: failed to clean up temp directory %s: %v\n", tempFile.Name(), err)
274+
}
275+
}()
276+
err := setLogOutputFromFlag(params.LogFileFlag, tempFile.Name())
277+
assert.ErrorContains(t, err, "expected a directory path but got a file")
278+
}
279+
280+
func TestSetLogOutputFromFlag_DirPathPermissionDenied(t *testing.T) {
281+
tempDir, _ := os.MkdirTemp("", "tempdir")
282+
_ = os.Chmod(tempDir, 0000)
283+
defer func(path string) {
284+
_ = os.RemoveAll(path)
285+
}(tempDir)
286+
err := setLogOutputFromFlag(params.LogFileFlag, tempDir)
287+
assert.ErrorContains(t, err, "permission denied: cannot write to directory")
288+
}
289+
290+
func TestSetLogOutputFromFlag_DirPath_Success(t *testing.T) {
291+
tempDir, _ := os.MkdirTemp("", "tempdir")
292+
defer func() {
293+
if err := os.RemoveAll(tempDir); err != nil {
294+
fmt.Printf("Warning: failed to clean up temp directory %s: %v\n", tempDir, err)
295+
}
296+
}()
297+
err := setLogOutputFromFlag(params.LogFileFlag, tempDir)
298+
assert.NilError(t, err)
299+
}
300+
301+
func TestSetLogOutputFromFlag_DirPath_Console_Success(t *testing.T) {
302+
tempDir, _ := os.MkdirTemp("", "tempdir")
303+
defer func() {
304+
if err := os.RemoveAll(tempDir); err != nil {
305+
fmt.Printf("Warning: failed to clean up temp directory %s: %v\n", tempDir, err)
306+
}
307+
}()
308+
err := setLogOutputFromFlag(params.LogFileConsoleFlag, tempDir)
309+
assert.NilError(t, err)
310+
}

internal/logger/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func Printf(msg string, args ...interface{}) {
3838
}
3939

4040
func PrintIfVerbose(msg string) {
41-
if viper.GetBool(params.DebugFlag) {
41+
if viper.GetBool(params.DebugFlag) || viper.GetString(params.LogFileFlag) != "" || viper.GetString(params.LogFileConsoleFlag) != "" {
4242
Print(msg)
4343
}
4444
}

internal/params/flags.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ const (
164164
ScaHideDevAndTestDepFlag = "sca-hide-dev-test-dependencies"
165165
LimitFlag = "limit"
166166
ConfigFilePathFlag = "config-file-path"
167-
167+
LogFileFlag = "log-file"
168+
LogFileUsage = "Saves logs to the specified file path only"
169+
LogFileConsoleFlag = "log-file-console"
170+
LogFileConsoleUsage = "Saves logs to the specified file path as well as to the console"
168171
// INDIVIDUAL FILTER FLAGS
169172
SastFilterFlag = "sast-filter"
170173
SastFilterUsage = "SAST filter"

internal/wrappers/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func SendHTTPRequestByFullURLContentLength(
268268

269269
func addReqMonitor(req *http.Request) *http.Request {
270270
startTime := time.Now().UnixNano() / int64(time.Millisecond)
271-
if viper.GetBool(commonParams.DebugFlag) {
271+
if viper.GetBool(commonParams.DebugFlag) || viper.GetString(commonParams.LogFileFlag) != "" || viper.GetString(commonParams.LogFileConsoleFlag) != "" {
272272
trace := &httptrace.ClientTrace{
273273
GetConn: func(hostPort string) {
274274
startTime = time.Now().UnixNano() / int64(time.Millisecond)

test/integration/root_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
commonParams "github.com/checkmarx/ast-cli/internal/params"
1313
"github.com/checkmarx/ast-cli/internal/wrappers"
1414
"github.com/spf13/viper"
15+
"gotest.tools/assert"
1516
)
1617

1718
const (
@@ -123,3 +124,50 @@ func isFFEnabled(t *testing.T, featureFlag string) bool {
123124
flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, featureFlag)
124125
return flagResponse.Status
125126
}
127+
128+
func TestSetLogOutputFromFlag_InvalidDir(t *testing.T) {
129+
err, _ := executeCommand(t, "auth", "validate", "--log-file", "/custom/path")
130+
assert.ErrorContains(t, err, "the specified directory path does not exist.")
131+
}
132+
133+
func TestSetLogOutputFromFlag_EmptyDirPath(t *testing.T) {
134+
err, _ := executeCommand(t, "auth", "validate", "--log-file", "")
135+
assert.ErrorContains(t, err, "flag needs an argument")
136+
}
137+
138+
func TestSetLogOutputFromFlag_DirPathIsFilePath(t *testing.T) {
139+
tempFile, _ := os.CreateTemp("", "ast-cli.txt")
140+
defer func(path string) {
141+
_ = os.Remove(path)
142+
}(tempFile.Name())
143+
err, _ := executeCommand(t, "auth", "validate", "--log-file", tempFile.Name())
144+
assert.ErrorContains(t, err, "expected a directory path but got a file")
145+
}
146+
147+
func TestSetLogOutputFromFlag_DirPathPermissionDenied(t *testing.T) {
148+
tempDir, _ := os.MkdirTemp("", "tempdir")
149+
_ = os.Chmod(tempDir, 0000)
150+
defer func(path string) {
151+
_ = os.RemoveAll(path)
152+
}(tempDir)
153+
err, _ := executeCommand(t, "auth", "validate", "--log-file", tempDir)
154+
assert.ErrorContains(t, err, "permission denied: cannot write to directory")
155+
}
156+
157+
func TestSetLogOutputFromFlag_DirPath_Success(t *testing.T) {
158+
tempDir, _ := os.MkdirTemp("", "tempdir")
159+
defer func(path string) {
160+
_ = os.RemoveAll(path)
161+
}(tempDir)
162+
err, _ := executeCommand(t, "auth", "validate", "--log-file", tempDir)
163+
assert.NilError(t, err)
164+
}
165+
166+
func TestSetLogOutputFromFlag_DirPath_Console_Success(t *testing.T) {
167+
tempDir, _ := os.MkdirTemp("", "tempdir")
168+
defer func(path string) {
169+
_ = os.RemoveAll(path)
170+
}(tempDir)
171+
err, _ := executeCommand(t, "auth", "validate", "--log-file-console", tempDir)
172+
assert.NilError(t, err)
173+
}

0 commit comments

Comments
 (0)