Skip to content

Commit bbddac1

Browse files
Fix attest with --key (#2551)
Fix passing "--key" to the attest command. Additionally, pull in an update to the clio CLI library to permit unit testing that flags and env vars are parsed to the correct field on command options structs. This testing strategy was needed here because testing attestation in an end to end test requires a prohibitive amount of setup. --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Signed-off-by: Will Murphy <will.murphy@anchore.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
1 parent 3893f80 commit bbddac1

File tree

9 files changed

+147
-49
lines changed

9 files changed

+147
-49
lines changed

cmd/syft/cli/cli.go

+2-44
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ import (
88
"github.com/spf13/cobra"
99

1010
"github.com/anchore/clio"
11-
"github.com/anchore/stereoscope"
12-
handler "github.com/anchore/syft/cmd/syft/cli/ui"
11+
"github.com/anchore/syft/cmd/syft/internal"
1312
"github.com/anchore/syft/cmd/syft/internal/commands"
14-
"github.com/anchore/syft/cmd/syft/internal/ui"
15-
"github.com/anchore/syft/internal/bus"
16-
"github.com/anchore/syft/internal/log"
17-
"github.com/anchore/syft/internal/redact"
1813
)
1914

2015
// Application constructs the `syft packages` command and aliases the root command to `syft packages`.
@@ -34,44 +29,7 @@ func Command(id clio.Identification) *cobra.Command {
3429
}
3530

3631
func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Command) {
37-
clioCfg := clio.NewSetupConfig(id).
38-
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
39-
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
40-
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
41-
WithUIConstructor(
42-
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
43-
func(cfg clio.Config) ([]clio.UI, error) {
44-
noUI := ui.None(out, cfg.Log.Quiet)
45-
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
46-
return []clio.UI{noUI}, nil
47-
}
48-
49-
return []clio.UI{
50-
ui.New(out, cfg.Log.Quiet,
51-
handler.New(handler.DefaultHandlerConfig()),
52-
),
53-
noUI,
54-
}, nil
55-
},
56-
).
57-
WithInitializers(
58-
func(state *clio.State) error {
59-
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
60-
// we can hoist them into the internal packages for global use.
61-
stereoscope.SetBus(state.Bus)
62-
bus.Set(state.Bus)
63-
64-
redact.Set(state.RedactStore)
65-
66-
log.Set(state.Logger)
67-
stereoscope.SetLogger(state.Logger)
68-
69-
return nil
70-
},
71-
).
72-
WithPostRuns(func(state *clio.State, err error) {
73-
stereoscope.Cleanup()
74-
})
32+
clioCfg := internal.AppClioSetupConfig(id, out)
7533

7634
app := clio.New(*clioCfg)
7735

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package internal
2+
3+
import (
4+
"io"
5+
"os"
6+
7+
"github.com/anchore/clio"
8+
"github.com/anchore/stereoscope"
9+
ui2 "github.com/anchore/syft/cmd/syft/cli/ui"
10+
"github.com/anchore/syft/cmd/syft/internal/ui"
11+
"github.com/anchore/syft/internal/bus"
12+
"github.com/anchore/syft/internal/log"
13+
"github.com/anchore/syft/internal/redact"
14+
)
15+
16+
func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig {
17+
clioCfg := clio.NewSetupConfig(id).
18+
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
19+
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
20+
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
21+
WithUIConstructor(
22+
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
23+
func(cfg clio.Config) ([]clio.UI, error) {
24+
noUI := ui.None(out, cfg.Log.Quiet)
25+
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
26+
return []clio.UI{noUI}, nil
27+
}
28+
29+
return []clio.UI{
30+
ui.New(out, cfg.Log.Quiet,
31+
ui2.New(ui2.DefaultHandlerConfig()),
32+
),
33+
noUI,
34+
}, nil
35+
},
36+
).
37+
WithInitializers(
38+
func(state *clio.State) error {
39+
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
40+
// we can hoist them into the internal packages for global use.
41+
stereoscope.SetBus(state.Bus)
42+
bus.Set(state.Bus)
43+
44+
redact.Set(state.RedactStore)
45+
46+
log.Set(state.Logger)
47+
stereoscope.SetLogger(state.Logger)
48+
return nil
49+
},
50+
).
51+
WithPostRuns(func(state *clio.State, err error) {
52+
stereoscope.Cleanup()
53+
})
54+
return clioCfg
55+
}

cmd/syft/internal/commands/attest.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type attestOptions struct {
4242
options.Output `yaml:",inline" mapstructure:",squash"`
4343
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
4444
options.Catalog `yaml:",inline" mapstructure:",squash"`
45-
options.Attest `yaml:",inline" mapstructure:",squash"`
45+
Attest options.Attest `yaml:"attest" mapstructure:"attest"`
4646
}
4747

