Skip to content

Commit 5f63567

Browse files
committed
feat(#32): configure by file with internal package
1 parent 5b4886d commit 5f63567

File tree

17 files changed

+379
-180
lines changed

17 files changed

+379
-180
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ go.work.sum
2929
tmp_scans/
3030

3131
# Sheriff configuration file, untracked for use in local development
32-
sheriff.toml
32+
/sheriff.toml

internal/cli/app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This file is formatted in TOML and can contain any of the flags that can be set
2222
`,
2323
Flags: PatrolFlags,
2424
Action: PatrolAction,
25-
Before: CombineBeforeFuncs(ConfigureLogs, GetConfigFileLoader(PatrolFlags, configFlag), LogArguments),
25+
Before: ConfigureLogs,
2626
},
2727
},
2828
}

internal/cli/patrol.go

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"strings"
1414

1515
"github.com/urfave/cli/v2"
16-
"github.com/urfave/cli/v2/altsrc"
1716
)
1817

1918
type CommandCategory string
@@ -36,7 +35,6 @@ const silentReportFlag = "silent"
3635
const gitlabTokenFlag = "gitlab-token"
3736
const slackTokenFlag = "slack-token"
3837

39-
var sensitiveFlags = []string{gitlabTokenFlag, slackTokenFlag}
4038
var necessaryScanners = []string{scanner.OsvCommandName}
4139

4240
var PatrolFlags = []cli.Flag{
@@ -52,38 +50,38 @@ var PatrolFlags = []cli.Flag{
5250
Category: string(Miscellaneous),
5351
Value: false,
5452
},
55-
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
53+
&cli.StringSliceFlag{
5654
Name: urlFlag,
5755
Usage: "Groups and projects to scan for vulnerabilities (list argument which can be repeated)",
5856
Category: string(Scanning),
59-
}),
60-
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
57+
},
58+
&cli.StringSliceFlag{
6159
Name: reportToEmailFlag,
6260
Usage: "Enable reporting to the provided list of emails",
6361
Category: string(Reporting),
64-
}),
65-
altsrc.NewBoolFlag(&cli.BoolFlag{
62+
},
63+
&cli.BoolFlag{
6664
Name: reportToIssueFlag,
6765
Usage: "Enable or disable reporting to the project's issue on the associated platform (gitlab, github, ...)",
6866
Category: string(Reporting),
69-
}),
70-
altsrc.NewStringFlag(&cli.StringFlag{
67+
},
68+
&cli.StringFlag{
7169
Name: reportToSlackChannel,
7270
Usage: "Enable reporting to the provided slack channel",
7371
Category: string(Reporting),
74-
}),
75-
altsrc.NewBoolFlag(&cli.BoolFlag{
72+
},
73+
&cli.BoolFlag{
7674
Name: reportEnableProjectReportToFlag,
7775
Usage: "Enable project-level configuration for '--report-to-*'.",
7876
Category: string(Reporting),
7977
Value: true,
80-
}),
81-
altsrc.NewBoolFlag(&cli.BoolFlag{
78+
},
79+
&cli.BoolFlag{
8280
Name: silentReportFlag,
8381
Usage: "Disable report output to stdout.",
8482
Category: string(Reporting),
8583
Value: false,
86-
}),
84+
},
8785
// Secret tokens
8886
&cli.StringFlag{
8987
Name: gitlabTokenFlag,
@@ -102,13 +100,20 @@ var PatrolFlags = []cli.Flag{
102100

103101
func PatrolAction(cCtx *cli.Context) error {
104102
config, err := config.GetPatrolConfiguration(config.PatrolCLIOpts{
105-
Urls: cCtx.StringSlice(urlFlag),
106-
ReportToIssue: cCtx.Bool(reportToIssueFlag),
107-
ReportToEmails: cCtx.StringSlice(reportToEmailFlag),
108-
ReportToSlackChannel: cCtx.String(reportToSlackChannel),
109-
EnableProjectReportTo: cCtx.Bool(reportEnableProjectReportToFlag),
110-
SilentReport: cCtx.Bool(silentReportFlag),
111-
Verbose: cCtx.Bool(verboseFlag),
103+
PatrolCommonOpts: config.PatrolCommonOpts{
104+
Urls: getStringSliceIfSet(cCtx, urlFlag),
105+
Report: config.PatrolReportOpts{
106+
To: config.PatrolReportToOpts{
107+
Issue: getBoolIfSet(cCtx, reportToIssueFlag),
108+
Emails: getStringSliceIfSet(cCtx, reportToEmailFlag),
109+
SlackChannel: getStringIfSet(cCtx, reportToSlackChannel),
110+
EnableProjectReportTo: getBoolIfSet(cCtx, reportEnableProjectReportToFlag),
111+
},
112+
SilentReport: getBoolIfSet(cCtx, silentReportFlag),
113+
},
114+
},
115+
Config: cCtx.String(configFlag),
116+
Verbose: cCtx.Bool(verboseFlag),
112117
})
113118
if err != nil {
114119
return errors.Join(errors.New("failed to get patrol configuration"), err)

internal/cli/utils.go

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,42 @@
11
package cli
22

33
import (
4-
"errors"
5-
"fmt"
6-
"os"
74
"sheriff/internal/log"
8-
"slices"
95

106
zerolog "github.com/rs/zerolog/log"
117
"github.com/urfave/cli/v2"
12-
"github.com/urfave/cli/v2/altsrc"
138
)
149

15-
func CombineBeforeFuncs(beforeFuncs ...cli.BeforeFunc) cli.BeforeFunc {
16-
return func(cCtx *cli.Context) error {
17-
for _, beforeFunc := range beforeFuncs {
18-
if err := beforeFunc(cCtx); err != nil {
19-
return err
20-
}
21-
}
22-
return nil
23-
}
24-
}
25-
2610
func ConfigureLogs(cCtx *cli.Context) error {
2711
log.ConfigureLogs(cCtx.Bool(verboseFlag))
2812
zerolog.Info().Msg("Logging configured")
2913
return nil
3014
}
3115

32-
func GetConfigFileLoader(flags []cli.Flag, fileNameFlag string) cli.BeforeFunc {
33-
return func(cCtx *cli.Context) error {
34-
fileName := cCtx.String(fileNameFlag)
35-
if _, err := os.Stat(fileName); err == nil {
36-
// Config file exists
37-
zerolog.Info().Str("file", fileName).Msg("Loading configuration file")
38-
return altsrc.InitInputSourceWithContext(flags, func(cCtx *cli.Context) (altsrc.InputSourceContext, error) {
39-
return altsrc.NewTomlSourceFromFile(fileName)
40-
})(cCtx)
41-
} else if errors.Is(err, os.ErrNotExist) {
42-
if cCtx.IsSet(fileNameFlag) {
43-
// Config file was explicitly set but does not exist
44-
return fmt.Errorf("config file %v does not exist", fileName)
45-
}
46-
zerolog.Info().Str("file", fileName).Msg("No configuration file found")
47-
return nil // No config file, do nothing
48-
} else {
49-
// Error stating config file
50-
return errors.Join(fmt.Errorf("failed to stat config file %s", fileName), err)
51-
}
16+
func getStringSliceIfSet(cCtx *cli.Context, flagName string) *[]string {
17+
if cCtx.IsSet(flagName) {
18+
v := cCtx.StringSlice(flagName)
19+
return &v
5220
}
53-
}
5421

55-
func LogArguments(cCtx *cli.Context) error {
56-
flags := cCtx.FlagNames()
22+
return nil
23+
}
5724

58-
flagList := make([]string, 0, len(flags))
25+
func getStringIfSet(cCtx *cli.Context, flagName string) *string {
26+
if cCtx.IsSet(flagName) {
27+
v := cCtx.String(flagName)
28+
print(v)
29+
return &v
30+
}
5931

60-
for _, flag := range flags {
61-
val := cCtx.Value(flag)
62-
if slices.Contains(sensitiveFlags, flag) {
63-
val = "REDACTED"
64-
} else if sliceVal, ok := val.(cli.StringSlice); ok {
65-
val = sliceVal.String()
66-
}
32+
return nil
33+
}
6734

68-
flagList = append(flagList, fmt.Sprintf("%s=%v", flag, val))
35+
func getBoolIfSet(cCtx *cli.Context, flagName string) *bool {
36+
if cCtx.IsSet(flagName) {
37+
v := cCtx.Bool(flagName)
38+
return &v
6939
}
7040

71-
zerolog.Info().Strs("arguments", flagList).Msg("Running with configuration")
72-
7341
return nil
7442
}

internal/cli/utils_test.go

Lines changed: 119 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,15 @@ package cli
22

33
import (
44
"flag"
5+
"strconv"
6+
"strings"
57
"testing"
68

79
"github.com/rs/zerolog"
810
"github.com/stretchr/testify/assert"
9-
"github.com/stretchr/testify/mock"
1011
"github.com/urfave/cli/v2"
1112
)
1213

13-
func TestCombineBeforeFuncs(t *testing.T) {
14-
mockBeforeFuncs := new(mockBeforeFuncs)
15-
mockBeforeFuncs.On("Func1", mock.Anything).Return(nil)
16-
mockBeforeFuncs.On("Func2", mock.Anything).Return(nil)
17-
18-
before_func := CombineBeforeFuncs(mockBeforeFuncs.Func1, mockBeforeFuncs.Func2)
19-
20-
_ = before_func(nil)
21-
22-
mockBeforeFuncs.AssertExpectations(t)
23-
}
24-
25-
type mockBeforeFuncs struct {
26-
mock.Mock
27-
}
28-
29-
func (m *mockBeforeFuncs) Func1(cCtx *cli.Context) error {
30-
args := m.Called(cCtx)
31-
return args.Error(0)
32-
}
33-
34-
func (m *mockBeforeFuncs) Func2(cCtx *cli.Context) error {
35-
args := m.Called(cCtx)
36-
return args.Error(0)
37-
}
38-
3914
func TestConfigureLogs(t *testing.T) {
4015
testCases := map[bool]zerolog.Level{
4116
true: zerolog.DebugLevel,
@@ -53,26 +28,127 @@ func TestConfigureLogs(t *testing.T) {
5328
}
5429
}
5530

56-
// Tests for ConfigFileLoader when no file is found.
57-
// There should be an equivalent test for when the file is found, but it's tough.
58-
func TestConfigFileLoaderNoFile(t *testing.T) {
59-
flag := flag.NewFlagSet("config", flag.ContinueOnError)
60-
flag.String("config", "nonexistent", "")
61-
context := cli.NewContext(nil, flag, nil)
31+
func TestGetStringIfSettest(t *testing.T) {
32+
want := "hello"
33+
flagName := "testFlag"
6234

63-
beforeFunc := GetConfigFileLoader(nil, "config")
64-
err := beforeFunc(context)
35+
flag := flag.NewFlagSet("", flag.ContinueOnError)
36+
flag.String(flagName, "", "")
37+
_ = flag.Set(flagName, want)
38+
cCtx := cli.NewContext(nil, flag, nil)
6539

66-
assert.Nil(t, err)
40+
got := getStringIfSet(cCtx, flagName)
41+
42+
assert.Equal(t, want, *got)
6743
}
6844

69-
func TestLogArguments(t *testing.T) {
70-
flag := flag.NewFlagSet("flag", flag.ContinueOnError)
71-
flag.String("some-flag", "", "")
72-
_ = flag.Set("some-flag", "value")
73-
context := cli.NewContext(nil, flag, nil)
45+
func TestGetStringIfSet(t *testing.T) {
46+
testCases := []struct {
47+
name string
48+
want string
49+
set bool
50+
}{{
51+
name: "value",
52+
want: "hello",
53+
set: true,
54+
}, {
55+
name: "nil",
56+
want: "",
57+
set: false,
58+
}}
59+
60+
flagName := "testFlag"
61+
for _, tc := range testCases {
62+
t.Run(tc.name, func(t *testing.T) {
63+
flag := flag.NewFlagSet("", flag.ContinueOnError)
64+
flag.String(flagName, "", "")
65+
if tc.set {
66+
_ = flag.Set(flagName, tc.want)
67+
}
68+
cCtx := cli.NewContext(nil, flag, nil)
69+
70+
got := getStringIfSet(cCtx, flagName)
71+
72+
if tc.set {
73+
assert.NotNil(t, got)
74+
assert.Equal(t, tc.want, *got)
75+
} else {
76+
assert.Nil(t, got)
77+
}
78+
})
79+
}
80+
}
7481

75-
_ = LogArguments(context)
82+
func TestGetStringSliceIfSet(t *testing.T) {
83+
testCases := []struct {
84+
name string
85+
want []string
86+
set bool
87+
}{{
88+
name: "value",
89+
want: []string{"hello", "world"},
90+
set: true,
91+
}, {
92+
name: "nil",
93+
want: []string{},
94+
set: false,
95+
}}
96+
97+
flagName := "testFlag"
98+
for _, tc := range testCases {
99+
t.Run(tc.name, func(t *testing.T) {
100+
flag := flag.NewFlagSet("", flag.ContinueOnError)
101+
flag.Var(&cli.StringSlice{}, flagName, "")
102+
if tc.set {
103+
_ = flag.Set(flagName, strings.Join(tc.want, ", "))
104+
}
105+
cCtx := cli.NewContext(nil, flag, nil)
106+
107+
got := getStringSliceIfSet(cCtx, flagName)
108+
109+
if tc.set {
110+
assert.NotNil(t, got)
111+
assert.Equal(t, tc.want, *got)
112+
} else {
113+
assert.Nil(t, got)
114+
}
115+
})
116+
}
117+
}
76118

77-
// How to assert that the log message was correct?
119+
func TestGetBoolIfSet(t *testing.T) {
120+
testCases := []struct {
121+
name string
122+
want bool
123+
set bool
124+
}{{
125+
name: "value",
126+
want: true,
127+
set: true,
128+
}, {
129+
name: "nil",
130+
want: false,
131+
set: false,
132+
}}
133+
134+
flagName := "testFlag"
135+
for _, tc := range testCases {
136+
t.Run(tc.name, func(t *testing.T) {
137+
flag := flag.NewFlagSet("", flag.ContinueOnError)
138+
flag.Bool(flagName, false, "")
139+
if tc.set {
140+
_ = flag.Set(flagName, strconv.FormatBool(tc.want))
141+
}
142+
cCtx := cli.NewContext(nil, flag, nil)
143+
144+
got := getBoolIfSet(cCtx, flagName)
145+
146+
if tc.set {
147+
assert.NotNil(t, got)
148+
assert.Equal(t, tc.want, *got)
149+
} else {
150+
assert.Nil(t, got)
151+
}
152+
})
153+
}
78154
}

0 commit comments

Comments
 (0)