diff --git a/cmd/cmd.go b/cmd/cmd.go index 70197c968..e39ec4efe 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -4,6 +4,8 @@ import ( "fmt" "os" + "github.com/gookit/color" + "github.com/spf13/cobra" "github.com/anchore/syft/syft/presenter" @@ -11,7 +13,6 @@ import ( "github.com/anchore/stereoscope" "github.com/anchore/syft/internal/config" - "github.com/anchore/syft/internal/format" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/logger" "github.com/anchore/syft/syft" @@ -111,7 +112,7 @@ func logAppConfig() { if err != nil { log.Debugf("Could not display application config: %+v", err) } else { - log.Debugf("Application config:\n%+v", format.Magenta.Format(string(appCfgStr))) + log.Debugf("Application config:\n%+v", color.Magenta.Sprint(appCfgStr)) } } diff --git a/internal/constants.go b/internal/constants.go index 249eaf148..61fc3a2e4 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -1,4 +1,4 @@ package internal -// note: do not change this +// ApplicationName is the non-capitalized name of the application (do not change this) const ApplicationName = "syft" diff --git a/internal/docs.go b/internal/docs.go new file mode 100644 index 000000000..d06d47f6a --- /dev/null +++ b/internal/docs.go @@ -0,0 +1,4 @@ +/* +Package internal contains miscellaneous functions and objects useful within syft but should not be used externally. +*/ +package internal diff --git a/internal/file/glob_match.go b/internal/file/glob_match.go index 81575de24..6befe32e4 100644 --- a/internal/file/glob_match.go +++ b/internal/file/glob_match.go @@ -1,5 +1,6 @@ package file +// GlobMatch evaluates the given glob pattern against the given "name" string, indicating if there is a match or not. // Source: https://research.swtch.com/glob.go func GlobMatch(pattern, name string) bool { px := 0 diff --git a/internal/file/opener.go b/internal/file/opener.go index c79e2a845..969930f11 100644 --- a/internal/file/opener.go +++ b/internal/file/opener.go @@ -5,10 +5,12 @@ import ( "os" ) +// Opener is an object that stores a path to later be opened as a file. type Opener struct { path string } +// Open the stored path as a io.ReadCloser. func (o Opener) Open() (io.ReadCloser, error) { return os.Open(o.path) } diff --git a/internal/file/zip_file_manifest.go b/internal/file/zip_file_manifest.go index 7b19c2070..d7b7b80be 100644 --- a/internal/file/zip_file_manifest.go +++ b/internal/file/zip_file_manifest.go @@ -12,16 +12,20 @@ import ( "github.com/anchore/syft/internal/log" ) +// ZipFileManifest is a collection of paths and their file metadata. type ZipFileManifest map[string]os.FileInfo +// newZipManifest creates an empty ZipFileManifest. func newZipManifest() ZipFileManifest { return make(ZipFileManifest) } +// Add a new path and it's file metadata to the collection. func (z ZipFileManifest) Add(entry string, info os.FileInfo) { z[entry] = info } +// GlobMatch returns the path keys that match the given value(s). func (z ZipFileManifest) GlobMatch(patterns ...string) []string { uniqueMatches := internal.NewStringSet() @@ -43,6 +47,7 @@ func (z ZipFileManifest) GlobMatch(patterns ...string) []string { return results } +// NewZipFileManifest creates and returns a new ZipFileManifest populated with path and metadata from the given zip archive path. func NewZipFileManifest(archivePath string) (ZipFileManifest, error) { zipReader, err := zip.OpenReader(archivePath) manifest := newZipManifest() @@ -62,6 +67,7 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) { return manifest, nil } +// normalizeZipEntryName takes the given path entry and ensures it is prefixed with "/". func normalizeZipEntryName(entry string) string { if !strings.HasPrefix(entry, "/") { return "/" + entry diff --git a/internal/file/zip_file_traversal.go b/internal/file/zip_file_traversal.go index 0f8fe149e..742a88dbc 100644 --- a/internal/file/zip_file_traversal.go +++ b/internal/file/zip_file_traversal.go @@ -15,6 +15,7 @@ import ( ) const ( + // represents the order of bytes _ = iota KB = 1 << (10 * iota) MB @@ -33,6 +34,7 @@ func newZipTraverseRequest(paths ...string) zipTraversalRequest { return results } +// TraverseFilesInZip enumerates all paths stored within a zip archive using the visitor pattern. func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths ...string) error { request := newZipTraverseRequest(paths...) @@ -63,6 +65,7 @@ func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths return nil } +// ExtractFromZipToUniqueTempFile extracts select paths for the given archive to a temporary directory, returning file openers for each file extracted. func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]Opener, error) { results := make(map[string]Opener) @@ -121,6 +124,7 @@ func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (m return results, TraverseFilesInZip(archivePath, visitor, paths...) } +// ContentsFromZip extracts select paths for the given archive and returns a set of string contents for each path. func ContentsFromZip(archivePath string, paths ...string) (map[string]string, error) { results := make(map[string]string) @@ -162,6 +166,7 @@ func ContentsFromZip(archivePath string, paths ...string) (map[string]string, er return results, TraverseFilesInZip(archivePath, visitor, paths...) } +// UnzipToDir extracts a zip archive to a target directory. func UnzipToDir(archivePath, targetDir string) error { visitor := func(file *zip.File) error { // the zip-slip attack protection is still being erroneously detected diff --git a/internal/format/color.go b/internal/format/color.go deleted file mode 100644 index fa1757c34..000000000 --- a/internal/format/color.go +++ /dev/null @@ -1,21 +0,0 @@ -package format - -import "fmt" - -const ( - DefaultColor Color = iota + 30 - Red - Green - Yellow - Blue - Magenta - Cyan - White -) - -type Color uint8 - -// TODO: not cross platform (windows...) -func (c Color) Format(s string) string { - return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, s) -} diff --git a/internal/log/log.go b/internal/log/log.go index 5ea65c839..0dd2199c6 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -1,37 +1,49 @@ +/* +Package log contains the singleton object and helper functions for facilitating logging within the syft library. +*/ package log import "github.com/anchore/syft/syft/logger" +// Log is the singleton used to facilitate logging internally within syft var Log logger.Logger = &nopLogger{} +// Errorf takes a formatted template string and template arguments for the error logging level. func Errorf(format string, args ...interface{}) { Log.Errorf(format, args...) } +// Error logs the given arguments at the error logging level. func Error(args ...interface{}) { Log.Error(args...) } +// Warnf takes a formatted template string and template arguments for the warning logging level. func Warnf(format string, args ...interface{}) { Log.Warnf(format, args...) } +// Warn logs the given arguments at the warning logging level. func Warn(args ...interface{}) { Log.Warn(args...) } +// Infof takes a formatted template string and template arguments for the info logging level. func Infof(format string, args ...interface{}) { Log.Infof(format, args...) } +// Info logs the given arguments at the info logging level. func Info(args ...interface{}) { Log.Info(args...) } +// Debugf takes a formatted template string and template arguments for the debug logging level. func Debugf(format string, args ...interface{}) { Log.Debugf(format, args...) } +// Debug logs the given arguments at the debug logging level. func Debug(args ...interface{}) { Log.Debug(args...) } diff --git a/internal/logger/doc.go b/internal/logger/doc.go new file mode 100644 index 000000000..59647a80c --- /dev/null +++ b/internal/logger/doc.go @@ -0,0 +1,4 @@ +/* +Package logger contains implementations for the syft.logger.Logger interface. +*/ +package logger diff --git a/internal/logger/logrus.go b/internal/logger/logrus.go index 006e5f9c9..96fad888c 100644 --- a/internal/logger/logrus.go +++ b/internal/logger/logrus.go @@ -10,6 +10,7 @@ import ( prefixed "github.com/x-cray/logrus-prefixed-formatter" ) +// LogrusConfig contains all configurable values for the Logrus logger type LogrusConfig struct { EnableConsole bool EnableFile bool @@ -18,16 +19,19 @@ type LogrusConfig struct { FileLocation string } +// LogrusLogger contains all runtime values for using Logrus with the configured output target and input configuration values. type LogrusLogger struct { Config LogrusConfig Logger *logrus.Logger Output io.Writer } +// LogrusNestedLogger is a wrapper for Logrus to enable nested logging configuration (loggers that always attach key-value pairs to all log entries) type LogrusNestedLogger struct { Logger *logrus.Entry } +// NewLogrusLogger creates a new LogrusLogger with the given configuration func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger { appLogger := logrus.New() @@ -76,66 +80,82 @@ func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger { } } +// Debugf takes a formatted template string and template arguments for the debug logging level. func (l *LogrusLogger) Debugf(format string, args ...interface{}) { l.Logger.Debugf(format, args...) } +// Infof takes a formatted template string and template arguments for the info logging level. func (l *LogrusLogger) Infof(format string, args ...interface{}) { l.Logger.Infof(format, args...) } +// Warnf takes a formatted template string and template arguments for the warning logging level. func (l *LogrusLogger) Warnf(format string, args ...interface{}) { l.Logger.Warnf(format, args...) } +// Errorf takes a formatted template string and template arguments for the error logging level. func (l *LogrusLogger) Errorf(format string, args ...interface{}) { l.Logger.Errorf(format, args...) } +// Debug logs the given arguments at the debug logging level. func (l *LogrusLogger) Debug(args ...interface{}) { l.Logger.Debug(args...) } +// Info logs the given arguments at the info logging level. func (l *LogrusLogger) Info(args ...interface{}) { l.Logger.Info(args...) } +// Warn logs the given arguments at the warning logging level. func (l *LogrusLogger) Warn(args ...interface{}) { l.Logger.Warn(args...) } +// Error logs the given arguments at the error logging level. func (l *LogrusLogger) Error(args ...interface{}) { l.Logger.Error(args...) } +// Debugf takes a formatted template string and template arguments for the debug logging level. func (l *LogrusNestedLogger) Debugf(format string, args ...interface{}) { l.Logger.Debugf(format, args...) } +// Infof takes a formatted template string and template arguments for the info logging level. func (l *LogrusNestedLogger) Infof(format string, args ...interface{}) { l.Logger.Infof(format, args...) } +// Warnf takes a formatted template string and template arguments for the warning logging level. func (l *LogrusNestedLogger) Warnf(format string, args ...interface{}) { l.Logger.Warnf(format, args...) } +// Errorf takes a formatted template string and template arguments for the error logging level. func (l *LogrusNestedLogger) Errorf(format string, args ...interface{}) { l.Logger.Errorf(format, args...) } +// Debug logs the given arguments at the debug logging level. func (l *LogrusNestedLogger) Debug(args ...interface{}) { l.Logger.Debug(args...) } +// Info logs the given arguments at the info logging level. func (l *LogrusNestedLogger) Info(args ...interface{}) { l.Logger.Info(args...) } +// Warn logs the given arguments at the warning logging level. func (l *LogrusNestedLogger) Warn(args ...interface{}) { l.Logger.Warn(args...) } +// Error logs the given arguments at the error logging level. func (l *LogrusNestedLogger) Error(args ...interface{}) { l.Logger.Error(args...) } diff --git a/internal/stringset.go b/internal/stringset.go index 41518aaad..2bd68a4b1 100644 --- a/internal/stringset.go +++ b/internal/stringset.go @@ -1,11 +1,16 @@ package internal +import "sort" + +// StringSet represents a set of string types. type StringSet map[string]struct{} +// NewStringSet creates a new empty StringSet. func NewStringSet() StringSet { return make(StringSet) } +// NewStringSetFromSlice creates a StringSet populated with values from the given slice. func NewStringSetFromSlice(start []string) StringSet { ret := make(StringSet) for _, s := range start { @@ -14,19 +19,23 @@ func NewStringSetFromSlice(start []string) StringSet { return ret } +// Add a string to the set. func (s StringSet) Add(i string) { s[i] = struct{}{} } +// Remove a string from the set. func (s StringSet) Remove(i string) { delete(s, i) } +// Contains indicates if the given string is contained within the set. func (s StringSet) Contains(i string) bool { _, ok := s[i] return ok } +// ToSlice returns a sorted slice of strings that are contained within the set. func (s StringSet) ToSlice() []string { ret := make([]string, len(s)) idx := 0 @@ -34,5 +43,6 @@ func (s StringSet) ToSlice() []string { ret[idx] = v idx++ } + sort.Strings(ret) return ret } diff --git a/internal/version/build.go b/internal/version/build.go index e0c960228..3d6702c0c 100644 --- a/internal/version/build.go +++ b/internal/version/build.go @@ -1,3 +1,6 @@ +/* +Package version contains all build time metadata (version, build time, git commit, etc). +*/ package version import ( diff --git a/internal/version/update.go b/internal/version/update.go index bdba75f4d..3aa13ef3a 100644 --- a/internal/version/update.go +++ b/internal/version/update.go @@ -18,6 +18,7 @@ var latestAppVersionURL = struct { path: fmt.Sprintf("/%s/releases/latest/VERSION", internal.ApplicationName), } +// IsUpdateAvailable indicates if there is a newer application version available, and if so, what the new version is. func IsUpdateAvailable() (bool, string, error) { currentVersionStr := FromBuild().Version currentVersion, err := hashiVersion.NewVersion(currentVersionStr) diff --git a/syft/cataloger/python/package_cataloger_test.go b/syft/cataloger/python/package_cataloger_test.go index be3033b97..2ad16da73 100644 --- a/syft/cataloger/python/package_cataloger_test.go +++ b/syft/cataloger/python/package_cataloger_test.go @@ -142,12 +142,12 @@ func TestPythonPackageWheelCataloger(t *testing.T) { AuthorEmail: "me@kennethreitz.org", SitePackagesRootPath: "test-fixtures", Files: []pkg.PythonFileRecord{ - {Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.Digest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"}, - {Path: "requests/__init__.py", Digest: &pkg.Digest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"}, + {Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"}, + {Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"}, {Path: "requests/__pycache__/__version__.cpython-38.pyc"}, {Path: "requests/__pycache__/utils.cpython-38.pyc"}, - {Path: "requests/__version__.py", Digest: &pkg.Digest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"}, - {Path: "requests/utils.py", Digest: &pkg.Digest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"}, + {Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"}, + {Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"}, }, TopLevelPackages: []string{"requests"}, }, @@ -174,11 +174,11 @@ func TestPythonPackageWheelCataloger(t *testing.T) { AuthorEmail: "georg@python.org", SitePackagesRootPath: "test-fixtures", Files: []pkg.PythonFileRecord{ - {Path: "../../../bin/pygmentize", Digest: &pkg.Digest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"}, - {Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.Digest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"}, + {Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"}, + {Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"}, {Path: "Pygments-2.6.1.dist-info/RECORD"}, {Path: "pygments/__pycache__/__init__.cpython-38.pyc"}, - {Path: "pygments/util.py", Digest: &pkg.Digest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"}, + {Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"}, }, TopLevelPackages: []string{"pygments", "something_else"}, }, diff --git a/syft/cataloger/python/parse_wheel_egg_record.go b/syft/cataloger/python/parse_wheel_egg_record.go index 42faafa3f..4f3e828ac 100644 --- a/syft/cataloger/python/parse_wheel_egg_record.go +++ b/syft/cataloger/python/parse_wheel_egg_record.go @@ -44,7 +44,7 @@ func parseWheelOrEggRecord(reader io.Reader) ([]pkg.PythonFileRecord, error) { return nil, fmt.Errorf("unexpected python record digest: %q", item) } - record.Digest = &pkg.Digest{ + record.Digest = &pkg.PythonFileDigest{ Algorithm: fields[0], Value: fields[1], } diff --git a/syft/cataloger/python/parse_wheel_egg_record_test.go b/syft/cataloger/python/parse_wheel_egg_record_test.go index d14868e0f..c0cf578b5 100644 --- a/syft/cataloger/python/parse_wheel_egg_record_test.go +++ b/syft/cataloger/python/parse_wheel_egg_record_test.go @@ -16,22 +16,22 @@ func TestParseWheelEggRecord(t *testing.T) { { Fixture: "test-fixtures/egg-info/RECORD", ExpectedMetadata: []pkg.PythonFileRecord{ - {Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.Digest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"}, - {Path: "requests/__init__.py", Digest: &pkg.Digest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"}, + {Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"}, + {Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"}, {Path: "requests/__pycache__/__version__.cpython-38.pyc"}, {Path: "requests/__pycache__/utils.cpython-38.pyc"}, - {Path: "requests/__version__.py", Digest: &pkg.Digest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"}, - {Path: "requests/utils.py", Digest: &pkg.Digest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"}, + {Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"}, + {Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"}, }, }, { Fixture: "test-fixtures/dist-info/RECORD", ExpectedMetadata: []pkg.PythonFileRecord{ - {Path: "../../../bin/pygmentize", Digest: &pkg.Digest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"}, - {Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.Digest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"}, + {Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"}, + {Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"}, {Path: "Pygments-2.6.1.dist-info/RECORD"}, {Path: "pygments/__pycache__/__init__.cpython-38.pyc"}, - {Path: "pygments/util.py", Digest: &pkg.Digest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"}, + {Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"}, }, }, } diff --git a/syft/cataloger/rpmdb/parse_rpmdb.go b/syft/cataloger/rpmdb/parse_rpmdb.go index fcf251cd0..9f0b3ed80 100644 --- a/syft/cataloger/rpmdb/parse_rpmdb.go +++ b/syft/cataloger/rpmdb/parse_rpmdb.go @@ -51,9 +51,8 @@ func parseRpmDB(resolver source.FileResolver, dbLocation source.Location, reader } p := pkg.Package{ - Name: entry.Name, - Version: fmt.Sprintf("%s-%s", entry.Version, entry.Release), // this is what engine does - //Version: fmt.Sprintf("%d:%s-%s.%s", entry.Epoch, entry.Version, entry.Release, entry.Arch), + Name: entry.Name, + Version: fmt.Sprintf("%s-%s", entry.Version, entry.Release), // this is what engine does, instead of fmt.Sprintf("%d:%s-%s.%s", entry.Epoch, entry.Version, entry.Release, entry.Arch) Locations: []source.Location{dbLocation}, FoundBy: catalogerName, Type: pkg.RpmPkg, diff --git a/syft/distro/distro.go b/syft/distro/distro.go index 446357401..a78f5ab33 100644 --- a/syft/distro/distro.go +++ b/syft/distro/distro.go @@ -6,6 +6,7 @@ import ( hashiVer "github.com/hashicorp/go-version" ) +// Distro represents a Linux Distribution. type Distro struct { Type Type Version *hashiVer.Version @@ -20,6 +21,7 @@ func NewUnknownDistro() Distro { } } +// NewDistro creates a new Distro object populated with the given values. func NewDistro(t Type, ver, like string) (Distro, error) { if ver == "" { return Distro{Type: t}, nil @@ -36,6 +38,12 @@ func NewDistro(t Type, ver, like string) (Distro, error) { }, nil } +// Name provides a string repr of the distro +func (d Distro) Name() string { + return string(d.Type) +} + +// MajorVersion returns the major version value from the pseudo-semantically versioned distro version value. func (d Distro) MajorVersion() string { if d.Version == nil { return fmt.Sprint("(version unknown)") @@ -43,10 +51,12 @@ func (d Distro) MajorVersion() string { return fmt.Sprintf("%d", d.Version.Segments()[0]) } +// FullVersion returns the original user version value. func (d Distro) FullVersion() string { return d.RawVersion } +// String returns a human-friendly representation of the Linux distribution. func (d Distro) String() string { versionStr := "(version unknown)" if d.RawVersion != "" { @@ -54,8 +64,3 @@ func (d Distro) String() string { } return fmt.Sprintf("%s %s", d.Type, versionStr) } - -// Name provides a string repr of the distro -func (d Distro) Name() string { - return string(d.Type) -} diff --git a/syft/distro/type.go b/syft/distro/type.go index 9b6f4c035..aac86384a 100644 --- a/syft/distro/type.go +++ b/syft/distro/type.go @@ -1,8 +1,10 @@ package distro +// Type represents the different Linux distribution options type Type string const ( + // represents the set of valid/supported Linux Distributions UnknownDistroType Type = "UnknownDistroType" Debian Type = "debian" Ubuntu Type = "ubuntu" @@ -17,6 +19,7 @@ const ( OpenSuseLeap Type = "opensuseleap" ) +// All contains all Linux distribution options var All = []Type{ Debian, Ubuntu, @@ -46,6 +49,7 @@ var IDMapping = map[string]Type{ "opensuse-leap": OpenSuseLeap, } +// String returns the string representation of the given Linux distribution. func (t Type) String() string { return string(t) } diff --git a/syft/event/event.go b/syft/event/event.go index e76ebd983..caf1e41f5 100644 --- a/syft/event/event.go +++ b/syft/event/event.go @@ -7,7 +7,12 @@ package event import "github.com/wagoodman/go-partybus" const ( + // AppUpdateAvailable is a partybus event that occurs when an application update is available AppUpdateAvailable partybus.EventType = "syft-app-update-available" - CatalogerStarted partybus.EventType = "syft-cataloger-started-event" - CatalogerFinished partybus.EventType = "syft-cataloger-finished-event" + + // CatalogerStarted is a partybus event that occurs when the package cataloging has begun + CatalogerStarted partybus.EventType = "syft-cataloger-started-event" + + // CatalogerFinished is a partybus event that occurs when the package cataloging has completed + CatalogerFinished partybus.EventType = "syft-cataloger-finished-event" ) diff --git a/syft/logger/logger.go b/syft/logger/logger.go index 3816296ff..3ace363f5 100644 --- a/syft/logger/logger.go +++ b/syft/logger/logger.go @@ -1,8 +1,9 @@ /* -Defines the logging interface which is used throughout the syft library. +Package logger defines the logging interface which is used throughout the syft library. */ package logger +// Logger represents the behavior for logging within the syft library. type Logger interface { Errorf(format string, args ...interface{}) Error(args ...interface{}) diff --git a/syft/pkg/apk_metadata.go b/syft/pkg/apk_metadata.go index ef036509a..705528fd0 100644 --- a/syft/pkg/apk_metadata.go +++ b/syft/pkg/apk_metadata.go @@ -35,6 +35,7 @@ type ApkFileRecord struct { Checksum string `json:"checksum,omitempty"` } +// PackageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec) func (m ApkMetadata) PackageURL() string { pURL := packageurl.NewPackageURL( // note: this is currently a candidate and not technically within spec diff --git a/syft/pkg/dpkg_metadata.go b/syft/pkg/dpkg_metadata.go index f1e80802e..c810a8198 100644 --- a/syft/pkg/dpkg_metadata.go +++ b/syft/pkg/dpkg_metadata.go @@ -17,11 +17,13 @@ type DpkgMetadata struct { Files []DpkgFileRecord `json:"files"` } +// DpkgFileRecord represents a single file attributed to a debian package. type DpkgFileRecord struct { Path string `json:"path"` MD5 string `json:"md5"` } +// PackageURL returns the PURL for the specific Debian package (see https://github.com/package-url/purl-spec) func (m DpkgMetadata) PackageURL(d distro.Distro) string { pURL := packageurl.NewPackageURL( // TODO: replace with `packageurl.TypeDebian` upon merge of https://github.com/package-url/packageurl-go/pull/21 diff --git a/syft/pkg/gem_metadata.go b/syft/pkg/gem_metadata.go index c06a244b2..51a07a148 100644 --- a/syft/pkg/gem_metadata.go +++ b/syft/pkg/gem_metadata.go @@ -1,5 +1,6 @@ package pkg +// GemMetadata represents all metadata parsed from the gemspec file type GemMetadata struct { Name string `mapstructure:"name" json:"name"` Version string `mapstructure:"version" json:"version"` diff --git a/syft/pkg/java_metadata.go b/syft/pkg/java_metadata.go index d4852eb55..beb03463f 100644 --- a/syft/pkg/java_metadata.go +++ b/syft/pkg/java_metadata.go @@ -26,6 +26,7 @@ type JavaManifest struct { NamedSections map[string]map[string]string `json:"namedSections,omitempty"` } +// PackageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec) func (m JavaMetadata) PackageURL() string { if m.PomProperties != nil { pURL := packageurl.NewPackageURL( diff --git a/syft/pkg/language.go b/syft/pkg/language.go index 6fc1b184a..9f2ab7dae 100644 --- a/syft/pkg/language.go +++ b/syft/pkg/language.go @@ -1,8 +1,10 @@ package pkg +// Language represents a single programming language. type Language string const ( + // the full set of supported programming languages UnknownLanguage Language = "UnknownLanguage" Java Language = "java" JavaScript Language = "javascript" @@ -11,6 +13,7 @@ const ( Go Language = "go" ) +// AllLanguages is a set of all programming languages detected by syft. var AllLanguages = []Language{ Java, JavaScript, @@ -19,6 +22,7 @@ var AllLanguages = []Language{ Go, } +// String returns the string representation of the language. func (l Language) String() string { return string(l) } diff --git a/syft/pkg/metadata.go b/syft/pkg/metadata.go index c1e402934..65d58724f 100644 --- a/syft/pkg/metadata.go +++ b/syft/pkg/metadata.go @@ -1,14 +1,16 @@ package pkg +// MetadataType represents the data shape stored within pkg.Package.Metadata. type MetadataType string const ( + // this is the full set of data shapes that can be represented within the pkg.Package.Metadata field UnknownMetadataType MetadataType = "UnknownMetadata" - ApkMetadataType MetadataType = "apk-metadata" - DpkgMetadataType MetadataType = "dpkg-metadata" - GemMetadataType MetadataType = "gem-metadata" - JavaMetadataType MetadataType = "java-metadata" - NpmPackageJSONMetadataType MetadataType = "npm-package-json-metadata" - RpmdbMetadataType MetadataType = "rpmdb-metadata" - PythonPackageMetadataType MetadataType = "python-package-metadata" + ApkMetadataType MetadataType = "ApkMetadata" + DpkgMetadataType MetadataType = "DpkgMetadata" + GemMetadataType MetadataType = "GemMetadata" + JavaMetadataType MetadataType = "JavaMetadata" + NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata" + RpmdbMetadataType MetadataType = "RpmdbMetadata" + PythonPackageMetadataType MetadataType = "PythonPackageMetadata" ) diff --git a/syft/pkg/package.go b/syft/pkg/package.go index 7e97ee8a8..d2c83a363 100644 --- a/syft/pkg/package.go +++ b/syft/pkg/package.go @@ -14,6 +14,7 @@ import ( "github.com/package-url/packageurl-go" ) +// ID represents a unique value for each package added to a package catalog. type ID int64 // Package represents an application or library that has been bundled into a distributable format. diff --git a/syft/pkg/python_package_metadata.go b/syft/pkg/python_package_metadata.go index 637e6220c..e01771602 100644 --- a/syft/pkg/python_package_metadata.go +++ b/syft/pkg/python_package_metadata.go @@ -1,15 +1,16 @@ package pkg -type Digest struct { +// PythonFileDigest represents the file metadata for a single file attributed to a python package. +type PythonFileDigest struct { Algorithm string `json:"algorithm"` Value string `json:"value"` } // PythonFileRecord represents a single entry within a RECORD file for a python wheel or egg package type PythonFileRecord struct { - Path string `json:"path"` - Digest *Digest `json:"digest,omitempty"` - Size string `json:"size,omitempty"` + Path string `json:"path"` + Digest *PythonFileDigest `json:"digest,omitempty"` + Size string `json:"size,omitempty"` } // PythonPackageMetadata represents all captured data for a python egg or wheel package. diff --git a/syft/pkg/rpmdb_metadata.go b/syft/pkg/rpmdb_metadata.go index c9823c1a8..6b3ea723c 100644 --- a/syft/pkg/rpmdb_metadata.go +++ b/syft/pkg/rpmdb_metadata.go @@ -21,6 +21,7 @@ type RpmdbMetadata struct { Files []RpmdbFileRecord `json:"files"` } +// RpmdbFileRecord represents the file metadata for a single file attributed to a RPM package. type RpmdbFileRecord struct { Path string `json:"path"` Mode RpmdbFileMode `json:"mode"` @@ -28,8 +29,10 @@ type RpmdbFileRecord struct { SHA256 string `json:"sha256"` } +// RpmdbFileMode is the raw file mode for a single file. This can be interpreted as the linux stat.h mode (see https://pubs.opengroup.org/onlinepubs/007908799/xsh/sysstat.h.html) type RpmdbFileMode uint16 +// PackageURL returns the PURL for the specific RHEL package (see https://github.com/package-url/purl-spec) func (m RpmdbMetadata) PackageURL(d distro.Distro) string { pURL := packageurl.NewPackageURL( packageurl.TypeRPM, diff --git a/syft/pkg/type.go b/syft/pkg/type.go index 85dc83159..49d3d3b26 100644 --- a/syft/pkg/type.go +++ b/syft/pkg/type.go @@ -6,6 +6,7 @@ import "github.com/package-url/packageurl-go" type Type string const ( + // the full set of supported packages UnknownPkg Type = "UnknownPackage" ApkPkg Type = "apk" GemPkg Type = "gem" @@ -18,6 +19,7 @@ const ( GoModulePkg Type = "go-module" ) +// AllPkgs represents all supported package types var AllPkgs = []Type{ ApkPkg, GemPkg, @@ -30,6 +32,7 @@ var AllPkgs = []Type{ GoModulePkg, } +// PackageURLType returns the PURL package type for the current package. func (t Type) PackageURLType() string { switch t { case ApkPkg: diff --git a/syft/presenter/json/artifact.go b/syft/presenter/json/artifact.go index e26bdc654..d5bf19872 100644 --- a/syft/presenter/json/artifact.go +++ b/syft/presenter/json/artifact.go @@ -34,7 +34,6 @@ type artifactMetadataUnpacker struct { } func NewArtifact(p *pkg.Package) (Artifact, error) { - return Artifact{ artifactBasicMetadata: artifactBasicMetadata{ Name: p.Name, @@ -67,6 +66,7 @@ func (a Artifact) ToPackage() pkg.Package { } } +// nolint:funlen func (a *Artifact) UnmarshalJSON(b []byte) error { var basic artifactBasicMetadata if err := json.Unmarshal(b, &basic); err != nil { @@ -128,7 +128,6 @@ func (a *Artifact) UnmarshalJSON(b []byte) error { // there may be packages with no metadata, which is OK default: return fmt.Errorf("unsupported package metadata type: %+v", a.MetadataType) - } return nil diff --git a/syft/presenter/json/source.go b/syft/presenter/json/source.go index cd777b93e..7a5ad6ad2 100644 --- a/syft/presenter/json/source.go +++ b/syft/presenter/json/source.go @@ -55,7 +55,6 @@ func (s *Source) UnmarshalJSON(b []byte) error { s.Target = payload default: return fmt.Errorf("unsupported package metadata type: %+v", s.Type) - } return nil diff --git a/syft/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden b/syft/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden index b88f2ffef..4876f5949 100644 --- a/syft/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden +++ b/syft/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden @@ -14,7 +14,7 @@ "MIT" ], "language": "python", - "metadataType": "python-package-metadata", + "metadataType": "PythonPackageMetadata", "metadata": { "name": "package-1", "version": "1.0.1", @@ -37,7 +37,7 @@ ], "licenses": null, "language": "", - "metadataType": "dpkg-metadata", + "metadataType": "DpkgMetadata", "metadata": { "package": "package-2", "source": "", diff --git a/syft/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden b/syft/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden index 854f05ea0..81ba778fd 100644 --- a/syft/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden +++ b/syft/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden @@ -15,7 +15,7 @@ "MIT" ], "language": "python", - "metadataType": "python-package-metadata", + "metadataType": "PythonPackageMetadata", "metadata": { "name": "package-1", "version": "1.0.1", @@ -39,7 +39,7 @@ ], "licenses": null, "language": "", - "metadataType": "dpkg-metadata", + "metadataType": "DpkgMetadata", "metadata": { "package": "package-2", "source": "", diff --git a/syft/presenter/text/presenter.go b/syft/presenter/text/presenter.go index 57764eb8b..8e86fea47 100644 --- a/syft/presenter/text/presenter.go +++ b/syft/presenter/text/presenter.go @@ -10,11 +10,13 @@ import ( "github.com/anchore/syft/syft/source" ) +// Presenter is a human-friendly text presenter to represent package and source data. type Presenter struct { catalog *pkg.Catalog srcMetadata source.Metadata } +// NewPresenter creates a new presenter for the given set of catalog and image data. func NewPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *Presenter { return &Presenter{ catalog: catalog, diff --git a/syft/source/all_layers_resolver.go b/syft/source/all_layers_resolver.go index 1c58e89b1..7b94328c1 100644 --- a/syft/source/all_layers_resolver.go +++ b/syft/source/all_layers_resolver.go @@ -102,6 +102,7 @@ func (r *AllLayersResolver) FilesByPath(paths ...string) ([]Location, error) { } // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image. +// nolint:gocognit func (r *AllLayersResolver) FilesByGlob(patterns ...string) ([]Location, error) { uniqueFileIDs := file.NewFileReferenceSet() uniqueLocations := make([]Location, 0) @@ -141,6 +142,8 @@ func (r *AllLayersResolver) FilesByGlob(patterns ...string) ([]Location, error) return uniqueLocations, nil } +// RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. +// This is helpful when attempting to find a file that is in the same layer or lower as another file. func (r *AllLayersResolver) RelativeFileByPath(location Location, path string) *Location { entry, err := r.img.FileCatalog.Get(location.ref) if err != nil { @@ -157,13 +160,13 @@ func (r *AllLayersResolver) RelativeFileByPath(location Location, path string) * return &relativeLocation } -// MultipleFileContentsByRef returns the file contents for all file.References relative to the image. Note that a +// MultipleFileContentsByLocation returns the file contents for all file.References relative to the image. Note that a // file.Reference is a path relative to a particular layer. func (r *AllLayersResolver) MultipleFileContentsByLocation(locations []Location) (map[Location]string, error) { return mapLocationRefs(r.img.MultipleFileContentsByRef, locations) } -// FileContentsByRef fetches file contents for a single file reference, irregardless of the source layer. +// FileContentsByLocation fetches file contents for a single file reference, irregardless of the source layer. // If the path does not exist an error is returned. func (r *AllLayersResolver) FileContentsByLocation(location Location) (string, error) { return r.img.FileContentsByRef(location.ref) diff --git a/syft/source/directory_resolver.go b/syft/source/directory_resolver.go index 3d6904e74..c05d229c4 100644 --- a/syft/source/directory_resolver.go +++ b/syft/source/directory_resolver.go @@ -80,6 +80,9 @@ func (s DirectoryResolver) FilesByGlob(patterns ...string) ([]Location, error) { return result, nil } +// RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. +// This is helpful when attempting to find a file that is in the same layer or lower as another file. For the +// DirectoryResolver, this is a simple path lookup. func (s *DirectoryResolver) RelativeFileByPath(_ Location, path string) *Location { paths, err := s.FilesByPath(path) if err != nil { @@ -92,7 +95,7 @@ func (s *DirectoryResolver) RelativeFileByPath(_ Location, path string) *Locatio return &paths[0] } -// MultipleFileContentsByRef returns the file contents for all file.References relative a directory. +// MultipleFileContentsByLocation returns the file contents for all file.References relative a directory. func (s DirectoryResolver) MultipleFileContentsByLocation(locations []Location) (map[Location]string, error) { refContents := make(map[Location]string) for _, location := range locations { @@ -106,7 +109,7 @@ func (s DirectoryResolver) MultipleFileContentsByLocation(locations []Location) return refContents, nil } -// FileContentsByRef fetches file contents for a single file reference relative to a directory. +// FileContentsByLocation fetches file contents for a single file reference relative to a directory. // If the path does not exist an error is returned. func (s DirectoryResolver) FileContentsByLocation(location Location) (string, error) { contents, err := fileContents(location.Path) diff --git a/syft/source/image_metadata.go b/syft/source/image_metadata.go index 9bd8a30a9..ce6c59223 100644 --- a/syft/source/image_metadata.go +++ b/syft/source/image_metadata.go @@ -2,6 +2,8 @@ package source import "github.com/anchore/stereoscope/pkg/image" +// ImageMetadata represents all static metadata that defines what a container image is. This is useful to later describe +// "what" was cataloged without needing the more complicated stereoscope Image objects or Resolver objects. type ImageMetadata struct { UserInput string `json:"userInput"` Scope Scope `json:"scope"` // specific perspective to catalog @@ -12,12 +14,14 @@ type ImageMetadata struct { Tags []string `json:"tags"` } +// LayerMetadata represents all static metadata that defines what a container image layer is. type LayerMetadata struct { MediaType string `json:"mediaType"` Digest string `json:"digest"` Size int64 `json:"size"` } +// NewImageMetadata creates a new ImageMetadata object populated from the given stereoscope Image object and user configuration. func NewImageMetadata(img *image.Image, userInput string, scope Scope) ImageMetadata { // populate artifacts... tags := make([]string, len(img.Metadata.Tags)) diff --git a/syft/source/image_squash_resolver.go b/syft/source/image_squash_resolver.go index 0e34b3355..3df142a58 100644 --- a/syft/source/image_squash_resolver.go +++ b/syft/source/image_squash_resolver.go @@ -104,6 +104,9 @@ func (r *ImageSquashResolver) FilesByGlob(patterns ...string) ([]Location, error return uniqueLocations, nil } +// RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. +// This is helpful when attempting to find a file that is in the same layer or lower as another file. For the +// ImageSquashResolver, this is a simple path lookup. func (r *ImageSquashResolver) RelativeFileByPath(_ Location, path string) *Location { paths, err := r.FilesByPath(path) if err != nil { @@ -116,13 +119,13 @@ func (r *ImageSquashResolver) RelativeFileByPath(_ Location, path string) *Locat return &paths[0] } -// MultipleFileContentsByRef returns the file contents for all file.References relative to the image. Note that a +// MultipleFileContentsByLocation returns the file contents for all file.References relative to the image. Note that a // file.Reference is a path relative to a particular layer, in this case only from the squashed representation. func (r *ImageSquashResolver) MultipleFileContentsByLocation(locations []Location) (map[Location]string, error) { return mapLocationRefs(r.img.MultipleFileContentsByRef, locations) } -// FileContentsByRef fetches file contents for a single file reference, irregardless of the source layer. +// FileContentsByLocation fetches file contents for a single file reference, irregardless of the source layer. // If the path does not exist an error is returned. func (r *ImageSquashResolver) FileContentsByLocation(location Location) (string, error) { return r.img.FileContentsByRef(location.ref) diff --git a/syft/source/location.go b/syft/source/location.go index 1fc1250ae..774fd6413 100644 --- a/syft/source/location.go +++ b/syft/source/location.go @@ -7,18 +7,21 @@ import ( "github.com/anchore/stereoscope/pkg/image" ) +// Location represents a path relative to a particular filesystem. type Location struct { - Path string `json:"path"` - FileSystemID string `json:"layerID,omitempty"` // TODO: comment - ref file.Reference + Path string `json:"path"` // The string path of the location (e.g. /etc/hosts) + FileSystemID string `json:"layerID,omitempty"` // An ID representing the filesystem. For container images this is a layer digest, directories or root filesystem this is blank. + ref file.Reference // The file reference relative to the stereoscope.FileCatalog that has more information about this location. } +// NewLocation creates a new Location representing a path without denoting a filesystem or FileCatalog reference. func NewLocation(path string) Location { return Location{ Path: path, } } +// NewLocationFromImage creates a new Location representing the given path (extracted from the ref) relative to the given image. func NewLocationFromImage(ref file.Reference, img *image.Image) Location { entry, err := img.FileCatalog.Get(ref) if err != nil { diff --git a/syft/source/metadata.go b/syft/source/metadata.go index ef3092564..b9747362e 100644 --- a/syft/source/metadata.go +++ b/syft/source/metadata.go @@ -1,5 +1,6 @@ package source +// Metadata represents any static source data that helps describe "what" was cataloged. type Metadata struct { Scheme Scheme // the source data scheme type (directory or image) ImageMetadata ImageMetadata // all image info (image only) diff --git a/syft/source/resolver.go b/syft/source/resolver.go index d6193c4c3..0f22c2096 100644 --- a/syft/source/resolver.go +++ b/syft/source/resolver.go @@ -19,7 +19,7 @@ type ContentResolver interface { // TODO: we should consider refactoring to return a set of io.Readers or file.Openers instead of the full contents themselves (allow for optional buffering). } -// FileResolver knows how to get file.References for given string paths and globs +// FileResolver knows how to get file.References for given string paths and globs type FileResolver interface { // FilesByPath fetches a set of file references which have the given path (for an image, there may be multiple matches) FilesByPath(paths ...string) ([]Location, error) diff --git a/syft/source/scheme.go b/syft/source/scheme.go index c12a1f69f..d2f1c73eb 100644 --- a/syft/source/scheme.go +++ b/syft/source/scheme.go @@ -9,12 +9,16 @@ import ( "github.com/spf13/afero" ) +// Scheme represents the optional prefixed string at the beginning of a user request (e.g. "docker:"). type Scheme string const ( - UnknownScheme Scheme = "unknown-scheme" - DirectoryScheme Scheme = "directory-scheme" - ImageScheme Scheme = "image-scheme" + // UnknownScheme is the default scheme + UnknownScheme Scheme = "UnknownScheme" + // DirectoryScheme indicates the source being cataloged is a directory on the root filesystem + DirectoryScheme Scheme = "DirectoryScheme" + // ImageScheme indicates the source being cataloged is a container image + ImageScheme Scheme = "ImageScheme" ) func detectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (Scheme, string, error) { diff --git a/syft/source/scope.go b/syft/source/scope.go index 92cb0b9b1..e959d1a42 100644 --- a/syft/source/scope.go +++ b/syft/source/scope.go @@ -2,19 +2,25 @@ package source import "strings" +// Scope indicates "how" or from "which perspectives" the source object should be cataloged from. type Scope string const ( - UnknownScope Scope = "UnknownScope" - SquashedScope Scope = "Squashed" + // UnknownScope is the default scope + UnknownScope Scope = "UnknownScope" + // SquashedScope indicates to only catalog content visible from the squashed filesystem representation (what can be seen only within the container at runtime) + SquashedScope Scope = "Squashed" + // AllLayersScope indicates to catalog content on all layers, irregardless if it is visible from the container at runtime. AllLayersScope Scope = "AllLayers" ) +// AllScopes is a slice containing all possible scope options var AllScopes = []Scope{ SquashedScope, AllLayersScope, } +// ParseScope returns a scope as indicated from the given string. func ParseScope(userStr string) Scope { switch strings.ToLower(userStr) { case strings.ToLower(SquashedScope.String()):