Skip to content

Commit 4842e87

Browse files
authored
Merge branch 'main' into other/Add-file-path-in-ignoredFile-packages
2 parents a4ce090 + 55cb135 commit 4842e87

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"
@@ -102,12 +104,21 @@ func NewAstCLI(
102104
rootCmd.PersistentFlags().Bool(params.ApikeyOverrideFlag, false, "")
103105

104106
_ = rootCmd.PersistentFlags().MarkHidden(params.ApikeyOverrideFlag)
107+
rootCmd.PersistentFlags().String(params.LogFileFlag, "", params.LogFileUsage)
108+
rootCmd.PersistentFlags().String(params.LogFileConsoleFlag, "", params.LogFileConsoleUsage)
105109

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

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

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
@@ -159,7 +159,10 @@ const (
159159
ScaHideDevAndTestDepFlag = "sca-hide-dev-test-dependencies"
160160
LimitFlag = "limit"
161161
ConfigFilePathFlag = "config-file-path"
162-
162+
LogFileFlag = "log-file"
163+
LogFileUsage = "Saves logs to the specified file path only"
164+
LogFileConsoleFlag = "log-file-console"
165+
LogFileConsoleUsage = "Saves logs to the specified file path as well as to the console"
163166
// INDIVIDUAL FILTER FLAGS
164167
SastFilterFlag = "sast-filter"
165168
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)