mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Fix file creation for output options (#875)
This commit is contained in:
parent
5123f073c7
commit
07d3c9af52
@ -153,11 +153,11 @@ func attestExec(ctx context.Context, _ *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("attest command can only be used with image sources fetch directly from the registry, but discovered an image source of %q when given %q", si.ImageSource, userInput)
|
return fmt.Errorf("attest command can only be used with image sources fetch directly from the registry, but discovered an image source of %q when given %q", si.ImageSource, userInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(appConfig.Output) > 1 {
|
if len(appConfig.Outputs) > 1 {
|
||||||
return fmt.Errorf("unable to generate attestation for more than one output")
|
return fmt.Errorf("unable to generate attestation for more than one output")
|
||||||
}
|
}
|
||||||
|
|
||||||
format := syft.FormatByName(appConfig.Output[0])
|
format := syft.FormatByName(appConfig.Outputs[0])
|
||||||
predicateType := formatPredicateType(format)
|
predicateType := formatPredicateType(format)
|
||||||
if predicateType == "" {
|
if predicateType == "" {
|
||||||
return fmt.Errorf("could not produce attestation predicate for given format: %q. Available formats: %+v", formatAliases(format.ID()), formatAliases(attestFormats...))
|
return fmt.Errorf("could not produce attestation predicate for given format: %q. Available formats: %+v", formatAliases(format.ID()), formatAliases(attestFormats...))
|
||||||
|
|||||||
@ -58,10 +58,7 @@ func parseOptions(outputs []string, defaultFile string) (out []sbom.WriterOption
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
out = append(out, sbom.WriterOption{
|
out = append(out, sbom.NewWriterOption(format, file))
|
||||||
Format: format,
|
|
||||||
Path: file,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return out, errs
|
return out, errs
|
||||||
}
|
}
|
||||||
|
|||||||
@ -222,7 +222,7 @@ func validateInputArgs(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func packagesExec(_ *cobra.Command, args []string) error {
|
func packagesExec(_ *cobra.Command, args []string) error {
|
||||||
writer, err := makeWriter(appConfig.Output, appConfig.File)
|
writer, err := makeWriter(appConfig.Outputs, appConfig.File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,10 +73,12 @@ func powerUserExec(_ *cobra.Command, args []string) error {
|
|||||||
// could be an image or a directory, with or without a scheme
|
// could be an image or a directory, with or without a scheme
|
||||||
userInput := args[0]
|
userInput := args[0]
|
||||||
|
|
||||||
writer, err := sbom.NewWriter(sbom.WriterOption{
|
writer, err := sbom.NewWriter(
|
||||||
Format: syftjson.Format(),
|
sbom.NewWriterOption(
|
||||||
Path: appConfig.File,
|
syftjson.Format(),
|
||||||
})
|
appConfig.File,
|
||||||
|
),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ type parser interface {
|
|||||||
// Application is the main syft application configuration.
|
// Application is the main syft application configuration.
|
||||||
type Application struct {
|
type Application struct {
|
||||||
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
|
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
|
||||||
Output []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
||||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||||
@ -104,6 +104,7 @@ func (cfg *Application) parseConfigValues() error {
|
|||||||
for _, optionFn := range []func() error{
|
for _, optionFn := range []func() error{
|
||||||
cfg.parseUploadOptions,
|
cfg.parseUploadOptions,
|
||||||
cfg.parseLogLevelOption,
|
cfg.parseLogLevelOption,
|
||||||
|
cfg.parseFile,
|
||||||
} {
|
} {
|
||||||
if err := optionFn(); err != nil {
|
if err := optionFn(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -126,6 +127,17 @@ func (cfg *Application) parseConfigValues() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Application) parseFile() error {
|
||||||
|
if cfg.File != "" {
|
||||||
|
expandedPath, err := homedir.Expand(cfg.File)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to expand file path=%q: %w", cfg.File, err)
|
||||||
|
}
|
||||||
|
cfg.File = expandedPath
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *Application) parseUploadOptions() error {
|
func (cfg *Application) parseUploadOptions() error {
|
||||||
if cfg.Anchore.Host == "" && cfg.Anchore.Dockerfile != "" {
|
if cfg.Anchore.Host == "" && cfg.Anchore.Dockerfile != "" {
|
||||||
return fmt.Errorf("cannot provide dockerfile option without enabling upload")
|
return fmt.Errorf("cannot provide dockerfile option without enabling upload")
|
||||||
|
|||||||
52
internal/config/application_test.go
Normal file
52
internal/config/application_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplication_parseFile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config Application
|
||||||
|
expected string
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "expand home dir",
|
||||||
|
config: Application{
|
||||||
|
File: "~/place.txt",
|
||||||
|
},
|
||||||
|
expected: filepath.Join(homedir.Get(), "place.txt"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "passthrough other paths",
|
||||||
|
config: Application{
|
||||||
|
File: "/other/place.txt",
|
||||||
|
},
|
||||||
|
expected: "/other/place.txt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no path",
|
||||||
|
config: Application{
|
||||||
|
File: "",
|
||||||
|
},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := tt.config
|
||||||
|
|
||||||
|
if tt.wantErr == nil {
|
||||||
|
tt.wantErr = require.NoError
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.wantErr(t, cfg.parseFile())
|
||||||
|
assert.Equal(t, tt.expected, cfg.File)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,28 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type attest struct {
|
type attest struct {
|
||||||
Key string `yaml:"key" json:"key" mapstructure:"key"`
|
Key string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key
|
||||||
// IMPORTANT: do not show the password in any YAML/JSON output (sensitive information)
|
// IMPORTANT: do not show the password in any YAML/JSON output (sensitive information)
|
||||||
Password string `yaml:"-" json:"-" mapstructure:"password"`
|
Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:unparam
|
|
||||||
func (cfg *attest) parseConfigValues() error {
|
func (cfg *attest) parseConfigValues() error {
|
||||||
|
if cfg.Key != "" {
|
||||||
|
expandedPath, err := homedir.Expand(cfg.Key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to expand key path=%q: %w", cfg.Key, err)
|
||||||
|
}
|
||||||
|
cfg.Key = expandedPath
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Password == "" {
|
if cfg.Password == "" {
|
||||||
// we allow for configuration via syft config/env vars and additionally interop with known cosign config env vars
|
// we allow for configuration via syft config/env vars and additionally interop with known cosign config env vars
|
||||||
if pw, ok := os.LookupEnv("COSIGN_PASSWORD"); ok {
|
if pw, ok := os.LookupEnv("COSIGN_PASSWORD"); ok {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -13,6 +16,17 @@ type logging struct {
|
|||||||
FileLocation string `yaml:"file" json:"file-location" mapstructure:"file"` // the file path to write logs to
|
FileLocation string `yaml:"file" json:"file-location" mapstructure:"file"` // the file path to write logs to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *logging) parseConfigValues() error {
|
||||||
|
if cfg.FileLocation != "" {
|
||||||
|
expandedPath, err := homedir.Expand(cfg.FileLocation)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to expand log file path=%q: %w", cfg.FileLocation, err)
|
||||||
|
}
|
||||||
|
cfg.FileLocation = expandedPath
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg logging) loadDefaultValues(v *viper.Viper) {
|
func (cfg logging) loadDefaultValues(v *viper.Viper) {
|
||||||
v.SetDefault("log.structured", false)
|
v.SetDefault("log.structured", false)
|
||||||
v.SetDefault("log.file", "")
|
v.SetDefault("log.file", "")
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
)
|
)
|
||||||
|
|
||||||
// multiWriter holds a list of child sbom.Writers to apply all Write and Close operations to
|
// multiWriter holds a list of child sbom.Writers to apply all Write and Close operations to
|
||||||
@ -21,8 +21,21 @@ type WriterOption struct {
|
|||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewWriterOption(f Format, p string) WriterOption {
|
||||||
|
expandedPath, err := homedir.Expand(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("could not expand given writer output path=%q: %w", p, err)
|
||||||
|
// ignore errors
|
||||||
|
expandedPath = p
|
||||||
|
}
|
||||||
|
return WriterOption{
|
||||||
|
Format: f,
|
||||||
|
Path: expandedPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewWriter create all report writers from input options; if a file is not specified, os.Stdout is used
|
// NewWriter create all report writers from input options; if a file is not specified, os.Stdout is used
|
||||||
func NewWriter(options ...WriterOption) (Writer, error) {
|
func NewWriter(options ...WriterOption) (_ Writer, err error) {
|
||||||
if len(options) == 0 {
|
if len(options) == 0 {
|
||||||
return nil, fmt.Errorf("no output options provided")
|
return nil, fmt.Errorf("no output options provided")
|
||||||
}
|
}
|
||||||
@ -30,10 +43,12 @@ func NewWriter(options ...WriterOption) (Writer, error) {
|
|||||||
out := &multiWriter{}
|
out := &multiWriter{}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
// close any previously opened files; we can't really recover from any errors
|
// close any previously opened files; we can't really recover from any errors
|
||||||
if err := out.Close(); err != nil {
|
if err := out.Close(); err != nil {
|
||||||
log.Warnf("unable to close sbom writers: %+v", err)
|
log.Warnf("unable to close sbom writers: %+v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
|
|||||||
@ -2,9 +2,11 @@ package sbom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -170,3 +172,33 @@ func TestOutputWriter(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewWriterOption(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "expand home dir",
|
||||||
|
path: "~/place.txt",
|
||||||
|
expected: filepath.Join(homedir.Get(), "place.txt"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "passthrough other paths",
|
||||||
|
path: "/other/place.txt",
|
||||||
|
expected: "/other/place.txt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no path",
|
||||||
|
path: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
o := NewWriterOption(dummyFormat("table"), tt.path)
|
||||||
|
assert.Equal(t, tt.expected, o.Path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -192,6 +193,26 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "json-file-flag",
|
||||||
|
args: []string{"packages", "-o", "json", "--file", filepath.Join(tmp, "output-1.json"), coverageImage},
|
||||||
|
assertions: []traitAssertion{
|
||||||
|
assertSuccessfulReturnCode,
|
||||||
|
assertFileOutput(t, filepath.Join(tmp, "output-1.json"),
|
||||||
|
assertJsonReport,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json-output-flag-to-file",
|
||||||
|
args: []string{"packages", "-o", fmt.Sprintf("json=%s", filepath.Join(tmp, "output-2.json")), coverageImage},
|
||||||
|
assertions: []traitAssertion{
|
||||||
|
assertSuccessfulReturnCode,
|
||||||
|
assertFileOutput(t, filepath.Join(tmp, "output-2.json"),
|
||||||
|
assertJsonReport,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@ -10,10 +10,26 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/acarl005/stripansi"
|
"github.com/acarl005/stripansi"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type traitAssertion func(tb testing.TB, stdout, stderr string, rc int)
|
type traitAssertion func(tb testing.TB, stdout, stderr string, rc int)
|
||||||
|
|
||||||
|
func assertFileOutput(tb testing.TB, path string, assertions ...traitAssertion) traitAssertion {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
return func(tb testing.TB, _, stderr string, rc int) {
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
require.NoError(tb, err)
|
||||||
|
contentStr := string(content)
|
||||||
|
|
||||||
|
for _, assertion := range assertions {
|
||||||
|
// treat the file content as stdout
|
||||||
|
assertion(tb, contentStr, stderr, rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func assertJsonReport(tb testing.TB, stdout, _ string, _ int) {
|
func assertJsonReport(tb testing.TB, stdout, _ string, _ int) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
var data interface{}
|
var data interface{}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user