4848
func Attest(app clio.Application) *cobra.Command {

cmd/syft/internal/commands/attest_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7+
"io"
78
"os/exec"
89
"regexp"
910
"strings"
1011
"testing"
1112

13+
"github.com/google/go-cmp/cmp"
1214
"github.com/scylladb/go-set/strset"
15+
"github.com/spf13/cobra"
1316
"github.com/stretchr/testify/assert"
1417
"github.com/stretchr/testify/require"
1518

1619
"github.com/anchore/clio"
20+
"github.com/anchore/clio/cliotestutils"
21+
"github.com/anchore/syft/cmd/syft/internal"
1722
"github.com/anchore/syft/cmd/syft/internal/options"
1823
"github.com/anchore/syft/syft/sbom"
1924
"github.com/anchore/syft/syft/source"
@@ -267,3 +272,60 @@ func Test_buildSBOMForAttestation(t *testing.T) {
267272
})
268273
}
269274
}
275+
276+
func Test_attestCLIWiring(t *testing.T) {
277+
id := clio.Identification{
278+
Name: "syft",
279+
Version: "testing",
280+
}
281+
cfg := internal.AppClioSetupConfig(id, io.Discard)
282+
tests := []struct {
283+
name string
284+
assertionFunc func(*testing.T, *cobra.Command, []string, ...any)
285+
wantOpts attestOptions
286+
args []string
287+
env map[string]string
288+
}{
289+
{
290+
name: "key flag is accepted",
291+
args: []string{"some-image:some-tag", "--key", "some-cosign-key.key"},
292+
assertionFunc: hasAttestOpts(options.Attest{Key: "some-cosign-key.key"}),
293+
},
294+
{
295+
name: "key password is read from env",
296+
args: []string{"some-image:some-tag", "--key", "cosign.key"},
297+
env: map[string]string{
298+
"SYFT_ATTEST_PASSWORD": "some-password",
299+
},
300+
assertionFunc: hasAttestOpts(options.Attest{
301+
Key: "cosign.key",
302+
Password: "some-password",
303+
}),
304+
},
305+
}
306+
for _, tt := range tests {
307+
t.Run(tt.name, func(t *testing.T) {
308+
if tt.env != nil {
309+
for k, v := range tt.env {
310+
t.Setenv(k, v)
311+
}
312+
}
313+
app := cliotestutils.NewApplication(t, cfg, tt.assertionFunc)
314+
cmd := Attest(app)
315+
cmd.SetArgs(tt.args)
316+
err := cmd.Execute()
317+
assert.NoError(t, err)
318+
})
319+
}
320+
}
321+
322+
func hasAttestOpts(wantOpts options.Attest) cliotestutils.AssertionFunc {
323+
return func(t *testing.T, _ *cobra.Command, _ []string, cfgs ...any) {
324+
assert.Equal(t, len(cfgs), 1)
325+
attestOpts, ok := cfgs[0].(*attestOptions)
326+
require.True(t, ok)
327+
if d := cmp.Diff(wantOpts, attestOpts.Attest); d != "" {
328+
t.Errorf("mismatched attest options (-want +got):\n%s", d)
329+
}
330+
}
331+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package commands
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
gologgerredact "github.com/anchore/go-logger/adapter/redact"
8+
"github.com/anchore/syft/internal/redact"
9+
)
10+
11+
func TestMain(m *testing.M) {
12+
// Initialize global state needed to test clio/cobra commands directly
13+
// Should be kept minimal.
14+
15+
// Initialize redact store once for all tests in the commands package
16+
// Redact store must be wired up here because syft will panic unless
17+
// a redact store is wired up exactly once
18+
redact.Set(gologgerredact.NewStore())
19+
os.Exit(m.Run())
20+
}

cmd/syft/internal/options/attest.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"github.com/anchore/clio"
55
)
66

7+
var _ clio.FlagAdder = (*Attest)(nil)
8+
79
type Attest struct {
810
// IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information)
911
Key secret `yaml:"key" json:"key" mapstructure:"key"`
@@ -12,6 +14,6 @@ type Attest struct {
1214

1315
var _ clio.FlagAdder = (*Attest)(nil)
1416

15-
func (o Attest) AddFlags(flags clio.FlagSet) {
17+
func (o *Attest) AddFlags(flags clio.FlagSet) {
1618
flags.StringVarP((*string)(&o.Key), "key", "k", "the key to use for the attestation")
1719
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
1010
github.com/acobaugh/osrelease v0.1.0
1111
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
12-
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc
12+
github.com/anchore/clio v0.0.0-20240131202212-9eba61247448
1313
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b
1414
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
1515
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58Pa
9393
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
9494
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU=
9595
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
96-
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ=
97-
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU=
96+
github.com/anchore/clio v0.0.0-20240131202212-9eba61247448 h1:ZgecmkxhH5im+9jPs7Ra1Thmv/p4IBDsoCFD6W8pENg=
97+
github.com/anchore/clio v0.0.0-20240131202212-9eba61247448/go.mod h1:t5Mld8naKcG8RTPjW/2n7bfyBKFl1A6PvtXw+v64gK0=
9898
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A=
9999
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo=
100100
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=

test/cli/scan_cmd_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ func TestPackagesCmdFlags(t *testing.T) {
191191
},
192192
},
193193
{
194+
// TODO: this could be a unit test
194195
name: "responds-to-package-cataloger-search-options",
195196
args: []string{"--help"},
196197
env: map[string]string{

0 commit comments

Comments
 (0)