Skip to content

Commit

Permalink
Merge branch 'main' into 1899-deprecated-license-variant
Browse files Browse the repository at this point in the history
* main:
  feat: CLI flag for directory base (#1867)
  Fix CPE gen for k8s python client (#1921)
  chore: update iterations to protect against race (#1927)
  chore(deps): update bootstrap tools to latest versions (#1922)
  fix: Don't use the actual redis or grpc CPEs for gems (#1926)
  fix(install): return with right error code (#1915)
  Remove erroneous Java CPEs from generation (#1918)
  chore(deps): bump golang.org/x/net from 0.11.0 to 0.12.0 (#1916)
  Switch UI to bubbletea (#1888)
  fix: use filepath.EvalSymlinks if os.Readlink fails to evaluate the link (#1884)
  add file source digest support (#1914)
  chore(deps): update bootstrap tools to latest versions (#1908)
  chore(deps): bump golang.org/x/mod from 0.11.0 to 0.12.0 (#1912)
  chore(deps): bump golang.org/x/term from 0.9.0 to 0.10.0 (#1913)
  doc(readme): add installation section with scoop (#1909)
  Refactor source API (#1846)
  chore(deps): update bootstrap tools to latest versions (#1905)
  • Loading branch information
spiffcs committed Jul 11, 2023
2 parents ef05a45 + 4ab9f39 commit f96dcac
Show file tree
Hide file tree
Showing 210 changed files with 11,036 additions and 3,902 deletions.
2 changes: 1 addition & 1 deletion .github/actions/bootstrap/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ inputs:
go-version:
description: "Go version to install"
required: true
default: "1.19.x"
default: "1.20.x"
use-go-cache:
description: "Restore go cache"
required: true
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
go.work
go.work.sum
/bin
/.bin
CHANGELOG.md
VERSION
/test/results
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ GOLANGCILINT_VERSION := v1.53.3
GOSIMPORTS_VERSION := v0.3.8
BOUNCER_VERSION := v0.4.0
CHRONICLE_VERSION := v0.6.0
GORELEASER_VERSION := v1.18.2
GORELEASER_VERSION := v1.19.2
YAJSV_VERSION := v1.4.1
COSIGN_VERSION := v2.1.1
QUILL_VERSION := v0.2.0
Expand Down Expand Up @@ -302,7 +302,7 @@ compare-test-rpm-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR)

.PHONY: generate-json-schema
generate-json-schema: ## Generate a new json schema
cd schema/json && go generate . && go run .
cd syft/internal && go generate . && cd jsonschema && go run .

.PHONY: generate-license-list
generate-license-list: ## Generate an updated spdx license list
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ The chocolatey distribution of syft is community maintained and not distributed
choco install syft -y
```

### Scoop

```powershell
scoop install syft
```

### Homebrew
```bash
brew install syft
Expand Down Expand Up @@ -599,7 +605,7 @@ file-metadata:
# SYFT_FILE_METADATA_CATALOGER_SCOPE env var
scope: "squashed"

# the file digest algorithms to use when cataloging files (options: "sha256", "md5", "sha1")
# the file digest algorithms to use when cataloging files (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")
# SYFT_FILE_METADATA_DIGESTS env var
digests: ["sha256"]

Expand Down Expand Up @@ -637,11 +643,27 @@ secrets:
# SYFT_SECRETS_EXCLUDE_PATTERN_NAMES env var
exclude-pattern-names: []

# options that apply to all scan sources
source:
# alias name for the source
# SYFT_SOURCE_NAME env var; --source-name flag
name: ""

# alias version for the source
# SYFT_SOURCE_VERSION env var; --source-version flag
version: ""

# options affecting the file source type
file:
# the file digest algorithms to use on the scanned file (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")
digests: ["sha256"]

# options when pulling directly from a registry via the "registry:" scheme
registry:
# skip TLS verification when communicating with the registry
# SYFT_REGISTRY_INSECURE_SKIP_TLS_VERIFY env var
insecure-skip-tls-verify: false

# use http instead of https when connecting to the registry
# SYFT_REGISTRY_INSECURE_USE_HTTP env var
insecure-use-http: false
Expand Down
1 change: 1 addition & 0 deletions cmd/syft/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po
RunE: func(cmd *cobra.Command, args []string) error {
if app.CheckForAppUpdate {
checkForApplicationUpdate()
// TODO: this is broke, the bus isn't available yet
}

return attest.Run(cmd.Context(), app, args)
Expand Down
85 changes: 63 additions & 22 deletions cmd/syft/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import (
"golang.org/x/exp/slices"

"github.com/anchore/stereoscope"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/cmd/syft/cli/eventloop"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/cli/packages"
"github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
Expand All @@ -34,16 +36,14 @@ func Run(_ context.Context, app *config.Application, args []string) error {
return err
}

// could be an image or a directory, with or without a scheme
// TODO: validate that source is image
// note: must be a container image
userInput := args[0]
si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}

if si.Scheme != source.ImageScheme {
return fmt.Errorf("attestations are only supported for oci images at this time")
_, err = exec.LookPath("cosign")
if err != nil {
// when cosign is not installed the error will be rendered like so:
// 2023/06/30 08:31:52 error during command execution: 'syft attest' requires cosign to be installed: exec: "cosign": executable file not found in $PATH
return fmt.Errorf("'syft attest' requires cosign to be installed: %w", err)
}

eventBus := partybus.NewBus()
Expand All @@ -52,21 +52,62 @@ func Run(_ context.Context, app *config.Application, args []string) error {
subscription := eventBus.Subscribe()

return eventloop.EventLoop(
execWorker(app, *si),
execWorker(app, userInput),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}

func buildSBOM(app *config.Application, si source.Input, errs chan error) (*sbom.SBOM, error) {
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
if cleanup != nil {
defer cleanup()
func buildSBOM(app *config.Application, userInput string, errs chan error) (*sbom.SBOM, error) {
cfg := source.DetectConfig{
DefaultImageSource: app.DefaultImagePullSource,
}
detection, err := source.Detect(userInput, cfg)
if err != nil {
return nil, fmt.Errorf("could not deteremine source: %w", err)
}

if detection.IsContainerImage() {
return nil, fmt.Errorf("attestations are only supported for oci images at this time")
}

var platform *image.Platform

if app.Platform != "" {
platform, err = image.NewPlatform(app.Platform)
if err != nil {
return nil, fmt.Errorf("invalid platform: %w", err)
}
}

hashers, err := file.Hashers(app.Source.File.Digests...)
if err != nil {
return nil, fmt.Errorf("invalid hash: %w", err)
}

src, err := detection.NewSource(
source.DetectionSourceConfig{
Alias: source.Alias{
Name: app.Source.Name,
Version: app.Source.Version,
},
RegistryOptions: app.Registry.ToOptions(),
Platform: platform,
Exclude: source.ExcludeConfig{
Paths: app.Exclusions,
},
DigestAlgorithms: hashers,
BasePath: app.BasePath,
},
)

if src != nil {
defer src.Close()
}
if err != nil {
return nil, fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
return nil, fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
}

s, err := packages.GenerateSBOM(src, errs, app)
Expand All @@ -75,20 +116,20 @@ func buildSBOM(app *config.Application, si source.Input, errs chan error) (*sbom
}

if s == nil {
return nil, fmt.Errorf("no SBOM produced for %q", si.UserInput)
return nil, fmt.Errorf("no SBOM produced for %q", userInput)
}

return s, nil
}

//nolint:funlen
func execWorker(app *config.Application, si source.Input) <-chan error {
func execWorker(app *config.Application, userInput string) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
defer bus.Publish(partybus.Event{Type: event.Exit})
defer bus.Exit()

s, err := buildSBOM(app, si, errs)
s, err := buildSBOM(app, userInput, errs)
if err != nil {
errs <- fmt.Errorf("unable to build SBOM: %w", err)
return
Expand Down Expand Up @@ -136,7 +177,7 @@ func execWorker(app *config.Application, si source.Input) <-chan error {
predicateType = "custom"
}

args := []string{"attest", si.UserInput, "--predicate", f.Name(), "--type", predicateType}
args := []string{"attest", userInput, "--predicate", f.Name(), "--type", predicateType}
if app.Attest.Key != "" {
args = append(args, "--key", app.Attest.Key)
}
Expand Down Expand Up @@ -174,8 +215,8 @@ func execWorker(app *config.Application, si source.Input) <-chan error {
Context: "cosign",
},
Value: &monitor.ShellProgress{
Reader: r,
Manual: mon,
Reader: r,
Progressable: mon,
},
},
)
Expand Down
2 changes: 1 addition & 1 deletion cmd/syft/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func checkForApplicationUpdate() {
log.Infof("new version of %s is available: %s (current version is %s)", internal.ApplicationName, newVersion, version.FromBuild().Version)

bus.Publish(partybus.Event{
Type: event.AppUpdateAvailable,
Type: event.CLIAppUpdateAvailable,
Value: newVersion,
})
} else {
Expand Down
1 change: 1 addition & 0 deletions cmd/syft/cli/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func Convert(v *viper.Viper, app *config.Application, ro *options.RootOptions, p
RunE: func(cmd *cobra.Command, args []string) error {
if app.CheckForAppUpdate {
checkForApplicationUpdate()
// TODO: this is broke, the bus isn't available yet
}
return convert.Run(cmd.Context(), app, args)
},
Expand Down
51 changes: 45 additions & 6 deletions cmd/syft/cli/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,29 @@ import (
"io"
"os"

"github.com/wagoodman/go-partybus"

"github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/cli/eventloop"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/formats"
"github.com/anchore/syft/syft/sbom"
)

func Run(_ context.Context, app *config.Application, args []string) error {
log.Warn("convert is an experimental feature, run `syft convert -h` for help")

writer, err := options.MakeSBOMWriter(app.Outputs, app.File, app.OutputTemplatePath)
if err != nil {
return err
}

// this can only be a SBOM file
// could be an image or a directory, with or without a scheme
userInput := args[0]

var reader io.ReadCloser
Expand All @@ -37,10 +46,40 @@ func Run(_ context.Context, app *config.Application, args []string) error {
reader = f
}

sbom, _, err := formats.Decode(reader)
if err != nil {
return fmt.Errorf("failed to decode SBOM: %w", err)
}
eventBus := partybus.NewBus()
stereoscope.SetBus(eventBus)
syft.SetBus(eventBus)
subscription := eventBus.Subscribe()

return eventloop.EventLoop(
execWorker(reader, writer),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}

func execWorker(reader io.Reader, writer sbom.Writer) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
defer bus.Exit()

return writer.Write(*sbom)
s, _, err := formats.Decode(reader)
if err != nil {
errs <- fmt.Errorf("failed to decode SBOM: %w", err)
return
}

if s == nil {
errs <- fmt.Errorf("no SBOM produced")
return
}

if err := writer.Write(*s); err != nil {
errs <- fmt.Errorf("failed to write SBOM: %w", err)
}
}()
return errs
}
14 changes: 7 additions & 7 deletions cmd/syft/cli/eventloop/event_loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/wagoodman/go-partybus"

"github.com/anchore/clio"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
)

// eventLoop listens to worker errors (from execution path), worker events (from a partybus subscription), and
// EventLoop listens to worker errors (from execution path), worker events (from a partybus subscription), and
// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until
// an eventual graceful exit.
func EventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...ui.UI) error {
func EventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...clio.UI) error {
defer cleanupFn()
events := subscription.Events()
var err error
var ux ui.UI
var ux clio.UI

if ux, err = setupUI(subscription.Unsubscribe, uxs...); err != nil {
if ux, err = setupUI(subscription, uxs...); err != nil {
return err
}

Expand Down Expand Up @@ -85,9 +85,9 @@ func EventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
// during teardown. With the given UIs, the first UI which the ui.Setup() function does not return an error
// will be utilized in execution. Providing a set of UIs allows for the caller to provide graceful fallbacks
// when there are environmental problem (e.g. unable to setup a TUI with the current TTY).
func setupUI(unsubscribe func() error, uis ...ui.UI) (ui.UI, error) {
func setupUI(subscription *partybus.Subscription, uis ...clio.UI) (clio.UI, error) {
for _, ux := range uis {
if err := ux.Setup(unsubscribe); err != nil {
if err := ux.Setup(subscription); err != nil {
log.Warnf("unable to setup given UI, falling back to alternative UI: %+v", err)
continue
}
Expand Down
Loading

0 comments on commit f96dcac

Please sign in to comment.