mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 10:36:45 +01:00
Add ability to pull images directly from a registry (#378)
* add registry image source Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * use explicit source for fetching image + add scheme and registry tests Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * adjust test variable name and add credential helper function Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
c02ab88d5f
commit
c363b2b532
30
README.md
30
README.md
@ -45,6 +45,16 @@ syft packages path/to/image.tar
|
|||||||
syft packages path/to/dir
|
syft packages path/to/dir
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Sources can be explicitly provided with a scheme:
|
||||||
|
```
|
||||||
|
docker:yourrepo/yourimage:tag use images from the Docker 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)
|
||||||
|
dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||||
|
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||||
|
```
|
||||||
|
|
||||||
The output format for Syft is configurable as well:
|
The output format for Syft is configurable as well:
|
||||||
```
|
```
|
||||||
syft packages <image> -o <format>
|
syft packages <image> -o <format>
|
||||||
@ -184,6 +194,26 @@ secrets:
|
|||||||
# SYFT_SECRETS_EXCLUDE_PATTERN_NAMES env var
|
# SYFT_SECRETS_EXCLUDE_PATTERN_NAMES env var
|
||||||
exclude-pattern-names: []
|
exclude-pattern-names: []
|
||||||
|
|
||||||
|
# options when pulling directly from a registry via the "registry:" scheme
|
||||||
|
registry:
|
||||||
|
# skip TLS verification when communicating with the registry
|
||||||
|
# SYFT_REGISTRY_INSECURE_SKIP_TLS_VERIFY env var
|
||||||
|
insecure-skip-tls-verify: false
|
||||||
|
|
||||||
|
# credentials for specific registries
|
||||||
|
auth:
|
||||||
|
- # the URL to the registry (e.g. "docker.io", "localhost:5000", etc.)
|
||||||
|
# SYFT_REGISTRY_AUTH_AUTHORITY env var
|
||||||
|
authority: ""
|
||||||
|
# SYFT_REGISTRY_AUTH_USERNAME env var
|
||||||
|
username: ""
|
||||||
|
# SYFT_REGISTRY_AUTH_PASSWORD env var
|
||||||
|
password: ""
|
||||||
|
# note: token and username/password are mutually exclusive
|
||||||
|
# SYFT_REGISTRY_AUTH_TOKEN env var
|
||||||
|
token: ""
|
||||||
|
- ... # note, more credentials can be provided via config file only
|
||||||
|
|
||||||
log:
|
log:
|
||||||
# use structured logging
|
# use structured logging
|
||||||
# same as SYFT_LOG_STRUCTURED env var
|
# same as SYFT_LOG_STRUCTURED env var
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const (
|
|||||||
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
|
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
|
||||||
|
|
||||||
Supports the following image sources:
|
Supports the following image sources:
|
||||||
{{.appName}} {{.command}} yourrepo/yourimage:tag defaults to using images from a Docker daemon
|
{{.appName}} {{.command}} yourrepo/yourimage:tag defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry.
|
||||||
{{.appName}} {{.command}} path/to/a/file/or/dir a Docker tar, OCI tar, OCI directory, or generic filesystem directory
|
{{.appName}} {{.command}} path/to/a/file/or/dir a Docker tar, OCI tar, OCI directory, or generic filesystem directory
|
||||||
|
|
||||||
You can also explicitly specify the scheme to use:
|
You can also explicitly specify the scheme to use:
|
||||||
@ -41,6 +41,7 @@ const (
|
|||||||
{{.appName}} {{.command}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
{{.appName}} {{.command}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||||
{{.appName}} {{.command}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
{{.appName}} {{.command}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||||
{{.appName}} {{.command}} dir:path/to/yourproject read directly from a path on disk (any directory)
|
{{.appName}} {{.command}} dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||||
|
{{.appName}} {{.command}} registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -187,7 +188,7 @@ func packagesExecWorker(userInput string) <-chan error {
|
|||||||
|
|
||||||
checkForApplicationUpdate()
|
checkForApplicationUpdate()
|
||||||
|
|
||||||
src, cleanup, err := source.New(userInput)
|
src, cleanup, err := source.New(userInput, appConfig.Registry.ToOptions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs <- fmt.Errorf("failed to determine image source: %+v", err)
|
errs <- fmt.Errorf("failed to determine image source: %+v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -81,7 +81,7 @@ func powerUserExecWorker(userInput string) <-chan error {
|
|||||||
|
|
||||||
checkForApplicationUpdate()
|
checkForApplicationUpdate()
|
||||||
|
|
||||||
src, cleanup, err := source.New(userInput)
|
src, cleanup, err := source.New(userInput, appConfig.Registry.ToOptions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs <- err
|
errs <- err
|
||||||
return
|
return
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -10,7 +10,7 @@ require (
|
|||||||
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12
|
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12
|
||||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||||
github.com/anchore/stereoscope v0.0.0-20210405181843-73d71fd93233
|
github.com/anchore/stereoscope v0.0.0-20210412194439-0b9e0281ef0c
|
||||||
github.com/antihax/optional v1.0.0
|
github.com/antihax/optional v1.0.0
|
||||||
github.com/bmatcuk/doublestar/v2 v2.0.4
|
github.com/bmatcuk/doublestar/v2 v2.0.4
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
||||||
|
|||||||
6
go.sum
6
go.sum
@ -115,10 +115,8 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v
|
|||||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||||
github.com/anchore/stereoscope v0.0.0-20210323182342-47b72675ff65 h1:r3tiir6UCgj/YeTqy4s2bfhZ9SuJYNlXx1Z9e/eLrbI=
|
github.com/anchore/stereoscope v0.0.0-20210412194439-0b9e0281ef0c h1:iAkv8iBnbHQzcROt55IbEh7r7qUJxj64E8bM4EnaBlA=
|
||||||
github.com/anchore/stereoscope v0.0.0-20210323182342-47b72675ff65/go.mod h1:G7tFR0iI9r6AvibmXKA9v010pRS1IIJgd0t6fOMDxCw=
|
github.com/anchore/stereoscope v0.0.0-20210412194439-0b9e0281ef0c/go.mod h1:vhh1M99rfWx5ejMvz1lkQiFZUrC5wu32V12R4JXH+ZI=
|
||||||
github.com/anchore/stereoscope v0.0.0-20210405181843-73d71fd93233 h1:XkoyUFdQGYT2tb7SH2YBsouw/9q1kZTgXVy52PzM4JE=
|
|
||||||
github.com/anchore/stereoscope v0.0.0-20210405181843-73d71fd93233/go.mod h1:G7tFR0iI9r6AvibmXKA9v010pRS1IIJgd0t6fOMDxCw=
|
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
|
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
|||||||
@ -40,6 +40,7 @@ type Application struct {
|
|||||||
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
||||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||||
|
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions) *Application {
|
func newApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions) *Application {
|
||||||
|
|||||||
72
internal/config/registry.go
Normal file
72
internal/config/registry.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegistryCredentials struct {
|
||||||
|
Authority string `yaml:"authority" json:"authority" mapstructure:"authority"`
|
||||||
|
// IMPORTANT: do not show the username in any YAML/JSON output (sensitive information)
|
||||||
|
Username string `yaml:"-" json:"-" mapstructure:"username"`
|
||||||
|
// IMPORTANT: do not show the password in any YAML/JSON output (sensitive information)
|
||||||
|
Password string `yaml:"-" json:"-" mapstructure:"password"`
|
||||||
|
// IMPORTANT: do not show the token in any YAML/JSON output (sensitive information)
|
||||||
|
Token string `yaml:"-" json:"-" mapstructure:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type registry struct {
|
||||||
|
InsecureSkipTLSVerify bool `yaml:"insecure-skip-tls-verify" json:"insecure-skip-tls-verify" mapstructure:"insecure-skip-tls-verify"`
|
||||||
|
Auth []RegistryCredentials `yaml:"auth" json:"auth" mapstructure:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg registry) loadDefaultValues(v *viper.Viper) {
|
||||||
|
v.SetDefault("registry.insecure-skip-tls-verify", false)
|
||||||
|
v.SetDefault("registry.auth", []RegistryCredentials{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: unparam
|
||||||
|
func (cfg *registry) parseConfigValues() error {
|
||||||
|
// there may be additional credentials provided by env var that should be appended to the set of credentials
|
||||||
|
authority, username, password, token :=
|
||||||
|
os.Getenv("SYFT_REGISTRY_AUTH_AUTHORITY"),
|
||||||
|
os.Getenv("SYFT_REGISTRY_AUTH_USERNAME"),
|
||||||
|
os.Getenv("SYFT_REGISTRY_AUTH_PASSWORD"),
|
||||||
|
os.Getenv("SYFT_REGISTRY_AUTH_TOKEN")
|
||||||
|
|
||||||
|
if hasNonEmptyCredentials(authority, password, token) {
|
||||||
|
// note: we prepend the credentials such that the environment variables take precedence over on-disk configuration.
|
||||||
|
cfg.Auth = append([]RegistryCredentials{
|
||||||
|
{
|
||||||
|
Authority: authority,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Token: token,
|
||||||
|
},
|
||||||
|
}, cfg.Auth...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasNonEmptyCredentials(authority, password, token string) bool {
|
||||||
|
return authority != "" && password != "" || authority != "" && token != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *registry) ToOptions() *image.RegistryOptions {
|
||||||
|
var auth = make([]image.RegistryCredentials, len(cfg.Auth))
|
||||||
|
for i, a := range cfg.Auth {
|
||||||
|
auth[i] = image.RegistryCredentials{
|
||||||
|
Authority: a.Authority,
|
||||||
|
Username: a.Username,
|
||||||
|
Password: a.Password,
|
||||||
|
Token: a.Token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &image.RegistryOptions{
|
||||||
|
InsecureSkipTLSVerify: cfg.InsecureSkipTLSVerify,
|
||||||
|
Credentials: auth,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -223,6 +223,10 @@
|
|||||||
"exclude-pattern-names": null,
|
"exclude-pattern-names": null,
|
||||||
"reveal-values": false,
|
"reveal-values": false,
|
||||||
"skip-files-above-size": 0
|
"skip-files-above-size": 0
|
||||||
|
},
|
||||||
|
"registry": {
|
||||||
|
"insecure-skip-tls-verify": false,
|
||||||
|
"auth": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -21,39 +21,39 @@ const (
|
|||||||
ImageScheme Scheme = "ImageScheme"
|
ImageScheme Scheme = "ImageScheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
func detectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (Scheme, string, error) {
|
func detectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (Scheme, image.Source, string, error) {
|
||||||
if strings.HasPrefix(userInput, "dir:") {
|
if strings.HasPrefix(userInput, "dir:") {
|
||||||
// blindly trust the user's scheme
|
// blindly trust the user's scheme
|
||||||
dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
|
dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownScheme, "", fmt.Errorf("unable to expand directory path: %w", err)
|
return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand directory path: %w", err)
|
||||||
}
|
}
|
||||||
return DirectoryScheme, dirLocation, nil
|
return DirectoryScheme, image.UnknownSource, dirLocation, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should attempt to let stereoscope determine what the source is first --just because the source is a valid directory
|
// we should attempt to let stereoscope determine what the source is first --but, just because the source is a valid directory
|
||||||
// doesn't mean we yet know if it is an OCI layout directory (to be treated as an image) or if it is a generic filesystem directory.
|
// doesn't mean we yet know if it is an OCI layout directory (to be treated as an image) or if it is a generic filesystem directory.
|
||||||
source, imageSpec, err := imageDetector(userInput)
|
source, imageSpec, err := imageDetector(userInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownScheme, "", fmt.Errorf("unable to detect the scheme from %q: %w", userInput, err)
|
return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to detect the scheme from %q: %w", userInput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if source == image.UnknownSource {
|
if source == image.UnknownSource {
|
||||||
dirLocation, err := homedir.Expand(userInput)
|
dirLocation, err := homedir.Expand(userInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownScheme, "", fmt.Errorf("unable to expand potential directory path: %w", err)
|
return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand potential directory path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileMeta, err := fs.Stat(dirLocation)
|
fileMeta, err := fs.Stat(dirLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownScheme, "", nil
|
return UnknownScheme, source, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileMeta.IsDir() {
|
if fileMeta.IsDir() {
|
||||||
return DirectoryScheme, dirLocation, nil
|
return DirectoryScheme, source, dirLocation, nil
|
||||||
}
|
}
|
||||||
return UnknownScheme, "", nil
|
return UnknownScheme, source, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImageScheme, imageSpec, nil
|
return ImageScheme, source, imageSpec, nil
|
||||||
}
|
}
|
||||||
|
|||||||
263
syft/source/scheme_test.go
Normal file
263
syft/source/scheme_test.go
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
package source
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetectScheme(t *testing.T) {
|
||||||
|
type detectorResult struct {
|
||||||
|
src image.Source
|
||||||
|
ref string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
userInput string
|
||||||
|
dirs []string
|
||||||
|
detection detectorResult
|
||||||
|
expectedScheme Scheme
|
||||||
|
expectedLocation string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "docker-image-ref",
|
||||||
|
userInput: "wagoodman/dive:latest",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.DockerDaemonSource,
|
||||||
|
ref: "wagoodman/dive:latest",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "wagoodman/dive:latest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docker-image-ref-no-tag",
|
||||||
|
userInput: "wagoodman/dive",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.DockerDaemonSource,
|
||||||
|
ref: "wagoodman/dive",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "wagoodman/dive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry-image-explicit-scheme",
|
||||||
|
userInput: "registry:wagoodman/dive:latest",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.OciRegistrySource,
|
||||||
|
ref: "wagoodman/dive:latest",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "wagoodman/dive:latest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docker-image-explicit-scheme",
|
||||||
|
userInput: "docker:wagoodman/dive:latest",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.DockerDaemonSource,
|
||||||
|
ref: "wagoodman/dive:latest",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "wagoodman/dive:latest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docker-image-explicit-scheme-no-tag",
|
||||||
|
userInput: "docker:wagoodman/dive",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.DockerDaemonSource,
|
||||||
|
ref: "wagoodman/dive",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "wagoodman/dive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docker-image-edge-case",
|
||||||
|
userInput: "docker:latest",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.DockerDaemonSource,
|
||||||
|
ref: "latest",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
// we want to be able to handle this case better, however, I don't see a way to do this
|
||||||
|
// the user will need to provide more explicit input (docker:docker:latest)
|
||||||
|
expectedLocation: "latest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docker-image-edge-case-explicit",
|
||||||
|
userInput: "docker:docker:latest",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.DockerDaemonSource,
|
||||||
|
ref: "docker:latest",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
// we want to be able to handle this case better, however, I don't see a way to do this
|
||||||
|
// the user will need to provide more explicit input (docker:docker:latest)
|
||||||
|
expectedLocation: "docker:latest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "oci-tar",
|
||||||
|
userInput: "some/path-to-file",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.OciTarballSource,
|
||||||
|
ref: "some/path-to-file",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "some/path-to-file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "oci-dir",
|
||||||
|
userInput: "some/path-to-dir",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.OciDirectorySource,
|
||||||
|
ref: "some/path-to-dir",
|
||||||
|
},
|
||||||
|
dirs: []string{"some/path-to-dir"},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "some/path-to-dir",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "guess-dir",
|
||||||
|
userInput: "some/path-to-dir",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.UnknownSource,
|
||||||
|
ref: "",
|
||||||
|
},
|
||||||
|
dirs: []string{"some/path-to-dir"},
|
||||||
|
expectedScheme: DirectoryScheme,
|
||||||
|
expectedLocation: "some/path-to-dir",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "generic-dir-does-not-exist",
|
||||||
|
userInput: "some/path-to-dir",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.DockerDaemonSource,
|
||||||
|
ref: "some/path-to-dir",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "some/path-to-dir",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit-dir",
|
||||||
|
userInput: "dir:some/path-to-dir",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.UnknownSource,
|
||||||
|
ref: "",
|
||||||
|
},
|
||||||
|
dirs: []string{"some/path-to-dir"},
|
||||||
|
expectedScheme: DirectoryScheme,
|
||||||
|
expectedLocation: "some/path-to-dir",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit-current-dir",
|
||||||
|
userInput: "dir:.",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.UnknownSource,
|
||||||
|
ref: "",
|
||||||
|
},
|
||||||
|
expectedScheme: DirectoryScheme,
|
||||||
|
expectedLocation: ".",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "current-dir",
|
||||||
|
userInput: ".",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.UnknownSource,
|
||||||
|
ref: "",
|
||||||
|
},
|
||||||
|
expectedScheme: DirectoryScheme,
|
||||||
|
expectedLocation: ".",
|
||||||
|
},
|
||||||
|
// we should support tilde expansion
|
||||||
|
{
|
||||||
|
name: "tilde-expansion-image-implicit",
|
||||||
|
userInput: "~/some-path",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.OciDirectorySource,
|
||||||
|
ref: "~/some-path",
|
||||||
|
},
|
||||||
|
expectedScheme: ImageScheme,
|
||||||
|
expectedLocation: "~/some-path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tilde-expansion-dir-implicit",
|
||||||
|
userInput: "~/some-path",
|
||||||
|
detection: detectorResult{
|
||||||
|
src: image.UnknownSource,
|
||||||
|
ref: "",
|
||||||
|
},
|
||||||
|
dirs: []string{"~/some-path"},
|
||||||
|
expectedScheme: DirectoryScheme,
|
||||||
|
expectedLocation: "~/some-path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tilde-expansion-dir-explicit-exists",
|
||||||
|
userInput: "dir:~/some-path",
|
||||||
|
dirs: []string{"~/some-path"},
|
||||||
|
expectedScheme: DirectoryScheme,
|
||||||
|
expectedLocation: "~/some-path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tilde-expansion-dir-explicit-dne",
|
||||||
|
userInput: "dir:~/some-path",
|
||||||
|
expectedScheme: DirectoryScheme,
|
||||||
|
expectedLocation: "~/some-path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tilde-expansion-dir-implicit-dne",
|
||||||
|
userInput: "~/some-path",
|
||||||
|
expectedScheme: UnknownScheme,
|
||||||
|
expectedLocation: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
|
||||||
|
for _, p := range test.dirs {
|
||||||
|
expandedExpectedLocation, err := homedir.Expand(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to expand path=%q: %+v", p, err)
|
||||||
|
}
|
||||||
|
err = fs.Mkdir(expandedExpectedLocation, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create dummy tar: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imageDetector := func(string) (image.Source, string, error) {
|
||||||
|
// lean on the users real home directory value
|
||||||
|
switch test.detection.src {
|
||||||
|
case image.OciDirectorySource, image.DockerTarballSource, image.OciTarballSource:
|
||||||
|
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
||||||
|
}
|
||||||
|
return test.detection.src, expandedExpectedLocation, test.detection.err
|
||||||
|
default:
|
||||||
|
return test.detection.src, test.detection.ref, test.detection.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actualScheme, actualSource, actualLocation, err := detectScheme(fs, imageDetector, test.userInput)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected err : %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, test.detection.src, actualSource, "mismatched source")
|
||||||
|
assert.Equal(t, test.expectedScheme, actualScheme, "mismatched scheme")
|
||||||
|
|
||||||
|
// lean on the users real home directory value
|
||||||
|
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expandedExpectedLocation, actualLocation, "mismatched location")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,11 +8,9 @@ package source
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
|
|
||||||
"github.com/anchore/stereoscope"
|
"github.com/anchore/stereoscope"
|
||||||
|
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used
|
// Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used
|
||||||
@ -25,9 +23,9 @@ type Source struct {
|
|||||||
type sourceDetector func(string) (image.Source, string, error)
|
type sourceDetector func(string) (image.Source, string, error)
|
||||||
|
|
||||||
// New produces a Source based on userInput like dir: or image:tag
|
// New produces a Source based on userInput like dir: or image:tag
|
||||||
func New(userInput string) (Source, func(), error) {
|
func New(userInput string, registryOptions *image.RegistryOptions) (Source, func(), error) {
|
||||||
fs := afero.NewOsFs()
|
fs := afero.NewOsFs()
|
||||||
parsedScheme, location, err := detectScheme(fs, image.DetectSource, userInput)
|
parsedScheme, imageSource, location, err := detectScheme(fs, image.DetectSource, userInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Source{}, func() {}, fmt.Errorf("unable to parse input=%q: %w", userInput, err)
|
return Source{}, func() {}, fmt.Errorf("unable to parse input=%q: %w", userInput, err)
|
||||||
}
|
}
|
||||||
@ -50,7 +48,7 @@ func New(userInput string) (Source, func(), error) {
|
|||||||
return s, func() {}, nil
|
return s, func() {}, nil
|
||||||
|
|
||||||
case ImageScheme:
|
case ImageScheme:
|
||||||
img, err := stereoscope.GetImage(location)
|
img, err := stereoscope.GetImageFromSource(location, imageSource, registryOptions)
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
stereoscope.Cleanup()
|
stereoscope.Cleanup()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewFromImageFails(t *testing.T) {
|
func TestNewFromImageFails(t *testing.T) {
|
||||||
@ -173,248 +170,3 @@ func TestFilesByGlob(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDetectScheme(t *testing.T) {
|
|
||||||
type detectorResult struct {
|
|
||||||
src image.Source
|
|
||||||
ref string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
userInput string
|
|
||||||
dirs []string
|
|
||||||
detection detectorResult
|
|
||||||
expectedScheme Scheme
|
|
||||||
expectedLocation string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "docker-image-ref",
|
|
||||||
userInput: "wagoodman/dive:latest",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.DockerDaemonSource,
|
|
||||||
ref: "wagoodman/dive:latest",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "wagoodman/dive:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "docker-image-ref-no-tag",
|
|
||||||
userInput: "wagoodman/dive",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.DockerDaemonSource,
|
|
||||||
ref: "wagoodman/dive",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "wagoodman/dive",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "docker-image-explicit-scheme",
|
|
||||||
userInput: "docker:wagoodman/dive:latest",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.DockerDaemonSource,
|
|
||||||
ref: "wagoodman/dive:latest",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "wagoodman/dive:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "docker-image-explicit-scheme-no-tag",
|
|
||||||
userInput: "docker:wagoodman/dive",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.DockerDaemonSource,
|
|
||||||
ref: "wagoodman/dive",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "wagoodman/dive",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "docker-image-edge-case",
|
|
||||||
userInput: "docker:latest",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.DockerDaemonSource,
|
|
||||||
ref: "latest",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
// we want to be able to handle this case better, however, I don't see a way to do this
|
|
||||||
// the user will need to provide more explicit input (docker:docker:latest)
|
|
||||||
expectedLocation: "latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "docker-image-edge-case-explicit",
|
|
||||||
userInput: "docker:docker:latest",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.DockerDaemonSource,
|
|
||||||
ref: "docker:latest",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
// we want to be able to handle this case better, however, I don't see a way to do this
|
|
||||||
// the user will need to provide more explicit input (docker:docker:latest)
|
|
||||||
expectedLocation: "docker:latest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "oci-tar",
|
|
||||||
userInput: "some/path-to-file",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.OciTarballSource,
|
|
||||||
ref: "some/path-to-file",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "some/path-to-file",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "oci-dir",
|
|
||||||
userInput: "some/path-to-dir",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.OciDirectorySource,
|
|
||||||
ref: "some/path-to-dir",
|
|
||||||
},
|
|
||||||
dirs: []string{"some/path-to-dir"},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "some/path-to-dir",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "guess-dir",
|
|
||||||
userInput: "some/path-to-dir",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.UnknownSource,
|
|
||||||
ref: "",
|
|
||||||
},
|
|
||||||
dirs: []string{"some/path-to-dir"},
|
|
||||||
expectedScheme: DirectoryScheme,
|
|
||||||
expectedLocation: "some/path-to-dir",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "generic-dir-does-not-exist",
|
|
||||||
userInput: "some/path-to-dir",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.DockerDaemonSource,
|
|
||||||
ref: "some/path-to-dir",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "some/path-to-dir",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "explicit-dir",
|
|
||||||
userInput: "dir:some/path-to-dir",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.UnknownSource,
|
|
||||||
ref: "",
|
|
||||||
},
|
|
||||||
dirs: []string{"some/path-to-dir"},
|
|
||||||
expectedScheme: DirectoryScheme,
|
|
||||||
expectedLocation: "some/path-to-dir",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "explicit-current-dir",
|
|
||||||
userInput: "dir:.",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.UnknownSource,
|
|
||||||
ref: "",
|
|
||||||
},
|
|
||||||
expectedScheme: DirectoryScheme,
|
|
||||||
expectedLocation: ".",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "current-dir",
|
|
||||||
userInput: ".",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.UnknownSource,
|
|
||||||
ref: "",
|
|
||||||
},
|
|
||||||
expectedScheme: DirectoryScheme,
|
|
||||||
expectedLocation: ".",
|
|
||||||
},
|
|
||||||
// we should support tilde expansion
|
|
||||||
{
|
|
||||||
name: "tilde-expansion-image-implicit",
|
|
||||||
userInput: "~/some-path",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.OciDirectorySource,
|
|
||||||
ref: "~/some-path",
|
|
||||||
},
|
|
||||||
expectedScheme: ImageScheme,
|
|
||||||
expectedLocation: "~/some-path",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tilde-expansion-dir-implicit",
|
|
||||||
userInput: "~/some-path",
|
|
||||||
detection: detectorResult{
|
|
||||||
src: image.UnknownSource,
|
|
||||||
ref: "",
|
|
||||||
},
|
|
||||||
dirs: []string{"~/some-path"},
|
|
||||||
expectedScheme: DirectoryScheme,
|
|
||||||
expectedLocation: "~/some-path",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tilde-expansion-dir-explicit-exists",
|
|
||||||
userInput: "dir:~/some-path",
|
|
||||||
dirs: []string{"~/some-path"},
|
|
||||||
expectedScheme: DirectoryScheme,
|
|
||||||
expectedLocation: "~/some-path",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tilde-expansion-dir-explicit-dne",
|
|
||||||
userInput: "dir:~/some-path",
|
|
||||||
expectedScheme: DirectoryScheme,
|
|
||||||
expectedLocation: "~/some-path",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tilde-expansion-dir-implicit-dne",
|
|
||||||
userInput: "~/some-path",
|
|
||||||
expectedScheme: UnknownScheme,
|
|
||||||
expectedLocation: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range testCases {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
fs := afero.NewMemMapFs()
|
|
||||||
|
|
||||||
for _, p := range test.dirs {
|
|
||||||
expandedExpectedLocation, err := homedir.Expand(p)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to expand path=%q: %+v", p, err)
|
|
||||||
}
|
|
||||||
err = fs.Mkdir(expandedExpectedLocation, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create dummy tar: %+v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imageDetector := func(string) (image.Source, string, error) {
|
|
||||||
// lean on the users real home directory value
|
|
||||||
switch test.detection.src {
|
|
||||||
case image.OciDirectorySource, image.DockerTarballSource, image.OciTarballSource:
|
|
||||||
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
|
||||||
}
|
|
||||||
return test.detection.src, expandedExpectedLocation, test.detection.err
|
|
||||||
default:
|
|
||||||
return test.detection.src, test.detection.ref, test.detection.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actualScheme, actualLocation, err := detectScheme(fs, imageDetector, test.userInput)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err : %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if actualScheme != test.expectedScheme {
|
|
||||||
t.Errorf("expected scheme %q , got %q", test.expectedScheme, actualScheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lean on the users real home directory value
|
|
||||||
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if actualLocation != expandedExpectedLocation {
|
|
||||||
t.Errorf("expected location %q , got %q", expandedExpectedLocation, actualLocation)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
args: []string{"packages", "-o", "json", request},
|
args: []string{"packages", "-o", "json", request},
|
||||||
assertions: []traitAssertion{
|
assertions: []traitAssertion{
|
||||||
assertJsonReport,
|
assertJsonReport,
|
||||||
assertSource(source.SquashedScope),
|
assertScope(source.SquashedScope),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -56,7 +56,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
name: "squashed-scope-flag",
|
name: "squashed-scope-flag",
|
||||||
args: []string{"packages", "-o", "json", "-s", "squashed", request},
|
args: []string{"packages", "-o", "json", "-s", "squashed", request},
|
||||||
assertions: []traitAssertion{
|
assertions: []traitAssertion{
|
||||||
assertSource(source.SquashedScope),
|
assertScope(source.SquashedScope),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -64,7 +64,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
name: "all-layers-scope-flag",
|
name: "all-layers-scope-flag",
|
||||||
args: []string{"packages", "-o", "json", "-s", "all-layers", request},
|
args: []string{"packages", "-o", "json", "-s", "all-layers", request},
|
||||||
assertions: []traitAssertion{
|
assertions: []traitAssertion{
|
||||||
assertSource(source.AllLayersScope),
|
assertScope(source.AllLayersScope),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -75,7 +75,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
args: []string{"packages", "-o", "json", request},
|
args: []string{"packages", "-o", "json", request},
|
||||||
assertions: []traitAssertion{
|
assertions: []traitAssertion{
|
||||||
assertSource(source.AllLayersScope),
|
assertScope(source.AllLayersScope),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -137,3 +137,75 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegistryAuth(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
env map[string]string
|
||||||
|
assertions []traitAssertion
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "fallback to keychain",
|
||||||
|
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||||
|
assertions: []traitAssertion{
|
||||||
|
assertInOutput("source=OciRegistry"),
|
||||||
|
assertInOutput("localhost:5000/something:latest"),
|
||||||
|
assertInOutput("no registry credentials configured, using the default keychain"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "use creds",
|
||||||
|
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||||
|
env: map[string]string{
|
||||||
|
"SYFT_REGISTRY_AUTH_AUTHORITY": "localhost:5000",
|
||||||
|
"SYFT_REGISTRY_AUTH_USERNAME": "username",
|
||||||
|
"SYFT_REGISTRY_AUTH_PASSWORD": "password",
|
||||||
|
},
|
||||||
|
assertions: []traitAssertion{
|
||||||
|
assertInOutput("source=OciRegistry"),
|
||||||
|
assertInOutput("localhost:5000/something:latest"),
|
||||||
|
assertInOutput(`using registry credentials for "localhost:5000"`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "use token",
|
||||||
|
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||||
|
env: map[string]string{
|
||||||
|
"SYFT_REGISTRY_AUTH_AUTHORITY": "localhost:5000",
|
||||||
|
"SYFT_REGISTRY_AUTH_TOKEN": "token",
|
||||||
|
},
|
||||||
|
assertions: []traitAssertion{
|
||||||
|
assertInOutput("source=OciRegistry"),
|
||||||
|
assertInOutput("localhost:5000/something:latest"),
|
||||||
|
assertInOutput(`using registry token for "localhost:5000"`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not enough info fallsback to keychain",
|
||||||
|
args: []string{"packages", "-vv", "registry:localhost:5000/something:latest"},
|
||||||
|
env: map[string]string{
|
||||||
|
"SYFT_REGISTRY_AUTH_AUTHORITY": "localhost:5000",
|
||||||
|
},
|
||||||
|
assertions: []traitAssertion{
|
||||||
|
assertInOutput("source=OciRegistry"),
|
||||||
|
assertInOutput("localhost:5000/something:latest"),
|
||||||
|
assertInOutput(`no registry credentials configured, using the default keychain`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
cmd, stdout, stderr := runSyftCommand(t, test.env, test.args...)
|
||||||
|
for _, traitAssertionFn := range test.assertions {
|
||||||
|
traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||||
|
}
|
||||||
|
if t.Failed() {
|
||||||
|
t.Log("STDOUT:\n", stdout)
|
||||||
|
t.Log("STDERR:\n", stderr)
|
||||||
|
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ func assertTableReport(tb testing.TB, stdout, _ string, _ int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertSource(scope source.Scope) traitAssertion {
|
func assertScope(scope source.Scope) traitAssertion {
|
||||||
return func(tb testing.TB, stdout, stderr string, rc int) {
|
return func(tb testing.TB, stdout, stderr string, rc int) {
|
||||||
// we can only verify source with the json report
|
// we can only verify source with the json report
|
||||||
assertJsonReport(tb, stdout, stderr, rc)
|
assertJsonReport(tb, stdout, stderr, rc)
|
||||||
|
|||||||
@ -23,7 +23,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
|||||||
var pc *pkg.Catalog
|
var pc *pkg.Catalog
|
||||||
for _, c := range cataloger.ImageCatalogers() {
|
for _, c := range cataloger.ImageCatalogers() {
|
||||||
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
|
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
|
||||||
theSource, cleanupSource, err := source.New("docker-archive:" + tarPath)
|
theSource, cleanupSource, err := source.New("docker-archive:"+tarPath, nil)
|
||||||
b.Cleanup(cleanupSource)
|
b.Cleanup(cleanupSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("unable to get source: %+v", err)
|
b.Fatalf("unable to get source: %+v", err)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (*pkg.Catalog, *
|
|||||||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||||
|
|
||||||
theSource, cleanupSource, err := source.New("docker-archive:" + tarPath)
|
theSource, cleanupSource, err := source.New("docker-archive:"+tarPath, nil)
|
||||||
t.Cleanup(cleanupSource)
|
t.Cleanup(cleanupSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get source: %+v", err)
|
t.Fatalf("unable to get source: %+v", err)
|
||||||
@ -29,7 +29,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (*pkg.Catalog, *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func catalogDirectory(t *testing.T, dir string) (*pkg.Catalog, *distro.Distro, source.Source) {
|
func catalogDirectory(t *testing.T, dir string) (*pkg.Catalog, *distro.Distro, source.Source) {
|
||||||
theSource, cleanupSource, err := source.New("dir:" + dir)
|
theSource, cleanupSource, err := source.New("dir:"+dir, nil)
|
||||||
t.Cleanup(cleanupSource)
|
t.Cleanup(cleanupSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get source: %+v", err)
|
t.Fatalf("unable to get source: %+v", err)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user