mirror of
https://github.com/anchore/syft.git
synced 2026-10-17 16:47:19 +02:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a90cff9ae1 | ||
|
|
7b3cc85219 | ||
|
|
913bdda8df | ||
|
|
b2ab4671b9 | ||
|
|
ad322b3314 | ||
|
|
4a8a9ce290 | ||
|
|
635904fcb6 | ||
|
|
1e75cb0418 | ||
|
|
afc0c1acd9 | ||
|
|
edac8c7bf7 | ||
|
|
d2f28e0eb1 | ||
|
|
1d1a7de314 |
@@ -7,10 +7,10 @@ name: "CodeQL Security Scan"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [main]
|
||||
branches:
|
||||
# only run when there are pushes to the main branch (not on PRs)
|
||||
- main
|
||||
|
||||
schedule:
|
||||
- cron: '0 0 * * 3'
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ name: "Validations"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
|
||||
@@ -19,7 +19,6 @@ CHANGELOG.md
|
||||
.tmp/
|
||||
coverage.txt
|
||||
bin/
|
||||
.goreleaser.yaml
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
dist: snapshot
|
||||
release:
|
||||
prerelease: auto
|
||||
draft: true
|
||||
|
||||
env:
|
||||
# required to support multi architecture docker builds
|
||||
- DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
release:
|
||||
prerelease: auto
|
||||
draft: true
|
||||
|
||||
before:
|
||||
hooks:
|
||||
@@ -94,3 +93,41 @@ brews:
|
||||
description: *description
|
||||
license: "Apache License 2.0"
|
||||
|
||||
dockers:
|
||||
- image_templates:
|
||||
- "anchore/syft:latest"
|
||||
- "anchore/syft:{{ .Tag }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- "anchore/syft:{{ .Tag }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64/v8"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
docker_manifests:
|
||||
- name_template: anchore/syft:{{ .Tag }}
|
||||
image_templates:
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
- name_template: anchore/syft:latest
|
||||
image_templates:
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
@@ -1,24 +0,0 @@
|
||||
TARGET=../goreleaser.yaml
|
||||
|
||||
.PHONY: snapshot-config
|
||||
snapshot-config:
|
||||
cat snapshot.yaml > $(TARGET)
|
||||
cat env-skip-signing.yaml >> $(TARGET)
|
||||
cat main.yaml >> $(TARGET)
|
||||
cat docker.yaml >> $(TARGET)
|
||||
cat $(TARGET)
|
||||
|
||||
.PHONY: snapshot-with-signing-config
|
||||
snapshot-with-signing-config:
|
||||
# we leave out docker entirely such that this can easily be tested in CI
|
||||
cat snapshot.yaml > $(TARGET)
|
||||
cat env-default.yaml >> $(TARGET)
|
||||
cat main.yaml >> $(TARGET)
|
||||
cat $(TARGET)
|
||||
|
||||
.PHONY: release-config
|
||||
release-config:
|
||||
cat env-default.yaml > $(TARGET)
|
||||
cat docker.yaml >> $(TARGET)
|
||||
cat main.yaml >> $(TARGET)
|
||||
cat $(TARGET)
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
dockers:
|
||||
- image_templates:
|
||||
- "anchore/syft:latest"
|
||||
- "anchore/syft:{{ .Tag }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- "anchore/syft:{{ .Tag }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64/v8"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
docker_manifests:
|
||||
- name_template: anchore/syft:{{ .Tag }}
|
||||
image_templates:
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
- name_template: anchore/syft:latest
|
||||
image_templates:
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
env:
|
||||
# required to support multi architecture docker builds
|
||||
- DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
env:
|
||||
# required to support multi architecture docker builds
|
||||
- DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
- SKIP_SIGNING=true
|
||||
@@ -1,91 +0,0 @@
|
||||
release:
|
||||
prerelease: auto
|
||||
draft: true
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- ./.github/scripts/apple-signing/setup.sh {{ .IsSnapshot }}
|
||||
|
||||
builds:
|
||||
- id: linux-build
|
||||
binary: syft
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
|
||||
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
|
||||
env: &build-env
|
||||
- CGO_ENABLED=0
|
||||
ldflags: &build-ldflags |
|
||||
-w
|
||||
-s
|
||||
-extldflags '-static'
|
||||
-X github.com/anchore/syft/internal/version.version={{.Version}}
|
||||
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
|
||||
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
|
||||
-X github.com/anchore/syft/internal/version.gitDescription={{.Summary}}
|
||||
|
||||
- id: darwin-build
|
||||
binary: syft
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
mod_timestamp: *build-timestamp
|
||||
env: *build-env
|
||||
ldflags: *build-ldflags
|
||||
hooks:
|
||||
post:
|
||||
# we must have signing as a build hook instead of the signs section. The signs section must register a new
|
||||
# asset, where we want to replace an existing asset. A post-build hook has the advantage of not needing to
|
||||
# unpackage and repackage a tar.gz with a signed binary
|
||||
- ./.github/scripts/apple-signing/sign.sh "{{ .Path }}" "{{ .IsSnapshot }}" "{{ .Target }}"
|
||||
|
||||
- id: windows-build
|
||||
binary: syft
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
mod_timestamp: *build-timestamp
|
||||
env: *build-env
|
||||
ldflags: *build-ldflags
|
||||
|
||||
archives:
|
||||
- id: linux-archives
|
||||
builds:
|
||||
- linux-build
|
||||
|
||||
# note: the signing process is depending on tar.gz archives. If this format changes then .github/scripts/apple-signing/*.sh will need to be adjusted
|
||||
- id: darwin-archives
|
||||
builds:
|
||||
- darwin-build
|
||||
|
||||
- id: windows-archives
|
||||
format: zip
|
||||
builds:
|
||||
- windows-build
|
||||
|
||||
nfpms:
|
||||
- license: "Apache 2.0"
|
||||
maintainer: "Anchore, Inc"
|
||||
homepage: &website "https://github.com/anchore/syft"
|
||||
description: &description "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems"
|
||||
formats:
|
||||
- rpm
|
||||
- deb
|
||||
|
||||
brews:
|
||||
- tap:
|
||||
owner: anchore
|
||||
name: homebrew-syft
|
||||
ids:
|
||||
- darwin-archives
|
||||
- linux-archives
|
||||
homepage: *website
|
||||
description: *description
|
||||
license: "Apache License 2.0"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
dist: snapshot
|
||||
@@ -25,7 +25,7 @@ COVERAGE_THRESHOLD := 62
|
||||
|
||||
# CI cache busting values; change these if you want CI to not use previous stored cache
|
||||
INTEGRATION_CACHE_BUSTER="894d8ca"
|
||||
CLI_CACHE_BUSTER="894d8ca"
|
||||
CLI_CACHE_BUSTER="e5cdfd8"
|
||||
BOOTSTRAP_CACHE="c7afb99ad"
|
||||
|
||||
## Build variables
|
||||
@@ -234,24 +234,25 @@ build: $(SNAPSHOTDIR) ## Build release snapshot binaries and packages
|
||||
$(SNAPSHOTDIR): ## Build snapshot release binaries and packages
|
||||
$(call title,Building snapshot artifacts)
|
||||
|
||||
cd .goreleaser && make snapshot-config
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(SNAPSHOTDIR)" > $(TEMPDIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# build release snapshots
|
||||
bash -c "$(SNAPSHOT_CMD) --skip-sign"
|
||||
bash -c "SKIP_SIGNING=true $(SNAPSHOT_CMD) --skip-sign --config $(TEMPDIR)/goreleaser.yaml"
|
||||
|
||||
.PHONY: snapshot-with-signing
|
||||
snapshot-with-signing: ## Build snapshot release binaries and packages (with dummy signing)
|
||||
$(call title,Building snapshot artifacts (+ signing))
|
||||
|
||||
cd .goreleaser && make snapshot-with-signing-config
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(SNAPSHOTDIR)" > $(TEMPDIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
rm -f .github/scripts/apple-signing/log/*.txt
|
||||
|
||||
# remove the keychain with the trusted self-signed cert automatically (from failed previous runs)
|
||||
.github/scripts/apple-signing/cleanup.sh
|
||||
|
||||
# build release snapshots
|
||||
bash -c "$(SNAPSHOT_CMD) || (cat .github/scripts/apple-signing/log/*.txt && false)"
|
||||
bash -c "$(SNAPSHOT_CMD) --config $(TEMPDIR)/goreleaser.yaml || (cat .github/scripts/apple-signing/log/*.txt && false)"
|
||||
|
||||
# remove the keychain with the trusted self-signed cert automatically
|
||||
.github/scripts/apple-signing/cleanup.sh
|
||||
@@ -315,13 +316,16 @@ CHANGELOG.md:
|
||||
release: clean-dist CHANGELOG.md ## Build and publish final binaries and packages. Intended to be run only on macOS.
|
||||
$(call title,Publishing release artifacts)
|
||||
|
||||
cd .goreleaser && make release-config
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
rm -f .github/scripts/apple-signing/log/*.txt
|
||||
|
||||
# note: notarization cannot be done in parallel, thus --parallelism 1
|
||||
bash -c "\
|
||||
$(RELEASE_CMD) \
|
||||
--config $(TEMPDIR)/goreleaser.yaml \
|
||||
--parallelism 1 \
|
||||
--release-notes <(cat CHANGELOG.md)\
|
||||
|| (cat .github/scripts/apple-signing/log/*.txt && false)"
|
||||
|
||||
@@ -114,6 +114,7 @@ Sources can be explicitly provided with a scheme:
|
||||
|
||||
```
|
||||
docker:yourrepo/yourimage:tag use images from the Docker daemon
|
||||
podman:yourrepo/yourimage:tag use images from the Podman daemon
|
||||
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
|
||||
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
|
||||
+43
-14
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pkg/profile"
|
||||
@@ -27,7 +26,6 @@ import (
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
"github.com/sigstore/cosign/pkg/cosign/attestation"
|
||||
"github.com/sigstore/sigstore/pkg/signature/dsse"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
@@ -125,14 +123,26 @@ func selectPassFunc(keypath string) (cosign.PassFunc, error) {
|
||||
func attestExec(ctx context.Context, _ *cobra.Command, args []string) error {
|
||||
// can only be an image for attestation or OCI DIR
|
||||
userInput := args[0]
|
||||
fs := afero.NewOsFs()
|
||||
parsedScheme, _, _, err := source.DetectScheme(fs, image.DetectSource, userInput)
|
||||
si, err := source.ParseInput(userInput, false)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not generate source input for attest command: %w", err)
|
||||
}
|
||||
|
||||
if parsedScheme != source.ImageScheme {
|
||||
return fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", parsedScheme, userInput)
|
||||
switch si.Scheme {
|
||||
case source.ImageScheme, source.UnknownScheme:
|
||||
// at this point we know that it cannot be dir: or file: schemes, so we will assume that the unknown scheme could represent an image
|
||||
si.Scheme = source.ImageScheme
|
||||
default:
|
||||
return fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Scheme, userInput)
|
||||
}
|
||||
|
||||
// if the original detection was from a local daemon we want to short circuit
|
||||
// that and attempt to generate the image source from a registry source instead
|
||||
switch si.ImageSource {
|
||||
case image.UnknownSource, image.OciRegistrySource:
|
||||
si.ImageSource = image.OciRegistrySource
|
||||
default:
|
||||
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 {
|
||||
@@ -162,7 +172,7 @@ func attestExec(ctx context.Context, _ *cobra.Command, args []string) error {
|
||||
defer sv.Close()
|
||||
|
||||
return eventLoop(
|
||||
attestationExecWorker(userInput, output, predicateType, sv),
|
||||
attestationExecWorker(*si, output, predicateType, sv),
|
||||
setupSignals(),
|
||||
eventSubscription,
|
||||
stereoscope.Cleanup,
|
||||
@@ -170,12 +180,21 @@ func attestExec(ctx context.Context, _ *cobra.Command, args []string) error {
|
||||
)
|
||||
}
|
||||
|
||||
func attestationExecWorker(userInput string, output format.Option, predicateType string, sv *sign.SignerVerifier) <-chan error {
|
||||
func attestationExecWorker(sourceInput source.Input, output format.Option, predicateType string, sv *sign.SignerVerifier) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
|
||||
s, src, err := generateSBOM(userInput, errs)
|
||||
src, cleanup, err := source.NewFromRegistry(sourceInput, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("failed to construct source from user input %q: %w", sourceInput.UserInput, err)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := generateSBOM(src, errs)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
@@ -210,10 +229,20 @@ func assertPredicateType(output format.Option) string {
|
||||
}
|
||||
}
|
||||
|
||||
func findValidDigest(digests []string) string {
|
||||
// since we are only using the OCI repo provider for this source we are safe that this is only 1 value
|
||||
// see https://github.com/anchore/stereoscope/blob/25ebd49a842b5ac0a20c2e2b4b81335b64ad248c/pkg/image/oci/registry_provider.go#L57-L63
|
||||
split := strings.Split(digests[0], "sha256:")
|
||||
return split[1]
|
||||
}
|
||||
|
||||
func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error {
|
||||
h, err := v1.NewHash(src.Image.Metadata.ManifestDigest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not hash manifest digest for image")
|
||||
switch len(src.Image.Metadata.RepoDigests) {
|
||||
case 0:
|
||||
return fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command")
|
||||
case 1:
|
||||
default:
|
||||
return fmt.Errorf("cannot generate attestation since multiple repo digests were found for the image: %+v", src.Image.Metadata.RepoDigests)
|
||||
}
|
||||
|
||||
wrapped := dsse.WrapSigner(sv, intotoJSONDsseType)
|
||||
@@ -221,7 +250,7 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe
|
||||
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
|
||||
Predicate: bytes.NewBuffer(predicate),
|
||||
Type: predicateType,
|
||||
Digest: h.Hex,
|
||||
Digest: findValidDigest(src.Image.Metadata.RepoDigests),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+22
-15
@@ -42,6 +42,7 @@ const (
|
||||
|
||||
schemeHelpHeader = "You can also explicitly specify the scheme to use:"
|
||||
imageSchemeHelp = ` {{.appName}} {{.command}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
|
||||
{{.appName}} {{.command}} podman:yourrepo/yourimage:tag explicitly use the Podman daemon
|
||||
{{.appName}} {{.command}} registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
{{.appName}} {{.command}} docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
|
||||
{{.appName}} {{.command}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
@@ -228,9 +229,13 @@ func packagesExec(_ *cobra.Command, args []string) error {
|
||||
|
||||
// could be an image or a directory, with or without a scheme
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInput(userInput, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
|
||||
return eventLoop(
|
||||
packagesExecWorker(userInput, writer),
|
||||
packagesExecWorker(*si, writer),
|
||||
setupSignals(),
|
||||
eventSubscription,
|
||||
stereoscope.Cleanup,
|
||||
@@ -249,18 +254,10 @@ func isVerbose() (result bool) {
|
||||
return appConfig.CliOptions.Verbosity > 0 || isPipedInput
|
||||
}
|
||||
|
||||
func generateSBOM(userInput string, errs chan error) (*sbom.SBOM, *source.Source, error) {
|
||||
func generateSBOM(src *source.Source, errs chan error) (*sbom.SBOM, error) {
|
||||
tasks, err := tasks()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
src, cleanup, err := source.New(userInput, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
|
||||
}
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := sbom.SBOM{
|
||||
@@ -274,7 +271,7 @@ func generateSBOM(userInput string, errs chan error) (*sbom.SBOM, *source.Source
|
||||
|
||||
buildRelationships(&s, src, tasks, errs)
|
||||
|
||||
return &s, src, nil
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func buildRelationships(s *sbom.SBOM, src *source.Source, tasks []task, errs chan error) {
|
||||
@@ -288,18 +285,28 @@ func buildRelationships(s *sbom.SBOM, src *source.Source, tasks []task, errs cha
|
||||
s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
|
||||
}
|
||||
|
||||
func packagesExecWorker(userInput string, writer sbom.Writer) <-chan error {
|
||||
func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
s, src, err := generateSBOM(userInput, errs)
|
||||
|
||||
src, cleanup, err := source.New(si, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := generateSBOM(src, errs)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
panic("nil sbomb returned with no error")
|
||||
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
|
||||
}
|
||||
|
||||
if appConfig.Anchore.Host != "" {
|
||||
|
||||
+8
-2
@@ -26,7 +26,7 @@ const powerUserExample = ` {{.appName}} {{.command}} <image>
|
||||
|
||||
DEPRECATED - THIS COMMAND WILL BE REMOVED in v1.0.0
|
||||
|
||||
Only image sources are supported (e.g. docker: , docker-archive: , oci: , etc.), the directory source (dir:) is not supported.
|
||||
Only image sources are supported (e.g. docker: , podman: , docker-archive: , oci: , etc.), the directory source (dir:) is not supported.
|
||||
|
||||
All behavior is controlled via application configuration and environment variables (see https://github.com/anchore/syft#configuration)
|
||||
`
|
||||
@@ -115,7 +115,13 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error {
|
||||
return
|
||||
}
|
||||
|
||||
src, cleanup, err := source.New(userInput, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
si, err := source.ParseInput(userInput, true)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
|
||||
src, cleanup, err := source.New(*si, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29
|
||||
github.com/anchore/stereoscope v0.0.0-20220217141419-c6f02aed9ed2
|
||||
github.com/anchore/stereoscope v0.0.0-20220301220648-8aa8a4a0bf50
|
||||
github.com/antihax/optional v1.0.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2
|
||||
github.com/docker/docker v20.10.12+incompatible
|
||||
@@ -56,7 +56,7 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require github.com/sigstore/cosign v1.5.2-0.20220222220736-ec79daf63c24
|
||||
require github.com/sigstore/cosign v1.5.2-0.20220302132634-73235ac02e4a
|
||||
|
||||
require (
|
||||
bitbucket.org/creachadair/shell v0.0.6 // indirect
|
||||
@@ -125,21 +125,21 @@ require (
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.20.1 // indirect
|
||||
github.com/go-openapi/errors v0.20.1 // indirect
|
||||
github.com/go-openapi/analysis v0.21.2 // indirect
|
||||
github.com/go-openapi/errors v0.20.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/loads v0.21.0 // indirect
|
||||
github.com/go-openapi/runtime v0.23.0 // indirect
|
||||
github.com/go-openapi/loads v0.21.1 // indirect
|
||||
github.com/go-openapi/runtime v0.23.1 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.2 // indirect
|
||||
github.com/go-openapi/swag v0.21.1 // indirect
|
||||
github.com/go-openapi/validate v0.20.3 // indirect
|
||||
github.com/go-openapi/validate v0.21.0 // indirect
|
||||
github.com/go-piv/piv-go v1.9.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
@@ -200,14 +200,14 @@ require (
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.0 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.1 // indirect
|
||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7 // indirect
|
||||
github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.0.0-beta.11 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.0.0-beta.12 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
|
||||
github.com/thales-e-security/pool v0.0.2 // indirect
|
||||
@@ -230,7 +230,7 @@ require (
|
||||
go.etcd.io/etcd/server/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/tests/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/v3 v3.5.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.7.5 // indirect
|
||||
go.mongodb.org/mongo-driver v1.8.3 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/contrib v1.3.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
|
||||
@@ -244,7 +244,7 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.20.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
google.golang.org/api v0.70.0 // indirect
|
||||
@@ -285,7 +285,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839
|
||||
github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
|
||||
@@ -234,9 +234,11 @@ github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v
|
||||
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
|
||||
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
|
||||
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/ReneKroon/ttlcache/v2 v2.10.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY=
|
||||
@@ -282,8 +284,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29 h1:K9LfnxwhqvihqU0+MF325FNy7fsKV9EGaUxdfR4gnWk=
|
||||
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29/go.mod h1:Oc1UkGaJwY6ND6vtAqPSlYrptKRJngHwkwB6W7l1uP0=
|
||||
github.com/anchore/stereoscope v0.0.0-20220217141419-c6f02aed9ed2 h1:QuvMG+rqqJmtFRL+jqj5pFgjQcJSnEHEbtj1lKowLLQ=
|
||||
github.com/anchore/stereoscope v0.0.0-20220217141419-c6f02aed9ed2/go.mod h1:QpDHHV2h1NNfu7klzU75XC8RvSlaPK6HHgi0dy8A6sk=
|
||||
github.com/anchore/stereoscope v0.0.0-20220301220648-8aa8a4a0bf50 h1:+Fe67xv6NRLdZ5V9X2kw959XsKQCUx5/6RL/wQZfs44=
|
||||
github.com/anchore/stereoscope v0.0.0-20220301220648-8aa8a4a0bf50/go.mod h1:QpDHHV2h1NNfu7klzU75XC8RvSlaPK6HHgi0dy8A6sk=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
@@ -831,8 +833,9 @@ github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2
|
||||
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
|
||||
github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk=
|
||||
github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
|
||||
github.com/go-openapi/analysis v0.20.1 h1:zdVbw8yoD4SWZeq+cWdGgquaB0W4VrsJvDJHJND/Ktc=
|
||||
github.com/go-openapi/analysis v0.20.1/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
|
||||
github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
|
||||
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
|
||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||
@@ -841,14 +844,17 @@ github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX
|
||||
github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.20.1 h1:j23mMDtRxMwIobkpId7sWh7Ddcx4ivaoqUbfXx5P+a8=
|
||||
github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
|
||||
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
@@ -866,8 +872,9 @@ github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hs
|
||||
github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc=
|
||||
github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4=
|
||||
github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o=
|
||||
github.com/go-openapi/loads v0.21.0 h1:jYtUO4wwP7psAweisP/MDoOpdzsYEESdoPcsWjHDR68=
|
||||
github.com/go-openapi/loads v0.21.0/go.mod h1:rHYve9nZrQ4CJhyeIIFJINGCg1tQpx2yJrrNo8sf1ws=
|
||||
github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0=
|
||||
github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
|
||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
|
||||
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
|
||||
@@ -875,8 +882,9 @@ github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2g
|
||||
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
|
||||
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
|
||||
github.com/go-openapi/runtime v0.21.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
|
||||
github.com/go-openapi/runtime v0.23.0 h1:HX6ET2sHCIvaKeDDQoU01CtO1ekg5EkekHSkLTtWXH0=
|
||||
github.com/go-openapi/runtime v0.23.0/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
|
||||
github.com/go-openapi/runtime v0.23.1 h1:/Drg9R96eMmgKJHVWZADz78XbE39/6QiIiB45mc+epo=
|
||||
github.com/go-openapi/runtime v0.23.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
@@ -903,6 +911,7 @@ github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+W
|
||||
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os=
|
||||
github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
@@ -922,8 +931,9 @@ github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbN
|
||||
github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4=
|
||||
github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI=
|
||||
github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0=
|
||||
github.com/go-openapi/validate v0.20.3 h1:GZPPhhKSZrE8HjB4eEkoYAZmoWA4+tCemSgINH1/vKw=
|
||||
github.com/go-openapi/validate v0.20.3/go.mod h1:goDdqVGiigM3jChcrYJxD2joalke3ZXeftD16byIjA4=
|
||||
github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI=
|
||||
github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
|
||||
github.com/go-piv/piv-go v1.9.0 h1:P6j2gjfP7zO7T3nCk/jwCgsvFRwB8shEqAJ4q85jgXc=
|
||||
github.com/go-piv/piv-go v1.9.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
@@ -951,8 +961,9 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
@@ -1501,6 +1512,7 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@@ -1910,8 +1922,9 @@ github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQw
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.2.0/go.mod h1:eIjBmIP8LD2MLBL/DkQWayLiz006Q4p+hCu79rvWleY=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.0 h1:PH0mUKuUSXVEVDbrKMgGPcrqrnKA8gJii614+EKKi7g=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.0/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.1 h1:LJuyMziazadwmQRRu1M7GMUo5S1oH1+YxU9FjuSFU8k=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.1/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U=
|
||||
github.com/securego/gosec/v2 v2.9.1/go.mod h1:oDcDLcatOJxkCGaCaq8lua1jTnYf6Sou4wdiJ1n4iHc=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
@@ -1927,8 +1940,8 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sigstore/cosign v1.5.2-0.20220222220736-ec79daf63c24 h1:DzEiTgBIVtBY8zGLYmSNJltvaCLIOz7hAUbCsClsvRw=
|
||||
github.com/sigstore/cosign v1.5.2-0.20220222220736-ec79daf63c24/go.mod h1:eedJzrb6Lf1Bq2nA3ipcNHE61Lgkpvt3hmxfZAMi02I=
|
||||
github.com/sigstore/cosign v1.5.2-0.20220302132634-73235ac02e4a h1:4QV0ZqC9l82QRtDHdm3jYatY3VC70+a+kD9qbQ6Dn2Q=
|
||||
github.com/sigstore/cosign v1.5.2-0.20220302132634-73235ac02e4a/go.mod h1:54MEOuZykYBTeq/ds2M/3HIwuWDYJmgjX2GhvMJ8JNc=
|
||||
github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7 h1:XE7A9lJ+wYhmUFBWYTaw3Ph943zHB4iBYd5R0SX0ZOA=
|
||||
github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7/go.mod h1:ANQivY/lfOp9hN92S813LEthkm/kit96hzeIF3SNoZA=
|
||||
github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 h1:mbqXrm8YZXN/cJMGeBkgPnswtfrOxDE1f7QZdJ+POQE=
|
||||
@@ -2007,8 +2020,8 @@ github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhU
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
|
||||
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
|
||||
github.com/spiffe/go-spiffe/v2 v2.0.0-beta.11 h1:YBSTauKWMRhexLkyz0QD1wJ3xblC8q3c55xfB8DngAM=
|
||||
github.com/spiffe/go-spiffe/v2 v2.0.0-beta.11/go.mod h1:TEfgrEcyFhuSuvqohJt6IxENUNeHfndWCCV1EX7UaVk=
|
||||
github.com/spiffe/go-spiffe/v2 v2.0.0-beta.12 h1:Lh9MRyNNsAbGbMiKbVHDw+qk/DlkjS9wBTlH8v7OnaA=
|
||||
github.com/spiffe/go-spiffe/v2 v2.0.0-beta.12/go.mod h1:TEfgrEcyFhuSuvqohJt6IxENUNeHfndWCCV1EX7UaVk=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||
@@ -2234,8 +2247,9 @@ go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4S
|
||||
go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
|
||||
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
|
||||
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
|
||||
go.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI=
|
||||
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
|
||||
go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4=
|
||||
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||
go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
|
||||
@@ -2302,8 +2316,9 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc=
|
||||
go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI=
|
||||
gocloud.dev v0.24.1-0.20211119014450-028788aaaa4c/go.mod h1:EIJSlY7nvfeoWaV2GauF6es27gZfqtTVon47QFueoyE=
|
||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -2339,6 +2354,7 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
@@ -2703,6 +2719,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -3136,6 +3153,7 @@ k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs=
|
||||
k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38=
|
||||
k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ=
|
||||
k8s.io/apiextensions-apiserver v0.22.5/go.mod h1:tIXeZ0BrDxUb1PoAz+tgOz43Zi1Bp4BEEqVtUccMJbE=
|
||||
k8s.io/apimachinery v0.19.7/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q=
|
||||
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
|
||||
k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
|
||||
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
|
||||
@@ -3172,6 +3190,7 @@ k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4=
|
||||
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
|
||||
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
|
||||
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
|
||||
@@ -3186,6 +3205,7 @@ k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
|
||||
k8s.io/utils v0.0.0-20220127004650-9b3446523e65 h1:ONWS0Wgdg5wRiQIAui7L/023aC9+IxrIrydY7l8llsE=
|
||||
k8s.io/utils v0.0.0-20220127004650-9b3446523e65/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
knative.dev/hack v0.0.0-20220118141833-9b2ed8471e30/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI=
|
||||
knative.dev/hack/schema v0.0.0-20220224013837-e1785985d364/go.mod h1:ffjwmdcrH5vN3mPhO8RrF2KfNnbHeCE2C60A+2cv3U0=
|
||||
knative.dev/pkg v0.0.0-20220202132633-df430fa0dd96 h1:JU0DFa06CaUtwSkAY0b3j47ohEJLIYlpPPgNgbPHlAo=
|
||||
knative.dev/pkg v0.0.0-20220202132633-df430fa0dd96/go.mod h1:etVT7Tm8pSDf4RKhGk4r7j/hj3dNBpvT7bO6a6wpahs=
|
||||
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
||||
@@ -3206,6 +3226,7 @@ sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87J
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
||||
sigs.k8s.io/release-utils v0.4.1-0.20220207182343-6dadf2228617 h1:qbKzP8fFAdlnj4WqB9KrAuW4ply+bUQkdRlVrH4Jus0=
|
||||
sigs.k8s.io/release-utils v0.4.1-0.20220207182343-6dadf2228617/go.mod h1:t9pL38kZkTBVDcjL1y7ajrkNQFLiArVAjOVO0sxzFF0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/formats/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func encodeComponent(p pkg.Package) cyclonedx.Component {
|
||||
props := encodeProperties(p, "syft:package")
|
||||
props = append(props, encodeCPEs(p)...)
|
||||
if len(p.Locations) > 0 {
|
||||
props = append(props, encodeProperties(p.Locations, "syft:location")...)
|
||||
}
|
||||
if hasMetadata(p) {
|
||||
props = append(props, encodeProperties(p.Metadata, "syft:metadata")...)
|
||||
}
|
||||
|
||||
var properties *[]cyclonedx.Property
|
||||
if len(props) > 0 {
|
||||
properties = &props
|
||||
}
|
||||
|
||||
return cyclonedx.Component{
|
||||
Type: cyclonedx.ComponentTypeLibrary,
|
||||
Name: p.Name,
|
||||
@@ -20,12 +32,12 @@ func encodeComponent(p pkg.Package) cyclonedx.Component {
|
||||
Version: p.Version,
|
||||
PackageURL: p.PURL,
|
||||
Licenses: encodeLicenses(p),
|
||||
CPE: encodeCPE(p),
|
||||
CPE: encodeSingleCPE(p),
|
||||
Author: encodeAuthor(p),
|
||||
Publisher: encodePublisher(p),
|
||||
Description: encodeDescription(p),
|
||||
ExternalReferences: encodeExternalReferences(p),
|
||||
Properties: encodeProperties(p),
|
||||
Properties: properties,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,131 +46,56 @@ func hasMetadata(p pkg.Package) bool {
|
||||
}
|
||||
|
||||
func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
||||
typ := pkg.Type(findPropertyValue(c, "type"))
|
||||
purl := c.PackageURL
|
||||
if typ == "" && purl != "" {
|
||||
typ = pkg.TypeFromPURL(purl)
|
||||
values := map[string]string{}
|
||||
for _, p := range *c.Properties {
|
||||
values[p.Name] = p.Value
|
||||
}
|
||||
|
||||
metaType, meta := decodePackageMetadata(c)
|
||||
|
||||
p := &pkg.Package{
|
||||
Name: c.Name,
|
||||
Version: c.Version,
|
||||
FoundBy: findPropertyValue(c, "foundBy"),
|
||||
Locations: decodeLocations(c),
|
||||
Licenses: decodeLicenses(c),
|
||||
Language: pkg.Language(findPropertyValue(c, "language")),
|
||||
Type: typ,
|
||||
CPEs: decodeCPEs(c),
|
||||
PURL: purl,
|
||||
MetadataType: metaType,
|
||||
Metadata: meta,
|
||||
Name: c.Name,
|
||||
Version: c.Version,
|
||||
Locations: decodeLocations(values),
|
||||
Licenses: decodeLicenses(c),
|
||||
CPEs: decodeCPEs(c),
|
||||
PURL: c.PackageURL,
|
||||
}
|
||||
|
||||
common.DecodeInto(p, values, "syft:package", CycloneDXFields)
|
||||
|
||||
p.Metadata = decodePackageMetadata(values, c, p.MetadataType)
|
||||
|
||||
if p.Type == "" {
|
||||
p.Type = pkg.TypeFromPURL(p.PURL)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func decodeLocations(c *cyclonedx.Component) (out []source.Location) {
|
||||
if c.Properties != nil {
|
||||
props := *c.Properties
|
||||
for i := 0; i < len(props)-1; i++ {
|
||||
if props[i].Name == "path" && props[i+1].Name == "layerID" {
|
||||
out = append(out, source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: props[i].Value,
|
||||
FileSystemID: props[i+1].Value,
|
||||
},
|
||||
})
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
func decodeLocations(vals map[string]string) []source.Location {
|
||||
v := common.Decode(reflect.TypeOf([]source.Location{}), vals, "syft:location", CycloneDXFields)
|
||||
out, _ := v.([]source.Location)
|
||||
return out
|
||||
}
|
||||
|
||||
func mapAllProps(c *cyclonedx.Component, obj reflect.Value) {
|
||||
value := obj
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typ pkg.MetadataType) interface{} {
|
||||
if typ != "" && c.Properties != nil {
|
||||
metaTyp, ok := pkg.MetadataTypeByName[typ]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
metaPtrTyp := reflect.PtrTo(metaTyp)
|
||||
metaPtr := common.Decode(metaPtrTyp, vals, "syft:metadata", CycloneDXFields)
|
||||
|
||||
// Map all explicit metadata properties
|
||||
decodeAuthor(c.Author, metaPtr)
|
||||
decodeGroup(c.Group, metaPtr)
|
||||
decodePublisher(c.Publisher, metaPtr)
|
||||
decodeDescription(c.Description, metaPtr)
|
||||
decodeExternalReferences(c, metaPtr)
|
||||
|
||||
// return the actual interface{} -> struct ... not interface{} -> *struct
|
||||
return common.PtrToStruct(metaPtr)
|
||||
}
|
||||
|
||||
structType := value.Type()
|
||||
if structType.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
field := structType.Field(i)
|
||||
fieldType := field.Type
|
||||
fieldValue := value.Field(i)
|
||||
|
||||
name, mapped := field.Tag.Lookup("cyclonedx")
|
||||
if !mapped {
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
if fieldValue.IsNil() {
|
||||
newValue := reflect.New(fieldType)
|
||||
fieldValue.Set(newValue)
|
||||
}
|
||||
fieldValue = fieldValue.Elem()
|
||||
}
|
||||
|
||||
propertyValue := findPropertyValue(c, name)
|
||||
switch fieldType.Kind() {
|
||||
case reflect.String:
|
||||
if fieldValue.CanSet() {
|
||||
fieldValue.SetString(propertyValue)
|
||||
} else {
|
||||
msg := fmt.Sprintf("unable to set field: %s.%s", structType.Name(), field.Name)
|
||||
log.Info(msg)
|
||||
}
|
||||
case reflect.Bool:
|
||||
if b, err := strconv.ParseBool(propertyValue); err == nil {
|
||||
fieldValue.SetBool(b)
|
||||
}
|
||||
case reflect.Int:
|
||||
if i, err := strconv.Atoi(propertyValue); err == nil {
|
||||
fieldValue.SetInt(int64(i))
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if i, err := strconv.ParseFloat(propertyValue, 64); err == nil {
|
||||
fieldValue.SetFloat(i)
|
||||
}
|
||||
case reflect.Struct:
|
||||
mapAllProps(c, fieldValue)
|
||||
case reflect.Complex128, reflect.Complex64:
|
||||
fallthrough
|
||||
case reflect.Ptr:
|
||||
msg := fmt.Sprintf("decoding CycloneDX properties to a pointer is not supported: %s.%s", field.Type.Name(), field.Name)
|
||||
log.Warnf(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodePackageMetadata(c *cyclonedx.Component) (pkg.MetadataType, interface{}) {
|
||||
if c.Properties != nil {
|
||||
typ := pkg.MetadataType(findPropertyValue(c, "metadataType"))
|
||||
if typ != "" {
|
||||
meta := reflect.New(pkg.MetadataTypeByName[typ])
|
||||
metaPtr := meta.Interface()
|
||||
|
||||
// Map all dynamic properties
|
||||
mapAllProps(c, meta.Elem())
|
||||
|
||||
// Map all explicit metadata properties
|
||||
decodeAuthor(c.Author, metaPtr)
|
||||
decodeGroup(c.Group, metaPtr)
|
||||
decodePublisher(c.Publisher, metaPtr)
|
||||
decodeDescription(c.Description, metaPtr)
|
||||
decodeExternalReferences(c, metaPtr)
|
||||
|
||||
// return the actual interface{} | struct ( not interface{} | *struct )
|
||||
return typ, meta.Elem().Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return pkg.UnknownMetadataType, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
+29
-26
@@ -4,12 +4,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_encodeProperties(t *testing.T) {
|
||||
func Test_encodeComponentProperties(t *testing.T) {
|
||||
epoch := 2
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -46,13 +47,14 @@ func Test_encodeProperties(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
{Name: "foundBy", Value: "cataloger"},
|
||||
{Name: "path", Value: "test"},
|
||||
{Name: "originPackage", Value: "libc-dev"},
|
||||
{Name: "installedSize", Value: "4096"},
|
||||
{Name: "pullDependencies", Value: "musl-utils"},
|
||||
{Name: "pullChecksum", Value: "Q1p78yvTLG094tHE1+dToJGbmYzQE="},
|
||||
{Name: "gitCommitOfApkPort", Value: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479"},
|
||||
{Name: "syft:package:foundBy", Value: "cataloger"},
|
||||
{Name: "syft:location:0:path", Value: "test"},
|
||||
{Name: "syft:metadata:gitCommitOfApkPort", Value: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479"},
|
||||
{Name: "syft:metadata:installedSize", Value: "4096"},
|
||||
{Name: "syft:metadata:originPackage", Value: "libc-dev"},
|
||||
{Name: "syft:metadata:pullChecksum", Value: "Q1p78yvTLG094tHE1+dToJGbmYzQE="},
|
||||
{Name: "syft:metadata:pullDependencies", Value: "musl-utils"},
|
||||
{Name: "syft:metadata:size", Value: "0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -71,10 +73,10 @@ func Test_encodeProperties(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
{Name: "metadataType", Value: "DpkgMetadata"},
|
||||
{Name: "source", Value: "tzdata-dev"},
|
||||
{Name: "sourceVersion", Value: "1.0"},
|
||||
{Name: "installedSize", Value: "3036"},
|
||||
{Name: "syft:package:metadataType", Value: "DpkgMetadata"},
|
||||
{Name: "syft:metadata:installedSize", Value: "3036"},
|
||||
{Name: "syft:metadata:source", Value: "tzdata-dev"},
|
||||
{Name: "syft:metadata:sourceVersion", Value: "1.0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -92,12 +94,12 @@ func Test_encodeProperties(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
{Name: "language", Value: pkg.Go.String()},
|
||||
{Name: "type", Value: "go-module"},
|
||||
{Name: "metadataType", Value: "GolangBinMetadata"},
|
||||
{Name: "goCompiledVersion", Value: "1.17"},
|
||||
{Name: "architecture", Value: "amd64"},
|
||||
{Name: "h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="},
|
||||
{Name: "syft:package:language", Value: pkg.Go.String()},
|
||||
{Name: "syft:package:metadataType", Value: "GolangBinMetadata"},
|
||||
{Name: "syft:package:type", Value: "go-module"},
|
||||
{Name: "syft:metadata:architecture", Value: "amd64"},
|
||||
{Name: "syft:metadata:goCompiledVersion", Value: "1.17"},
|
||||
{Name: "syft:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -121,18 +123,19 @@ func Test_encodeProperties(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
{Name: "type", Value: "rpm"},
|
||||
{Name: "metadataType", Value: "RpmdbMetadata"},
|
||||
{Name: "epoch", Value: "2"},
|
||||
{Name: "release", Value: "1"},
|
||||
{Name: "sourceRpm", Value: "dive-0.9.2-1.src.rpm"},
|
||||
{Name: "size", Value: "12406784"},
|
||||
{Name: "syft:package:metadataType", Value: "RpmdbMetadata"},
|
||||
{Name: "syft:package:type", Value: "rpm"},
|
||||
{Name: "syft:metadata:epoch", Value: "2"},
|
||||
{Name: "syft:metadata:release", Value: "1"},
|
||||
{Name: "syft:metadata:size", Value: "12406784"},
|
||||
{Name: "syft:metadata:sourceRpm", Value: "dive-0.9.2-1.src.rpm"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, encodeProperties(test.input))
|
||||
c := encodeComponent(test.input)
|
||||
assert.Equal(t, test.expected, c.Properties)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@ package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func encodeCPE(p pkg.Package) string {
|
||||
func encodeSingleCPE(p pkg.Package) string {
|
||||
// Since the CPEs in a package are sorted by specificity
|
||||
// we can extract the first CPE as the one to output in cyclonedx
|
||||
if len(p.CPEs) > 0 {
|
||||
@@ -15,16 +16,42 @@ func encodeCPE(p pkg.Package) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func decodeCPEs(c *cyclonedx.Component) []pkg.CPE {
|
||||
// FIXME we not encoding all the CPEs (see above), so here we just use the single provided one
|
||||
func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
|
||||
for i, c := range p.CPEs {
|
||||
// first CPE is "most specific" and already encoded as the component CPE
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, cyclonedx.Property{
|
||||
Name: "syft:cpe23",
|
||||
Value: pkg.CPEString(c),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
|
||||
if c.CPE != "" {
|
||||
cp, err := pkg.NewCPE(c.CPE)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", c.CPE)
|
||||
} else {
|
||||
return []pkg.CPE{cp}
|
||||
out = append(out, cp)
|
||||
}
|
||||
}
|
||||
|
||||
return []pkg.CPE{}
|
||||
if c.Properties != nil {
|
||||
for _, p := range *c.Properties {
|
||||
if p.Name == "syft:cpe23" {
|
||||
cp, err := pkg.NewCPE(p.Value)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", p.Value)
|
||||
} else {
|
||||
out = append(out, cp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package cyclonedxhelpers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_encodeCPE(t *testing.T) {
|
||||
@@ -51,7 +52,7 @@ func Test_encodeCPE(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, encodeCPE(test.input))
|
||||
assert.Equal(t, test.expected, encodeSingleCPE(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
@@ -11,7 +13,6 @@ import (
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
|
||||
@@ -74,9 +75,10 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
|
||||
if len(*eRefs) == 0 {
|
||||
eRefs = nil
|
||||
}
|
||||
props := getCycloneDXProperties(*distro)
|
||||
if len(*props) == 0 {
|
||||
props = nil
|
||||
props := encodeProperties(distro, "syft:distro")
|
||||
var properties *[]cyclonedx.Property
|
||||
if len(props) > 0 {
|
||||
properties = &props
|
||||
}
|
||||
return []cyclonedx.Component{
|
||||
{
|
||||
@@ -93,7 +95,7 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
|
||||
// TODO should we add a PURL?
|
||||
CPE: distro.CPEName,
|
||||
ExternalReferences: eRefs,
|
||||
Properties: props,
|
||||
Properties: properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,21 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common"
|
||||
)
|
||||
|
||||
func encodeProperties(p pkg.Package) *[]cyclonedx.Property {
|
||||
props := []cyclonedx.Property{}
|
||||
props = append(props, *getCycloneDXProperties(p)...)
|
||||
if len(p.Locations) > 0 {
|
||||
for _, l := range p.Locations {
|
||||
props = append(props, *getCycloneDXProperties(l.Coordinates)...)
|
||||
}
|
||||
}
|
||||
if hasMetadata(p) {
|
||||
props = append(props, *getCycloneDXProperties(p.Metadata)...)
|
||||
}
|
||||
if len(props) > 0 {
|
||||
return &props
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
CycloneDXFields = common.RequiredTag("cyclonedx")
|
||||
)
|
||||
|
||||
func getCycloneDXProperties(m interface{}) *[]cyclonedx.Property {
|
||||
props := []cyclonedx.Property{}
|
||||
structValue := reflect.ValueOf(m)
|
||||
if structValue.Kind() != reflect.Struct {
|
||||
return &props
|
||||
func encodeProperties(obj interface{}, prefix string) (out []cyclonedx.Property) {
|
||||
for _, p := range common.Sorted(common.Encode(obj, prefix, CycloneDXFields)) {
|
||||
out = append(out, cyclonedx.Property{
|
||||
Name: p.Name,
|
||||
Value: p.Value,
|
||||
})
|
||||
}
|
||||
structType := structValue.Type()
|
||||
for i := 0; i < structValue.NumField(); i++ {
|
||||
if name, value := getCycloneDXPropertyName(structType.Field(i)), getCycloneDXPropertyValue(structValue.Field(i)); name != "" && value != "" {
|
||||
// In the case of the value is a struct and has cyclonedx tag with name "-"
|
||||
// call the getCycloneDXProperties recursively.
|
||||
if name == "-" && reflect.ValueOf(value).Kind() == reflect.Struct {
|
||||
props = append(props, *getCycloneDXProperties(value)...)
|
||||
} else if reflect.ValueOf(value).Kind() == reflect.String {
|
||||
props = append(props, cyclonedx.Property{
|
||||
Name: name,
|
||||
Value: fmt.Sprint(value),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return &props
|
||||
}
|
||||
|
||||
func getCycloneDXPropertyName(field reflect.StructField) string {
|
||||
if value, exists := field.Tag.Lookup("cyclonedx"); exists {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getCycloneDXPropertyValue(field reflect.Value) interface{} {
|
||||
if field.IsZero() {
|
||||
return ""
|
||||
}
|
||||
switch field.Kind() {
|
||||
case reflect.String, reflect.Bool, reflect.Int, reflect.Float32, reflect.Float64, reflect.Complex128, reflect.Complex64:
|
||||
if field.CanInterface() {
|
||||
return fmt.Sprint(field.Interface())
|
||||
}
|
||||
return ""
|
||||
case reflect.Struct:
|
||||
if field.CanInterface() {
|
||||
return field.Interface()
|
||||
}
|
||||
return ""
|
||||
case reflect.Ptr:
|
||||
return getCycloneDXPropertyValue(reflect.Indirect(field))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findPropertyValue(c *cyclonedx.Component, name string) string {
|
||||
for _, p := range *c.Properties {
|
||||
if p.Name == name {
|
||||
return p.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// FieldName return a flag to indicate this is a valid field and a name to use
|
||||
type FieldName func(field reflect.StructField) (string, bool)
|
||||
|
||||
// OptionalTag given a tag name, will return the defined tag or fall back to lower camel case field name
|
||||
func OptionalTag(tag string) FieldName {
|
||||
return func(f reflect.StructField) (string, bool) {
|
||||
if n, ok := f.Tag.Lookup(tag); ok {
|
||||
return n, true
|
||||
}
|
||||
return lowerFirst(f.Name), true
|
||||
}
|
||||
}
|
||||
|
||||
// TrimOmitempty trims `,omitempty` from the name
|
||||
func TrimOmitempty(fn FieldName) FieldName {
|
||||
return func(f reflect.StructField) (string, bool) {
|
||||
if v, ok := fn(f); ok {
|
||||
return strings.TrimSuffix(v, ",omitempty"), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// RequiredTag based on the given tag, only use a field if present
|
||||
func RequiredTag(tag string) FieldName {
|
||||
return func(f reflect.StructField) (string, bool) {
|
||||
if n, ok := f.Tag.Lookup(tag); ok {
|
||||
return n, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// OptionalJSONTag uses field names defined in json tags, if available
|
||||
OptionalJSONTag = TrimOmitempty(OptionalTag("json"))
|
||||
)
|
||||
|
||||
// lowerFirst converts the first character of the string to lower case
|
||||
func lowerFirst(s string) string {
|
||||
return strings.ToLower(s[0:1]) + s[1:]
|
||||
}
|
||||
|
||||
// Encode recursively encodes the object's properties as an ordered set of NameValue pairs
|
||||
func Encode(obj interface{}, prefix string, fn FieldName) map[string]string {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
props := map[string]string{}
|
||||
encode(props, reflect.ValueOf(obj), prefix, fn)
|
||||
return props
|
||||
}
|
||||
|
||||
// NameValue a simple type to store stringified name/value pairs
|
||||
type NameValue struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Sorted returns a sorted set of NameValue pairs
|
||||
func Sorted(values map[string]string) (out []NameValue) {
|
||||
var keys []string
|
||||
for k := range values {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
out = append(out, NameValue{
|
||||
Name: k,
|
||||
Value: values[k],
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func encode(out map[string]string, value reflect.Value, prefix string, fn FieldName) {
|
||||
if !value.IsValid() || value.Type() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
typ := value.Type()
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Ptr:
|
||||
if value.IsNil() {
|
||||
return
|
||||
}
|
||||
value = value.Elem()
|
||||
encode(out, value, prefix, fn)
|
||||
case reflect.String:
|
||||
v := value.String()
|
||||
if v != "" {
|
||||
out[prefix] = v
|
||||
}
|
||||
case reflect.Bool:
|
||||
v := value.Bool()
|
||||
out[prefix] = strconv.FormatBool(v)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
v := value.Int()
|
||||
out[prefix] = strconv.FormatInt(v, 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
v := value.Uint()
|
||||
out[prefix] = strconv.FormatUint(v, 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v := value.Float()
|
||||
out[prefix] = fmt.Sprintf("%f", v)
|
||||
case reflect.Array, reflect.Slice:
|
||||
for idx := 0; idx < value.Len(); idx++ {
|
||||
encode(out, value.Index(idx), fmt.Sprintf("%s:%d", prefix, idx), fn)
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
pv := value.Field(i)
|
||||
f := typ.Field(i)
|
||||
name, ok := fieldName(f, prefix, fn)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
encode(out, pv, name, fn)
|
||||
}
|
||||
default:
|
||||
log.Warnf("skipping encoding of unsupported property: %s", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// fieldName gets the name of the field using the provided FieldName function
|
||||
func fieldName(f reflect.StructField, prefix string, fn FieldName) (string, bool) {
|
||||
name, ok := fn(f)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if name == "" {
|
||||
return prefix, true
|
||||
}
|
||||
if prefix != "" {
|
||||
name = fmt.Sprintf("%s:%s", prefix, name)
|
||||
}
|
||||
return name, true
|
||||
}
|
||||
|
||||
// Decode based on the given type, applies all values to hydrate a new instance
|
||||
func Decode(typ reflect.Type, values map[string]string, prefix string, fn FieldName) interface{} {
|
||||
isPtr := false
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
isPtr = true
|
||||
}
|
||||
|
||||
isSlice := false
|
||||
if typ.Kind() == reflect.Slice {
|
||||
typ = reflect.PtrTo(typ)
|
||||
isSlice = true
|
||||
}
|
||||
|
||||
v := reflect.New(typ)
|
||||
|
||||
decode(values, v, prefix, fn)
|
||||
|
||||
switch {
|
||||
case isSlice && isPtr:
|
||||
return v.Elem().Interface()
|
||||
case isSlice:
|
||||
return PtrToStruct(v.Elem().Interface())
|
||||
case isPtr:
|
||||
return v.Interface()
|
||||
}
|
||||
return v.Elem().Interface()
|
||||
}
|
||||
|
||||
// DecodeInto decodes all values to hydrate the given object instance
|
||||
func DecodeInto(obj interface{}, values map[string]string, prefix string, fn FieldName) {
|
||||
value := reflect.ValueOf(obj)
|
||||
|
||||
for value.Type().Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
decode(values, value, prefix, fn)
|
||||
}
|
||||
|
||||
// nolint: funlen, gocognit, gocyclo
|
||||
func decode(vals map[string]string, value reflect.Value, prefix string, fn FieldName) bool {
|
||||
if !value.IsValid() || value.Type() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
typ := value.Type()
|
||||
|
||||
incoming, valid := vals[prefix]
|
||||
switch typ.Kind() {
|
||||
case reflect.Ptr:
|
||||
t := typ.Elem()
|
||||
v := value
|
||||
if v.IsNil() {
|
||||
v = reflect.New(t)
|
||||
}
|
||||
if decode(vals, v.Elem(), prefix, fn) && value.CanSet() {
|
||||
o := v.Interface()
|
||||
log.Infof("%v", o)
|
||||
value.Set(v)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case reflect.String:
|
||||
if valid {
|
||||
value.SetString(incoming)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case reflect.Bool:
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
if b, err := strconv.ParseBool(incoming); err == nil {
|
||||
value.SetBool(b)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
if i, err := strconv.ParseInt(incoming, 10, 64); err == nil {
|
||||
value.SetInt(i)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
if i, err := strconv.ParseUint(incoming, 10, 64); err == nil {
|
||||
value.SetUint(i)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
if i, err := strconv.ParseFloat(incoming, 64); err == nil {
|
||||
value.SetFloat(i)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
values := false
|
||||
t := typ.Elem()
|
||||
slice := reflect.MakeSlice(typ, 0, 0)
|
||||
for idx := 0; ; idx++ {
|
||||
// test for index
|
||||
str := fmt.Sprintf("%s:%d", prefix, idx)
|
||||
// create new placeholder and decode values
|
||||
newType := t
|
||||
if t.Kind() == reflect.Ptr {
|
||||
newType = t.Elem()
|
||||
}
|
||||
v := reflect.New(newType)
|
||||
if decode(vals, v.Elem(), str, fn) {
|
||||
// append to slice
|
||||
if t.Kind() != reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
slice = reflect.Append(slice, v)
|
||||
values = true
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if values {
|
||||
value.Set(slice)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case reflect.Struct:
|
||||
values := false
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
f := typ.Field(i)
|
||||
v := value.Field(i)
|
||||
|
||||
name, ok := fieldName(f, prefix, fn)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if decode(vals, v, name, fn) {
|
||||
values = true
|
||||
}
|
||||
}
|
||||
return values
|
||||
default:
|
||||
log.Warnf("unable to set field: %s", prefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func PtrToStruct(ptr interface{}) interface{} {
|
||||
v := reflect.ValueOf(ptr)
|
||||
if v.IsZero() {
|
||||
return nil
|
||||
}
|
||||
switch v.Type().Kind() {
|
||||
case reflect.Ptr:
|
||||
return PtrToStruct(v.Elem().Interface())
|
||||
case reflect.Interface:
|
||||
return PtrToStruct(v.Elem().Interface())
|
||||
}
|
||||
return v.Interface()
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type T1 struct {
|
||||
Name string
|
||||
Val int
|
||||
ValU uint
|
||||
Flag bool `json:"bool_flag"`
|
||||
Float float64
|
||||
T2 T2
|
||||
T2Ptr *T2
|
||||
T2Arr []T2
|
||||
T2PtrArr []*T2
|
||||
StrArr []string
|
||||
IntArr []int
|
||||
FloatArr []float64
|
||||
BoolArr []bool
|
||||
T3Arr []T3
|
||||
}
|
||||
|
||||
type T2 struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type T3 struct {
|
||||
T4Arr []T4
|
||||
}
|
||||
|
||||
type T4 struct {
|
||||
Typ string
|
||||
IntPtr *int
|
||||
}
|
||||
|
||||
func Test_EncodeDecodeCycle(t *testing.T) {
|
||||
val := 99
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
}{
|
||||
{
|
||||
name: "all values",
|
||||
value: T1{
|
||||
Name: "name",
|
||||
Val: 10,
|
||||
ValU: 16,
|
||||
Flag: true,
|
||||
Float: 1.2,
|
||||
T2: T2{
|
||||
Name: "embedded t2",
|
||||
},
|
||||
T2Ptr: &T2{
|
||||
"t2 ptr",
|
||||
},
|
||||
T2Arr: []T2{
|
||||
{"t2 elem 0"},
|
||||
{"t2 elem 1"},
|
||||
},
|
||||
T2PtrArr: []*T2{
|
||||
{"t2 ptr v1"},
|
||||
{"t2 ptr v2"},
|
||||
},
|
||||
StrArr: []string{"s 1", "s 2", "s 3"},
|
||||
IntArr: []int{9, 12, -1},
|
||||
FloatArr: []float64{-23.99, 15.234321, 39912342314},
|
||||
BoolArr: []bool{false, true, true, true, false},
|
||||
T3Arr: []T3{
|
||||
{
|
||||
T4Arr: []T4{
|
||||
{
|
||||
Typ: "t4 nested typ 1",
|
||||
},
|
||||
{
|
||||
Typ: "t4 nested typ 2",
|
||||
IntPtr: &val,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil values",
|
||||
value: T1{
|
||||
Name: "t1 test",
|
||||
Val: 0,
|
||||
ValU: 0,
|
||||
Flag: false,
|
||||
Float: 0,
|
||||
T2: T2{},
|
||||
T2Ptr: nil,
|
||||
T2Arr: nil,
|
||||
T2PtrArr: nil,
|
||||
StrArr: nil,
|
||||
IntArr: nil,
|
||||
FloatArr: nil,
|
||||
BoolArr: nil,
|
||||
T3Arr: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "array values",
|
||||
value: []T2{
|
||||
{"t2 elem 0"},
|
||||
{"t2 elem 1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "array ptr",
|
||||
value: &[]T2{
|
||||
{"t2 elem 0"},
|
||||
{"t2 elem 1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
vals := Encode(test.value, "props", OptionalJSONTag)
|
||||
|
||||
typ := reflect.TypeOf(test.value)
|
||||
|
||||
if typ.Kind() != reflect.Slice && typ.Kind() != reflect.Ptr {
|
||||
assert.NotEmpty(t, vals["props:bool_flag"])
|
||||
|
||||
t2 := T1{}
|
||||
DecodeInto(&t2, vals, "props", OptionalJSONTag)
|
||||
|
||||
assert.EqualValues(t, test.value, t2)
|
||||
}
|
||||
|
||||
t3 := Decode(typ, vals, "props", OptionalJSONTag)
|
||||
|
||||
assert.EqualValues(t, test.value, t3)
|
||||
})
|
||||
}
|
||||
}
|
||||
+28
-20
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.3",
|
||||
"serialNumber": "urn:uuid:326afa86-5620-4a80-8f2b-7f283b954b9b",
|
||||
"serialNumber": "urn:uuid:195a66a2-6d39-472e-b62b-0cafb9bfedd4",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-02-10T17:19:38-05:00",
|
||||
"timestamp": "2022-02-25T12:54:25-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@@ -35,23 +35,23 @@
|
||||
"purl": "a-purl-2",
|
||||
"properties": [
|
||||
{
|
||||
"name": "foundBy",
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "the-cataloger-1"
|
||||
},
|
||||
{
|
||||
"name": "language",
|
||||
"name": "syft:package:language",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "metadataType",
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "PythonPackageMetadata"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"name": "syft:package:type",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/some/path/pkg1"
|
||||
}
|
||||
]
|
||||
@@ -64,20 +64,24 @@
|
||||
"purl": "a-purl-2",
|
||||
"properties": [
|
||||
{
|
||||
"name": "foundBy",
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "the-cataloger-2"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"value": "deb"
|
||||
},
|
||||
{
|
||||
"name": "metadataType",
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "DpkgMetadata"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"name": "syft:package:type",
|
||||
"value": "deb"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/some/path/pkg1"
|
||||
},
|
||||
{
|
||||
"name": "syft:metadata:installedSize",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -93,15 +97,19 @@
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "prettyName",
|
||||
"name": "syft:distro:id",
|
||||
"value": "debian"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"name": "syft:distro:idLike:0",
|
||||
"value": "like!"
|
||||
},
|
||||
{
|
||||
"name": "syft:distro:prettyName",
|
||||
"value": "debian"
|
||||
},
|
||||
{
|
||||
"name": "versionID",
|
||||
"name": "syft:distro:versionID",
|
||||
"value": "1.2.3"
|
||||
}
|
||||
]
|
||||
|
||||
+32
-24
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.3",
|
||||
"serialNumber": "urn:uuid:761a2036-0f25-4787-bf28-f5e9a7d9a0bf",
|
||||
"serialNumber": "urn:uuid:78116a1b-b709-4734-8411-d0e339308edd",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-02-10T17:19:38-05:00",
|
||||
"timestamp": "2022-02-25T12:54:25-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@@ -35,28 +35,28 @@
|
||||
"purl": "a-purl-1",
|
||||
"properties": [
|
||||
{
|
||||
"name": "foundBy",
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "the-cataloger-1"
|
||||
},
|
||||
{
|
||||
"name": "language",
|
||||
"name": "syft:package:language",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "metadataType",
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "PythonPackageMetadata"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"value": "/somefile-1.txt"
|
||||
"name": "syft:package:type",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "layerID",
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:41e7295da66c405eb3a4df29188dcf80f622f9304d487033a86d4a22e3f01abe"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/somefile-1.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -68,24 +68,28 @@
|
||||
"purl": "a-purl-2",
|
||||
"properties": [
|
||||
{
|
||||
"name": "foundBy",
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "the-cataloger-2"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"value": "deb"
|
||||
},
|
||||
{
|
||||
"name": "metadataType",
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "DpkgMetadata"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"name": "syft:package:type",
|
||||
"value": "deb"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:68a2c166dcb3acf6b7303e995ca1fe7d794bd3b5852a0b4048f9c96b796086aa"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/somefile-2.txt"
|
||||
},
|
||||
{
|
||||
"name": "layerID",
|
||||
"value": "sha256:68a2c166dcb3acf6b7303e995ca1fe7d794bd3b5852a0b4048f9c96b796086aa"
|
||||
"name": "syft:metadata:installedSize",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -101,15 +105,19 @@
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "prettyName",
|
||||
"name": "syft:distro:id",
|
||||
"value": "debian"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"name": "syft:distro:idLike:0",
|
||||
"value": "like!"
|
||||
},
|
||||
{
|
||||
"name": "syft:distro:prettyName",
|
||||
"value": "debian"
|
||||
},
|
||||
{
|
||||
"name": "versionID",
|
||||
"name": "syft:distro:versionID",
|
||||
"value": "1.2.3"
|
||||
}
|
||||
]
|
||||
|
||||
+16
-14
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:25133adc-01af-46ad-a198-002034e54097" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:dd1d1863-04be-414c-9b2a-bdc0e0f25e9f" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-02-10T17:18:31-05:00</timestamp>
|
||||
<timestamp>2022-02-25T12:54:44-05:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -26,11 +26,11 @@
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
<purl>a-purl-2</purl>
|
||||
<properties>
|
||||
<property name="foundBy">the-cataloger-1</property>
|
||||
<property name="language">python</property>
|
||||
<property name="type">python</property>
|
||||
<property name="metadataType">PythonPackageMetadata</property>
|
||||
<property name="path">/some/path/pkg1</property>
|
||||
<property name="syft:package:foundBy">the-cataloger-1</property>
|
||||
<property name="syft:package:language">python</property>
|
||||
<property name="syft:package:metadataType">PythonPackageMetadata</property>
|
||||
<property name="syft:package:type">python</property>
|
||||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component type="library">
|
||||
@@ -39,10 +39,11 @@
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
<purl>a-purl-2</purl>
|
||||
<properties>
|
||||
<property name="foundBy">the-cataloger-2</property>
|
||||
<property name="type">deb</property>
|
||||
<property name="metadataType">DpkgMetadata</property>
|
||||
<property name="path">/some/path/pkg1</property>
|
||||
<property name="syft:package:foundBy">the-cataloger-2</property>
|
||||
<property name="syft:package:metadataType">DpkgMetadata</property>
|
||||
<property name="syft:package:type">deb</property>
|
||||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component type="operating-system">
|
||||
@@ -51,9 +52,10 @@
|
||||
<description>debian</description>
|
||||
<swid tagId="debian" name="debian" version="1.2.3"></swid>
|
||||
<properties>
|
||||
<property name="prettyName">debian</property>
|
||||
<property name="id">debian</property>
|
||||
<property name="versionID">1.2.3</property>
|
||||
<property name="syft:distro:id">debian</property>
|
||||
<property name="syft:distro:idLike:0">like!</property>
|
||||
<property name="syft:distro:prettyName">debian</property>
|
||||
<property name="syft:distro:versionID">1.2.3</property>
|
||||
</properties>
|
||||
</component>
|
||||
</components>
|
||||
|
||||
+18
-16
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:e702d78f-9372-4458-ac38-be8e80fc3005" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:153353a9-d9f4-40f6-be23-3d56487930c1" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-02-10T17:18:31-05:00</timestamp>
|
||||
<timestamp>2022-02-25T12:54:44-05:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -26,12 +26,12 @@
|
||||
<cpe>cpe:2.3:*:some:package:1:*:*:*:*:*:*:*</cpe>
|
||||
<purl>a-purl-1</purl>
|
||||
<properties>
|
||||
<property name="foundBy">the-cataloger-1</property>
|
||||
<property name="language">python</property>
|
||||
<property name="type">python</property>
|
||||
<property name="metadataType">PythonPackageMetadata</property>
|
||||
<property name="path">/somefile-1.txt</property>
|
||||
<property name="layerID">sha256:41e7295da66c405eb3a4df29188dcf80f622f9304d487033a86d4a22e3f01abe</property>
|
||||
<property name="syft:package:foundBy">the-cataloger-1</property>
|
||||
<property name="syft:package:language">python</property>
|
||||
<property name="syft:package:metadataType">PythonPackageMetadata</property>
|
||||
<property name="syft:package:type">python</property>
|
||||
<property name="syft:location:0:layerID">sha256:41e7295da66c405eb3a4df29188dcf80f622f9304d487033a86d4a22e3f01abe</property>
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component type="library">
|
||||
@@ -40,11 +40,12 @@
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
<purl>a-purl-2</purl>
|
||||
<properties>
|
||||
<property name="foundBy">the-cataloger-2</property>
|
||||
<property name="type">deb</property>
|
||||
<property name="metadataType">DpkgMetadata</property>
|
||||
<property name="path">/somefile-2.txt</property>
|
||||
<property name="layerID">sha256:68a2c166dcb3acf6b7303e995ca1fe7d794bd3b5852a0b4048f9c96b796086aa</property>
|
||||
<property name="syft:package:foundBy">the-cataloger-2</property>
|
||||
<property name="syft:package:metadataType">DpkgMetadata</property>
|
||||
<property name="syft:package:type">deb</property>
|
||||
<property name="syft:location:0:layerID">sha256:68a2c166dcb3acf6b7303e995ca1fe7d794bd3b5852a0b4048f9c96b796086aa</property>
|
||||
<property name="syft:location:0:path">/somefile-2.txt</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component type="operating-system">
|
||||
@@ -53,9 +54,10 @@
|
||||
<description>debian</description>
|
||||
<swid tagId="debian" name="debian" version="1.2.3"></swid>
|
||||
<properties>
|
||||
<property name="prettyName">debian</property>
|
||||
<property name="id">debian</property>
|
||||
<property name="versionID">1.2.3</property>
|
||||
<property name="syft:distro:id">debian</property>
|
||||
<property name="syft:distro:idLike:0">like!</property>
|
||||
<property name="syft:distro:prettyName">debian</property>
|
||||
<property name="syft:distro:versionID">1.2.3</property>
|
||||
</properties>
|
||||
</component>
|
||||
</components>
|
||||
|
||||
@@ -152,13 +152,19 @@ func toFiles(s sbom.SBOM) []model.File {
|
||||
func toFileChecksums(digests []file.Digest) (checksums []model.Checksum) {
|
||||
for _, digest := range digests {
|
||||
checksums = append(checksums, model.Checksum{
|
||||
Algorithm: digest.Algorithm,
|
||||
Algorithm: toChecksumAlgorithm(digest.Algorithm),
|
||||
ChecksumValue: digest.Value,
|
||||
})
|
||||
}
|
||||
return checksums
|
||||
}
|
||||
|
||||
func toChecksumAlgorithm(algorithm string) string {
|
||||
// basically, we need an uppercase version of our algorithm:
|
||||
// https://github.com/spdx/spdx-spec/blob/development/v2.2.2/schemas/spdx-schema.json#L165
|
||||
return strings.ToUpper(algorithm)
|
||||
}
|
||||
|
||||
func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
|
||||
@@ -142,7 +142,7 @@ func Test_toFileChecksums(t *testing.T) {
|
||||
name: "has digests",
|
||||
digests: []file.Digest{
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Algorithm: "SHA256",
|
||||
Value: "deadbeefcafe",
|
||||
},
|
||||
{
|
||||
@@ -152,11 +152,11 @@ func Test_toFileChecksums(t *testing.T) {
|
||||
},
|
||||
expected: []model.Checksum{
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Algorithm: "SHA256",
|
||||
ChecksumValue: "deadbeefcafe",
|
||||
},
|
||||
{
|
||||
Algorithm: "md5",
|
||||
Algorithm: "MD5",
|
||||
ChecksumValue: "meh",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -16,18 +16,15 @@ import (
|
||||
var _ common.ParserFn = parseYarnLock
|
||||
|
||||
var (
|
||||
// composedNameExp matches the "composed" variant of yarn.lock entry names,
|
||||
// where the name appears in quotes and is prefixed with @<some-namespace>.
|
||||
// For example: "@babel/code-frame@^7.0.0"
|
||||
composedNameExp = regexp.MustCompile(`^"(@[^@]+)`)
|
||||
|
||||
// simpleNameExp matches the "simple" variant of yarn.lock entry names, for packages with no namespace prefix.
|
||||
// For example: aws-sdk@2.706.0
|
||||
simpleNameExp = regexp.MustCompile(`^(\w[\w-_.]*)@`)
|
||||
// packageNameExp matches the name of the dependency in yarn.lock
|
||||
// including scope/namespace prefix if found.
|
||||
// For example: "aws-sdk@2.706.0" returns "aws-sdk"
|
||||
// "@babel/code-frame@^7.0.0" returns "@babel/code-frame"
|
||||
packageNameExp = regexp.MustCompile(`^"?((?:@\w[\w-_.]*\/)?\w[\w-_.]*)@`)
|
||||
|
||||
// versionExp matches the "version" line of a yarn.lock entry and captures the version value.
|
||||
// For example: version "4.10.1" (...and the value "4.10.1" is captured)
|
||||
versionExp = regexp.MustCompile(`^\W+version\W+"([\w-_.]+)"`)
|
||||
versionExp = regexp.MustCompile(`^\W+version(?:\W+"|:\W+)([\w-_.]+)"?`)
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -87,11 +84,7 @@ func parseYarnLock(path string, reader io.Reader) ([]*pkg.Package, []artifact.Re
|
||||
}
|
||||
|
||||
func findPackageName(line string) string {
|
||||
if matches := composedNameExp.FindStringSubmatch(line); len(matches) >= 2 {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
if matches := simpleNameExp.FindStringSubmatch(line); len(matches) >= 2 {
|
||||
if matches := packageNameExp.FindStringSubmatch(line); len(matches) >= 2 {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseYarnLock(t *testing.T) {
|
||||
@@ -65,16 +67,182 @@ func TestParseYarnLock(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fixture, err := os.Open("test-fixtures/yarn/yarn.lock")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
testFixtures := []string{
|
||||
"test-fixtures/yarn/yarn.lock",
|
||||
"test-fixtures/yarn-berry/yarn.lock",
|
||||
}
|
||||
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseYarnLock(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse yarn.lock: %+v", err)
|
||||
}
|
||||
for _, file := range testFixtures {
|
||||
file := file
|
||||
t.Run(file, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assertPkgsEqual(t, actual, expected)
|
||||
fixture, err := os.Open(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: no relationships are under test yet
|
||||
actual, _, err := parseYarnLock(fixture.Name(), fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertPkgsEqual(t, actual, expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYarnFindPackageNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
line string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
line: `"@babel/code-frame@npm:7.10.4":`,
|
||||
expected: "@babel/code-frame",
|
||||
},
|
||||
{
|
||||
line: `"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":`,
|
||||
expected: "@babel/code-frame",
|
||||
},
|
||||
{
|
||||
line: "ajv@^6.10.2, ajv@^6.5.5:",
|
||||
expected: "ajv",
|
||||
},
|
||||
{
|
||||
line: "aws-sdk@2.706.0:",
|
||||
expected: "aws-sdk",
|
||||
},
|
||||
{
|
||||
line: "asn1.js@^4.0.0:",
|
||||
expected: "asn1.js",
|
||||
},
|
||||
{
|
||||
line: "c0n-fab_u.laTION@^7.0.0",
|
||||
expected: "c0n-fab_u.laTION",
|
||||
},
|
||||
{
|
||||
line: `"newtest@workspace:.":`,
|
||||
expected: "newtest",
|
||||
},
|
||||
{
|
||||
line: `"color-convert@npm:^1.9.0":`,
|
||||
expected: "color-convert",
|
||||
},
|
||||
{
|
||||
line: `"@npmcorp/code-frame@^7.1.0", "@npmcorp/code-frame@^7.10.4":`,
|
||||
expected: "@npmcorp/code-frame",
|
||||
},
|
||||
{
|
||||
line: `"@npmcorp/code-frame@^7.2.3":`,
|
||||
expected: "@npmcorp/code-frame",
|
||||
},
|
||||
{
|
||||
line: `"@s/odd-name@^7.1.2":`,
|
||||
expected: "@s/odd-name",
|
||||
},
|
||||
{
|
||||
line: `"@/code-frame@^7.3.4":`,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: `"code-frame":`,
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.expected, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := findPackageName(test.line)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYarnFindPackageVersions(t *testing.T) {
|
||||
tests := []struct {
|
||||
line string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
line: ` version "7.10.4"`,
|
||||
expected: "7.10.4",
|
||||
},
|
||||
{
|
||||
line: ` version "7.11.5"`,
|
||||
expected: "7.11.5",
|
||||
},
|
||||
{
|
||||
line: `version "7.12.6"`,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: ` version "0.0.0"`,
|
||||
expected: "0.0.0",
|
||||
},
|
||||
{
|
||||
line: ` version "2" `,
|
||||
expected: "2",
|
||||
},
|
||||
{
|
||||
line: ` version "9.3"`,
|
||||
expected: "9.3",
|
||||
},
|
||||
{
|
||||
line: "ajv@^6.10.2, ajv@^6.5.5",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: "atob@^2.1.2:",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: `"color-convert@npm:^1.9.0":`,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: " version: 1.9.3",
|
||||
expected: "1.9.3",
|
||||
},
|
||||
{
|
||||
line: " version: 2",
|
||||
expected: "2",
|
||||
},
|
||||
{
|
||||
line: " version: 9.3",
|
||||
expected: "9.3",
|
||||
},
|
||||
{
|
||||
line: "ajv@^6.10.2, ajv@^6.5.5",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: "atob@^2.1.2:",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: " version: 1.0.0-alpha+001",
|
||||
expected: "1.0.0-alpha",
|
||||
},
|
||||
{
|
||||
line: " version: 1.0.0-beta_test+exp.sha.5114f85",
|
||||
expected: "1.0.0-beta_test",
|
||||
},
|
||||
{
|
||||
line: " version: 1.0.0+21AF26D3-117B344092BD",
|
||||
expected: "1.0.0",
|
||||
},
|
||||
{
|
||||
line: " version: 0.0.0-use.local",
|
||||
expected: "0.0.0-use.local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.expected, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := findPackageVersion(test.line)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 6
|
||||
cacheKey: 8
|
||||
|
||||
"@babel/code-frame@npm:7.10.4":
|
||||
version: 7.10.4
|
||||
resolution: "@babel/code-frame@npm:7.10.4"
|
||||
dependencies:
|
||||
"@babel/highlight": ^7.10.4
|
||||
checksum: feb4543c8a509fe30f0f6e8d7aa84f82b41148b963b826cd330e34986f649a85cb63b2f13dd4effdf434ac555d16f14940b8ea5f4433297c2f5ff85486ded019
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/minimatch@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@types/minimatch@npm:3.0.3"
|
||||
checksum: b80259d55b96ef24cb3bb961b6dc18b943f2bb8838b4d8e7bead204f3173e551a416ffa49f9aaf1dc431277fffe36214118628eacf4aea20119df8835229901b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/qs@npm:6.9.4":
|
||||
version: 6.9.4
|
||||
resolution: "@types/qs@npm:6.9.4"
|
||||
checksum: 77e509ed213f7694ae35f84a58b88da8744aad019e93556af6aeab4289287abbe71836c051d00649dbac0289ea199e408442590cfb1785009de11c3c8d0cbbea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/qs@npm:6.9.4":
|
||||
version: 6.9.4
|
||||
resolution: "@types/qs@npm:6.9.4"
|
||||
checksum: 77e509ed213f7694ae35f84a58b88da8744aad019e93556af6aeab4289287abbe71836c051d00649dbac0289ea199e408442590cfb1785009de11c3c8d0cbbea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ajv@npm:6.12.3":
|
||||
version: 6.12.3
|
||||
resolution: "ajv@npm:6.12.3"
|
||||
dependencies:
|
||||
fast-deep-equal: ^3.1.1
|
||||
fast-json-stable-stringify: ^2.0.0
|
||||
json-schema-traverse: ^0.4.1
|
||||
uri-js: ^4.2.2
|
||||
checksum: ca559d34710e6969d33bc1316282e1ece4d4d99ff5fdca4bfe31947740f8f90e7824238cdc2954e499cf75b2432e3e6c56b32814ebe04fccf8abcc3fbf36b348
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"atob@npm:2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "atob@npm:2.1.2"
|
||||
bin:
|
||||
atob: bin/atob.js
|
||||
checksum: dfeeeb70090c5ebea7be4b9f787f866686c645d9f39a0d184c817252d0cf08455ed25267d79c03254d3be1f03ac399992a792edcd5ffb9c91e097ab5ef42833a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aws-sdk@npm:2.706.0":
|
||||
version: 2.706.0
|
||||
resolution: "aws-sdk@npm:2.706.0"
|
||||
dependencies:
|
||||
buffer: 4.9.2
|
||||
events: 1.1.1
|
||||
ieee754: 1.1.13
|
||||
jmespath: 0.15.0
|
||||
querystring: 0.2.0
|
||||
sax: 1.2.1
|
||||
url: 0.10.3
|
||||
uuid: 3.3.2
|
||||
xml2js: 0.4.19
|
||||
checksum: bf8ca2fc4f758bdebd04051ec15729affad3eb0e18eed4ae41db5b7d6ff2aed2cf3a12ae082c11b955df0125378c57b8406e1f91006e48f0c162fdbe4ee4e330
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jhipster-core@npm:7.3.4":
|
||||
version: 7.3.4
|
||||
resolution: "jhipster-core@npm:7.3.4"
|
||||
dependencies:
|
||||
chevrotain: 7.0.1
|
||||
fs-extra: 8.1.0
|
||||
lodash: 4.17.15
|
||||
winston: 3.2.1
|
||||
checksum: 6a97741d574a42a138f98596c668370b41ec8870335bcd758b6b890e279ba30d4d2be447f8cecbf416286f2c53636b406a63a773c7b00709c95af0a9a3f9b397
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asn1.js@npm:4.10.1":
|
||||
version: 4.10.1
|
||||
resolution: "asn1.js@npm:4.10.1"
|
||||
dependencies:
|
||||
bn.js: ^4.0.0
|
||||
inherits: ^2.0.1
|
||||
minimalistic-assert: ^1.0.0
|
||||
checksum: 9289a1a55401238755e3142511d7b8f6fc32f08c86ff68bd7100da8b6c186179dd6b14234fba2f7f6099afcd6758a816708485efe44bc5b2a6ec87d9ceeddbb5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"c0n-fab_u.laTION@workspace:.":
|
||||
version: 7.7.7
|
||||
resolution: "newtest@workspace:."
|
||||
dependencies:
|
||||
"@babel/code-frame": 7.10.4
|
||||
"@types/minimatch": 3.0.3
|
||||
"@types/qs": 6.9.4
|
||||
ajv: 6.12.3
|
||||
asn1.js: 4.10.1
|
||||
atob: 2.1.2
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// Location represents a path relative to a particular filesystem resolved to a specific file.Reference. This struct is used as a key
|
||||
// in content fetching to uniquely identify a file relative to a request (the VirtualPath).
|
||||
type Location struct {
|
||||
Coordinates
|
||||
Coordinates `cyclonedx:""` // Empty string here means there is no intermediate property name, e.g. syft:locations:0:path without "coordinates"
|
||||
// note: it is IMPORTANT to ignore anything but the coordinates for a Location when considering the ID (hash value)
|
||||
// since the coordinates are the minimally correct ID for a location (symlinks should not come into play)
|
||||
VirtualPath string `hash:"ignore"` // The path to the file which may or may not have hardlinks / symlinks
|
||||
|
||||
@@ -55,7 +55,6 @@ func DetectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (
|
||||
}
|
||||
|
||||
// next: let's try more generic sources (dir, file, etc.)
|
||||
|
||||
location, err := homedir.Expand(userInput)
|
||||
if err != nil {
|
||||
return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand potential directory path: %w", err)
|
||||
@@ -69,5 +68,6 @@ func DetectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (
|
||||
if fileMeta.IsDir() {
|
||||
return DirectoryScheme, source, location, nil
|
||||
}
|
||||
|
||||
return FileScheme, source, location, nil
|
||||
}
|
||||
|
||||
+81
-27
@@ -33,28 +33,80 @@ type Source struct {
|
||||
Exclusions []string
|
||||
}
|
||||
|
||||
type sourceDetector func(string) (image.Source, string, error)
|
||||
// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
|
||||
// It acts as a struct input for some source constructors.
|
||||
type Input struct {
|
||||
UserInput string
|
||||
Scheme Scheme
|
||||
ImageSource image.Source
|
||||
Location string
|
||||
autoDetectAvailableImageSources bool
|
||||
}
|
||||
|
||||
// New produces a Source based on userInput like dir: or image:tag
|
||||
func New(userInput string, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
|
||||
// ParseInput generates a source Input that can be used as an argument to generate a new source
|
||||
// from specific providers including a registry.
|
||||
func ParseInput(userInput string, detectAvailableImageSources bool) (*Input, error) {
|
||||
fs := afero.NewOsFs()
|
||||
parsedScheme, imageSource, location, err := DetectScheme(fs, image.DetectSource, userInput)
|
||||
scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
|
||||
if err != nil {
|
||||
return &Source{}, func() {}, fmt.Errorf("unable to parse input=%q: %w", userInput, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source := &Source{}
|
||||
if source == image.UnknownSource {
|
||||
// only run for these two scheme
|
||||
// only check on packages command, attest we automatically try to pull from userInput
|
||||
switch scheme {
|
||||
case ImageScheme, UnknownScheme:
|
||||
if detectAvailableImageSources {
|
||||
if imagePullSource := image.DetermineDefaultImagePullSource(userInput); imagePullSource != image.UnknownSource {
|
||||
scheme = ImageScheme
|
||||
source = imagePullSource
|
||||
location = userInput
|
||||
}
|
||||
}
|
||||
if location == "" {
|
||||
location = userInput
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// collect user input for downstream consumption
|
||||
return &Input{
|
||||
UserInput: userInput,
|
||||
Scheme: scheme,
|
||||
ImageSource: source,
|
||||
Location: location,
|
||||
autoDetectAvailableImageSources: detectAvailableImageSources,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type sourceDetector func(string) (image.Source, string, error)
|
||||
|
||||
func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
|
||||
source, cleanupFn, err := generateImageSource(in, registryOptions)
|
||||
if source != nil {
|
||||
source.Exclusions = exclusions
|
||||
}
|
||||
return source, cleanupFn, err
|
||||
}
|
||||
|
||||
// New produces a Source based on userInput like dir: or image:tag
|
||||
func New(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
|
||||
var err error
|
||||
fs := afero.NewOsFs()
|
||||
var source *Source
|
||||
cleanupFn := func() {}
|
||||
|
||||
switch parsedScheme {
|
||||
switch in.Scheme {
|
||||
case FileScheme:
|
||||
source, cleanupFn, err = generateFileSource(fs, location)
|
||||
source, cleanupFn, err = generateFileSource(fs, in.Location)
|
||||
case DirectoryScheme:
|
||||
source, cleanupFn, err = generateDirectorySource(fs, location)
|
||||
source, cleanupFn, err = generateDirectorySource(fs, in.Location)
|
||||
case ImageScheme:
|
||||
source, cleanupFn, err = generateImageSource(userInput, location, imageSource, registryOptions)
|
||||
source, cleanupFn, err = generateImageSource(in, registryOptions)
|
||||
default:
|
||||
err = fmt.Errorf("unable to process input for scanning: '%s'", userInput)
|
||||
err = fmt.Errorf("unable to process input for scanning: %q", in.UserInput)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -64,15 +116,15 @@ func New(userInput string, registryOptions *image.RegistryOptions, exclusions []
|
||||
return source, cleanupFn, err
|
||||
}
|
||||
|
||||
func generateImageSource(userInput, location string, imageSource image.Source, registryOptions *image.RegistryOptions) (*Source, func(), error) {
|
||||
img, cleanup, err := getImageWithRetryStrategy(userInput, location, imageSource, registryOptions)
|
||||
func generateImageSource(in Input, registryOptions *image.RegistryOptions) (*Source, func(), error) {
|
||||
img, cleanup, err := getImageWithRetryStrategy(in, registryOptions)
|
||||
if err != nil || img == nil {
|
||||
return &Source{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", location, err)
|
||||
return nil, cleanup, fmt.Errorf("could not fetch image %q: %w", in.Location, err)
|
||||
}
|
||||
|
||||
s, err := NewFromImage(img, location)
|
||||
s, err := NewFromImage(img, in.Location)
|
||||
if err != nil {
|
||||
return &Source{}, cleanup, fmt.Errorf("could not populate source with image: %w", err)
|
||||
return nil, cleanup, fmt.Errorf("could not populate source with image: %w", err)
|
||||
}
|
||||
|
||||
return &s, cleanup, nil
|
||||
@@ -87,7 +139,7 @@ func parseScheme(userInput string) string {
|
||||
return parts[0]
|
||||
}
|
||||
|
||||
func getImageWithRetryStrategy(userInput, location string, imageSource image.Source, registryOptions *image.RegistryOptions) (*image.Image, func(), error) {
|
||||
func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions) (*image.Image, func(), error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
var opts []stereoscope.Option
|
||||
@@ -95,13 +147,13 @@ func getImageWithRetryStrategy(userInput, location string, imageSource image.Sou
|
||||
opts = append(opts, stereoscope.WithRegistryOptions(*registryOptions))
|
||||
}
|
||||
|
||||
img, err := stereoscope.GetImageFromSource(ctx, location, imageSource, opts...)
|
||||
img, err := stereoscope.GetImageFromSource(ctx, in.Location, in.ImageSource, opts...)
|
||||
if err == nil {
|
||||
// Success on the first try!
|
||||
return img, stereoscope.Cleanup, nil
|
||||
}
|
||||
|
||||
scheme := parseScheme(userInput)
|
||||
scheme := parseScheme(in.UserInput)
|
||||
if !(scheme == "docker" || scheme == "registry") {
|
||||
// Image retrieval failed, and we shouldn't retry it. It's most likely that the
|
||||
// user _did_ intend the parsed scheme, but there was a legitimate failure with
|
||||
@@ -118,14 +170,16 @@ func getImageWithRetryStrategy(userInput, location string, imageSource image.Sou
|
||||
"scheme %q specified, but it coincides with a common image name; re-examining user input %q"+
|
||||
" without scheme parsing because image retrieval using scheme parsing was unsuccessful: %v",
|
||||
scheme,
|
||||
userInput,
|
||||
in.UserInput,
|
||||
err,
|
||||
)
|
||||
|
||||
// We need to determine the image source again, such that this determination
|
||||
// doesn't take scheme parsing into account.
|
||||
imageSource = image.DetermineImagePullSource(userInput)
|
||||
img, err = stereoscope.GetImageFromSource(ctx, userInput, imageSource, opts...)
|
||||
if in.autoDetectAvailableImageSources {
|
||||
in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput)
|
||||
}
|
||||
img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -136,16 +190,16 @@ func getImageWithRetryStrategy(userInput, location string, imageSource image.Sou
|
||||
func generateDirectorySource(fs afero.Fs, location string) (*Source, func(), error) {
|
||||
fileMeta, err := fs.Stat(location)
|
||||
if err != nil {
|
||||
return &Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err)
|
||||
return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err)
|
||||
}
|
||||
|
||||
if !fileMeta.IsDir() {
|
||||
return &Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err)
|
||||
return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err)
|
||||
}
|
||||
|
||||
s, err := NewFromDirectory(location)
|
||||
if err != nil {
|
||||
return &Source{}, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err)
|
||||
return nil, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err)
|
||||
}
|
||||
|
||||
return &s, func() {}, nil
|
||||
@@ -154,11 +208,11 @@ func generateDirectorySource(fs afero.Fs, location string) (*Source, func(), err
|
||||
func generateFileSource(fs afero.Fs, location string) (*Source, func(), error) {
|
||||
fileMeta, err := fs.Stat(location)
|
||||
if err != nil {
|
||||
return &Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err)
|
||||
return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err)
|
||||
}
|
||||
|
||||
if fileMeta.IsDir() {
|
||||
return &Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err)
|
||||
return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err)
|
||||
}
|
||||
|
||||
s, cleanupFn := NewFromFile(location)
|
||||
|
||||
@@ -22,6 +22,30 @@ import (
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
)
|
||||
|
||||
func TestParseInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected Scheme
|
||||
}{
|
||||
{
|
||||
name: "ParseInput parses a file input",
|
||||
input: "test-fixtures/image-simple/file-1.txt",
|
||||
expected: FileScheme,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sourceInput, err := ParseInput(test.input, true)
|
||||
if err != nil {
|
||||
t.Errorf("failed to ParseInput")
|
||||
}
|
||||
assert.Equal(t, sourceInput.Scheme, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFromImageFails(t *testing.T) {
|
||||
t.Run("no image given", func(t *testing.T) {
|
||||
_, err := NewFromImage(nil, "")
|
||||
@@ -428,7 +452,9 @@ func TestDirectoryExclusions(t *testing.T) {
|
||||
registryOpts := &image.RegistryOptions{}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
src, fn, err := New("dir:"+test.input, registryOpts, test.exclusions)
|
||||
sourceInput, err := ParseInput("dir:"+test.input, false)
|
||||
require.NoError(t, err)
|
||||
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
||||
defer fn()
|
||||
|
||||
if test.err {
|
||||
@@ -520,7 +546,9 @@ func TestImageExclusions(t *testing.T) {
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input)
|
||||
src, fn, err := New(archiveLocation, registryOpts, test.exclusions)
|
||||
sourceInput, err := ParseInput(archiveLocation, false)
|
||||
require.NoError(t, err)
|
||||
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
||||
defer fn()
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestAttestCmd(t *testing.T) {
|
||||
coverageImage := "docker-archive:" + getFixtureImage(t, "image-pkg-coverage")
|
||||
img := "registry:busybox:latest"
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -26,19 +26,17 @@ func TestAttestCmd(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "can encode syft.json as the predicate given a password",
|
||||
args: []string{"attest", "-o", "json", coverageImage},
|
||||
args: []string{"attest", "-o", "json", img},
|
||||
assertions: []traitAssertion{
|
||||
assertSuccessfulReturnCode,
|
||||
// assertVerifyAttestation(coverageImage), Follow up on this assertion with verify blog or ephemperal registry
|
||||
},
|
||||
pw: "test",
|
||||
},
|
||||
{
|
||||
name: "can encode syft.json as the predicate given a blank password",
|
||||
args: []string{"attest", "-o", "json", coverageImage},
|
||||
args: []string{"attest", "-o", "json", img},
|
||||
assertions: []traitAssertion{
|
||||
assertSuccessfulReturnCode,
|
||||
// assertVerifyAttestation(coverageImage), Follow up on this assertion with verify blog or ephemperal registry
|
||||
},
|
||||
pw: "",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func runAndShow(t *testing.T, cmd *exec.Cmd) {
|
||||
t.Helper()
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
require.NoErrorf(t, err, "could not get stderr: +v", err)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
require.NoErrorf(t, err, "could not get stdout: +v", err)
|
||||
|
||||
err = cmd.Start()
|
||||
require.NoErrorf(t, err, "failed to start cmd: %+v", err)
|
||||
|
||||
show := func(label string, reader io.ReadCloser) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
t.Logf("%s: %s", label, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
show("out", stdout)
|
||||
show("err", stderr)
|
||||
}
|
||||
|
||||
func TestCosignWorkflow(t *testing.T) {
|
||||
// found under test-fixtures/registry/Makefile
|
||||
img := "localhost:5000/attest:latest"
|
||||
attestationFile := "attestation.json"
|
||||
tests := []struct {
|
||||
name string
|
||||
syftArgs []string
|
||||
cosignAttachArgs []string
|
||||
cosignVerifyArgs []string
|
||||
env map[string]string
|
||||
assertions []traitAssertion
|
||||
setup func(*testing.T)
|
||||
cleanup func()
|
||||
}{
|
||||
{
|
||||
name: "cosign verify syft attest",
|
||||
syftArgs: []string{
|
||||
"attest",
|
||||
"-o",
|
||||
"json",
|
||||
img,
|
||||
},
|
||||
// cosign attach attestation --attestation image_latest_sbom_attestation.json caphill4/attest:latest
|
||||
cosignAttachArgs: []string{
|
||||
"attach",
|
||||
"attestation",
|
||||
"--attestation",
|
||||
attestationFile,
|
||||
img,
|
||||
},
|
||||
// cosign verify-attestation -key cosign.pub caphill4/attest:latest
|
||||
cosignVerifyArgs: []string{
|
||||
"verify-attestation",
|
||||
"-key",
|
||||
"cosign.pub",
|
||||
img,
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
setup: func(t *testing.T) {
|
||||
cwd, err := os.Getwd()
|
||||
require.NoErrorf(t, err, "unable to get cwd: %+v", err)
|
||||
|
||||
// get working directory for local registry
|
||||
fixturesPath := filepath.Join(cwd, "test-fixtures", "registry")
|
||||
makeTask := filepath.Join(fixturesPath, "Makefile")
|
||||
t.Logf("Generating Fixture from 'make %s'", makeTask)
|
||||
|
||||
cmd := exec.Command("make")
|
||||
cmd.Dir = fixturesPath
|
||||
runAndShow(t, cmd)
|
||||
|
||||
var done = make(chan struct{})
|
||||
defer close(done)
|
||||
for interval := range testRetryIntervals(done) {
|
||||
resp, err := http.Get("http://127.0.0.1:5000/v2/")
|
||||
if err != nil {
|
||||
t.Logf("waiting for registry err=%+v", err)
|
||||
} else {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
break
|
||||
}
|
||||
t.Logf("waiting for registry code=%+v", resp.StatusCode)
|
||||
}
|
||||
|
||||
time.Sleep(interval)
|
||||
}
|
||||
|
||||
cmd = exec.Command("make", "push")
|
||||
cmd.Dir = fixturesPath
|
||||
runAndShow(t, cmd)
|
||||
|
||||
},
|
||||
cleanup: func() {
|
||||
cwd, err := os.Getwd()
|
||||
assert.NoErrorf(t, err, "unable to get cwd: %+v", err)
|
||||
|
||||
fixturesPath := filepath.Join(cwd, "test-fixtures", "registry")
|
||||
makeTask := filepath.Join(fixturesPath, "Makefile")
|
||||
t.Logf("Generating Fixture from 'make %s'", makeTask)
|
||||
|
||||
// delete attestation file
|
||||
os.Remove(attestationFile)
|
||||
|
||||
cmd := exec.Command("make", "stop")
|
||||
cmd.Dir = fixturesPath
|
||||
|
||||
runAndShow(t, cmd)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Cleanup(tt.cleanup)
|
||||
tt.setup(t)
|
||||
pkiCleanup := setupPKI(t, "") // blank password
|
||||
defer pkiCleanup()
|
||||
|
||||
// attest
|
||||
cmd, stdout, stderr := runSyft(t, tt.env, tt.syftArgs...)
|
||||
for _, traitFn := range tt.assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
checkCmdFailure(t, stdout, stderr, cmd)
|
||||
require.NoError(t, os.WriteFile(attestationFile, []byte(stdout), 0666))
|
||||
|
||||
// attach
|
||||
cmd, stdout, stderr = runCosign(t, tt.env, tt.cosignAttachArgs...)
|
||||
for _, traitFn := range tt.assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
checkCmdFailure(t, stdout, stderr, cmd)
|
||||
|
||||
// attest
|
||||
cmd, stdout, stderr = runCosign(t, tt.env, tt.cosignAttachArgs...)
|
||||
for _, traitFn := range tt.assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
checkCmdFailure(t, stdout, stderr, cmd)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkCmdFailure(t testing.TB, stdout, stderr string, cmd *exec.Cmd) {
|
||||
require.Falsef(t, t.Failed(), "%s %s trait assertion failed", cmd.Path, strings.Join(cmd.Args, " "))
|
||||
if t.Failed() {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
**/*
|
||||
@@ -0,0 +1 @@
|
||||
FROM alpine:latest
|
||||
@@ -0,0 +1,23 @@
|
||||
all: build start
|
||||
|
||||
.PHONY: stop
|
||||
stop:
|
||||
docker kill registry
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
docker build -t localhost:5000/attest:latest .
|
||||
|
||||
.PHONY: start
|
||||
start:
|
||||
docker run --rm \
|
||||
-d \
|
||||
--name registry \
|
||||
-it \
|
||||
--privileged \
|
||||
-p 5000:5000 \
|
||||
registry:2.8
|
||||
|
||||
.PHONY: push
|
||||
push:
|
||||
docker push localhost:5000/attest:latest
|
||||
@@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
)
|
||||
@@ -95,6 +97,20 @@ func runSyft(t testing.TB, env map[string]string, args ...string) (*exec.Cmd, st
|
||||
return cmd, stdout, stderr
|
||||
}
|
||||
|
||||
func runCosign(t testing.TB, env map[string]string, args ...string) (*exec.Cmd, string, string) {
|
||||
cmd := getCosignCommand(t, args...)
|
||||
if env == nil {
|
||||
env = make(map[string]string)
|
||||
}
|
||||
|
||||
stdout, stderr := runCommand(cmd, env)
|
||||
return cmd, stdout, stderr
|
||||
}
|
||||
|
||||
func getCosignCommand(t testing.TB, args ...string) *exec.Cmd {
|
||||
return exec.Command(filepath.Join(repoRoot(t), ".tmp/cosign"), args...)
|
||||
}
|
||||
|
||||
func runCommand(cmd *exec.Cmd, env map[string]string) (string, string) {
|
||||
if env != nil {
|
||||
cmd.Env = append(os.Environ(), envMapToSlice(env)...)
|
||||
@@ -154,3 +170,40 @@ func repoRoot(t testing.TB) string {
|
||||
}
|
||||
return absRepoRoot
|
||||
}
|
||||
|
||||
func testRetryIntervals(done <-chan struct{}) <-chan time.Duration {
|
||||
return exponentialBackoffDurations(250*time.Millisecond, 4*time.Second, 2, done)
|
||||
}
|
||||
|
||||
func exponentialBackoffDurations(minDuration, maxDuration time.Duration, step float64, done <-chan struct{}) <-chan time.Duration {
|
||||
sleepDurations := make(chan time.Duration)
|
||||
go func() {
|
||||
defer close(sleepDurations)
|
||||
retryLoop:
|
||||
for attempt := 0; ; attempt++ {
|
||||
duration := exponentialBackoffDuration(minDuration, maxDuration, step, attempt)
|
||||
|
||||
select {
|
||||
case sleepDurations <- duration:
|
||||
break
|
||||
case <-done:
|
||||
break retryLoop
|
||||
}
|
||||
|
||||
if duration == maxDuration {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
return sleepDurations
|
||||
}
|
||||
|
||||
func exponentialBackoffDuration(minDuration, maxDuration time.Duration, step float64, attempt int) time.Duration {
|
||||
duration := time.Duration(float64(minDuration) * math.Pow(step, float64(attempt)))
|
||||
if duration < minDuration {
|
||||
return minDuration
|
||||
} else if duration > maxDuration {
|
||||
return maxDuration
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
@@ -5,6 +5,15 @@ TEST_INSTALL_SH=true
|
||||
. ../../install.sh
|
||||
set -u
|
||||
|
||||
echoerr() {
|
||||
echo "$@" 1>&2
|
||||
}
|
||||
|
||||
printferr() {
|
||||
printf "%s" "$*" >&2
|
||||
}
|
||||
|
||||
|
||||
assertTrue() {
|
||||
if eval "$1"; then
|
||||
echo "assertTrue failed: $2"
|
||||
@@ -93,16 +102,34 @@ setup_snapshot_server() {
|
||||
python3 -m http.server --directory "$(snapshot_dir)" $serve_port &> /dev/null &
|
||||
worker_pid=$!
|
||||
|
||||
# it takes some time for the server to be ready...
|
||||
sleep 3
|
||||
echoerr "serving up $(snapshot_dir) on port $serve_port"
|
||||
|
||||
echoerr "$(ls -1 $(snapshot_dir) | sed 's/^/ ▕―― /')"
|
||||
|
||||
check_snapshots_server_ready
|
||||
|
||||
echoerr "snapshot server ready! (worker=${worker_pid})"
|
||||
|
||||
echo "$worker_pid"
|
||||
}
|
||||
|
||||
check_snapshots_server_ready() {
|
||||
i=0
|
||||
until $(curl -m 3 --output /dev/null --silent --head --fail localhost:$serve_port/); do
|
||||
sleep 1
|
||||
((i=i+1))
|
||||
if [ "$i" -gt "30" ]; then
|
||||
echoerr "could not connect to local snapshot server! bailing..."
|
||||
exit 1
|
||||
fi
|
||||
printferr '.'
|
||||
done
|
||||
}
|
||||
|
||||
teardown_snapshot_server() {
|
||||
worker_pid="$1"
|
||||
|
||||
kill $worker_pid
|
||||
echoerr "stopping worker=${worker_pid}"
|
||||
kill "$worker_pid"
|
||||
}
|
||||
|
||||
snapshot_version() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
@@ -22,7 +23,10 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
||||
var pc *pkg.Catalog
|
||||
for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) {
|
||||
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
|
||||
theSource, cleanupSource, err := source.New("docker-archive:"+tarPath, nil, nil)
|
||||
userInput := "docker-archive:" + tarPath
|
||||
sourceInput, err := source.ParseInput(userInput, false)
|
||||
require.NoError(b, err)
|
||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||
b.Cleanup(cleanupSource)
|
||||
if err != nil {
|
||||
b.Fatalf("unable to get source: %+v", err)
|
||||
|
||||
@@ -23,21 +23,24 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
tests := []struct {
|
||||
format format.Option
|
||||
redactor func(in []byte) []byte
|
||||
json bool
|
||||
}{
|
||||
{
|
||||
format: format.JSONOption,
|
||||
json: true,
|
||||
},
|
||||
{
|
||||
format: format.CycloneDxJSONOption,
|
||||
redactor: func(in []byte) []byte {
|
||||
in = regexp.MustCompile("\"(timestamp|serialNumber|bom-ref)\": \"[^\"]+").ReplaceAll(in, []byte{})
|
||||
in = regexp.MustCompile("\"(timestamp|serialNumber|bom-ref)\": \"[^\"]+\",").ReplaceAll(in, []byte{})
|
||||
return in
|
||||
},
|
||||
json: true,
|
||||
},
|
||||
{
|
||||
format: format.CycloneDxXMLOption,
|
||||
redactor: func(in []byte) []byte {
|
||||
in = regexp.MustCompile("(serialNumber|bom-ref)=\"[^\"]+").ReplaceAll(in, []byte{})
|
||||
in = regexp.MustCompile("(serialNumber|bom-ref)=\"[^\"]+\"").ReplaceAll(in, []byte{})
|
||||
in = regexp.MustCompile("<timestamp>[^<]+</timestamp>").ReplaceAll(in, []byte{})
|
||||
return in
|
||||
},
|
||||
@@ -63,10 +66,16 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
by2 = test.redactor(by2)
|
||||
}
|
||||
|
||||
if !assert.True(t, bytes.Equal(by1, by2)) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(by1), string(by2), true)
|
||||
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
|
||||
if test.json {
|
||||
s1 := string(by1)
|
||||
s2 := string(by2)
|
||||
assert.JSONEq(t, s1, s2)
|
||||
} else {
|
||||
if !assert.True(t, bytes.Equal(by1, by2)) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(by1), string(by2), true)
|
||||
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
@@ -15,12 +16,12 @@ import (
|
||||
func catalogFixtureImage(t *testing.T, fixtureImageName string) (sbom.SBOM, *source.Source) {
|
||||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
|
||||
theSource, cleanupSource, err := source.New("docker-archive:"+tarPath, nil, nil)
|
||||
userInput := "docker-archive:" + tarPath
|
||||
sourceInput, err := source.ParseInput(userInput, false)
|
||||
require.NoError(t, err)
|
||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||
t.Cleanup(cleanupSource)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get source: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: this would be better with functional options (after/during API refactor)
|
||||
c := cataloger.DefaultConfig()
|
||||
@@ -50,11 +51,12 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (sbom.SBOM, *sou
|
||||
}
|
||||
|
||||
func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) {
|
||||
theSource, cleanupSource, err := source.New("dir:"+dir, nil, nil)
|
||||
userInput := "dir:" + dir
|
||||
sourceInput, err := source.ParseInput(userInput, false)
|
||||
require.NoError(t, err)
|
||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||
t.Cleanup(cleanupSource)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get source: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: this would be better with functional options (after/during API refactor)
|
||||
c := cataloger.DefaultConfig()
|
||||
|
||||
Reference in New Issue
Block a user