From ca742f27a4aba35b8932c23ab16748dd2297bc61 Mon Sep 17 00:00:00 2001 From: gnmahanth Date: Fri, 6 Jan 2023 14:49:52 +0000 Subject: [PATCH 01/23] skip paths not in catalogers glob patterns Signed-off-by: gnmahanth --- pkg/image/layer.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/pkg/image/layer.go b/pkg/image/layer.go index 96fe14c3..b887ae0e 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -15,6 +15,7 @@ import ( "github.com/anchore/stereoscope/pkg/event" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" + "github.com/bmatcuk/doublestar/v4" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sylabs/squashfs" @@ -24,6 +25,69 @@ import ( const SingularitySquashFSLayer = "application/vnd.sylabs.sif.layer.v1.squashfs" +var ( + binarySearchPaths = []string{ + "/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin", + "/usr/lib64", "/usr/lib", "/usr/share", "/usr/local/lib64", "/usr/local/lib", + } + catalogerGlobPatterns = map[string][]string{ + "alpmdb-cataloger": {"**/var/lib/pacman/local/**/desc"}, + "apkdb-cataloger": {"**/lib/apk/db/installed"}, + "conan-cataloger": {"**/conanfile.txt", "**/conan.lock"}, + "dartlang-lock-cataloger": {"**/pubspec.lock"}, + "dpkgdb-cataloger": {"**/var/lib/dpkg/{status,status.d/**}"}, + "dotnet-deps-cataloger": {"**/*.deps.json"}, + "go-mod-file-cataloger": {"**/go.mod"}, + "haskell-cataloger": {"**/stack.yaml", "**/stack.yaml.lock", "**/cabal.project.freeze"}, + "java-cataloger": { + // java archive + "**/*.jar", "**/*.war", "**/*.ear", "**/*.par", + "**/*.sar", "**/*.jpi", "**/*.hpi", "**/*.lpkg", + // zip java archive + "**/*.zip", + // tar java archive + "**/*.tar", "**/*.tar.gz", "**/*.tgz", "**/*.tar.bz", "**/*.tar.bz2", + "**/*.tbz", "**/*.tbz2", "**/*.tar.br", "**/*.tbr", "**/*.tar.lz4", "**/*.tlz4", + "**/*.tar.sz", "**/*.tsz", "**/*.tar.xz", "**/*.txz", "**/*.tar.zst", + }, + "java-pom-cataloger": {"**/pom.xml"}, + "javascript-package-cataloger": {"**/package.json"}, + "javascript-lock-cataloger": {"**/package-lock.json", "**/yarn.lock", "**/pnpm-lock.yaml"}, + "php-composer-installed-cataloger": {"**/installed.json"}, + "php-composer-lock-cataloger": {"**/composer.lock"}, + "portage-cataloger": {"**/var/db/pkg/*/*/CONTENTS"}, + "rpm-db-cataloger": {"**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}", "**/var/lib/rpmmanifest/container-manifest-2"}, + "rpm-file-cataloger": {"**/*.rpm"}, + "ruby-gemfile-cataloger": {"**/Gemfile.lock"}, + "ruby-gemspec-cataloger": {"**/specifications/**/*.gemspec"}, + "rust-cargo-lock-cataloger": {"**/Cargo.lock"}, + "cocoapods-cataloger": {"**/Podfile.lock"}, + + "sbom-cataloger": { + "**/*.syft.json", "**/*.bom.*", "**/*.bom", + "**/bom", "**/*.sbom.*", "**/*.sbom", "**/sbom", + "**/*.cdx.*", "**/*.cdx", "**/*.spdx.*", "**/*.spdx", + }, + + "python-index-cataloger": {"**/*requirements*.txt", "**/poetry.lock", "**/Pipfile.lock", "**/setup.py"}, + "python-package-cataloger": {"**/*egg-info/PKG-INFO", "**/*.egg-info", "**/*dist-info/METADATA", + "**/*.egg", "**/*.whl", // python egg and whl files + }, + + // TODO: binary cataloger needs different handling + "binary-cataloger": binarySearchPaths, + "go-module-binary-cataloger": binarySearchPaths, + "cargo-auditable-binary-cataloger": binarySearchPaths, + } + allPatterns = func() *[]string { + patterns := []string{} + for _, p := range catalogerGlobPatterns { + patterns = append(patterns, p...) + } + return &patterns + } +) + // Layer represents a single layer within a container image. type Layer struct { // layer is the raw layer metadata and content provider from the GCR lib @@ -190,6 +254,10 @@ func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { }() metadata := file.NewMetadata(entry.Header, entry.Sequence, contents) + if !AnyGlobMatches(allPatterns(), metadata.Path) { + return nil + } + // note: the tar header name is independent of surrounding structure, for example, there may be a tar header entry // for /some/path/to/file.txt without any entries to constituent paths (/some, /some/path, /some/path/to ). // This is ok, and the FileTree will account for this by automatically adding directories for non-existing @@ -237,6 +305,11 @@ func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { func (l *Layer) squashfsVisitor(monitor *progress.Manual) file.SquashFSVisitor { return func(fsys fs.FS, path string, d fs.DirEntry) error { + + if !AnyGlobMatches(allPatterns(), path) { + return nil + } + ff, err := fsys.Open(path) if err != nil { return err @@ -305,3 +378,15 @@ func trackReadProgress(metadata LayerMetadata) *progress.Manual { return p } + +func AnyGlobMatches(patterns *[]string, path string) bool { + for _, p := range *patterns { + match, err := doublestar.PathMatch(p, path) + if err != nil { + continue + } else if match { + return true + } + } + return false +} From 30a1c6d6744f7b62defb0b78a37e0ee438d880e1 Mon Sep 17 00:00:00 2001 From: gnmahanth Date: Mon, 9 Jan 2023 14:12:57 +0000 Subject: [PATCH 02/23] fix missing java and binary files from sbom Signed-off-by: gnmahanth --- pkg/image/layer.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/image/layer.go b/pkg/image/layer.go index b887ae0e..d7517f8f 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -27,8 +27,9 @@ const SingularitySquashFSLayer = "application/vnd.sylabs.sif.layer.v1.squashfs" var ( binarySearchPaths = []string{ - "/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin", - "/usr/lib64", "/usr/lib", "/usr/share", "/usr/local/lib64", "/usr/local/lib", + "/usr/lib/jvm/**", "/usr/share/java/**", + "/usr/local/sbin/*", "/usr/local/bin/*", "/usr/sbin/*", "/usr/bin/*", "/sbin/*", "/bin/*", + "/usr/lib64/*", "/usr/lib/*", "/usr/share/*", "/usr/local/lib64/*", "/usr/local/lib/*", } catalogerGlobPatterns = map[string][]string{ "alpmdb-cataloger": {"**/var/lib/pacman/local/**/desc"}, From ee0c9713629fd5ef25c974b24f329fd62a658767 Mon Sep 17 00:00:00 2001 From: gnmahanth Date: Tue, 10 Jan 2023 11:55:47 +0000 Subject: [PATCH 03/23] modify code to take path filter func as parmater instead of all paths Signed-off-by: gnmahanth --- client.go | 8 +- examples/basic.go | 4 +- pkg/image/image.go | 4 +- pkg/image/layer.go | 94 ++----------------- pkg/image/sif/provider_test.go | 4 +- pkg/imagetest/image_fixtures.go | 6 +- test/integration/fixture_image_simple_test.go | 3 +- test/integration/mime_type_detection_test.go | 6 +- test/integration/oci_registry_source_test.go | 9 +- test/integration/platform_test.go | 8 +- 10 files changed, 43 insertions(+), 103 deletions(-) diff --git a/client.go b/client.go index 4bd93625..1a206e71 100644 --- a/client.go +++ b/client.go @@ -67,7 +67,7 @@ func WithPlatform(platform string) Option { } // GetImageFromSource returns an image from the explicitly provided source. -func GetImageFromSource(ctx context.Context, imgStr string, source image.Source, options ...Option) (*image.Image, error) { +func GetImageFromSource(ctx context.Context, imgStr string, source image.Source, filter image.PathFilter, options ...Option) (*image.Image, error) { log.Debugf("image: source=%+v location=%+v", source, imgStr) var cfg config @@ -90,7 +90,7 @@ func GetImageFromSource(ctx context.Context, imgStr string, source image.Source, return nil, fmt.Errorf("unable to use %s source: %w", source, err) } - err = img.Read() + err = img.Read(filter) if err != nil { return nil, fmt.Errorf("could not read image: %+v", err) } @@ -153,12 +153,12 @@ func selectImageProvider(imgStr string, source image.Source, cfg config) (image. // GetImage parses the user provided image string and provides an image object; // note: the source where the image should be referenced from is automatically inferred. -func GetImage(ctx context.Context, userStr string, options ...Option) (*image.Image, error) { +func GetImage(ctx context.Context, userStr string, filter image.PathFilter, options ...Option) (*image.Image, error) { source, imgStr, err := image.DetectSource(userStr) if err != nil { return nil, err } - return GetImageFromSource(ctx, imgStr, source, options...) + return GetImageFromSource(ctx, imgStr, source, filter, options...) } func SetLogger(logger logger.Logger) { diff --git a/examples/basic.go b/examples/basic.go index 7d4522b5..09685644 100644 --- a/examples/basic.go +++ b/examples/basic.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "github.com/anchore/go-logger" "github.com/anchore/go-logger/adapter/logrus" @@ -34,7 +35,8 @@ func main() { // ./path/to.tar // // This will catalog the file metadata and resolve all squash trees - image, err := stereoscope.GetImage(ctx, os.Args[1]) + filter := func(path string) bool { return true } + image, err := stereoscope.GetImage(ctx, os.Args[1], filter) if err != nil { panic(err) } diff --git a/pkg/image/image.go b/pkg/image/image.go index 0748c2c4..630b0686 100644 --- a/pkg/image/image.go +++ b/pkg/image/image.go @@ -173,7 +173,7 @@ func (i *Image) applyOverrideMetadata() error { // Read parses information from the underlying image tar into this struct. This includes image metadata, layer // metadata, layer file trees, and layer squash trees (which implies the image squash tree). -func (i *Image) Read() error { +func (i *Image) Read(filter PathFilter) error { var layers = make([]*Layer, 0) var err error i.Metadata, err = readImageMetadata(i.image) @@ -201,7 +201,7 @@ func (i *Image) Read() error { for idx, v1Layer := range v1Layers { layer := NewLayer(v1Layer) - err := layer.Read(&i.FileCatalog, i.Metadata, idx, i.contentCacheDir) + err := layer.Read(&i.FileCatalog, i.Metadata, idx, i.contentCacheDir, filter) if err != nil { return err } diff --git a/pkg/image/layer.go b/pkg/image/layer.go index d7517f8f..e494259f 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -15,7 +15,6 @@ import ( "github.com/anchore/stereoscope/pkg/event" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" - "github.com/bmatcuk/doublestar/v4" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sylabs/squashfs" @@ -25,69 +24,8 @@ import ( const SingularitySquashFSLayer = "application/vnd.sylabs.sif.layer.v1.squashfs" -var ( - binarySearchPaths = []string{ - "/usr/lib/jvm/**", "/usr/share/java/**", - "/usr/local/sbin/*", "/usr/local/bin/*", "/usr/sbin/*", "/usr/bin/*", "/sbin/*", "/bin/*", - "/usr/lib64/*", "/usr/lib/*", "/usr/share/*", "/usr/local/lib64/*", "/usr/local/lib/*", - } - catalogerGlobPatterns = map[string][]string{ - "alpmdb-cataloger": {"**/var/lib/pacman/local/**/desc"}, - "apkdb-cataloger": {"**/lib/apk/db/installed"}, - "conan-cataloger": {"**/conanfile.txt", "**/conan.lock"}, - "dartlang-lock-cataloger": {"**/pubspec.lock"}, - "dpkgdb-cataloger": {"**/var/lib/dpkg/{status,status.d/**}"}, - "dotnet-deps-cataloger": {"**/*.deps.json"}, - "go-mod-file-cataloger": {"**/go.mod"}, - "haskell-cataloger": {"**/stack.yaml", "**/stack.yaml.lock", "**/cabal.project.freeze"}, - "java-cataloger": { - // java archive - "**/*.jar", "**/*.war", "**/*.ear", "**/*.par", - "**/*.sar", "**/*.jpi", "**/*.hpi", "**/*.lpkg", - // zip java archive - "**/*.zip", - // tar java archive - "**/*.tar", "**/*.tar.gz", "**/*.tgz", "**/*.tar.bz", "**/*.tar.bz2", - "**/*.tbz", "**/*.tbz2", "**/*.tar.br", "**/*.tbr", "**/*.tar.lz4", "**/*.tlz4", - "**/*.tar.sz", "**/*.tsz", "**/*.tar.xz", "**/*.txz", "**/*.tar.zst", - }, - "java-pom-cataloger": {"**/pom.xml"}, - "javascript-package-cataloger": {"**/package.json"}, - "javascript-lock-cataloger": {"**/package-lock.json", "**/yarn.lock", "**/pnpm-lock.yaml"}, - "php-composer-installed-cataloger": {"**/installed.json"}, - "php-composer-lock-cataloger": {"**/composer.lock"}, - "portage-cataloger": {"**/var/db/pkg/*/*/CONTENTS"}, - "rpm-db-cataloger": {"**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}", "**/var/lib/rpmmanifest/container-manifest-2"}, - "rpm-file-cataloger": {"**/*.rpm"}, - "ruby-gemfile-cataloger": {"**/Gemfile.lock"}, - "ruby-gemspec-cataloger": {"**/specifications/**/*.gemspec"}, - "rust-cargo-lock-cataloger": {"**/Cargo.lock"}, - "cocoapods-cataloger": {"**/Podfile.lock"}, - - "sbom-cataloger": { - "**/*.syft.json", "**/*.bom.*", "**/*.bom", - "**/bom", "**/*.sbom.*", "**/*.sbom", "**/sbom", - "**/*.cdx.*", "**/*.cdx", "**/*.spdx.*", "**/*.spdx", - }, - - "python-index-cataloger": {"**/*requirements*.txt", "**/poetry.lock", "**/Pipfile.lock", "**/setup.py"}, - "python-package-cataloger": {"**/*egg-info/PKG-INFO", "**/*.egg-info", "**/*dist-info/METADATA", - "**/*.egg", "**/*.whl", // python egg and whl files - }, - - // TODO: binary cataloger needs different handling - "binary-cataloger": binarySearchPaths, - "go-module-binary-cataloger": binarySearchPaths, - "cargo-auditable-binary-cataloger": binarySearchPaths, - } - allPatterns = func() *[]string { - patterns := []string{} - for _, p := range catalogerGlobPatterns { - patterns = append(patterns, p...) - } - return &patterns - } -) +// PathFilter decides if a path has to be included in the index +type PathFilter = func(path string) bool // Layer represents a single layer within a container image. type Layer struct { @@ -143,7 +81,7 @@ func (l *Layer) uncompressedTarCache(uncompressedLayersCacheDir string) (string, // Read parses information from the underlying layer tar into this struct. This includes layer metadata, the layer // file tree, and the layer squash tree. -func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncompressedLayersCacheDir string) error { +func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncompressedLayersCacheDir string, filter PathFilter) error { var err error l.Tree = filetree.NewFileTree() l.fileCatalog = catalog @@ -173,7 +111,7 @@ func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncomp return err } - l.indexedContent, err = file.NewTarIndex(tarFilePath, l.indexer(monitor)) + l.indexedContent, err = file.NewTarIndex(tarFilePath, l.indexer(monitor, filter)) if err != nil { return fmt.Errorf("failed to read layer=%q tar : %w", l.Metadata.Digest, err) } @@ -187,9 +125,9 @@ func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncomp // Walk the more efficient walk if we're blessed with an io.ReaderAt. if ra, ok := r.(io.ReaderAt); ok { - err = file.WalkSquashFS(ra, l.squashfsVisitor(monitor)) + err = file.WalkSquashFS(ra, l.squashfsVisitor(monitor, filter)) } else { - err = file.WalkSquashFSFromReader(r, l.squashfsVisitor(monitor)) + err = file.WalkSquashFSFromReader(r, l.squashfsVisitor(monitor, filter)) } if err != nil { return fmt.Errorf("failed to walk layer=%q: %w", l.Metadata.Digest, err) @@ -242,7 +180,7 @@ func (l *Layer) FilesByMIMETypeFromSquash(mimeTypes ...string) ([]file.Reference return refs, nil } -func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { +func (l *Layer) indexer(monitor *progress.Manual, filter PathFilter) file.TarIndexVisitor { return func(index file.TarIndexEntry) error { var err error var entry = index.ToTarFileEntry() @@ -255,7 +193,7 @@ func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { }() metadata := file.NewMetadata(entry.Header, entry.Sequence, contents) - if !AnyGlobMatches(allPatterns(), metadata.Path) { + if !filter(metadata.Path) { return nil } @@ -304,10 +242,10 @@ func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { } } -func (l *Layer) squashfsVisitor(monitor *progress.Manual) file.SquashFSVisitor { +func (l *Layer) squashfsVisitor(monitor *progress.Manual, filter PathFilter) file.SquashFSVisitor { return func(fsys fs.FS, path string, d fs.DirEntry) error { - if !AnyGlobMatches(allPatterns(), path) { + if !filter(path) { return nil } @@ -379,15 +317,3 @@ func trackReadProgress(metadata LayerMetadata) *progress.Manual { return p } - -func AnyGlobMatches(patterns *[]string, path string) bool { - for _, p := range *patterns { - match, err := doublestar.PathMatch(p, path) - if err != nil { - continue - } else if match { - return true - } - } - return false -} diff --git a/pkg/image/sif/provider_test.go b/pkg/image/sif/provider_test.go index 15cacee8..c48979e8 100644 --- a/pkg/image/sif/provider_test.go +++ b/pkg/image/sif/provider_test.go @@ -36,6 +36,8 @@ func TestSingularityImageProvider_Provide(t *testing.T) { t.Run(tt.name, func(t *testing.T) { p := NewProviderFromPath(tt.path, file.NewTempDirGenerator("")) + filter := func(path string) bool { return true } + i, err := p.Provide(context.Background(), tt.userMetadata...) t.Cleanup(func() { _ = i.Cleanup() }) @@ -44,7 +46,7 @@ func TestSingularityImageProvider_Provide(t *testing.T) { } if err == nil { - if err := i.Read(); err != nil { + if err := i.Read(filter); err != nil { t.Fatal(err) } } diff --git a/pkg/imagetest/image_fixtures.go b/pkg/imagetest/image_fixtures.go index 7429f542..03c29f90 100644 --- a/pkg/imagetest/image_fixtures.go +++ b/pkg/imagetest/image_fixtures.go @@ -59,8 +59,9 @@ func PrepareFixtureImage(t testing.TB, source, name string) string { func GetFixtureImage(t testing.TB, source, name string) *image.Image { request := PrepareFixtureImage(t, source, name) + filter := func(path string) bool { return true } - i, err := stereoscope.GetImage(context.TODO(), request) + i, err := stereoscope.GetImage(context.TODO(), request, filter) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, i.Cleanup()) @@ -109,8 +110,9 @@ func skopeoCopyDockerArchiveToPath(t testing.TB, dockerArchivePath, destination func getFixtureImageFromTar(t testing.TB, tarPath string) *image.Image { request := fmt.Sprintf("docker-archive:%s", tarPath) + filter := func(path string) bool { return true } - i, err := stereoscope.GetImage(context.TODO(), request) + i, err := stereoscope.GetImage(context.TODO(), request, filter) require.NoError(t, err) t.Cleanup(func() { diff --git a/test/integration/fixture_image_simple_test.go b/test/integration/fixture_image_simple_test.go index 8cacf75d..1c21d19f 100644 --- a/test/integration/fixture_image_simple_test.go +++ b/test/integration/fixture_image_simple_test.go @@ -145,12 +145,13 @@ func BenchmarkSimpleImage_GetImage(b *testing.B) { continue } request := imagetest.PrepareFixtureImage(b, c.source, "image-simple") + filter := func(path string) bool { return true } b.Run(c.source, func(b *testing.B) { var bi *image.Image for i := 0; i < b.N; i++ { - bi, err = stereoscope.GetImage(context.TODO(), request) + bi, err = stereoscope.GetImage(context.TODO(), request, filter) b.Cleanup(func() { require.NoError(b, bi.Cleanup()) }) diff --git a/test/integration/mime_type_detection_test.go b/test/integration/mime_type_detection_test.go index f905ad34..0f0c487c 100644 --- a/test/integration/mime_type_detection_test.go +++ b/test/integration/mime_type_detection_test.go @@ -2,17 +2,19 @@ package integration import ( "context" + "testing" + "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/imagetest" "github.com/scylladb/go-set/strset" "github.com/stretchr/testify/assert" - "testing" ) func TestContentMIMETypeDetection(t *testing.T) { request := imagetest.PrepareFixtureImage(t, "docker-archive", "image-simple") + filter := func(path string) bool { return true } - img, err := stereoscope.GetImage(context.TODO(), request) + img, err := stereoscope.GetImage(context.TODO(), request, filter) assert.NoError(t, err) t.Cleanup(stereoscope.Cleanup) diff --git a/test/integration/oci_registry_source_test.go b/test/integration/oci_registry_source_test.go index 61671d9a..a7be6945 100644 --- a/test/integration/oci_registry_source_test.go +++ b/test/integration/oci_registry_source_test.go @@ -3,9 +3,10 @@ package integration import ( "context" "fmt" - "github.com/stretchr/testify/require" "testing" + "github.com/stretchr/testify/require" + "github.com/anchore/stereoscope" "github.com/stretchr/testify/assert" ) @@ -31,13 +32,15 @@ func TestOciRegistrySourceMetadata(t *testing.T) { imgStr := "anchore/test_images" ref := fmt.Sprintf("%s@%s", imgStr, digest) - img, err := stereoscope.GetImage(context.TODO(), "registry:"+ref) + filter := func(path string) bool { return true } + + img, err := stereoscope.GetImage(context.TODO(), "registry:"+ref, filter) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, img.Cleanup()) }) - require.NoError(t, img.Read()) + require.NoError(t, img.Read(filter)) assert.Len(t, img.Metadata.RepoDigests, 1) assert.Equal(t, "index.docker.io/"+ref, img.Metadata.RepoDigests[0]) diff --git a/test/integration/platform_test.go b/test/integration/platform_test.go index 818c5ef3..9b1f392b 100644 --- a/test/integration/platform_test.go +++ b/test/integration/platform_test.go @@ -3,12 +3,13 @@ package integration import ( "context" "fmt" + "strings" + "testing" + "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/image" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "strings" - "testing" ) func TestPlatformSelection(t *testing.T) { @@ -65,7 +66,8 @@ func TestPlatformSelection(t *testing.T) { tt.expectedErr = require.NoError } platformOpt := stereoscope.WithPlatform(platform) - img, err := stereoscope.GetImageFromSource(context.TODO(), imageName, tt.source, platformOpt) + filter := func(path string) bool { return true } + img, err := stereoscope.GetImageFromSource(context.TODO(), imageName, tt.source, filter, platformOpt) tt.expectedErr(t, err) require.NotNil(t, img) From 08e5b9cb5be78e2d62448a3dba3e1bb83916fa18 Mon Sep 17 00:00:00 2001 From: gnmahanth Date: Wed, 11 Jan 2023 14:42:18 +0000 Subject: [PATCH 04/23] fix lint issue Signed-off-by: gnmahanth --- pkg/image/layer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/image/layer.go b/pkg/image/layer.go index e494259f..a6a4e9d0 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -244,7 +244,6 @@ func (l *Layer) indexer(monitor *progress.Manual, filter PathFilter) file.TarInd func (l *Layer) squashfsVisitor(monitor *progress.Manual, filter PathFilter) file.SquashFSVisitor { return func(fsys fs.FS, path string, d fs.DirEntry) error { - if !filter(path) { return nil } From 178bc5cb18c405d0f7912fbb73a44af0f632a16d Mon Sep 17 00:00:00 2001 From: Bradley Jones Date: Fri, 3 Feb 2023 15:27:23 +0000 Subject: [PATCH 05/23] update go to 1.19 (#155) * feat: update golang to 1.19 Go 1.18 will become EOL with the upcoming 1.20 release Signed-off-by: Bradley Jones * chore: update golangci lint Move to a newer version of golangci lint compatibile with go 1.19 and remove now deprecated linters. Signed-off-by: Bradley Jones * chore: update deprecated ioutil functions Signed-off-by: Bradley Jones * chore: add crypto/internal/boring to bouncer ignore list Signed-off-by: Bradley Jones * fix: nolintlint formatting updated Signed-off-by: Bradley Jones --------- Signed-off-by: Bradley Jones --- .bouncer.yaml | 6 +- .github/workflows/validations.yaml | 2 +- .golangci.yaml | 4 - Makefile | 2 +- examples/basic.go | 5 +- go.mod | 56 +++- go.sum | 259 +----------------- internal/podman/config.go | 4 +- internal/podman/ssh.go | 5 +- internal/podman/ssh_test.go | 3 +- pkg/file/lazy_bounded_read_closer_test.go | 9 +- pkg/file/lazy_read_closer_test.go | 3 +- pkg/file/tar_index_test.go | 7 +- pkg/file/tarutil_test.go | 3 +- pkg/filetree/depth_first_path_walker.go | 2 +- pkg/filetree/filetree.go | 3 +- pkg/image/docker/manifest.go | 5 +- pkg/image/docker/manifest_test.go | 11 +- pkg/image/file_catalog_test.go | 5 +- pkg/image/image_test.go | 3 +- pkg/image/oci/registry_provider.go | 2 +- test/integration/fixture_image_simple_test.go | 5 +- .../fixture_image_symlinks_test.go | 4 +- 23 files changed, 98 insertions(+), 310 deletions(-) diff --git a/.bouncer.yaml b/.bouncer.yaml index dd2de4ae..d6a52389 100644 --- a/.bouncer.yaml +++ b/.bouncer.yaml @@ -3,4 +3,8 @@ permit: - MIT.* - Apache.* - MPL.* - - CC0-1.0 \ No newline at end of file + - CC0-1.0 + +ignore-packages: + # crypto/internal/boring is released under the openSSL license as a part of the Golang Standard Libary + - crypto/internal/boring diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index a15b99d9..4ed98466 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -17,7 +17,7 @@ on: pull_request: env: - GO_VERSION: "1.18.x" + GO_VERSION: "1.19.x" jobs: diff --git a/.golangci.yaml b/.golangci.yaml index 87912804..1cf5f179 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -7,7 +7,6 @@ linters: enable: - asciicheck - bodyclose - - deadcode - depguard - dogsled - dupl @@ -29,15 +28,12 @@ linters: - nakedret - nolintlint - revive - - rowserrcheck - staticcheck - - structcheck - stylecheck - typecheck - unconvert - unparam - unused - - varcheck - whitespace # do not enable... diff --git a/Makefile b/Makefile index a65afea8..7bdbc175 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ bootstrap: $(RESULTSDIR) ## Download and install all project dependencies (+ pre go mod download # install utilities [ -f "$(TEMPDIR)/benchstat" ] || GO111MODULE=off GOBIN=$(shell realpath $(TEMPDIR)) go get -u golang.org/x/perf/cmd/benchstat - [ -f "$(TEMPDIR)/golangci" ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.47.2 + [ -f "$(TEMPDIR)/golangci" ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.50.1 [ -f "$(TEMPDIR)/bouncer" ] || curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.4.0 .PHONY: static-analysis diff --git a/examples/basic.go b/examples/basic.go index 7d4522b5..23d3883a 100644 --- a/examples/basic.go +++ b/examples/basic.go @@ -3,10 +3,11 @@ package main import ( "context" "fmt" + "io" + "github.com/anchore/go-logger" "github.com/anchore/go-logger/adapter/logrus" - "io/ioutil" "os" "github.com/anchore/stereoscope" @@ -93,7 +94,7 @@ func main() { panic(err) } - content, err := ioutil.ReadAll(contentReader) + content, err := io.ReadAll(contentReader) if err != nil { panic(err) } diff --git a/go.mod b/go.mod index 49c46eee..eeae65c5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/anchore/stereoscope -go 1.16 +go 1.19 require ( github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible @@ -10,8 +10,6 @@ require ( github.com/bmatcuk/doublestar/v4 v4.0.2 github.com/containerd/containerd v1.6.12 github.com/docker/cli v20.10.12+incompatible - // docker/distribution for https://github.com/advisories/GHSA-qq97-vm5h-rrhg - github.com/docker/distribution v2.8.0+incompatible // indirect github.com/docker/docker v20.10.12+incompatible github.com/gabriel-vasile/mimetype v1.4.0 github.com/go-test/deep v1.0.8 @@ -32,3 +30,55 @@ require ( github.com/wagoodman/go-progress v0.0.0-20200621122631-1a2120f0695a golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd ) + +require ( + cloud.google.com/go v0.97.0 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/aws/aws-sdk-go-v2 v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.5.0 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.3.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ecr v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.3.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.6.0 // indirect + github.com/aws/smithy-go v1.6.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.10.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + // docker/distribution for https://github.com/advisories/GHSA-qq97-vm5h-rrhg + github.com/docker/distribution v2.8.0+incompatible // indirect + github.com/docker/docker-credential-helpers v0.6.4 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/pierrec/lz4/v4 v4.1.15 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/therootcompany/xz v1.0.1 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect + github.com/vbatts/tar-split v0.11.2 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/grpc v1.47.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 62682211..18aa55bd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,4 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -47,24 +46,18 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -88,29 +81,19 @@ github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg3 github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= -github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= -github.com/Microsoft/hcsshim v0.9.5/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 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/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= -github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 h1:imgMA0gN0TZx7PSa/pdWqXadBvrz8WsN6zySzCe4XX0= github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8/go.mod h1:+gPap4jha079qzRTUaehv+UZ6sSdaNwkH0D3b6zhTuk= github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8= @@ -147,7 +130,6 @@ github.com/aws/smithy-go v1.6.0 h1:T6puApfBcYiTIsaI+SYWqanjMt5pc3aoyyDrI+0YH54= github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 h1:p2I85zYI9z5/c/3Q0LiO3RtNXcmXHTtJfml/hV16zNg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04/go.mod h1:Z+bXnIbhKJYSvxNwsNnwde7pDKxuqlEZCbUBoTwAqf0= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -155,7 +137,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -164,22 +145,14 @@ github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTS github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -188,9 +161,7 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -201,9 +172,6 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -218,13 +186,11 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -233,15 +199,12 @@ github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= -github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.12 h1:kJ9b3mOFKf8yqo05Ob+tMoxvt1pbVWhnB0re9Y+k+8c= github.com/containerd/containerd v1.6.12/go.mod h1:K4Bw7gjgh4TnkmQY+py/PYQGp4e7xgnHAeg87VeWb3A= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -251,8 +214,6 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -261,9 +222,6 @@ github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1S github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= @@ -273,12 +231,9 @@ github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= -github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/stargz-snapshotter/estargz v0.10.0 h1:glqzafvxBBAMo+x2w2sdDjUDZeTqqLJmqZPY05qehCU= github.com/containerd/stargz-snapshotter/estargz v0.10.0/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= @@ -286,7 +241,6 @@ github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDG github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= @@ -299,23 +253,15 @@ github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNR github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= -github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= -github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -330,12 +276,9 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= @@ -349,7 +292,6 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.10+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA= github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -358,7 +300,6 @@ github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY= github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -392,13 +333,10 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -406,7 +344,6 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -415,34 +352,18 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 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/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.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -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.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -450,7 +371,6 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -466,7 +386,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -498,7 +417,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -512,12 +430,10 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-containerregistry v0.7.0 h1:u0onUUOcyoCDHEiJoyR1R1gx5er1+r06V5DBhUU5ndk= github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -533,7 +449,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -548,13 +463,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -563,7 +475,6 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -604,30 +515,22 @@ github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -651,17 +554,13 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -672,17 +571,12 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -696,59 +590,38 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -758,8 +631,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20210730191737-8e42a01fb1b7/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -768,8 +639,6 @@ github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -780,9 +649,6 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= -github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= @@ -807,8 +673,6 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -820,8 +684,6 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -833,7 +695,6 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -842,18 +703,12 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= -github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -871,7 +726,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -882,9 +736,7 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -893,10 +745,8 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -923,8 +773,6 @@ github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+x github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -938,11 +786,9 @@ github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaW github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d h1:KOxOL6qpmqwoPloNwi+CEgc1ayjHNOFNrvoOmeDOjDg= github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d/go.mod h1:JPirS5jde/CF5qIjcK4WX+eQmKXdPc6vcZkJ/P0hfPw= github.com/wagoodman/go-progress v0.0.0-20200621122631-1a2120f0695a h1:lV3ioFpbqvfZ1bXSQfloLWzom1OPU/5UjyU0wmBlkNc= @@ -965,15 +811,10 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -982,32 +823,10 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -1026,10 +845,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -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= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1101,7 +917,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1110,23 +925,15 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1179,7 +986,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1210,20 +1016,16 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1231,7 +1033,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1242,36 +1043,20 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1288,14 +1073,10 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1311,11 +1092,9 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1336,21 +1115,17 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -1426,19 +1201,16 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1465,7 +1237,6 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211111162719-482062a4217b/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1498,7 +1269,6 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= @@ -1524,7 +1294,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -1532,7 +1301,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -1551,7 +1319,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1570,56 +1337,34 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= 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= -k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= -k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= -k8s.io/cri-api v0.25.0/go.mod h1:J1rAyQkSJ2Q6I+aBMOVgg2/cbbebso6FNa0UagiR0kc= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -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/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-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -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= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/podman/config.go b/internal/podman/config.go index e015d641..2f2004eb 100644 --- a/internal/podman/config.go +++ b/internal/podman/config.go @@ -2,8 +2,8 @@ package podman import ( "fmt" - "io/ioutil" "net/url" + "os" "path/filepath" "github.com/docker/docker/pkg/homedir" @@ -108,7 +108,7 @@ func isScheme(uri, scheme string) bool { } func parseContainerConfig(path string) (*containersConfig, error) { - configBytes, err := ioutil.ReadFile(path) + configBytes, err := os.ReadFile(path) if err != nil { return nil, err } diff --git a/internal/podman/ssh.go b/internal/podman/ssh.go index a45caf22..ffa5711e 100644 --- a/internal/podman/ssh.go +++ b/internal/podman/ssh.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "fmt" - "io/ioutil" "net" "net/http" "net/url" @@ -67,7 +66,7 @@ func getSigners(keyPath, passphrase string) (signers []ssh.Signer, err error) { return } - key, err := ioutil.ReadFile(keyPath) + key, err := os.ReadFile(keyPath) if err != nil { return nil, err } @@ -116,7 +115,7 @@ func getAuthMethods(params *sshClientConfig) ([]ssh.AuthMethod, error) { } func getSSHCallback(params *sshClientConfig) ssh.HostKeyCallback { - // nolint: gosec + //nolint: gosec cb := ssh.InsecureIgnoreHostKey() if !params.secure { return cb diff --git a/internal/podman/ssh_test.go b/internal/podman/ssh_test.go index 4f07cf0e..68fcce97 100644 --- a/internal/podman/ssh_test.go +++ b/internal/podman/ssh_test.go @@ -4,7 +4,6 @@ import ( "crypto/x509" "errors" "fmt" - "io/ioutil" "net" "os" "path/filepath" @@ -67,7 +66,7 @@ func TestGetSigners(t *testing.T) { for _, tt := range testdata.PEMEncryptedKeys { t.Run(tt.Name, func(t *testing.T) { - kf, err := ioutil.TempFile(os.TempDir(), "key-"+tt.Name) + kf, err := os.CreateTemp(os.TempDir(), "key-"+tt.Name) assert.NoError(t, err) s, err := kf.Write(tt.PEMBytes) diff --git a/pkg/file/lazy_bounded_read_closer_test.go b/pkg/file/lazy_bounded_read_closer_test.go index 1913b55d..168666b4 100644 --- a/pkg/file/lazy_bounded_read_closer_test.go +++ b/pkg/file/lazy_bounded_read_closer_test.go @@ -2,7 +2,6 @@ package file import ( "io" - "io/ioutil" "os" "testing" @@ -12,7 +11,7 @@ import ( func getFixture(t *testing.T, filepath string) []byte { fh, err := os.Open(filepath) require.NoError(t, err) - expectedContents, err := ioutil.ReadAll(fh) + expectedContents, err := io.ReadAll(fh) require.NoError(t, err) return expectedContents @@ -25,7 +24,7 @@ func TestDeferredPartialReadCloser(t *testing.T) { dReader := newLazyBoundedReadCloser(p, 0, int64(len(contents))) require.Nil(t, dReader.file) - actualContents, err := ioutil.ReadAll(dReader) + actualContents, err := io.ReadAll(dReader) require.NoError(t, err) require.Equal(t, contents, actualContents) @@ -46,7 +45,7 @@ func TestDeferredPartialReadCloser_Seek(t *testing.T) { seek, err := dReader.Seek(off, io.SeekStart) require.Equal(t, off, seek) require.NoError(t, err) - actualContent, err := ioutil.ReadAll(dReader) + actualContent, err := io.ReadAll(dReader) require.NoError(t, err) require.Equal(t, content[int(off):], actualContent) @@ -63,7 +62,7 @@ func TestDeferredPartialReadCloser_PartialRead(t *testing.T) { var start, size int64 = 10, 7 dReader := newLazyBoundedReadCloser(p, start, size) - actualContents, err := ioutil.ReadAll(dReader) + actualContents, err := io.ReadAll(dReader) require.NoError(t, err) require.Equal(t, contents[start:start+size], actualContents) } diff --git a/pkg/file/lazy_read_closer_test.go b/pkg/file/lazy_read_closer_test.go index 66ebfe30..780ec8b5 100644 --- a/pkg/file/lazy_read_closer_test.go +++ b/pkg/file/lazy_read_closer_test.go @@ -2,7 +2,6 @@ package file import ( "io" - "io/ioutil" "testing" "github.com/stretchr/testify/require" @@ -15,7 +14,7 @@ func TestDeferredReadCloser(t *testing.T) { dReader := NewLazyReadCloser(filepath) require.Nil(t, dReader.file, "should not have a file, but we do somehow") - actualContents, err := ioutil.ReadAll(dReader) + actualContents, err := io.ReadAll(dReader) require.NotNil(t, dReader.file, "should have a file, but we do not somehow") require.NoError(t, err) require.Equal(t, allContent, actualContents) diff --git a/pkg/file/tar_index_test.go b/pkg/file/tar_index_test.go index 3f3bfb90..00f8e22a 100644 --- a/pkg/file/tar_index_test.go +++ b/pkg/file/tar_index_test.go @@ -6,7 +6,6 @@ package file import ( "archive/tar" "io" - "io/ioutil" "os" "strings" "testing" @@ -56,7 +55,7 @@ func TestIndexedTarIndex_GoCase(t *testing.T) { t.Errorf("mismatched header name: %q != %q", entry.Header.Name, name) } - actualContents, err := ioutil.ReadAll(entry.Reader) + actualContents, err := io.ReadAll(entry.Reader) if err != nil { t.Errorf("could not read from file reader: %+v", err) continue @@ -94,7 +93,7 @@ func TestIndexedTarReader_DuplicateEntries(t *testing.T) { t.Errorf("mismatched header name: %q != %q", entry.Header.Name, path) } - actualContents, err := ioutil.ReadAll(entry.Reader) + actualContents, err := io.ReadAll(entry.Reader) if err != nil { t.Errorf("could not read from file reader: %+v", err) continue @@ -108,7 +107,7 @@ func TestIndexedTarReader_DuplicateEntries(t *testing.T) { } func duplicateEntryTarballFixture(t *testing.T) *os.File { - tempFile, err := ioutil.TempFile("", "stereoscope-dup-tar-entry-fixture-XXXXXX") + tempFile, err := os.CreateTemp("", "stereoscope-dup-tar-entry-fixture-XXXXXX") if err != nil { t.Fatalf("could not create tempfile: %+v", err) } diff --git a/pkg/file/tarutil_test.go b/pkg/file/tarutil_test.go index b29ffd25..cd67f23e 100644 --- a/pkg/file/tarutil_test.go +++ b/pkg/file/tarutil_test.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "fmt" "io" - "io/ioutil" "os" "os/exec" "path" @@ -34,7 +33,7 @@ func TestReaderFromTar_GoCase(t *testing.T) { t.Fatal("could not get file reader from tar:", err) } - contents, err := ioutil.ReadAll(fileReader) + contents, err := io.ReadAll(fileReader) if err != nil { t.Fatal("could not read from file reader:", err) } diff --git a/pkg/filetree/depth_first_path_walker.go b/pkg/filetree/depth_first_path_walker.go index 108c2830..f246d8a9 100644 --- a/pkg/filetree/depth_first_path_walker.go +++ b/pkg/filetree/depth_first_path_walker.go @@ -54,7 +54,7 @@ func NewDepthFirstPathWalker(tree *FileTree, visitor FileNodeVisitor, conditions return w } -// nolint:gocognit +//nolint:gocognit func (w *DepthFirstPathWalker) Walk(from file.Path) (file.Path, *filenode.FileNode, error) { w.pathStack.Push(from) diff --git a/pkg/filetree/filetree.go b/pkg/filetree/filetree.go index 43df31af..5ca0f413 100644 --- a/pkg/filetree/filetree.go +++ b/pkg/filetree/filetree.go @@ -701,7 +701,8 @@ func (t *FileTree) Walk(fn func(path file.Path, f filenode.FileNode) error, cond // merge takes the given Tree and combines it with the current Tree, preferring files in the other Tree if there // are path conflicts. This is the basis function for squashing (where the current Tree is the bottom Tree and the // given Tree is the top Tree). -// nolint:gocognit,funlen +// +//nolint:gocognit,funlen func (t *FileTree) merge(upper *FileTree) error { conditions := tree.WalkConditions{ ShouldContinueBranch: func(n node.Node) bool { diff --git a/pkg/image/docker/manifest.go b/pkg/image/docker/manifest.go index bf4a280d..4b2b98db 100644 --- a/pkg/image/docker/manifest.go +++ b/pkg/image/docker/manifest.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "os" "github.com/anchore/stereoscope/internal/log" @@ -62,7 +61,7 @@ func extractManifest(tarPath string) (*dockerManifest, error) { return nil, err } - contents, err := ioutil.ReadAll(manifestReader) + contents, err := io.ReadAll(manifestReader) if err != nil { return nil, fmt.Errorf("unable to read manifest.json: %w", err) } @@ -92,7 +91,7 @@ func generateOCIManifest(tarPath string, manifest *dockerManifest) (*v1.Manifest return nil, nil, fmt.Errorf("unable to find docker config: %w", err) } - configContents, err := ioutil.ReadAll(configReader) + configContents, err := io.ReadAll(configReader) if err != nil { return nil, nil, fmt.Errorf("unable to read docker config: %w", err) } diff --git a/pkg/image/docker/manifest_test.go b/pkg/image/docker/manifest_test.go index 78a83b70..8bb01a11 100644 --- a/pkg/image/docker/manifest_test.go +++ b/pkg/image/docker/manifest_test.go @@ -4,11 +4,12 @@ import ( "bytes" "encoding/json" "flag" - "github.com/sergi/go-diff/diffmatchpatch" - "io/ioutil" + "io" "os" "testing" + "github.com/sergi/go-diff/diffmatchpatch" + "github.com/anchore/go-testutils" "github.com/go-test/deep" ) @@ -48,7 +49,7 @@ func TestNewManifest(t *testing.T) { t.Fatalf("could not open fixture: %+v", err) } - contents, err := ioutil.ReadAll(fh) + contents, err := io.ReadAll(fh) if err != nil { t.Fatalf("could not read fixture: %+v", err) } @@ -105,7 +106,7 @@ func TestManifestTags(t *testing.T) { t.Fatalf("could not open fixture: %+v", err) } - contents, err := ioutil.ReadAll(fh) + contents, err := io.ReadAll(fh) if err != nil { t.Fatalf("could not read fixture: %+v", err) } @@ -129,7 +130,7 @@ func TestAssembleOCIManifest(t *testing.T) { t.Fatalf("could not open config: %+v", err) } - configBytes, err := ioutil.ReadAll(fh) + configBytes, err := io.ReadAll(fh) if err != nil { t.Fatalf("could not read config: %+v", err) } diff --git a/pkg/image/file_catalog_test.go b/pkg/image/file_catalog_test.go index f830f230..b7211a7b 100644 --- a/pkg/image/file_catalog_test.go +++ b/pkg/image/file_catalog_test.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "fmt" "io" - "io/ioutil" "os" "os/exec" "path" @@ -211,7 +210,7 @@ func TestFileCatalog_FileContents(t *testing.T) { } opener := func() io.ReadCloser { - return ioutil.NopCloser(entries[0].Reader) + return io.NopCloser(entries[0].Reader) } catalog := NewFileCatalog() @@ -222,7 +221,7 @@ func TestFileCatalog_FileContents(t *testing.T) { t.Fatalf("could not get contents by ref: %+v", err) } - actual, err := ioutil.ReadAll(reader) + actual, err := io.ReadAll(reader) if err != nil { t.Fatalf("could not read content reader: %+v", err) } diff --git a/pkg/image/image_test.go b/pkg/image/image_test.go index 66319bd9..cd38df1b 100644 --- a/pkg/image/image_test.go +++ b/pkg/image/image_test.go @@ -3,7 +3,6 @@ package image import ( "crypto/sha256" "fmt" - "io/ioutil" "os" "testing" @@ -90,7 +89,7 @@ func TestImageAdditionalMetadata(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tempFile, err := ioutil.TempFile("", "") + tempFile, err := os.CreateTemp("", "") if err != nil { t.Fatalf("could not create tempfile: %+v", err) } diff --git a/pkg/image/oci/registry_provider.go b/pkg/image/oci/registry_provider.go index fe56ef3f..7768a407 100644 --- a/pkg/image/oci/registry_provider.go +++ b/pkg/image/oci/registry_provider.go @@ -96,7 +96,7 @@ func prepareRemoteOptions(ctx context.Context, ref name.Reference, registryOptio if registryOptions.InsecureSkipTLSVerify { t := &http.Transport{ - // nolint: gosec + //nolint: gosec TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } options = append(options, remote.WithTransport(t)) diff --git a/test/integration/fixture_image_simple_test.go b/test/integration/fixture_image_simple_test.go index 8cacf75d..24a44c9f 100644 --- a/test/integration/fixture_image_simple_test.go +++ b/test/integration/fixture_image_simple_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "strings" "testing" @@ -182,7 +181,7 @@ func BenchmarkSimpleImage_FetchSquashedContents(b *testing.B) { if err != nil { b.Fatalf("unable to read: %+v", err) } - _, err = ioutil.ReadAll(f.Contents()) + _, err = io.ReadAll(f.Contents()) } } }) @@ -317,7 +316,7 @@ func assertImageSimpleContents(t *testing.T, i *image.Image) { if !ok { t.Errorf("extra path found: %+v", path) } - b, err := ioutil.ReadAll(actual) + b, err := io.ReadAll(actual) if err != nil { t.Errorf("failed to read %+v : %+v", path, err) } diff --git a/test/integration/fixture_image_symlinks_test.go b/test/integration/fixture_image_symlinks_test.go index 7e05344a..1c2280da 100644 --- a/test/integration/fixture_image_symlinks_test.go +++ b/test/integration/fixture_image_symlinks_test.go @@ -5,7 +5,7 @@ package integration import ( "fmt" - "io/ioutil" + "io" "testing" "github.com/anchore/stereoscope/pkg/file" @@ -140,7 +140,7 @@ func fetchContents(t *testing.T, i *image.Image, cfg linkFetchConfig) string { if err != nil { t.Fatalf("could not fetch contents of %+v: %+v", cfg.linkPath, err) } - b, err := ioutil.ReadAll(contents) + b, err := io.ReadAll(contents) if err != nil { t.Fatalf("unable to fetch contents for %+v : %+v", cfg, err) } From 61a4559a3f772addfc8c27d1f9929e76734575e4 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 8 Feb 2023 10:46:30 -0500 Subject: [PATCH 06/23] Add additional catalog indexes for performance (#154) * add additional catalog indexes for performance Signed-off-by: Alex Goodman * [wip] link resolution Signed-off-by: Alex Goodman * add leaf link resolution on tree responses (defer ancestor link resolution) Signed-off-by: Alex Goodman * add filetree search context Signed-off-by: Alex Goodman * add tests for new search context object Signed-off-by: Alex Goodman * remove unused tar header fields from file.Metadata struct Signed-off-by: Alex Goodman * use singular file type definitions Signed-off-by: Alex Goodman * add logging for filetree searches Signed-off-by: Alex Goodman * add limited support for glob classes and alternatives Signed-off-by: Alex Goodman * add failing test to show that index shortcircuits correct behavior Signed-off-by: Alex Goodman * add link resolution via filetree search context Signed-off-by: Alex Goodman * allow index symlink resolution to function through cycles Signed-off-by: Alex Goodman * add tests for filetree.Index Signed-off-by: Alex Goodman * add search by parent basename and fix requirements filtering Signed-off-by: Alex Goodman * sort search results Signed-off-by: Alex Goodman * change file.Type to int + fix layer 0 squashed search context Signed-off-by: Alex Goodman * more cleanup Signed-off-by: Alex Goodman * switch to generic set implementation Signed-off-by: Alex Goodman * update linter Signed-off-by: Alex Goodman * replace generic set implemetation with plain set (unstable in go1.19) Signed-off-by: Alex Goodman * introduce filtree builter and foster usage of reader interfaces Signed-off-by: Alex Goodman * rename content helper functions Signed-off-by: Alex Goodman * update docs with background Signed-off-by: Alex Goodman * fix get_xid for cross compilation Signed-off-by: Alex Goodman * upgrade CI validations workflow Signed-off-by: Alex Goodman * fix snapshot builds Signed-off-by: Alex Goodman * add tests for file.Index.GetByFileType Signed-off-by: Alex Goodman * rename file.Type and file.Resolution Signed-off-by: Alex Goodman * ensure that glob results match search facade Signed-off-by: Alex Goodman * replace stringset implementation + move resolution tests Signed-off-by: Alex Goodman * add note about podman dependency for testing Signed-off-by: Alex Goodman * address PR comments Signed-off-by: Alex Goodman * remove extra whitespace Signed-off-by: Alex Goodman * constrain OS build support Signed-off-by: Alex Goodman * update/remove TODO comments Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- .github/actions/bootstrap/action.yaml | 80 ++ .github/scripts/build.sh | 82 ++ .github/scripts/coverage.py | 36 + .github/scripts/go-mod-tidy-check.sh | 30 + .github/workflows/benchmark-testing.yaml | 58 + .github/workflows/validations.yaml | 173 +-- .gitignore | 1 + .golangci.yaml | 23 +- DEVELOPING.md | 53 + Makefile | 164 ++- examples/basic.go | 2 +- go.mod | 8 +- go.sum | 19 +- internal/string_set.go | 76 ++ internal/string_set_test.go | 226 ++++ internal/stringset.go | 38 - pkg/file/get_xid.go | 20 + pkg/file/get_xid_win.go | 12 + pkg/file/id.go | 20 + pkg/file/id_set.go | 75 ++ pkg/file/id_set_test.go | 226 ++++ pkg/file/metadata.go | 111 +- pkg/file/metadata_test.go | 58 +- pkg/file/path_set.go | 69 +- pkg/file/path_set_test.go | 226 ++++ pkg/file/reference.go | 9 +- pkg/file/resolution.go | 158 +++ pkg/file/resolution_test.go | 391 +++++++ pkg/file/tarutil.go | 2 +- pkg/file/tarutil_test.go | 42 +- .../link_to_link_to_new_readme | 1 + .../symlinks-simple/link_to_new_readme | 1 + pkg/file/test-fixtures/symlinks-simple/readme | 2 + pkg/file/type.go | 118 +- pkg/filetree/builder.go | 56 + pkg/filetree/depth_first_path_walker.go | 27 +- pkg/filetree/depth_first_path_walker_test.go | 4 +- pkg/filetree/filenode/filenode.go | 27 +- pkg/filetree/filetree.go | 349 +++--- pkg/filetree/filetree_test.go | 670 +++++++---- pkg/filetree/glob.go | 49 +- pkg/filetree/glob_parser.go | 371 ++++++ pkg/filetree/glob_parser_test.go | 663 +++++++++++ pkg/filetree/glob_test.go | 30 +- pkg/filetree/index.go | 297 +++++ pkg/filetree/index_test.go | 776 +++++++++++++ pkg/filetree/interfaces.go | 45 + pkg/filetree/node_access.go | 50 + pkg/filetree/search.go | 477 ++++++++ pkg/filetree/search_test.go | 1015 +++++++++++++++++ pkg/filetree/union_filetree.go | 14 +- pkg/filetree/union_filetree_test.go | 16 +- pkg/image/content_helpers.go | 33 +- pkg/image/docker/tarball_provider.go | 2 +- pkg/image/file_catalog.go | 97 +- pkg/image/file_catalog_test.go | 809 +++++++++++-- pkg/image/image.go | 73 +- pkg/image/image_test.go | 11 +- pkg/image/layer.go | 148 ++- pkg/image/oci/directory_provider.go | 2 +- pkg/image/oci/registry_provider.go | 2 +- pkg/image/sif/provider.go | 2 +- .../test-fixtures/generators/fixture-2.sh | 52 + pkg/tree/depth_first_walker.go | 2 +- pkg/tree/node/id.go | 70 +- pkg/tree/node/id_test.go | 226 ++++ pkg/tree/tree.go | 6 +- .../fixture_image_opaque_directory_test.go | 2 +- test/integration/fixture_image_simple_test.go | 30 +- .../fixture_image_symlinks_test.go | 50 +- test/integration/mime_type_detection_test.go | 2 +- test/integration/utils_test.go | 24 +- 72 files changed, 8047 insertions(+), 1142 deletions(-) create mode 100644 .github/actions/bootstrap/action.yaml create mode 100755 .github/scripts/build.sh create mode 100755 .github/scripts/coverage.py create mode 100755 .github/scripts/go-mod-tidy-check.sh create mode 100644 .github/workflows/benchmark-testing.yaml create mode 100644 DEVELOPING.md create mode 100644 internal/string_set.go create mode 100644 internal/string_set_test.go delete mode 100644 internal/stringset.go create mode 100644 pkg/file/get_xid.go create mode 100644 pkg/file/get_xid_win.go create mode 100644 pkg/file/id.go create mode 100644 pkg/file/id_set.go create mode 100644 pkg/file/id_set_test.go create mode 100644 pkg/file/path_set_test.go create mode 100644 pkg/file/resolution.go create mode 100644 pkg/file/resolution_test.go create mode 120000 pkg/file/test-fixtures/symlinks-simple/link_to_link_to_new_readme create mode 120000 pkg/file/test-fixtures/symlinks-simple/link_to_new_readme create mode 100644 pkg/file/test-fixtures/symlinks-simple/readme create mode 100644 pkg/filetree/builder.go create mode 100644 pkg/filetree/glob_parser.go create mode 100644 pkg/filetree/glob_parser_test.go create mode 100644 pkg/filetree/index.go create mode 100644 pkg/filetree/index_test.go create mode 100644 pkg/filetree/interfaces.go create mode 100644 pkg/filetree/node_access.go create mode 100644 pkg/filetree/search.go create mode 100644 pkg/filetree/search_test.go create mode 100755 pkg/image/test-fixtures/generators/fixture-2.sh create mode 100644 pkg/tree/node/id_test.go diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml new file mode 100644 index 00000000..d4ed5970 --- /dev/null +++ b/.github/actions/bootstrap/action.yaml @@ -0,0 +1,80 @@ +name: "Bootstrap" +description: "Bootstrap all tools and dependencies" +inputs: + go-version: + description: "Go version to install" + required: true + default: "1.19.x" + use-go-cache: + description: "Restore go cache" + required: true + default: "true" + cache-key-prefix: + description: "Prefix all cache keys with this value" + required: true + default: "831180ac25" + build-cache-key-prefix: + description: "Prefix build cache key with this value" + required: true + default: "f8b6d31dea" + bootstrap-apt-packages: + description: "Space delimited list of tools to install via apt" + default: "" + +runs: + using: "composite" + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ inputs.go-version }} + + - name: Restore tool cache + id: tool-cache + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/.tmp + key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }} + + # note: we need to keep restoring the go mod cache before bootstrapping tools since `go install` is used in + # some installations of project tools. + - name: Restore go module cache + id: go-mod-cache + if: inputs.use-go-cache == 'true' + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}- + + - name: (cache-miss) Bootstrap project tools + shell: bash + if: steps.tool-cache.outputs.cache-hit != 'true' + run: make bootstrap-tools + + - name: Restore go build cache + id: go-cache + if: inputs.use-go-cache == 'true' + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + key: ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}- + + - name: (cache-miss) Bootstrap go dependencies + shell: bash + if: steps.go-mod-cache.outputs.cache-hit != 'true' && inputs.use-go-cache == 'true' + run: make bootstrap-go + + - name: Bootstrap CI dependencies + shell: bash + run: make ci-bootstrap + + - name: Install apt packages + if: inputs.bootstrap-apt-packages != '' + shell: bash + run: | + DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }} diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh new file mode 100755 index 00000000..50beb016 --- /dev/null +++ b/.github/scripts/build.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -uo pipefail + +SNAPSHOT_DIR=$1 + +# Based on https://gist.github.com/eduncan911/68775dba9d3c028181e4 and https://gist.github.com/makeworld-the-better-one/e1bb127979ae4195f43aaa3ad46b1097 +# but improved to use the `go` command so it never goes out of date. + +type setopt >/dev/null 2>&1 + +contains() { + # Source: https://stackoverflow.com/a/8063398/7361270 + [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] +} + +mkdir -p "${SNAPSHOT_DIR}" + +BUILD_TARGET=./examples +OUTPUT=${SNAPSHOT_DIR}/stereoscope-example +FAILURES="" + +# You can set your own flags on the command line +FLAGS=${FLAGS:-"-ldflags=\"-s -w\""} + +# A list of OSes and architectures to not build for, space-separated +# It can be set from the command line when the script is called. +NOT_ALLOWED_OS=${NOT_ALLOWED_OS:-"js android ios solaris illumos aix dragonfly plan9 freebsd openbsd netbsd"} +NOT_ALLOWED_ARCH=${NOT_ALLOWED_ARCH:-"riscv64 mips mips64 mips64le ppc64 ppc64le s390x wasm"} + + +# Get all targets +while IFS= read -r target; do + GOOS=${target%/*} + GOARCH=${target#*/} + BIN_FILENAME="${OUTPUT}-${GOOS}-${GOARCH}" + + if contains "$NOT_ALLOWED_OS" "$GOOS" ; then + continue + fi + + if contains "$NOT_ALLOWED_ARCH" "$GOARCH" ; then + continue + fi + + # Check for arm and set arm version + if [[ $GOARCH == "arm" ]]; then + # Set what arm versions each platform supports + if [[ $GOOS == "darwin" ]]; then + arms="7" + elif [[ $GOOS == "windows" ]]; then + # This is a guess, it's not clear what Windows supports from the docs + # But I was able to build all these on my machine + arms="5 6 7" + elif [[ $GOOS == *"bsd" ]]; then + arms="6 7" + else + # Linux goes here + arms="5 6 7" + fi + + # Now do the arm build + for GOARM in $arms; do + BIN_FILENAME="${OUTPUT}-${GOOS}-${GOARCH}${GOARM}" + if [[ "${GOOS}" == "windows" ]]; then BIN_FILENAME="${BIN_FILENAME}.exe"; fi + CMD="GOARM=${GOARM} GOOS=${GOOS} GOARCH=${GOARCH} go build $FLAGS -o ${BIN_FILENAME} ${BUILD_TARGET}" + echo "${CMD}" + eval "${CMD}" || FAILURES="${FAILURES} ${GOOS}/${GOARCH}${GOARM}" + done + else + # Build non-arm here + if [[ "${GOOS}" == "windows" ]]; then BIN_FILENAME="${BIN_FILENAME}.exe"; fi + CMD="GOOS=${GOOS} GOARCH=${GOARCH} go build $FLAGS -o ${BIN_FILENAME} ${BUILD_TARGET}" + echo "${CMD}" + eval "${CMD}" || FAILURES="${FAILURES} ${GOOS}/${GOARCH}" + fi +done <<< "$(go tool dist list)" + +if [[ "${FAILURES}" != "" ]]; then + echo "" + echo "build failed for: ${FAILURES}" + exit 1 +fi \ No newline at end of file diff --git a/.github/scripts/coverage.py b/.github/scripts/coverage.py new file mode 100755 index 00000000..db14135c --- /dev/null +++ b/.github/scripts/coverage.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import shlex + + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +if len(sys.argv) < 3: + print("Usage: coverage.py [threshold] [go-coverage-report]") + sys.exit(1) + + +threshold = float(sys.argv[1]) +report = sys.argv[2] + + +args = shlex.split(f"go tool cover -func {report}") +p = subprocess.run(args, capture_output=True, text=True) + +percent_coverage = float(p.stdout.splitlines()[-1].split()[-1].replace("%", "")) +print(f"{bcolors.BOLD}Coverage: {percent_coverage}%{bcolors.ENDC}") + +if percent_coverage < threshold: + print(f"{bcolors.BOLD}{bcolors.FAIL}Coverage below threshold of {threshold}%{bcolors.ENDC}") + sys.exit(1) diff --git a/.github/scripts/go-mod-tidy-check.sh b/.github/scripts/go-mod-tidy-check.sh new file mode 100755 index 00000000..28f22fcd --- /dev/null +++ b/.github/scripts/go-mod-tidy-check.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -eu + +ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX") +TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX") + +trap "cp -p ${ORIGINAL_STATE_DIR}/* ./ && git update-index -q --refresh && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT + +# capturing original state of files... +cp go.mod go.sum "${ORIGINAL_STATE_DIR}" + +# capturing state of go.mod and go.sum after running go mod tidy... +go mod tidy +cp go.mod go.sum "${TIDY_STATE_DIR}" + +set +e + +# detect difference between the git HEAD state and the go mod tidy state +DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod") +DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum") + +if [[ -n "${DIFF_MOD}" || -n "${DIFF_SUM}" ]]; then + echo "go.mod diff:" + echo "${DIFF_MOD}" + echo "go.sum diff:" + echo "${DIFF_SUM}" + echo "" + printf "FAILED! go.mod and/or go.sum are NOT tidy; please run 'go mod tidy'.\n\n" + exit 1 +fi diff --git a/.github/workflows/benchmark-testing.yaml b/.github/workflows/benchmark-testing.yaml new file mode 100644 index 00000000..4cd87594 --- /dev/null +++ b/.github/workflows/benchmark-testing.yaml @@ -0,0 +1,58 @@ +name: "Benchmark testing" + +on: + workflow_dispatch: + pull_request: + +jobs: + + Benchmark-Test: + name: "Benchmark tests" + runs-on: ubuntu-20.04 + # note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and + # we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter + # the job by event. + steps: + - uses: actions/checkout@v3 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + + - name: Restore base benchmark result + uses: actions/cache@v3 + with: + path: test/results/benchmark-main.txt + # use base sha for PR or new commit hash for main push in benchmark result key + key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }} + + - name: Run benchmark tests + id: benchmark + run: | + REF_NAME=${GITHUB_REF##*/} make benchmark + OUTPUT=$(make show-benchstat) + OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters + OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters + OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters + echo "::set-output name=result::$OUTPUT" + + - uses: actions/upload-artifact@v3 + with: + name: benchmark-test-results + path: test/results/**/* + + - name: Update PR benchmark results comment + uses: marocchino/sticky-pull-request-comment@v2 + continue-on-error: true + with: + header: benchmark + message: | + ### Benchmark Test Results + +
+ Benchmark results from the latest changes vs base branch + + ``` + ${{ steps.benchmark.outputs.result }} + ``` + +
diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 4ed98466..f0314a6b 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -16,79 +16,28 @@ on: - main pull_request: -env: - GO_VERSION: "1.19.x" - jobs: Static-Analysis: name: "Static analysis" runs-on: ubuntu-20.04 steps: - - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} - - - uses: actions/checkout@v2 - - - name: Restore tool cache - id: tool-cache - uses: actions/cache@v2.1.3 - with: - path: ${{ github.workspace }}/.tmp - key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }} + - uses: actions/checkout@v3 - - name: Restore go cache - id: go-cache - uses: actions/cache@v2.1.3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go-${{ env.GO_VERSION }}- - - - name: (cache-miss) Bootstrap all project dependencies - if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true' - run: make bootstrap + - name: Bootstrap environment + uses: ./.github/actions/bootstrap - - name: Bootstrap CI environment dependencies - run: make ci-bootstrap - - - name: Run static analysis - run: make static-analysis + - name: Run static analysis + run: make static-analysis Unit-Test: name: "Unit tests" runs-on: ubuntu-20.04 steps: - - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} - - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - name: Restore tool cache - id: tool-cache - uses: actions/cache@v2.1.3 - with: - path: ${{ github.workspace }}/.tmp - key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }} - - - name: Restore go cache - id: go-cache - uses: actions/cache@v2.1.3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go-${{ env.GO_VERSION }}- - - - name: (cache-miss) Bootstrap all project dependencies - if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true' - run: make bootstrap - - - name: Bootstrap CI environment dependencies - run: make ci-bootstrap + - name: Bootstrap environment + uses: ./.github/actions/bootstrap - name: Run unit tests run: make unit @@ -102,11 +51,10 @@ jobs: name: "Integration tests" runs-on: ubuntu-20.04 steps: - - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} + - uses: actions/checkout@v3 - - uses: actions/checkout@v2 + - name: Bootstrap environment + uses: ./.github/actions/bootstrap - name: Enable systemd for podman socket activation run: | @@ -128,29 +76,6 @@ jobs: with: limit-access-to-actor: true - - name: Restore tool cache - id: tool-cache - uses: actions/cache@v2.1.3 - with: - path: ${{ github.workspace }}/.tmp - key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }} - - - name: Restore go cache - id: go-cache - uses: actions/cache@v2.1.3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go-${{ env.GO_VERSION }}- - - - name: (cache-miss) Bootstrap all project dependencies - if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true' - run: make bootstrap - - - name: Bootstrap CI environment dependencies - run: make ci-bootstrap - - name: Build key for test-fixture cache run: make integration-fingerprint @@ -177,77 +102,15 @@ jobs: - name: Run integration tests run: make integration - Benchmark-Test: - name: "Benchmark tests" + Build-Snapshot-Artifacts: + name: "Build snapshot artifacts" runs-on: ubuntu-20.04 - # note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and - # we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter - # the job by event. steps: - - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} - - - uses: actions/checkout@v2 - - - name: Restore tool cache - id: tool-cache - uses: actions/cache@v2.1.3 - with: - path: ${{ github.workspace }}/.tmp - key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }} - - - name: Restore go cache - id: go-cache - uses: actions/cache@v2.1.3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go-${{ env.GO_VERSION }}- - - - name: (cache-miss) Bootstrap all project dependencies - if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true' - run: make bootstrap - - - name: Bootstrap CI environment dependencies - run: make ci-bootstrap - - - name: Restore base benchmark result - uses: actions/cache@v2 - with: - path: test/results/benchmark-main.txt - # use base sha for PR or new commit hash for main push in benchmark result key - key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }} - - - name: Run benchmark tests - id: benchmark - run: | - REF_NAME=${GITHUB_REF##*/} make benchmark - OUTPUT=$(make show-benchstat) - OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters - OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters - OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters - echo "::set-output name=result::$OUTPUT" - - - uses: actions/upload-artifact@v2 - with: - name: benchmark-test-results - path: test/results/**/* - - - name: Update PR benchmark results comment - uses: marocchino/sticky-pull-request-comment@v2 - continue-on-error: true - with: - header: benchmark - message: | - ### Benchmark Test Results + - uses: actions/checkout@v3 -
- Benchmark results from the latest changes vs base branch + - name: Bootstrap environment + uses: ./.github/actions/bootstrap - ``` - ${{ steps.benchmark.outputs.result }} - ``` + - name: Build snapshot artifacts + run: make snapshot -
diff --git a/.gitignore b/.gitignore index 930ca30b..26630caf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ coverage.txt **/test-fixtures/cache/ **/*.fingerprint +snapshot/ # Binaries for programs and plugins *.exe diff --git a/.golangci.yaml b/.golangci.yaml index 1cf5f179..a92c5c43 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -26,7 +26,6 @@ linters: - ineffassign - misspell - nakedret - - nolintlint - revive - staticcheck - stylecheck @@ -37,19 +36,23 @@ linters: - whitespace # do not enable... +# - deadcode # The owner seems to have abandoned the linter. Replaced by "unused". # - gochecknoglobals # - gochecknoinits # this is too aggressive # - godot # - godox # - goerr113 -# - golint # deprecated -# - gomnd # this is too aggressive -# - interfacer # this is a good idea, but is no longer supported and is prone to false positives -# - lll # without a way to specify per-line exception cases, this is not usable -# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations +# - golint # deprecated +# - gomnd # this is too aggressive +# - interfacer # this is a good idea, but is no longer supported and is prone to false positives +# - lll # without a way to specify per-line exception cases, this is not usable +# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations # - nestif -# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code -# - scopelint # deprecated +# - nolintlint # as of go1.19 this conflicts with the behavior of gofmt, which is a deal-breaker (lint-fix will still fail when running lint) +# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code +# - rowserrcheck # not in a repo with sql, so this is not useful +# - scopelint # deprecated +# - structcheck # The owner seems to have abandoned the linter. Replaced by "unused". # - testpackage -# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90) - +# - varcheck # The owner seems to have abandoned the linter. Replaced by "unused". +# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90) diff --git a/DEVELOPING.md b/DEVELOPING.md new file mode 100644 index 00000000..43577bb2 --- /dev/null +++ b/DEVELOPING.md @@ -0,0 +1,53 @@ +# Developing + +## Getting started + +In order to test and develop in this repo you will need the following dependencies installed: +- Golang +- docker +- make +- podman (for benchmark and integration tests only) + +After cloning the following step can help you get setup: +1. run `make bootstrap` to download go mod dependencies, create the `/.tmp` dir, and download helper utilities. +2. run `make help` to view the selection of developer commands in the Makefile + +The main make tasks for common static analysis and testing are `lint`, `lint-fix`, `unit`, and `integration`. + +See `make help` for all the current make tasks. + +## Background + +Stereoscope is a library for reading and manipulating container images. It is capable of parsing multiple image +sources, providing a single abstraction for interacting with them. Ultimately this provides a squashfs-like +interface for interacting with image layers as well as a content API for accessing files contained within +the image. + +**Overview of objects:** +- `image.Image`: Once parsed with `image.Read()` this object represents a container image. Consists of a sequence of `image.Layer` objects, a `image.FileCatalog` for accessing files, and `filetree.SearchContext` for searching for files from the squashed representation of the image filesystem. Additionally exposes GGCR `v1.Image` objects for accessing the raw image metadata. +- `image.Layer`: represents a single layer of the image. Consists of a `filetree.FileTree` that represents the raw layer contents, and a `filetree.SearchContext` for searching for files relative to the raw (single layer) filetree as well as the squashed representation of the layer relative to all layers below this one. Additionally exposes GGCR `v1.Layer` objects for accessing the raw layer metadata. +- `filetree.FileTree`: a tree representing a filesystem. All nodes represent real paths (paths with no link resolution anywhere in the path) and are absolute paths (start with / and contain no relative path elements [e.g. ../ or ./]). This represents the filesystem structure and each node has a reference to the file metadata for that path. +- `file.Reference`: a unique file in the filesystem, identified by an absolute, real path as well as an integer ID (`file.ID`s). These are used to reference concrete nodes in the `filetree.FileTree` and `image.FileCatalog` objects. +- `file.Index`: stores all known `file.Reference` and `file.Metadata`. Entries are indexed with a variety of ways to provide fast access to references and metadata without needing to crawl the tree. This is especially useful for speeding up globbing. +- `image.FileCatalog`: an image-aware extension of `file.Index` that additionally relates `image.Layers` to `file.IDs` and provides a content API for any files contained within the image (regardless of which layer or squashed representation it exists in). + +### Searching for files + +Searching for files is exposed to users in three ways: +- search by file path +- search by file glob +- search by file content MIME type + +Searching itself is performed two different ways: +- search the `image.FileCatalog` on the image by a heuristic +- search the `filetree.FileTree` directly + +The "best way" to search is automatically determined in the `filetree.searchContext` object, exposed on `image.Image` and `image.Layer` objects as a `filetree.Searcher` for general use. + +### File trees + +The `filetree.FileTree` object represents a filesystem and consists of `filenode.Node` objects. The tree itself leverages `tree.Tree` as a generic datastructure. What `filetree.FileTree` adds is the concept of file types, the semantics of each type, the ability to resolve links based on a given strategy, merging of trees with the same semantics of a union filesystem (e.g. whiteout files), and the ability to search for files via direct paths or globs. + +The `fs.FS` abstraction has been implemented on `filetree.FileTree` to allow for easy integration with the standard library as well as to interop with the `doublestar` library to facilitate globing. Using the `fs.FS` abstraction for filetree operations is faster than OS interactions with the filesystem directly but relatively slower than the indexes provided by `image.FileCatalog` and `file.Index`. + +`filetre.FileTree` objects can be created with a corresponding `file.Index` object by leveraging the `filetree.Builder` object, which aids in the indexing of files. diff --git a/Makefile b/Makefile index 7bdbc175..b5f3d3d6 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,15 @@ -TEMPDIR = ./.tmp -RESULTSDIR = test/results -COVER_REPORT = $(RESULTSDIR)/unit-coverage-details.txt -COVER_TOTAL = $(RESULTSDIR)/unit-coverage-summary.txt -LINTCMD = $(TEMPDIR)/golangci-lint run --tests=false --config .golangci.yaml +TEMP_DIR = ./.tmp + +# Command templates ################################# +LINT_CMD = $(TEMP_DIR)/golangci-lint run --tests=false --config .golangci.yaml + +# Tool versions ################################# +GOLANGCILINT_VERSION := v1.51.0 +GOSIMPORTS_VERSION := v0.3.5 +BOUNCER_VERSION := v0.4.0 +CHRONICLE_VERSION := v0.5.1 + +# Formatting variables ################################# BOLD := $(shell tput -T linux bold) PURPLE := $(shell tput -T linux setaf 5) GREEN := $(shell tput -T linux setaf 2) @@ -11,57 +18,72 @@ RED := $(shell tput -T linux setaf 1) RESET := $(shell tput -T linux sgr0) TITLE := $(BOLD)$(PURPLE) SUCCESS := $(BOLD)$(GREEN) -# the quality gate lower threshold for unit test total % coverage (by function statements) -COVERAGE_THRESHOLD := 48 -ifeq "$(strip $(VERSION))" "" - override VERSION = $(shell git describe --always --tags --dirty) -endif +# Test variables ################################# +COVERAGE_THRESHOLD := 55 # the quality gate lower threshold for unit test total % coverage (by function statements) + +## Build variables ################################# +SNAPSHOT_DIR := ./snapshot +VERSION := $(shell git describe --dirty --always --tags) -ifndef TEMPDIR - $(error TEMPDIR is not set) +ifndef VERSION + $(error VERSION is not set) endif -ifndef REF_NAME - REF_NAME = $(VERSION) +ifndef TEMP_DIR + $(error TEMP_DIR is not set) endif define title @printf '$(TITLE)$(1)$(RESET)\n' endef +define safe_rm_rf + bash -c 'test -z "$(1)" && false || rm -rf $(1)' +endef + +define safe_rm_rf_children + bash -c 'test -z "$(1)" && false || rm -rf $(1)/*' +endef + .PHONY: all -all: static-analysis test ## Run all checks (linting, all tests, and dependencies license checks) +all: static-analysis test ## Run all linux-based checks (linting, license check, unit, integration, and linux compare tests) @printf '$(SUCCESS)All checks pass!$(RESET)\n' +.PHONY: static-analysis +static-analysis: check-go-mod-tidy lint check-licenses ## Run all static analysis checks + .PHONY: test -test: unit integration benchmark ## Run all levels of test +test: unit integration benchmark ## Run all tests (currently unit and integrations) -.PHONY: help -help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}' + +## Bootstrapping targets ################################# .PHONY: ci-bootstrap ci-bootstrap: bootstrap - sudo apt install -y bc curl -sLO https://github.com/sylabs/singularity/releases/download/v3.10.0/singularity-ce_3.10.0-focal_amd64.deb && sudo apt-get install -y -f ./singularity-ce_3.10.0-focal_amd64.deb -$(RESULTSDIR): - mkdir -p $(RESULTSDIR) - -.PHONY: boostrap -bootstrap: $(RESULTSDIR) ## Download and install all project dependencies (+ prep tooling in the ./tmp dir) - $(call title,Downloading dependencies) - @pwd - # prep temp dirs - mkdir -p $(TEMPDIR) - mkdir -p $(RESULTSDIR) - # install go dependencies +.PHONY: bootstrap +bootstrap: $(TEMP_DIR) bootstrap-go bootstrap-tools ## Download and install all tooling dependencies (+ prep tooling in the ./tmp dir) + $(call title,Bootstrapping dependencies) + +.PHONY: bootstrap-tools +bootstrap-tools: $(TEMP_DIR) + GO111MODULE=off GOBIN=$(realpath $(TEMP_DIR)) go get -u golang.org/x/perf/cmd/benchstat + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMP_DIR)/ $(GOLANGCILINT_VERSION) + curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMP_DIR)/ $(BOUNCER_VERSION) + curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMP_DIR)/ $(CHRONICLE_VERSION) + # the only difference between goimports and gosimports is that gosimports removes extra whitespace between import blocks (see https://github.com/golang/go/issues/20818) + GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION) + +.PHONY: bootstrap-go +bootstrap-go: go mod download - # install utilities - [ -f "$(TEMPDIR)/benchstat" ] || GO111MODULE=off GOBIN=$(shell realpath $(TEMPDIR)) go get -u golang.org/x/perf/cmd/benchstat - [ -f "$(TEMPDIR)/golangci" ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.50.1 - [ -f "$(TEMPDIR)/bouncer" ] || curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.4.0 + +$(TEMP_DIR): + mkdir -p $(TEMP_DIR) + +## Static analysis targets ################################# .PHONY: static-analysis static-analysis: check-licenses lint @@ -71,40 +93,55 @@ lint: ## Run gofmt + golangci lint checks $(call title,Running linters) @printf "files with gofmt issues: [$(shell gofmt -l -s .)]\n" @test -z "$(shell gofmt -l -s .)" - $(LINTCMD) + $(LINT_CMD) .PHONY: lint-fix lint-fix: ## Auto-format all source code + run golangci lint fixers $(call title,Running lint fixers) gofmt -w -s . - $(LINTCMD) --fix + $(LINT_CMD) --fix go mod tidy .PHONY: check-licenses check-licenses: $(call title,Validating licenses for go dependencies) - $(TEMPDIR)/bouncer check + $(TEMP_DIR)/bouncer check + +check-go-mod-tidy: + @ .github/scripts/go-mod-tidy-check.sh && echo "go.mod and go.sum are tidy!" + +## Testing targets ################################# .PHONY: unit -unit: $(RESULTSDIR) ## Run unit tests (with coverage) +unit: $(TEMP_DIR) ## Run unit tests (with coverage) $(call title,Running unit tests) - go test --race -coverprofile $(COVER_REPORT) $(shell go list ./... | grep -v anchore/stereoscope/test/integration) - @go tool cover -func $(COVER_REPORT) | grep total | awk '{print substr($$3, 1, length($$3)-1)}' > $(COVER_TOTAL) - @echo "Coverage: $$(cat $(COVER_TOTAL))" - @if [ $$(echo "$$(cat $(COVER_TOTAL)) >= $(COVERAGE_THRESHOLD)" | bc -l) -ne 1 ]; then echo "$(RED)$(BOLD)Failed coverage quality gate (> $(COVERAGE_THRESHOLD)%)$(RESET)" && false; fi + go test -coverprofile $(TEMP_DIR)/unit-coverage-details.txt $(shell go list ./... | grep -v anchore/stereoscope/test) + @.github/scripts/coverage.py $(COVERAGE_THRESHOLD) $(TEMP_DIR)/unit-coverage-details.txt + + +.PHONY: integration +integration: integration-tools ## Run integration tests + $(call title,Running integration tests) + go test -v ./test/integration + +## Benchmark test targets ################################# + .PHONY: benchmark -benchmark: $(RESULTSDIR) ## Run benchmark tests and compare against the baseline (if available) +benchmark: $(TEMP_DIR) ## Run benchmark tests and compare against the baseline (if available) $(call title,Running benchmark tests) - go test -cpu 2 -p 1 -run=^Benchmark -bench=. -count=5 -benchmem ./... | tee $(RESULTSDIR)/benchmark-$(REF_NAME).txt - (test -s $(RESULTSDIR)/benchmark-main.txt && \ - $(TEMPDIR)/benchstat $(RESULTSDIR)/benchmark-main.txt $(RESULTSDIR)/benchmark-$(REF_NAME).txt || \ - $(TEMPDIR)/benchstat $(RESULTSDIR)/benchmark-$(REF_NAME).txt) \ - | tee $(RESULTSDIR)/benchstat.txt + go test -cpu 2 -p 1 -run=^Benchmark -bench=. -count=5 -benchmem ./... | tee $(TEMP_DIR)/benchmark-$(VERSION).txt + (test -s $(TEMP_DIR)/benchmark-main.txt && \ + $(TEMP_DIR)/benchstat $(TEMP_DIR)/benchmark-main.txt $(TEMP_DIR)/benchmark-$(VERSION).txt || \ + $(TEMP_DIR)/benchstat $(TEMP_DIR)/benchmark-$(VERSION).txt) \ + | tee $(TEMP_DIR)/benchstat.txt + .PHONY: show-benchstat show-benchstat: - @cat $(RESULTSDIR)/benchstat.txt + @cat $(TEMP_DIR)/benchstat.txt + +## Test-fixture-related targets ################################# # note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted .PHONY: integration-fingerprint @@ -127,11 +164,30 @@ integration-tools-load: integration-tools-save: @cd test/integration/tools && make save-cache -.PHONY: integration -integration: integration-tools ## Run integration tests - $(call title,Running integration tests) - go test -v ./test/integration +## Build-related targets ################################# + +.PHONY: snapshot +snapshot: clean-snapshot ## Build the binary + $(call title,Build compatability test) + @.github/scripts/build.sh $(SNAPSHOT_DIR) + +## Cleanup targets ################################# + +.PHONY: clean +clean: clear-test-cache clean-snapshot ## Delete all generated artifacts + $(call safe_rm_rf_children,$(TEMP_DIR)) + +.PHONY: clean-snapshot +clean-snapshot: ## Delete all snapshot builds + $(call safe_rm_rf,$(SNAPSHOT_DIR)) .PHONY: clear-test-cache clear-test-cache: ## Delete all test cache (built docker image tars) find . -type f -wholename "**/test-fixtures/cache/*.tar" -delete + + +## Halp! ################################# + +.PHONY: help +help: ## Display this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}' diff --git a/examples/basic.go b/examples/basic.go index 23d3883a..687245b9 100644 --- a/examples/basic.go +++ b/examples/basic.go @@ -89,7 +89,7 @@ func main() { ////////////////////////////////////////////////////////////////// // Fetch file contents from the (squashed) image filePath := file.Path("/etc/group") - contentReader, err := image.FileContentsFromSquash(filePath) + contentReader, err := image.OpenPathFromSquash(filePath) if err != nil { panic(err) } diff --git a/go.mod b/go.mod index eeae65c5..9cec3854 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,14 @@ require ( github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 + github.com/becheran/wildmatch-go v1.0.0 github.com/bmatcuk/doublestar/v4 v4.0.2 github.com/containerd/containerd v1.6.12 github.com/docker/cli v20.10.12+incompatible github.com/docker/docker v20.10.12+incompatible github.com/gabriel-vasile/mimetype v1.4.0 github.com/go-test/deep v1.0.8 + github.com/google/go-cmp v0.5.8 github.com/google/go-containerregistry v0.7.0 github.com/hashicorp/go-multierror v1.1.1 github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 @@ -23,7 +25,7 @@ require ( github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e github.com/sergi/go-diff v1.2.0 github.com/spf13/afero v1.6.0 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.1 github.com/sylabs/sif/v2 v2.8.1 github.com/sylabs/squashfs v0.6.1 github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d @@ -66,14 +68,14 @@ require ( github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect - github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/vbatts/tar-split v0.11.2 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 18aa55bd..33eeee5a 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/aws/smithy-go v1.6.0 h1:T6puApfBcYiTIsaI+SYWqanjMt5pc3aoyyDrI+0YH54= github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 h1:p2I85zYI9z5/c/3Q0LiO3RtNXcmXHTtJfml/hV16zNg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04/go.mod h1:Z+bXnIbhKJYSvxNwsNnwde7pDKxuqlEZCbUBoTwAqf0= +github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= +github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -428,8 +430,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.7.0 h1:u0onUUOcyoCDHEiJoyR1R1gx5er1+r06V5DBhUU5ndk= github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -750,16 +753,21 @@ github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/sylabs/sif/v2 v2.8.1 h1:whr4Vz12RXfLnYyVGHoD/rD/hbF2g9OW7BJHa+WIqW8= github.com/sylabs/sif/v2 v2.8.1/go.mod h1:LQOdYXC9a8i7BleTKRw9lohi0rTbXkJOeS9u0ebvgyM= @@ -1054,8 +1062,8 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1137,7 +1145,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/internal/string_set.go b/internal/string_set.go new file mode 100644 index 00000000..42f00165 --- /dev/null +++ b/internal/string_set.go @@ -0,0 +1,76 @@ +package internal + +import ( + "sort" +) + +type StringSet map[string]struct{} + +func NewStringSet(is ...string) StringSet { + // TODO: replace with single generic implementation that also incorporates other set implementations + s := make(StringSet) + s.Add(is...) + return s +} + +func (s StringSet) Size() int { + return len(s) +} + +func (s StringSet) Merge(other StringSet) { + for _, i := range other.List() { + s.Add(i) + } +} + +func (s StringSet) Add(ids ...string) { + for _, i := range ids { + s[i] = struct{}{} + } +} + +func (s StringSet) Remove(ids ...string) { + for _, i := range ids { + delete(s, i) + } +} + +func (s StringSet) Contains(i string) bool { + _, ok := s[i] + return ok +} + +func (s StringSet) Clear() { + // TODO: replace this with the new 'clear' keyword when it's available in go 1.20 or 1.21 + for i := range s { + delete(s, i) + } +} + +func (s StringSet) List() []string { + ret := make([]string, 0, len(s)) + for i := range s { + ret = append(ret, i) + } + return ret +} + +func (s StringSet) Sorted() []string { + ids := s.List() + + sort.Slice(ids, func(i, j int) bool { + return ids[i] < ids[j] + }) + + return ids +} + +func (s StringSet) ContainsAny(ids ...string) bool { + for _, i := range ids { + _, ok := s[i] + if ok { + return true + } + } + return false +} diff --git a/internal/string_set_test.go b/internal/string_set_test.go new file mode 100644 index 00000000..e04727ae --- /dev/null +++ b/internal/string_set_test.go @@ -0,0 +1,226 @@ +package internal + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStringSet_Size(t *testing.T) { + type testCase struct { + name string + s StringSet + want int + } + tests := []testCase{ + { + name: "empty set", + s: NewStringSet(), + want: 0, + }, + { + name: "non-empty set", + s: NewStringSet("items", "in", "set"), + want: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Size(); got != tt.want { + t.Errorf("Size() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStringSet_Add(t *testing.T) { + type args struct { + ids []string + } + type testCase struct { + name string + s StringSet + args args + } + tests := []testCase{ + { + name: "add multiple", + s: NewStringSet(), + args: args{ids: []string{"a", "b", "c"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Add(tt.args.ids...) + for _, id := range tt.args.ids { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestStringSet_Remove(t *testing.T) { + type args struct { + ids []string + } + type testCase struct { + name string + s StringSet + args args + expected []string + } + tests := []testCase{ + { + name: "remove multiple", + s: NewStringSet("a", "b", "c"), + args: args{ids: []string{"a", "b"}}, + expected: []string{"c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Remove(tt.args.ids...) + for _, id := range tt.args.ids { + if tt.s.Contains(id) { + t.Errorf("expected set to NOT contain %q", id) + } + } + for _, id := range tt.expected { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestStringSet_Contains(t *testing.T) { + type args struct { + i string + } + type testCase struct { + name string + s StringSet + args args + want bool + } + tests := []testCase{ + { + name: "contains", + s: NewStringSet("a", "b", "c"), + args: args{i: "a"}, + want: true, + }, + { + name: "not contains", + s: NewStringSet("a", "b", "c"), + args: args{i: "x"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Contains(tt.args.i); got != tt.want { + t.Errorf("Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStringSet_Clear(t *testing.T) { + type testCase struct { + name string + s StringSet + } + tests := []testCase{ + { + name: "go case", + s: NewStringSet("a", "b", "c"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Clear() + assert.Equal(t, 0, tt.s.Size()) + }) + } +} + +func TestStringSet_List(t *testing.T) { + type testCase struct { + name string + s StringSet + want []string + } + tests := []testCase{ + { + name: "go case", + s: NewStringSet("a", "b", "c"), + want: []string{"a", "b", "c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, tt.want, tt.s.List(), "List()") + }) + } +} + +func TestStringSet_Sorted(t *testing.T) { + type testCase struct { + name string + s StringSet + want []string + } + tests := []testCase{ + { + name: "go case", + s: NewStringSet("a", "b", "c"), + want: []string{"a", "b", "c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.s.Sorted(), "Sorted()") + }) + } +} + +func TestStringSet_ContainsAny(t *testing.T) { + type args struct { + ids []string + } + type testCase struct { + name string + s StringSet + args args + want bool + } + tests := []testCase{ + { + name: "contains one", + s: NewStringSet("a", "b", "c"), + args: args{ids: []string{"a", "x"}}, + want: true, + }, + { + name: "contains all", + s: NewStringSet("a", "b", "c"), + args: args{ids: []string{"a", "b"}}, + want: true, + }, + { + name: "contains none", + s: NewStringSet("a", "b", "c"), + args: args{ids: []string{"x", "y"}}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.s.ContainsAny(tt.args.ids...), fmt.Sprintf("ContainsAny(%v)", tt.args.ids)) + }) + } +} diff --git a/internal/stringset.go b/internal/stringset.go deleted file mode 100644 index 327312b0..00000000 --- a/internal/stringset.go +++ /dev/null @@ -1,38 +0,0 @@ -package internal - -import "sort" - -type Set map[string]struct{} - -func NewStringSet(start ...string) Set { - ret := make(Set) - for _, s := range start { - ret.Add(s) - } - return ret -} - -func (s Set) Add(i string) { - s[i] = struct{}{} -} - -func (s Set) Remove(i string) { - delete(s, i) -} - -func (s Set) Contains(i string) bool { - _, ok := s[i] - return ok -} - -// ToSlice returns a sorted slice of strings that are contained within the set. -func (s Set) ToSlice() []string { - ret := make([]string, len(s)) - idx := 0 - for v := range s { - ret[idx] = v - idx++ - } - sort.Strings(ret) - return ret -} diff --git a/pkg/file/get_xid.go b/pkg/file/get_xid.go new file mode 100644 index 00000000..29a7b02a --- /dev/null +++ b/pkg/file/get_xid.go @@ -0,0 +1,20 @@ +//go:build !windows + +package file + +import ( + "os" + "syscall" +) + +// getXid is the UID GID system info for unix +func getXid(info os.FileInfo) (uid, gid int) { + uid = -1 + gid = -1 + if stat, ok := info.Sys().(*syscall.Stat_t); ok { + uid = int(stat.Uid) + gid = int(stat.Gid) + } + + return uid, gid +} diff --git a/pkg/file/get_xid_win.go b/pkg/file/get_xid_win.go new file mode 100644 index 00000000..abe28de8 --- /dev/null +++ b/pkg/file/get_xid_win.go @@ -0,0 +1,12 @@ +//go:build windows + +package file + +import ( + "os" +) + +// getXid is a placeholder for windows file information +func getXid(info os.FileInfo) (uid, gid int) { + return -1, -1 +} diff --git a/pkg/file/id.go b/pkg/file/id.go new file mode 100644 index 00000000..75f05dc5 --- /dev/null +++ b/pkg/file/id.go @@ -0,0 +1,20 @@ +package file + +var nextID = 0 // note: this is governed by the reference constructor + +// ID is used for file tree manipulation to uniquely identify tree nodes. +type ID uint64 + +type IDs []ID + +func (ids IDs) Len() int { + return len(ids) +} + +func (ids IDs) Less(i, j int) bool { + return ids[i] < ids[j] +} + +func (ids IDs) Swap(i, j int) { + ids[i], ids[j] = ids[j], ids[i] +} diff --git a/pkg/file/id_set.go b/pkg/file/id_set.go new file mode 100644 index 00000000..eebe00fa --- /dev/null +++ b/pkg/file/id_set.go @@ -0,0 +1,75 @@ +//nolint:dupl +package file + +import "sort" + +type IDSet map[ID]struct{} + +func NewIDSet(is ...ID) IDSet { + // TODO: replace with single generic implementation that also incorporates other set implementations + s := make(IDSet) + s.Add(is...) + return s +} + +func (s IDSet) Size() int { + return len(s) +} + +func (s IDSet) Merge(other IDSet) { + for _, i := range other.List() { + s.Add(i) + } +} + +func (s IDSet) Add(ids ...ID) { + for _, i := range ids { + s[i] = struct{}{} + } +} + +func (s IDSet) Remove(ids ...ID) { + for _, i := range ids { + delete(s, i) + } +} + +func (s IDSet) Contains(i ID) bool { + _, ok := s[i] + return ok +} + +func (s IDSet) Clear() { + // TODO: replace this with the new 'clear' keyword when it's available in go 1.20 or 1.21 + for i := range s { + delete(s, i) + } +} + +func (s IDSet) List() []ID { + ret := make([]ID, 0, len(s)) + for i := range s { + ret = append(ret, i) + } + return ret +} + +func (s IDSet) Sorted() []ID { + ids := s.List() + + sort.Slice(ids, func(i, j int) bool { + return ids[i] < ids[j] + }) + + return ids +} + +func (s IDSet) ContainsAny(ids ...ID) bool { + for _, i := range ids { + _, ok := s[i] + if ok { + return true + } + } + return false +} diff --git a/pkg/file/id_set_test.go b/pkg/file/id_set_test.go new file mode 100644 index 00000000..b0d146db --- /dev/null +++ b/pkg/file/id_set_test.go @@ -0,0 +1,226 @@ +package file + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestIDSet_Size(t *testing.T) { + type testCase struct { + name string + s IDSet + want int + } + tests := []testCase{ + { + name: "empty set", + s: NewIDSet(), + want: 0, + }, + { + name: "non-empty set", + s: NewIDSet(1, 2, 3), + want: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Size(); got != tt.want { + t.Errorf("Size() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIDSet_Add(t *testing.T) { + type args struct { + ids []ID + } + type testCase struct { + name string + s IDSet + args args + } + tests := []testCase{ + { + name: "add multiple", + s: NewIDSet(), + args: args{ids: []ID{1, 2, 3}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Add(tt.args.ids...) + for _, id := range tt.args.ids { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestIDSet_Remove(t *testing.T) { + type args struct { + ids []ID + } + type testCase struct { + name string + s IDSet + args args + expected []ID + } + tests := []testCase{ + { + name: "remove multiple", + s: NewIDSet(1, 2, 3), + args: args{ids: []ID{1, 2}}, + expected: []ID{3}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Remove(tt.args.ids...) + for _, id := range tt.args.ids { + if tt.s.Contains(id) { + t.Errorf("expected set to NOT contain %q", id) + } + } + for _, id := range tt.expected { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestIDSet_Contains(t *testing.T) { + type args struct { + i ID + } + type testCase struct { + name string + s IDSet + args args + want bool + } + tests := []testCase{ + { + name: "contains", + s: NewIDSet(1, 2, 3), + args: args{i: 1}, + want: true, + }, + { + name: "not contains", + s: NewIDSet(1, 2, 3), + args: args{i: 97}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Contains(tt.args.i); got != tt.want { + t.Errorf("Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIDSet_Clear(t *testing.T) { + type testCase struct { + name string + s IDSet + } + tests := []testCase{ + { + name: "go case", + s: NewIDSet(1, 2, 3), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Clear() + assert.Equal(t, 0, tt.s.Size()) + }) + } +} + +func TestIDSet_List(t *testing.T) { + type testCase struct { + name string + s IDSet + want []ID + } + tests := []testCase{ + { + name: "go case", + s: NewIDSet(1, 2, 3), + want: []ID{1, 2, 3}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, tt.want, tt.s.List(), "List()") + }) + } +} + +func TestIDSet_Sorted(t *testing.T) { + type testCase struct { + name string + s IDSet + want []ID + } + tests := []testCase{ + { + name: "go case", + s: NewIDSet(1, 2, 3), + want: []ID{1, 2, 3}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.s.Sorted(), "Sorted()") + }) + } +} + +func TestIDSet_ContainsAny(t *testing.T) { + type args struct { + ids []ID + } + type testCase struct { + name string + s IDSet + args args + want bool + } + tests := []testCase{ + { + name: "contains one", + s: NewIDSet(1, 2, 3), + args: args{ids: []ID{1, 97}}, + want: true, + }, + { + name: "contains all", + s: NewIDSet(1, 2, 3), + args: args{ids: []ID{1, 2}}, + want: true, + }, + { + name: "contains none", + s: NewIDSet(1, 2, 3), + args: args{ids: []ID{97, 98}}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.s.ContainsAny(tt.args.ids...), fmt.Sprintf("ContainsAny(%v)", tt.args.ids)) + }) + } +} diff --git a/pkg/file/metadata.go b/pkg/file/metadata.go index 1cbd0d92..990e7a4f 100644 --- a/pkg/file/metadata.go +++ b/pkg/file/metadata.go @@ -7,6 +7,8 @@ import ( "path" "path/filepath" + "github.com/anchore/stereoscope/internal/log" + "github.com/sylabs/squashfs" ) @@ -14,36 +16,29 @@ import ( type Metadata struct { // Path is the absolute path representation to the file Path string - // TarHeaderName is the exact entry name as found within a tar header - TarHeaderName string - // TarSequence is the nth header in the tar file this entry was found - TarSequence int64 - // Linkname is populated only for hardlinks / symlinks, can be an absolute or relative - Linkname string + // LinkDestination is populated only for hardlinks / symlinks, can be an absolute or relative + LinkDestination string // Size of the file in bytes - Size int64 - UserID int - GroupID int - // TypeFlag is the tar.TypeFlag entry for the file - TypeFlag byte + Size int64 + UserID int + GroupID int + Type Type IsDir bool Mode os.FileMode MIMEType string } -func NewMetadata(header tar.Header, sequence int64, content io.Reader) Metadata { +func NewMetadata(header tar.Header, content io.Reader) Metadata { return Metadata{ - Path: path.Clean(DirSeparator + header.Name), - TarHeaderName: header.Name, - TarSequence: sequence, - TypeFlag: header.Typeflag, - Linkname: header.Linkname, - Size: header.FileInfo().Size(), - Mode: header.FileInfo().Mode(), - UserID: header.Uid, - GroupID: header.Gid, - IsDir: header.FileInfo().IsDir(), - MIMEType: MIMEType(content), + Path: path.Clean(DirSeparator + header.Name), + Type: TypeFromTarType(header.Typeflag), + LinkDestination: header.Linkname, + Size: header.FileInfo().Size(), + Mode: header.FileInfo().Mode(), + UserID: header.Uid, + GroupID: header.Gid, + IsDir: header.FileInfo().IsDir(), + MIMEType: MIMEType(content), } } @@ -54,12 +49,37 @@ func NewMetadataFromSquashFSFile(path string, f *squashfs.File) (Metadata, error return Metadata{}, err } + var ty Type + switch { + case fi.IsDir(): + ty = TypeDirectory + case f.IsRegular(): + ty = TypeRegular + case f.IsSymlink(): + ty = TypeSymLink + default: + switch fi.Mode() & os.ModeType { + case os.ModeNamedPipe: + ty = TypeFIFO + case os.ModeSocket: + ty = TypeSocket + case os.ModeDevice: + ty = TypeBlockDevice + case os.ModeCharDevice: + ty = TypeCharacterDevice + case os.ModeIrregular: + ty = TypeIrregular + } + // note: cannot determine hardlink from squashfs.File (but case us not possible) + } + md := Metadata{ - Path: filepath.Clean(filepath.Join("/", path)), - Linkname: f.SymlinkPath(), - Size: fi.Size(), - IsDir: f.IsDir(), - Mode: fi.Mode(), + Path: filepath.Clean(filepath.Join("/", path)), + LinkDestination: f.SymlinkPath(), + Size: fi.Size(), + IsDir: f.IsDir(), + Mode: fi.Mode(), + Type: ty, } if f.IsRegular() { @@ -68,3 +88,38 @@ func NewMetadataFromSquashFSFile(path string, f *squashfs.File) (Metadata, error return md, nil } + +func NewMetadataFromPath(path string, info os.FileInfo) Metadata { + var mimeType string + uid, gid := getXid(info) + + ty := TypeFromMode(info.Mode()) + + if ty == TypeRegular { + f, err := os.Open(path) + if err != nil { + // TODO: it may be that the file is inaccessible, however, this is not an error or a warning. In the future we need to track these as known-unknowns + f = nil + } else { + defer func() { + if err := f.Close(); err != nil { + log.Warnf("unable to close file while obtaining metadata: %s", path) + } + }() + } + + mimeType = MIMEType(f) + } + + return Metadata{ + Path: path, + Mode: info.Mode(), + Type: ty, + // unsupported across platforms + UserID: uid, + GroupID: gid, + Size: info.Size(), + MIMEType: mimeType, + IsDir: info.IsDir(), + } +} diff --git a/pkg/file/metadata_test.go b/pkg/file/metadata_test.go index b89ed431..aa60bc95 100644 --- a/pkg/file/metadata_test.go +++ b/pkg/file/metadata_test.go @@ -4,6 +4,8 @@ package file import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "io" "os" "strings" @@ -16,13 +18,13 @@ func TestFileMetadataFromTar(t *testing.T) { tarReader := getTarFixture(t, "fixture-1") expected := []Metadata{ - {Path: "/path", TarSequence: 0, TarHeaderName: "path/", TypeFlag: 53, Linkname: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch", TarSequence: 1, TarHeaderName: "path/branch/", TypeFlag: 53, Linkname: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch/one", TarSequence: 2, TarHeaderName: "path/branch/one/", TypeFlag: 53, Linkname: "", Size: 0, Mode: os.ModeDir | 0o700, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch/one/file-1.txt", TarSequence: 3, TarHeaderName: "path/branch/one/file-1.txt", TypeFlag: 48, Linkname: "", Size: 11, Mode: 0o700, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, - {Path: "/path/branch/two", TarSequence: 4, TarHeaderName: "path/branch/two/", TypeFlag: 53, Linkname: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch/two/file-2.txt", TarSequence: 5, TarHeaderName: "path/branch/two/file-2.txt", TypeFlag: 48, Linkname: "", Size: 12, Mode: 0o755, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, - {Path: "/path/file-3.txt", TarSequence: 6, TarHeaderName: "path/file-3.txt", TypeFlag: 48, Linkname: "", Size: 11, Mode: 0o664, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, + {Path: "/path", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, + {Path: "/path/branch", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, + {Path: "/path/branch/one", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o700, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, + {Path: "/path/branch/one/file-1.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o700, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, + {Path: "/path/branch/two", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, + {Path: "/path/branch/two/file-2.txt", Type: TypeRegular, LinkDestination: "", Size: 12, Mode: 0o755, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, + {Path: "/path/file-3.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o664, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, } var actual []Metadata @@ -31,7 +33,7 @@ func TestFileMetadataFromTar(t *testing.T) { if strings.HasSuffix(entry.Header.Name, ".txt") { contents = strings.NewReader("#!/usr/bin/env bash\necho 'awesome script'") } - actual = append(actual, NewMetadata(entry.Header, entry.Sequence, contents)) + actual = append(actual, NewMetadata(entry.Header, contents)) return nil } @@ -43,3 +45,43 @@ func TestFileMetadataFromTar(t *testing.T) { t.Errorf("diff: %s", d) } } + +func TestFileMetadataFromPath(t *testing.T) { + + tests := []struct { + path string + expectedType Type + expectedMIMEType string + }{ + { + path: "test-fixtures/symlinks-simple/readme", + expectedType: TypeRegular, + expectedMIMEType: "text/plain", + }, + { + path: "test-fixtures/symlinks-simple/link_to_new_readme", + expectedType: TypeSymLink, + expectedMIMEType: "", + }, + { + path: "test-fixtures/symlinks-simple/link_to_link_to_new_readme", + expectedType: TypeSymLink, + expectedMIMEType: "", + }, + { + path: "test-fixtures/symlinks-simple", + expectedType: TypeDirectory, + expectedMIMEType: "", + }, + } + for _, test := range tests { + t.Run(test.path, func(t *testing.T) { + info, err := os.Lstat(test.path) + require.NoError(t, err) + + actual := NewMetadataFromPath(test.path, info) + assert.Equal(t, test.expectedMIMEType, actual.MIMEType, "unexpected MIME type for %s", test.path) + assert.Equal(t, test.expectedType, actual.Type, "unexpected type for %s", test.path) + }) + } +} diff --git a/pkg/file/path_set.go b/pkg/file/path_set.go index fe7280fc..a46f342a 100644 --- a/pkg/file/path_set.go +++ b/pkg/file/path_set.go @@ -1,20 +1,77 @@ +//nolint:dupl package file +import ( + "sort" +) + type PathSet map[Path]struct{} -func NewPathSet() PathSet { - return make(PathSet) +func NewPathSet(is ...Path) PathSet { + // TODO: replace with single generic implementation that also incorporates other set implementations + s := make(PathSet) + s.Add(is...) + return s +} + +func (s PathSet) Size() int { + return len(s) +} + +func (s PathSet) Merge(other PathSet) { + for _, i := range other.List() { + s.Add(i) + } } -func (s PathSet) Add(i Path) { - s[i] = struct{}{} +func (s PathSet) Add(ids ...Path) { + for _, i := range ids { + s[i] = struct{}{} + } } -func (s PathSet) Remove(i Path) { - delete(s, i) +func (s PathSet) Remove(ids ...Path) { + for _, i := range ids { + delete(s, i) + } } func (s PathSet) Contains(i Path) bool { _, ok := s[i] return ok } + +func (s PathSet) Clear() { + // TODO: replace this with the new 'clear' keyword when it's available in go 1.20 or 1.21 + for i := range s { + delete(s, i) + } +} + +func (s PathSet) List() []Path { + ret := make([]Path, 0, len(s)) + for i := range s { + ret = append(ret, i) + } + return ret +} + +func (s PathSet) Sorted() []Path { + ids := s.List() + + sort.Slice(ids, func(i, j int) bool { + return ids[i] < ids[j] + }) + + return ids +} + +func (s PathSet) ContainsAny(ids ...Path) bool { + for _, i := range ids { + _, ok := s[i] + if ok { + return true + } + } + return false +} diff --git a/pkg/file/path_set_test.go b/pkg/file/path_set_test.go new file mode 100644 index 00000000..5d296649 --- /dev/null +++ b/pkg/file/path_set_test.go @@ -0,0 +1,226 @@ +package file + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPathSet_Size(t *testing.T) { + type testCase struct { + name string + s PathSet + want int + } + tests := []testCase{ + { + name: "empty set", + s: NewPathSet(), + want: 0, + }, + { + name: "non-empty set", + s: NewPathSet("items", "in", "set"), + want: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Size(); got != tt.want { + t.Errorf("Size() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPathSet_Add(t *testing.T) { + type args struct { + ids []Path + } + type testCase struct { + name string + s PathSet + args args + } + tests := []testCase{ + { + name: "add multiple", + s: NewPathSet(), + args: args{ids: []Path{"a", "b", "c"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Add(tt.args.ids...) + for _, id := range tt.args.ids { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestPathSet_Remove(t *testing.T) { + type args struct { + ids []Path + } + type testCase struct { + name string + s PathSet + args args + expected []Path + } + tests := []testCase{ + { + name: "remove multiple", + s: NewPathSet("a", "b", "c"), + args: args{ids: []Path{"a", "b"}}, + expected: []Path{"c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Remove(tt.args.ids...) + for _, id := range tt.args.ids { + if tt.s.Contains(id) { + t.Errorf("expected set to NOT contain %q", id) + } + } + for _, id := range tt.expected { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestPathSet_Contains(t *testing.T) { + type args struct { + i Path + } + type testCase struct { + name string + s PathSet + args args + want bool + } + tests := []testCase{ + { + name: "contains", + s: NewPathSet("a", "b", "c"), + args: args{i: "a"}, + want: true, + }, + { + name: "not contains", + s: NewPathSet("a", "b", "c"), + args: args{i: "x"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Contains(tt.args.i); got != tt.want { + t.Errorf("Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPathSet_Clear(t *testing.T) { + type testCase struct { + name string + s PathSet + } + tests := []testCase{ + { + name: "go case", + s: NewPathSet("a", "b", "c"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Clear() + assert.Equal(t, 0, tt.s.Size()) + }) + } +} + +func TestPathSet_List(t *testing.T) { + type testCase struct { + name string + s PathSet + want []Path + } + tests := []testCase{ + { + name: "go case", + s: NewPathSet("a", "b", "c"), + want: []Path{"a", "b", "c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, tt.want, tt.s.List(), "List()") + }) + } +} + +func TestPathSet_Sorted(t *testing.T) { + type testCase struct { + name string + s PathSet + want []Path + } + tests := []testCase{ + { + name: "go case", + s: NewPathSet("a", "b", "c"), + want: []Path{"a", "b", "c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.s.Sorted(), "Sorted()") + }) + } +} + +func TestPathSet_ContainsAny(t *testing.T) { + type args struct { + ids []Path + } + type testCase struct { + name string + s PathSet + args args + want bool + } + tests := []testCase{ + { + name: "contains one", + s: NewPathSet("a", "b", "c"), + args: args{ids: []Path{"a", "x"}}, + want: true, + }, + { + name: "contains all", + s: NewPathSet("a", "b", "c"), + args: args{ids: []Path{"a", "b"}}, + want: true, + }, + { + name: "contains none", + s: NewPathSet("a", "b", "c"), + args: args{ids: []Path{"x", "y"}}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.s.ContainsAny(tt.args.ids...), fmt.Sprintf("ContainsAny(%v)", tt.args.ids)) + }) + } +} diff --git a/pkg/file/reference.go b/pkg/file/reference.go index 47ed8ae9..1109227d 100644 --- a/pkg/file/reference.go +++ b/pkg/file/reference.go @@ -1,13 +1,6 @@ package file -import ( - "fmt" -) - -var nextID = 0 - -// ID is used for file tree manipulation to uniquely identify tree nodes. -type ID uint64 +import "fmt" // Reference represents a unique file. This is useful when path is not good enough (i.e. you have the same file path for two files in two different container image layers, and you need to be able to distinguish them apart) type Reference struct { diff --git a/pkg/file/resolution.go b/pkg/file/resolution.go new file mode 100644 index 00000000..5f10c719 --- /dev/null +++ b/pkg/file/resolution.go @@ -0,0 +1,158 @@ +package file + +import ( + "sort" + + "github.com/scylladb/go-set/strset" +) + +// Resolution represents the fetching of a possibly non-existent file via a request path. +type Resolution struct { + RequestPath Path + *Reference + // LinkResolutions represents the traversal through the filesystem to access to current reference, including all symlink and hardlink resolution. + // note: today this only shows resolutions via the basename of the request path, but in the future it may show all resolutions. + LinkResolutions []Resolution +} + +type Resolutions []Resolution + +// NewResolution create a new Resolution for the given request path, showing the resolved reference (or +// nil if it does not exist), and the link resolution of the basename of the request path transitively. +func NewResolution(path Path, ref *Reference, leafs []Resolution) *Resolution { + return &Resolution{ + RequestPath: path, + Reference: ref, + LinkResolutions: leafs, + } +} + +func (f Resolutions) Len() int { + return len(f) +} + +func (f Resolutions) Less(i, j int) bool { + ith := f[i] + jth := f[j] + + ithIsReal := ith.Reference != nil && ith.Reference.RealPath == ith.RequestPath + jthIsReal := jth.Reference != nil && jth.Reference.RealPath == jth.RequestPath + + switch { + case ithIsReal && !jthIsReal: + return true + case !ithIsReal && jthIsReal: + return false + } + + return ith.RequestPath < jth.RequestPath +} + +func (f Resolutions) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +func (f *Resolution) HasReference() bool { + if f == nil { + return false + } + return f.Reference != nil +} + +func (f *Resolution) AllPaths() []Path { + set := strset.New() + set.Add(string(f.RequestPath)) + if f.Reference != nil { + set.Add(string(f.Reference.RealPath)) + } + for _, p := range f.LinkResolutions { + set.Add(string(p.RequestPath)) + if p.Reference != nil { + set.Add(string(p.Reference.RealPath)) + } + } + + paths := set.List() + sort.Strings(paths) + + var results []Path + for _, p := range paths { + results = append(results, Path(p)) + } + return results +} + +func (f *Resolution) AllRequestPaths() []Path { + set := strset.New() + set.Add(string(f.RequestPath)) + for _, p := range f.LinkResolutions { + set.Add(string(p.RequestPath)) + } + + paths := set.List() + sort.Strings(paths) + + var results []Path + for _, p := range paths { + results = append(results, Path(p)) + } + return results +} + +// RequestResolutionPath represents the traversal through the filesystem to access to current reference, including all symlink and hardlink resolution. +func (f *Resolution) RequestResolutionPath() []Path { + var paths []Path + var firstPath Path + var lastLinkResolutionIsDead bool + + if string(f.RequestPath) != "" { + firstPath = f.RequestPath + paths = append(paths, f.RequestPath) + } + for i, p := range f.LinkResolutions { + if i == 0 && p.RequestPath == f.RequestPath { + // ignore link resolution that starts with the same user requested path + continue + } + if firstPath == "" { + firstPath = p.RequestPath + } + + paths = append(paths, p.RequestPath) + + if i == len(f.LinkResolutions)-1 { + // we've reached the final link resolution + if p.Reference == nil { + lastLinkResolutionIsDead = true + } + } + } + if f.HasReference() && firstPath != f.Reference.RealPath && !lastLinkResolutionIsDead { + // we've reached the final reference that was resolved + // we should only do this if there was a link resolution + paths = append(paths, f.Reference.RealPath) + } + return paths +} + +// References represents the traversal through the filesystem to access to current reference, including all symlink and hardlink resolution. +func (f *Resolution) References() []Reference { + var refs []Reference + var lastLinkResolutionIsDead bool + + for i, p := range f.LinkResolutions { + if p.Reference != nil { + refs = append(refs, *p.Reference) + } + if i == len(f.LinkResolutions)-1 { + // we've reached the final link resolution + if p.Reference == nil { + lastLinkResolutionIsDead = true + } + } + } + if f.Reference != nil && !lastLinkResolutionIsDead { + refs = append(refs, *f.Reference) + } + return refs +} diff --git a/pkg/file/resolution_test.go b/pkg/file/resolution_test.go new file mode 100644 index 00000000..3a444950 --- /dev/null +++ b/pkg/file/resolution_test.go @@ -0,0 +1,391 @@ +package file + +import ( + "github.com/stretchr/testify/assert" + "sort" + "testing" +) + +func TestResolution_Less(t *testing.T) { + + realA := Resolution{ + RequestPath: "/parent/a", + Reference: &Reference{ + RealPath: "/parent/a", + }, + } + + realB := Resolution{ + RequestPath: "/parent/b", + Reference: &Reference{ + RealPath: "/parent/b", + }, + } + + linkToA := Resolution{ + RequestPath: "/parent-link/a", + Reference: &Reference{ + RealPath: "/a", + }, + } + + linkToB := Resolution{ + RequestPath: "/parent-link/b", + Reference: &Reference{ + RealPath: "/b", + }, + } + + tests := []struct { + name string + subject []Resolution + want []Resolution + }{ + { + name: "references to real files are preferred first", + subject: []Resolution{ + linkToA, + realA, + }, + want: []Resolution{ + realA, + linkToA, + }, + }, + { + name: "real files are treated equally by request name", + subject: []Resolution{ + realB, + realA, + }, + want: []Resolution{ + realA, + realB, + }, + }, + { + name: "link files are treated equally by request name", + subject: []Resolution{ + linkToB, + linkToA, + }, + want: []Resolution{ + linkToA, + linkToB, + }, + }, + { + name: "regression", + subject: []Resolution{ + { + + RequestPath: "/parent-link/file-4.txt", + Reference: &Reference{ + RealPath: "/parent/file-4.txt", + }, + }, + { + + RequestPath: "/parent/file-4.txt", + Reference: &Reference{ + RealPath: "/parent/file-4.txt", + }, + }, + }, + want: []Resolution{ + { + RequestPath: "/parent/file-4.txt", + Reference: &Reference{ + RealPath: "/parent/file-4.txt", + }, + }, + { + + RequestPath: "/parent-link/file-4.txt", + Reference: &Reference{ + RealPath: "/parent/file-4.txt", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sort.Sort(Resolutions(tt.subject)) + assert.Equal(t, tt.want, tt.subject) + }) + } +} + +func TestResolution_RequestResolutionPath(t *testing.T) { + tests := []struct { + name string + subject Resolution + want []Path + }{ + { + name: "empty", + subject: Resolution{ + LinkResolutions: nil, + }, + want: nil, + }, + { + name: "single ref", + subject: Resolution{ + RequestPath: "/home/wagoodman/file.txt", + Reference: &Reference{ + id: 1, + RealPath: "/home/wagoodman/file.txt", + }, + LinkResolutions: nil, + }, + want: []Path{ + "/home/wagoodman/file.txt", + }, + }, + { + // /home -> /another/place + name: "ref with 1 leaf link resolutions", + subject: Resolution{ + RequestPath: "/home", + Reference: &Reference{RealPath: "/another/place"}, + LinkResolutions: []Resolution{ + { + RequestPath: "/home", + Reference: &Reference{RealPath: "/home"}, + }, + }, + }, + want: []Path{ + "/home", + "/another/place", + }, + }, + { + // /home/wagoodman/file.txt -> /place/wagoodman/file.txt -> /1/file.txt -> /2/real-file.txt + + // this is the current state of the filetree + // . + // ├── 1 + // │ ├── file.txt -> 2/real-file.txt + // │ └── link-to-place -> place + // ├── 2 + // │ └── real-file.txt + // ├── home -> link-to-1/link-to-place + // ├── link-to-1 -> 1 + // └── place + // └── wagoodman + // └── file.txt -> link-to-1/file.txt + + name: "ref with 2 leaf link resolutions", + subject: Resolution{ + RequestPath: "/home/wagoodman/file.txt", + Reference: &Reference{RealPath: "/2/real-file.txt"}, + LinkResolutions: []Resolution{ + { + RequestPath: "/place/wagoodman/file.txt", + Reference: &Reference{RealPath: "/place/wagoodman/file.txt"}, + }, + { + RequestPath: "/1/file.txt", + Reference: &Reference{RealPath: "/1/file.txt"}, + }, + }, + }, + want: []Path{ + "/home/wagoodman/file.txt", // request + "/place/wagoodman/file.txt", // real intermediate path + "/1/file.txt", // real intermediate path + "/2/real-file.txt", // final resolved path on the reference + }, + }, + { + // /home/wagoodman/file.txt -> /place/wagoodman/file.txt -> /1/file.txt -> /2/real-file.txt + + // this is the current state of the filetree + // . + // ├── 1 + // │ ├── file.txt -> 2/real-file.txt + // │ └── link-to-place -> place + // ├── home -> link-to-1/link-to-place + // ├── link-to-1 -> 1 + // └── place + // └── wagoodman + // └── file.txt -> link-to-1/file.txt + + name: "ref with dead link", + subject: Resolution{ + RequestPath: "/home/wagoodman/file.txt", + // note: this falls back to the last path that exists which is the behavior for link resolution options: + // []LinkResolutionOption{FollowBasenameLinks, DoNotFollowDeadBasenameLinks} + Reference: &Reference{RealPath: "/1/file.txt"}, + LinkResolutions: []Resolution{ + { + RequestPath: "/place/wagoodman/file.txt", + Reference: &Reference{RealPath: "/place/wagoodman/file.txt"}, + }, + { + RequestPath: "/1/file.txt", + Reference: &Reference{RealPath: "/1/file.txt"}, + }, + { + RequestPath: "/2/real-file.txt", + // nope! it's dead! + //Reference: &file.Reference{RealPath: "/2/real-file.txt"}, + }, + }, + }, + want: []Path{ + "/home/wagoodman/file.txt", // request + "/place/wagoodman/file.txt", // real intermediate path + "/1/file.txt", // real intermediate path + "/2/real-file.txt", // final resolved path on the reference (that does not exist) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.subject.RequestResolutionPath(), "RequestResolutionPath()") + }) + } +} + +func TestResolution_References(t *testing.T) { + type fields struct { + ReferenceResolution Resolution + LeafLinkResolution []Resolution + } + tests := []struct { + name string + subject Resolution + want []Reference + }{ + { + name: "empty", + subject: Resolution{ + LinkResolutions: nil, + }, + want: nil, + }, + { + name: "single ref", + subject: Resolution{ + RequestPath: "/home/wagoodman/file.txt", + Reference: &Reference{ + id: 1, + RealPath: "/home/wagoodman/file.txt", + }, + LinkResolutions: nil, + }, + want: []Reference{ + { + id: 1, + RealPath: "/home/wagoodman/file.txt", + }, + }, + }, + { + // /home -> /another/place + name: "ref with 1 leaf link resolutions", + subject: Resolution{ + RequestPath: "/home", + Reference: &Reference{RealPath: "/another/place"}, + LinkResolutions: []Resolution{ + { + RequestPath: "/home", + Reference: &Reference{RealPath: "/home"}, + }, + }, + }, + want: []Reference{ + {RealPath: "/home"}, + {RealPath: "/another/place"}, + }, + }, + { + // /home/wagoodman/file.txt -> /place/wagoodman/file.txt -> /1/file.txt -> /2/real-file.txt + + // this is the current state of the filetree + // . + // ├── 1 + // │ ├── file.txt -> 2/real-file.txt + // │ └── link-to-place -> place + // ├── 2 + // │ └── real-file.txt + // ├── home -> link-to-1/link-to-place + // ├── link-to-1 -> 1 + // └── place + // └── wagoodman + // └── file.txt -> link-to-1/file.txt + + name: "ref with 2 leaf link resolutions", + subject: Resolution{ + RequestPath: "/home/wagoodman/file.txt", + Reference: &Reference{RealPath: "/2/real-file.txt"}, + LinkResolutions: []Resolution{ + { + RequestPath: "/place/wagoodman/file.txt", + Reference: &Reference{RealPath: "/place/wagoodman/file.txt"}, + }, + { + RequestPath: "/1/file.txt", + Reference: &Reference{RealPath: "/1/file.txt"}, + }, + }, + }, + want: []Reference{ + {RealPath: "/place/wagoodman/file.txt"}, + {RealPath: "/1/file.txt"}, + {RealPath: "/2/real-file.txt"}, + }, + }, + { + // /home/wagoodman/file.txt -> /place/wagoodman/file.txt -> /1/file.txt -> /2/real-file.txt + + // this is the current state of the filetree + // . + // ├── 1 + // │ ├── file.txt -> 2/real-file.txt + // │ └── link-to-place -> place + // ├── home -> link-to-1/link-to-place + // ├── link-to-1 -> 1 + // └── place + // └── wagoodman + // └── file.txt -> link-to-1/file.txt + + name: "ref with dead link", + subject: Resolution{ + RequestPath: "/home/wagoodman/file.txt", + // note: this falls back to the last path that exists which is the behavior for link resolution options: + // []LinkResolutionOption{FollowBasenameLinks, DoNotFollowDeadBasenameLinks} + Reference: &Reference{RealPath: "/1/file.txt"}, + LinkResolutions: []Resolution{ + { + RequestPath: "/place/wagoodman/file.txt", + Reference: &Reference{RealPath: "/place/wagoodman/file.txt"}, + }, + { + RequestPath: "/1/file.txt", + Reference: &Reference{RealPath: "/1/file.txt"}, + }, + { + RequestPath: "/2/real-file.txt", + // nope! it's dead! + //Reference: &file.Reference{RealPath: "/2/real-file.txt"}, + }, + }, + }, + want: []Reference{ + {RealPath: "/place/wagoodman/file.txt"}, + {RealPath: "/1/file.txt"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.subject.References(), "References()") + + }) + } +} diff --git a/pkg/file/tarutil.go b/pkg/file/tarutil.go index b08416b2..4a01c09a 100644 --- a/pkg/file/tarutil.go +++ b/pkg/file/tarutil.go @@ -108,7 +108,7 @@ func MetadataFromTar(reader io.ReadCloser, tarPath string) (Metadata, error) { if entry.Header.Size > 0 { content = reader } - m := NewMetadata(entry.Header, entry.Sequence, content) + m := NewMetadata(entry.Header, content) metadata = &m return ErrTarStopIteration } diff --git a/pkg/file/tarutil_test.go b/pkg/file/tarutil_test.go index cd67f23e..2103f242 100644 --- a/pkg/file/tarutil_test.go +++ b/pkg/file/tarutil_test.go @@ -62,40 +62,36 @@ func TestMetadataFromTar(t *testing.T) { name: "path/branch/two/file-2.txt", fixture: "fixture-1", expected: Metadata{ - Path: "/path/branch/two/file-2.txt", - TarHeaderName: "path/branch/two/file-2.txt", - TarSequence: 5, - Linkname: "", - Size: 12, - UserID: 1337, - GroupID: 5432, - TypeFlag: 0x30, - IsDir: false, - Mode: 0x1ed, - MIMEType: "application/octet-stream", + Path: "/path/branch/two/file-2.txt", + LinkDestination: "", + Size: 12, + UserID: 1337, + GroupID: 5432, + Type: TypeRegular, + IsDir: false, + Mode: 0x1ed, + MIMEType: "application/octet-stream", }, }, { name: "path/branch/two/", fixture: "fixture-1", expected: Metadata{ - Path: "/path/branch/two", - TarHeaderName: "path/branch/two/", - TarSequence: 4, - Linkname: "", - Size: 0, - UserID: 1337, - GroupID: 5432, - TypeFlag: 0x35, - IsDir: true, - Mode: 0x800001ed, - MIMEType: "", + Path: "/path/branch/two", + LinkDestination: "", + Size: 0, + UserID: 1337, + GroupID: 5432, + Type: TypeDirectory, + IsDir: true, + Mode: 0x800001ed, + MIMEType: "", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - f := getTarFixture(t, "fixture-1") + f := getTarFixture(t, test.fixture) metadata, err := MetadataFromTar(f, test.name) assert.NoError(t, err) assert.Equal(t, test.expected, metadata) diff --git a/pkg/file/test-fixtures/symlinks-simple/link_to_link_to_new_readme b/pkg/file/test-fixtures/symlinks-simple/link_to_link_to_new_readme new file mode 120000 index 00000000..e348d807 --- /dev/null +++ b/pkg/file/test-fixtures/symlinks-simple/link_to_link_to_new_readme @@ -0,0 +1 @@ +link_to_new_readme \ No newline at end of file diff --git a/pkg/file/test-fixtures/symlinks-simple/link_to_new_readme b/pkg/file/test-fixtures/symlinks-simple/link_to_new_readme new file mode 120000 index 00000000..ea786ff2 --- /dev/null +++ b/pkg/file/test-fixtures/symlinks-simple/link_to_new_readme @@ -0,0 +1 @@ +readme \ No newline at end of file diff --git a/pkg/file/test-fixtures/symlinks-simple/readme b/pkg/file/test-fixtures/symlinks-simple/readme new file mode 100644 index 00000000..df85b76a --- /dev/null +++ b/pkg/file/test-fixtures/symlinks-simple/readme @@ -0,0 +1,2 @@ +this directory exists for unit tests on irregular files. You can't see other files here because they are removed after each test. +This readme is a better version of Russell's teapot. diff --git a/pkg/file/type.go b/pkg/file/type.go index c2f9db47..67562ae9 100644 --- a/pkg/file/type.go +++ b/pkg/file/type.go @@ -1,25 +1,109 @@ package file -import "archive/tar" +import ( + "archive/tar" + "os" +) const ( - TypeReg Type = tar.TypeReg - TypeDir Type = tar.TypeDir - TypeSymlink Type = tar.TypeSymlink - TypeHardLink Type = tar.TypeLink - TypeCharacterDevice Type = tar.TypeChar - TypeBlockDevice Type = tar.TypeBlock - TypeFifo Type = tar.TypeFifo + TypeRegular Type = iota + TypeHardLink + TypeSymLink + TypeCharacterDevice + TypeBlockDevice + TypeDirectory + TypeFIFO + TypeSocket + TypeIrregular ) -var AllTypes = []Type{ - TypeReg, - TypeDir, - TypeSymlink, - TypeHardLink, - TypeCharacterDevice, - TypeBlockDevice, - TypeFifo, +// why use a rune type? we're looking for something that is memory compact but is easily human interpretable. + +type Type int + +func AllTypes() []Type { + return []Type{ + TypeRegular, + TypeHardLink, + TypeSymLink, + TypeCharacterDevice, + TypeBlockDevice, + TypeDirectory, + TypeFIFO, + TypeSocket, + TypeIrregular, + } +} + +func TypeFromTarType(ty byte) Type { + switch ty { + case tar.TypeReg, tar.TypeRegA: // nolint: staticcheck + return TypeRegular + case tar.TypeLink: + return TypeHardLink + case tar.TypeSymlink: + return TypeSymLink + case tar.TypeChar: + return TypeCharacterDevice + case tar.TypeBlock: + return TypeBlockDevice + case tar.TypeDir: + return TypeDirectory + case tar.TypeFifo: + return TypeFIFO + default: + return TypeIrregular + } } -type Type rune +func TypeFromMode(mode os.FileMode) Type { + switch { + case isSet(mode, os.ModeSymlink): + return TypeSymLink + case isSet(mode, os.ModeIrregular): + return TypeIrregular + case isSet(mode, os.ModeCharDevice): + return TypeCharacterDevice + case isSet(mode, os.ModeDevice): + return TypeBlockDevice + case isSet(mode, os.ModeNamedPipe): + return TypeFIFO + case isSet(mode, os.ModeSocket): + return TypeSocket + case mode.IsDir(): + return TypeDirectory + case mode.IsRegular(): + return TypeRegular + default: + return TypeIrregular + } +} + +func isSet(mode, field os.FileMode) bool { + return mode&field != 0 +} + +func (t Type) String() string { + switch t { + case TypeRegular: + return "RegularFile" + case TypeHardLink: + return "HardLink" + case TypeSymLink: + return "SymbolicLink" + case TypeCharacterDevice: + return "CharacterDevice" + case TypeBlockDevice: + return "BlockDevice" + case TypeDirectory: + return "Directory" + case TypeFIFO: + return "FIFONode" + case TypeSocket: + return "Socket" + case TypeIrregular: + return "IrregularFile" + default: + return "Unknown" + } +} diff --git a/pkg/filetree/builder.go b/pkg/filetree/builder.go new file mode 100644 index 00000000..1f017f8e --- /dev/null +++ b/pkg/filetree/builder.go @@ -0,0 +1,56 @@ +package filetree + +import ( + "fmt" + + "github.com/anchore/stereoscope/pkg/file" +) + +// Builder is a helper for building a filetree and accompanying index in a coordinated fashion. +type Builder struct { + tree Writer + index IndexWriter +} + +func NewBuilder(tree Writer, index IndexWriter) *Builder { + return &Builder{ + tree: tree, + index: index, + } +} + +func (b *Builder) Add(metadata file.Metadata) (*file.Reference, error) { + var ( + ref *file.Reference + err error + ) + switch metadata.Type { + case file.TypeSymLink: + ref, err = b.tree.AddSymLink(file.Path(metadata.Path), file.Path(metadata.LinkDestination)) + if err != nil { + return nil, err + } + case file.TypeHardLink: + ref, err = b.tree.AddHardLink(file.Path(metadata.Path), file.Path(metadata.LinkDestination)) + if err != nil { + return nil, err + } + case file.TypeDirectory: + ref, err = b.tree.AddDir(file.Path(metadata.Path)) + if err != nil { + return nil, err + } + default: + ref, err = b.tree.AddFile(file.Path(metadata.Path)) + if err != nil { + return nil, err + } + } + if ref == nil { + return nil, fmt.Errorf("could not add path=%q link=%q during tar iteration", metadata.Path, metadata.LinkDestination) + } + + b.index.Add(*ref, metadata) + + return ref, nil +} diff --git a/pkg/filetree/depth_first_path_walker.go b/pkg/filetree/depth_first_path_walker.go index f246d8a9..71d3d7f4 100644 --- a/pkg/filetree/depth_first_path_walker.go +++ b/pkg/filetree/depth_first_path_walker.go @@ -58,12 +58,15 @@ func NewDepthFirstPathWalker(tree *FileTree, visitor FileNodeVisitor, conditions func (w *DepthFirstPathWalker) Walk(from file.Path) (file.Path, *filenode.FileNode, error) { w.pathStack.Push(from) - var currentPath file.Path - var currentNode *filenode.FileNode - var err error + var ( + currentPath file.Path + currentNode *nodeAccess + err error + ) for w.pathStack.Size() > 0 { currentPath = w.pathStack.Pop() + // TODO: should we make these link resolutions configurable so you can observe the links on walk as well? (take link resolution options as a parameter) currentNode, err = w.tree.node(currentPath, linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, @@ -72,32 +75,32 @@ func (w *DepthFirstPathWalker) Walk(from file.Path) (file.Path, *filenode.FileNo if err != nil { return "", nil, err } - if currentNode == nil { + if !currentNode.HasFileNode() { return "", nil, fmt.Errorf("nil Node at path=%q", currentPath) } // prevent infinite loop if strings.Count(string(currentPath.Normalize()), file.DirSeparator) >= maxDirDepth { - return currentPath, currentNode, ErrMaxTraversalDepth + return currentPath, currentNode.FileNode, ErrMaxTraversalDepth } - if w.conditions.ShouldTerminate != nil && w.conditions.ShouldTerminate(currentPath, *currentNode) { - return currentPath, currentNode, nil + if w.conditions.ShouldTerminate != nil && w.conditions.ShouldTerminate(currentPath, *currentNode.FileNode) { + return currentPath, currentNode.FileNode, nil } currentPath = currentPath.Normalize() // visit if w.visitor != nil && !w.visitedPaths.Contains(currentPath) { - if w.conditions.ShouldVisit == nil || w.conditions.ShouldVisit != nil && w.conditions.ShouldVisit(currentPath, *currentNode) { - err := w.visitor(currentPath, *currentNode) + if w.conditions.ShouldVisit == nil || w.conditions.ShouldVisit != nil && w.conditions.ShouldVisit(currentPath, *currentNode.FileNode) { + err := w.visitor(currentPath, *currentNode.FileNode) if err != nil { - return currentPath, currentNode, err + return currentPath, currentNode.FileNode, err } w.visitedPaths.Add(currentPath) } } - if w.conditions.ShouldContinueBranch != nil && !w.conditions.ShouldContinueBranch(currentPath, *currentNode) { + if w.conditions.ShouldContinueBranch != nil && !w.conditions.ShouldContinueBranch(currentPath, *currentNode.FileNode) { continue } @@ -112,7 +115,7 @@ func (w *DepthFirstPathWalker) Walk(from file.Path) (file.Path, *filenode.FileNo } } - return currentPath, currentNode, nil + return currentPath, currentNode.FileNode, nil } func (w *DepthFirstPathWalker) WalkAll() error { diff --git a/pkg/filetree/depth_first_path_walker_test.go b/pkg/filetree/depth_first_path_walker_test.go index ee65a695..74678709 100644 --- a/pkg/filetree/depth_first_path_walker_test.go +++ b/pkg/filetree/depth_first_path_walker_test.go @@ -10,7 +10,7 @@ import ( ) func dfsTestTree(t *testing.T) (*FileTree, map[string]*file.Reference) { - tr := NewFileTree() + tr := New() possiblePaths := make(map[string]*file.Reference) @@ -233,7 +233,7 @@ func TestDFS_WalkAll_ConditionalBranchPruning(t *testing.T) { } func TestDFS_WalkAll_MaxDirDepthTerminatesTraversal(t *testing.T) { - tr := NewFileTree() + tr := New() possiblePaths := make(map[string]*file.Reference) diff --git a/pkg/filetree/filenode/filenode.go b/pkg/filetree/filenode/filenode.go index aa9b0fdf..14f0fe7a 100644 --- a/pkg/filetree/filenode/filenode.go +++ b/pkg/filetree/filenode/filenode.go @@ -2,6 +2,7 @@ package filenode import ( "path" + "path/filepath" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/tree/node" @@ -17,7 +18,7 @@ type FileNode struct { func NewDir(p file.Path, ref *file.Reference) *FileNode { return &FileNode{ RealPath: p, - FileType: file.TypeDir, + FileType: file.TypeDirectory, Reference: ref, } } @@ -25,7 +26,7 @@ func NewDir(p file.Path, ref *file.Reference) *FileNode { func NewFile(p file.Path, ref *file.Reference) *FileNode { return &FileNode{ RealPath: p, - FileType: file.TypeReg, + FileType: file.TypeRegular, Reference: ref, } } @@ -33,7 +34,7 @@ func NewFile(p file.Path, ref *file.Reference) *FileNode { func NewSymLink(p, linkPath file.Path, ref *file.Reference) *FileNode { return &FileNode{ RealPath: p, - FileType: file.TypeSymlink, + FileType: file.TypeSymLink, LinkPath: linkPath, Reference: ref, } @@ -64,9 +65,27 @@ func (n *FileNode) Copy() node.Node { } func (n *FileNode) IsLink() bool { - return n.FileType == file.TypeHardLink || n.FileType == file.TypeSymlink + return n.FileType == file.TypeHardLink || n.FileType == file.TypeSymLink } func IDByPath(p file.Path) node.ID { return node.ID(p) } + +func (n *FileNode) RenderLinkDestination() file.Path { + if !n.IsLink() { + return "" + } + + if n.LinkPath.IsAbsolutePath() { + // use links with absolute paths blindly + return n.LinkPath + } + + // resolve relative link paths + var parentDir string + parentDir, _ = filepath.Split(string(n.RealPath)) // TODO: alex: should this be path.Split, not filepath.Split? + + // assemble relative link path by normalizing: "/cur/dir/../file1.txt" --> "/cur/file1.txt" + return file.Path(path.Clean(path.Join(parentDir, string(n.LinkPath)))) +} diff --git a/pkg/filetree/filetree.go b/pkg/filetree/filetree.go index 5ca0f413..9352e8d9 100644 --- a/pkg/filetree/filetree.go +++ b/pkg/filetree/filetree.go @@ -4,10 +4,13 @@ import ( "errors" "fmt" "path" - "path/filepath" + "sort" "strings" - "github.com/anchore/stereoscope/internal" + "github.com/scylladb/go-set/strset" + + "github.com/scylladb/go-set/iset" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/anchore/stereoscope/pkg/tree" @@ -24,7 +27,13 @@ type FileTree struct { } // NewFileTree creates a new FileTree instance. +// Deprecated: use New() instead. func NewFileTree() *FileTree { + return New() +} + +// New creates a new FileTree instance. +func New() *FileTree { t := tree.NewTree() // Initialize FileTree with a root "/" Node @@ -36,8 +45,8 @@ func NewFileTree() *FileTree { } // Copy returns a Copy of the current FileTree. -func (t *FileTree) Copy() (*FileTree, error) { - ct := NewFileTree() +func (t *FileTree) Copy() (ReadWriter, error) { + ct := New() ct.tree = t.tree.Copy() return ct, nil } @@ -45,18 +54,18 @@ func (t *FileTree) Copy() (*FileTree, error) { // AllFiles returns all files within the FileTree (defaults to regular files only, but you can provide one or more allow types). func (t *FileTree) AllFiles(types ...file.Type) []file.Reference { if len(types) == 0 { - types = []file.Type{file.TypeReg} + types = []file.Type{file.TypeRegular} } - typeSet := internal.NewStringSet() + typeSet := iset.New() for _, t := range types { - typeSet.Add(string(t)) + typeSet.Add(int(t)) } var files []file.Reference for _, n := range t.tree.Nodes() { f := n.(*filenode.FileNode) - if typeSet.Contains(string(f.FileType)) && f.Reference != nil { + if typeSet.Has(int(f.FileType)) && f.Reference != nil { files = append(files, *f.Reference) } } @@ -75,7 +84,7 @@ func (t *FileTree) AllRealPaths() []file.Path { } func (t *FileTree) ListPaths(dir file.Path) ([]file.Path, error) { - n, err := t.node(dir, linkResolutionStrategy{ + fna, err := t.node(dir, linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, }) @@ -83,16 +92,16 @@ func (t *FileTree) ListPaths(dir file.Path) ([]file.Path, error) { return nil, err } - if n == nil { + if !fna.HasFileNode() { return nil, nil } - if n.FileType != file.TypeDir { + if fna.FileNode.FileType != file.TypeDirectory { return nil, nil } var listing []file.Path - children := t.tree.Children(n) + children := t.tree.Children(fna.FileNode) for _, child := range children { if child == nil { continue @@ -106,13 +115,25 @@ func (t *FileTree) ListPaths(dir file.Path) ([]file.Path, error) { return nil, err } - listing = append(listing, file.Path(path.Join(string(dir), fn.RealPath.Basename()))) + listing = append(listing, file.Path(path.Join(string(dir), fn.FileNode.RealPath.Basename()))) } return listing, nil } // File fetches a file.Reference for the given path. Returns nil if the path does not exist in the FileTree. -func (t *FileTree) File(path file.Path, options ...LinkResolutionOption) (bool, *file.Reference, error) { +func (t *FileTree) File(path file.Path, options ...LinkResolutionOption) (bool, *file.Resolution, error) { + currentNode, err := t.file(path, options...) + if err != nil { + return false, nil, err + } + if currentNode.HasFileNode() { + return true, currentNode.FileResolution(), err + } + return false, nil, err +} + +// file fetches a file.Reference for the given path. Returns nil if the path does not exist in the FileTree. +func (t *FileTree) file(path file.Path, options ...LinkResolutionOption) (*nodeAccess, error) { userStrategy := newLinkResolutionStrategy(options...) // For: /some/path/here // Where: /some/path -> /other/place @@ -131,13 +152,12 @@ func (t *FileTree) File(path file.Path, options ...LinkResolutionOption) (bool, // // Therefore we can safely lookup the path first without worrying about symlink resolution yet... if there is a // hit, return it! If not, fallback to symlink resolution. - currentNode, err := t.node(path, linkResolutionStrategy{}) if err != nil { - return false, nil, err + return nil, err } - if currentNode != nil && (!currentNode.IsLink() || currentNode.IsLink() && !userStrategy.FollowBasenameLinks) { - return true, currentNode.Reference, nil + if currentNode.HasFileNode() && (!currentNode.FileNode.IsLink() || currentNode.FileNode.IsLink() && !userStrategy.FollowBasenameLinks) { + return currentNode, nil } // symlink resolution!... within the context of container images (which is outside of the responsibility of this object) @@ -148,59 +168,97 @@ func (t *FileTree) File(path file.Path, options ...LinkResolutionOption) (bool, FollowBasenameLinks: userStrategy.FollowBasenameLinks, DoNotFollowDeadBasenameLinks: userStrategy.DoNotFollowDeadBasenameLinks, }) - if currentNode != nil { - return true, currentNode.Reference, err + if currentNode.HasFileNode() { + return currentNode, err } - return false, nil, err + return nil, err +} + +func newResolutions(nodePath []nodeAccess) []file.Resolution { + var refPath []file.Resolution + for i, n := range nodePath { + if i == len(nodePath)-1 && n.FileNode != nil { + // this is already on the parent Access object (unless it is a dead link) + break + } + access := file.Resolution{ + RequestPath: n.RequestPath, + } + if n.FileNode != nil { + access.Reference = n.FileNode.Reference + } + + refPath = append(refPath, access) + } + return refPath } -func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*filenode.FileNode, error) { +func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*nodeAccess, error) { normalizedPath := p.Normalize() nodeID := filenode.IDByPath(normalizedPath) if !strategy.FollowLinks() { n := t.tree.Node(nodeID) if n == nil { - return nil, nil + return &nodeAccess{ + RequestPath: normalizedPath, + FileNode: nil, + }, nil } - return n.(*filenode.FileNode), nil + return &nodeAccess{ + RequestPath: normalizedPath, + FileNode: n.(*filenode.FileNode), + }, nil } - var currentNode *filenode.FileNode + var currentNode *nodeAccess var err error if strategy.FollowAncestorLinks { currentNode, err = t.resolveAncestorLinks(normalizedPath, nil) if err != nil { + if currentNode != nil { + currentNode.RequestPath = normalizedPath + } return currentNode, err } } else { n := t.tree.Node(nodeID) if n != nil { - currentNode = n.(*filenode.FileNode) + currentNode = &nodeAccess{ + RequestPath: normalizedPath, + FileNode: n.(*filenode.FileNode), + } } } // link resolution has come up with nothing, return what we have so far - if currentNode == nil { + if !currentNode.HasFileNode() { + if currentNode != nil { + currentNode.RequestPath = normalizedPath + } return currentNode, nil } if strategy.FollowBasenameLinks { currentNode, err = t.resolveNodeLinks(currentNode, !strategy.DoNotFollowDeadBasenameLinks, nil) } + if currentNode != nil { + currentNode.RequestPath = normalizedPath + } + return currentNode, err } // return FileNode of the basename in the given path (no resolution is done at or past the basename). Note: it is // assumed that the given path has already been normalized. -func (t *FileTree) resolveAncestorLinks(path file.Path, attemptedPaths internal.Set) (*filenode.FileNode, error) { +func (t *FileTree) resolveAncestorLinks(path file.Path, attemptedPaths file.PathSet) (*nodeAccess, error) { // performance optimization... see if there is a node at the path (as if it is a real path). If so, // use it, otherwise, continue with ancestor resolution - currentNode, err := t.node(path, linkResolutionStrategy{}) + currentNodeAccess, err := t.node(path, linkResolutionStrategy{}) if err != nil { return nil, err } - if currentNode != nil { - return currentNode, nil + if currentNodeAccess.HasFileNode() { + return currentNodeAccess, nil } var pathParts = strings.Split(string(path), file.DirSeparator) @@ -219,101 +277,100 @@ func (t *FileTree) resolveAncestorLinks(path file.Path, attemptedPaths internal. currentPathStr = string(currentPath) // fetch the Node with NO link resolution strategy - currentNode, err = t.node(currentPath, linkResolutionStrategy{}) + currentNodeAccess, err = t.node(currentPath, linkResolutionStrategy{}) if err != nil { // should never occur return nil, err } - if currentNode == nil { + if !currentNodeAccess.HasFileNode() { // we've reached a point where the given path that has never been observed. This can happen for one reason: // 1. the current path is really invalid and we should return NIL indicating that it cannot be resolved. // 2. the current path is a link? no, this isn't possible since we are iterating through constituent paths // in order, so we are guaranteed to hit parent links in which we should adjust the search path accordingly. - return nil, nil + return currentNodeAccess, nil } // keep track of what we've resolved to so far... - currentPath = currentNode.RealPath + currentPath = currentNodeAccess.FileNode.RealPath // this is positively a path, however, there is no information about this Node. This may be OK since we // allow for adding children before parents (and even don't require the parent to ever be added --which is // potentially valid given the underlying messy data [tar headers]). In this case we keep building the path // (which we've already done at this point) and continue. - if currentNode.Reference == nil { + if currentNodeAccess.FileNode.Reference == nil { continue } // by this point we definitely have a file reference, if this is a link (and not the basename) resolve any // links until the next Node is resolved (or not). isLastPart := idx == len(pathParts)-1 - if !isLastPart && currentNode.IsLink() { - currentNode, err = t.resolveNodeLinks(currentNode, true, attemptedPaths) + if !isLastPart && currentNodeAccess.FileNode.IsLink() { + currentNodeAccess, err = t.resolveNodeLinks(currentNodeAccess, true, attemptedPaths) if err != nil { // only expected to happen on cycles - return currentNode, err + return currentNodeAccess, err } - if currentNode != nil { - currentPath = currentNode.RealPath + if currentNodeAccess.HasFileNode() { + currentPath = currentNodeAccess.FileNode.RealPath } currentPathStr = string(currentPath) } } // by this point we have processed all constituent paths; there were no un-added paths and the path is guaranteed // to have followed link resolution. - return currentNode, nil + return currentNodeAccess, nil } -// followNode takes the given FileNode and resolves all links at the base of the real path for the node (this implies +// resolveNodeLinks takes the given FileNode and resolves all links at the base of the real path for the node (this implies // that NO ancestors are considered). -func (t *FileTree) resolveNodeLinks(n *filenode.FileNode, followDeadBasenameLinks bool, attemptedPaths internal.Set) (*filenode.FileNode, error) { +// nolint: funlen +func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, attemptedPaths file.PathSet) (*nodeAccess, error) { if n == nil { return nil, fmt.Errorf("cannot resolve links with nil Node given") } // we need to short-circuit link resolution that never resolves (cycles) due to a cycle referencing nodes that do not exist if attemptedPaths == nil { - attemptedPaths = internal.NewStringSet() + attemptedPaths = file.NewPathSet() } // note: this assumes that callers are passing paths in which the constituent parts are NOT symlinks - var lastNode *filenode.FileNode + var lastNode *nodeAccess + var nodePath []nodeAccess + var nextPath file.Path - currentNode := n + currentNodeAccess := n // keep resolving links until a regular file or directory is found - alreadySeen := internal.NewStringSet() + alreadySeen := strset.New() var err error for { + nodePath = append(nodePath, *currentNodeAccess) + // if there is no next path, return this reference (dead link) - if currentNode == nil { + if !currentNodeAccess.HasFileNode() { + // the last path we tried to resolve is a dead link, persist the original path as the failed request + if len(nodePath) > 0 { + nodePath[len(nodePath)-1].RequestPath = nextPath + } break } - if alreadySeen.Contains(string(currentNode.RealPath)) { + if alreadySeen.Has(string(currentNodeAccess.FileNode.RealPath)) { return nil, ErrLinkCycleDetected } - if !currentNode.IsLink() { + if !currentNodeAccess.FileNode.IsLink() { // no resolution and there is no next link (pseudo dead link)... return what you found // any content fetches will fail, but that's ok break } // prepare for the next iteration - alreadySeen.Add(string(currentNode.RealPath)) + alreadySeen.Add(string(currentNodeAccess.FileNode.RealPath)) - var nextPath file.Path - if currentNode.LinkPath.IsAbsolutePath() { - // use links with absolute paths blindly - nextPath = currentNode.LinkPath - } else { - // resolve relative link paths - var parentDir string - parentDir, _ = filepath.Split(string(currentNode.RealPath)) - // assemble relative link path by normalizing: "/cur/dir/../file1.txt" --> "/cur/file1.txt" - nextPath = file.Path(path.Clean(path.Join(parentDir, string(currentNode.LinkPath)))) - } + nextPath = currentNodeAccess.FileNode.RenderLinkDestination() // no more links to follow if string(nextPath) == "" { @@ -321,32 +378,42 @@ func (t *FileTree) resolveNodeLinks(n *filenode.FileNode, followDeadBasenameLink } // preserve the current Node for the next loop (in case we shouldn't follow a potentially dead link) - lastNode = currentNode + lastNode = currentNodeAccess // break any cycles with non-existent paths (before attempting to look the path up again) - if attemptedPaths.Contains(string(nextPath)) { + if attemptedPaths.Contains(nextPath) { return nil, ErrLinkCycleDetected } // get the next Node (based on the next path) - attemptedPaths.Add(string(nextPath)) - currentNode, err = t.resolveAncestorLinks(nextPath, attemptedPaths) + attemptedPaths.Add(nextPath) + currentNodeAccess, err = t.resolveAncestorLinks(nextPath, attemptedPaths) if err != nil { + if currentNodeAccess != nil { + currentNodeAccess.LeafLinkResolution = append(currentNodeAccess.LeafLinkResolution, nodePath...) + } + // only expected to occur upon cycle detection - return currentNode, err + return currentNodeAccess, err } } - if currentNode == nil && !followDeadBasenameLinks { + if !currentNodeAccess.HasFileNode() && !followDeadBasenameLinks { + if lastNode != nil { + lastNode.LeafLinkResolution = append(lastNode.LeafLinkResolution, nodePath...) + } return lastNode, nil } - return currentNode, nil + if currentNodeAccess != nil { + currentNodeAccess.LeafLinkResolution = append(currentNodeAccess.LeafLinkResolution, nodePath...) + } + return currentNodeAccess, nil } // FilesByGlob fetches zero to many file.References for the given glob pattern (considers symlinks). -func (t *FileTree) FilesByGlob(query string, options ...LinkResolutionOption) ([]GlobResult, error) { - results := make([]GlobResult, 0) +func (t *FileTree) FilesByGlob(query string, options ...LinkResolutionOption) ([]file.Resolution, error) { + var results []file.Resolution if len(query) == 0 { return nil, fmt.Errorf("no glob pattern given") @@ -379,7 +446,7 @@ func (t *FileTree) FilesByGlob(query string, options ...LinkResolutionOption) ([ if !path.IsAbs(match) { matchPath = file.Path(path.Join("/", match)) } - fn, err := t.node(matchPath, linkResolutionStrategy{ + fna, err := t.node(matchPath, linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, DoNotFollowDeadBasenameLinks: doNotFollowDeadBasenameLinks, @@ -388,20 +455,20 @@ func (t *FileTree) FilesByGlob(query string, options ...LinkResolutionOption) ([ return nil, err } // the Node must exist and should not be a directory - if fn != nil && fn.FileType != file.TypeDir { - result := GlobResult{ - MatchPath: matchPath, - RealPath: fn.RealPath, - // we should not be given a link Node UNLESS it is dead - IsDeadLink: fn.IsLink(), + if fna.HasFileNode() && fna.FileNode.FileType != file.TypeDirectory { + result := file.NewResolution( + matchPath, + fna.FileNode.Reference, + newResolutions(fna.LeafLinkResolution), + ) + if result != nil { + results = append(results, *result) } - if fn.Reference != nil { - result.Reference = *fn.Reference - } - results = append(results, result) } } + sort.Sort(file.Resolutions(results)) + return results, nil } @@ -410,20 +477,20 @@ func (t *FileTree) FilesByGlob(query string, options ...LinkResolutionOption) ([ // hardlink resolution is performed on the given path --which implies that the given path MUST be a real path (have no // links in constituent paths) func (t *FileTree) AddFile(realPath file.Path) (*file.Reference, error) { - fn, err := t.node(realPath, linkResolutionStrategy{}) + fna, err := t.node(realPath, linkResolutionStrategy{}) if err != nil { return nil, err } - if fn != nil { + if fna.HasFileNode() { // this path already exists - if fn.FileType != file.TypeReg { + if fna.FileNode.FileType != file.TypeRegular { return nil, fmt.Errorf("path=%q already exists but is NOT a regular file", realPath) } // this is a regular file, provide a new or existing file.Reference - if fn.Reference == nil { - fn.Reference = file.NewFileReference(realPath) + if fna.FileNode.Reference == nil { + fna.FileNode.Reference = file.NewFileReference(realPath) } - return fn.Reference, nil + return fna.FileNode.Reference, nil } // this is a new path... add the new Node + parents @@ -438,20 +505,20 @@ func (t *FileTree) AddFile(realPath file.Path) (*file.Reference, error) { // link path captured and returned. Note: NO symlink or hardlink resolution is performed on the given path --which // implies that the given path MUST be a real path (have no links in constituent paths) func (t *FileTree) AddSymLink(realPath file.Path, linkPath file.Path) (*file.Reference, error) { - fn, err := t.node(realPath, linkResolutionStrategy{}) + fna, err := t.node(realPath, linkResolutionStrategy{}) if err != nil { return nil, err } - if fn != nil { + if fna.HasFileNode() { // this path already exists - if fn.FileType != file.TypeSymlink { + if fna.FileNode.FileType != file.TypeSymLink { return nil, fmt.Errorf("path=%q already exists but is NOT a symlink file", realPath) } // this is a symlink file, provide a new or existing file.Reference - if fn.Reference == nil { - fn.Reference = file.NewFileReference(realPath) + if fna.FileNode.Reference == nil { + fna.FileNode.Reference = file.NewFileReference(realPath) } - return fn.Reference, nil + return fna.FileNode.Reference, nil } // this is a new path... add the new Node + parents @@ -466,20 +533,20 @@ func (t *FileTree) AddSymLink(realPath file.Path, linkPath file.Path) (*file.Ref // path captured and returned. Note: NO symlink or hardlink resolution is performed on the given path --which // implies that the given path MUST be a real path (have no links in constituent paths) func (t *FileTree) AddHardLink(realPath file.Path, linkPath file.Path) (*file.Reference, error) { - fn, err := t.node(realPath, linkResolutionStrategy{}) + fna, err := t.node(realPath, linkResolutionStrategy{}) if err != nil { return nil, err } - if fn != nil { + if fna.HasFileNode() { // this path already exists - if fn.FileType != file.TypeHardLink { + if fna.FileNode.FileType != file.TypeHardLink { return nil, fmt.Errorf("path=%q already exists but is NOT a symlink file", realPath) } // this is a symlink file, provide a new or existing file.Reference - if fn.Reference == nil { - fn.Reference = file.NewFileReference(realPath) + if fna.FileNode.Reference == nil { + fna.FileNode.Reference = file.NewFileReference(realPath) } - return fn.Reference, nil + return fna.FileNode.Reference, nil } // this is a new path... add the new Node + parents @@ -497,20 +564,20 @@ func (t *FileTree) AddHardLink(realPath file.Path, linkPath file.Path) (*file.Re // Note: NO symlink or hardlink resolution is performed on the given path --which implies that the given path MUST // be a real path (have no links in constituent paths) func (t *FileTree) AddDir(realPath file.Path) (*file.Reference, error) { - fn, err := t.node(realPath, linkResolutionStrategy{}) + fna, err := t.node(realPath, linkResolutionStrategy{}) if err != nil { return nil, err } - if fn != nil { + if fna.HasFileNode() { // this path already exists - if fn.FileType != file.TypeDir { + if fna.FileNode.FileType != file.TypeDirectory { return nil, fmt.Errorf("path=%q already exists but is NOT a symlink file", realPath) } - // this is a symlink file, provide a new or existing file.Reference - if fn.Reference == nil { - fn.Reference = file.NewFileReference(realPath) + // this is a directory, provide a new or existing file.Reference + if fna.FileNode.Reference == nil { + fna.FileNode.Reference = file.NewFileReference(realPath) } - return fn.Reference, nil + return fna.FileNode.Reference, nil } // this is a new path... add the new Node + parents @@ -532,22 +599,22 @@ func (t *FileTree) addParentPaths(realPath file.Path) error { return fmt.Errorf("unable to determine parent path while adding path=%q: %w", realPath, err) } - fn, err := t.node(parentPath, linkResolutionStrategy{}) + fna, err := t.node(parentPath, linkResolutionStrategy{}) if err != nil { return err } - if fn == nil { + if !fna.HasFileNode() { // add parents of the Node until an existent parent is found it's important to do this in reverse order // to ensure we are checking the fewest amount of parents possible. var pathsToAdd []file.Path parentPaths := realPath.ConstituentPaths() for idx := len(parentPaths) - 1; idx >= 0; idx-- { - fn, err := t.node(parentPaths[idx], linkResolutionStrategy{}) + resolvedFna, err := t.node(parentPaths[idx], linkResolutionStrategy{}) if err != nil { return err } - if fn != nil { + if resolvedFna.HasFileNode() { break } pathsToAdd = append(pathsToAdd, parentPaths[idx]) @@ -584,11 +651,11 @@ func (t *FileTree) setFileNode(fn *filenode.FileNode) error { if err != nil { return err } - if parentNode == nil { + if !parentNode.HasFileNode() { return fmt.Errorf("unable to find parent path=%q while adding path=%q", parentPath, fn.RealPath) } - return t.tree.AddChild(parentNode, fn) + return t.tree.AddChild(parentNode.FileNode, fn) } // RemovePath deletes the file.Reference from the FileTree by the given path. If the basename of the given path @@ -599,18 +666,18 @@ func (t *FileTree) RemovePath(path file.Path) error { return ErrRemovingRoot } - fn, err := t.node(path, linkResolutionStrategy{ + fna, err := t.node(path, linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: false, }) if err != nil { return err } - if fn == nil { + if !fna.HasFileNode() { return nil } - _, err = t.tree.RemoveNode(fn) + _, err = t.tree.RemoveNode(fna.FileNode) if err != nil { return err } @@ -621,18 +688,18 @@ func (t *FileTree) RemovePath(path file.Path) error { // basename is a symlink, then the symlink is followed before resolving children. If the path does not exist, this is a // nop. func (t *FileTree) RemoveChildPaths(path file.Path) error { - fn, err := t.node(path, linkResolutionStrategy{ + fna, err := t.node(path, linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, }) if err != nil { return err } - if fn == nil { + if !fna.HasFileNode() { // can't remove child paths for Node that doesn't exist! return nil } - for _, child := range t.tree.Children(fn) { + for _, child := range t.tree.Children(fna.FileNode) { _, err := t.tree.RemoveNode(child) if err != nil { return err @@ -641,31 +708,31 @@ func (t *FileTree) RemoveChildPaths(path file.Path) error { return nil } -// Reader returns a tree.Reader useful for Tree traversal. -func (t *FileTree) Reader() tree.Reader { +// TreeReader returns a tree.Reader useful for Tree traversal. +func (t *FileTree) TreeReader() tree.Reader { return t.tree } // PathDiff shows the path differences between two trees (useful for testing) func (t *FileTree) PathDiff(other *FileTree) (extra, missing []file.Path) { - ourPaths := internal.NewStringSet() + ourPaths := strset.New() for _, fn := range t.tree.Nodes() { ourPaths.Add(string(fn.ID())) } - theirPaths := internal.NewStringSet() + theirPaths := strset.New() for _, fn := range other.tree.Nodes() { theirPaths.Add(string(fn.ID())) } for _, fn := range other.tree.Nodes() { - if !ourPaths.Contains(string(fn.ID())) { + if !ourPaths.Has(string(fn.ID())) { extra = append(extra, file.Path(fn.ID())) } } for _, fn := range t.tree.Nodes() { - if !theirPaths.Contains(string(fn.ID())) { + if !theirPaths.Has(string(fn.ID())) { missing = append(missing, file.Path(fn.ID())) } } @@ -698,12 +765,12 @@ func (t *FileTree) Walk(fn func(path file.Path, f filenode.FileNode) error, cond return NewDepthFirstPathWalker(t, fn, conditions).WalkAll() } -// merge takes the given Tree and combines it with the current Tree, preferring files in the other Tree if there +// Merge takes the given Tree and combines it with the current Tree, preferring files in the other Tree if there // are path conflicts. This is the basis function for squashing (where the current Tree is the bottom Tree and the // given Tree is the top Tree). // //nolint:gocognit,funlen -func (t *FileTree) merge(upper *FileTree) error { +func (t *FileTree) Merge(upper Reader) error { conditions := tree.WalkConditions{ ShouldContinueBranch: func(n node.Node) bool { p := file.Path(n.ID()) @@ -721,22 +788,22 @@ func (t *FileTree) merge(upper *FileTree) error { } upperNode := n.(*filenode.FileNode) // opaque directories must be processed first - if upper.hasOpaqueDirectory(upperNode.RealPath) { + if hasOpaqueDirectory(upper, upperNode.RealPath) { err := t.RemoveChildPaths(upperNode.RealPath) if err != nil { - return fmt.Errorf("filetree merge failed to remove child paths (upperPath=%s): %w", upperNode.RealPath, err) + return fmt.Errorf("filetree Merge failed to remove child paths (upperPath=%s): %w", upperNode.RealPath, err) } } if upperNode.RealPath.IsWhiteout() { lowerPath, err := upperNode.RealPath.UnWhiteoutPath() if err != nil { - return fmt.Errorf("filetree merge failed to find original upperPath for whiteout (upperPath=%s): %w", upperNode.RealPath, err) + return fmt.Errorf("filetree Merge failed to find original upperPath for whiteout (upperPath=%s): %w", upperNode.RealPath, err) } err = t.RemovePath(lowerPath) if err != nil { - return fmt.Errorf("filetree merge failed to remove upperPath (upperPath=%s): %w", lowerPath, err) + return fmt.Errorf("filetree Merge failed to remove upperPath (upperPath=%s): %w", lowerPath, err) } return nil @@ -747,9 +814,9 @@ func (t *FileTree) merge(upper *FileTree) error { FollowBasenameLinks: false, }) if err != nil { - return fmt.Errorf("filetree merge failed when looking for path=%q : %w", upperNode.RealPath, err) + return fmt.Errorf("filetree Merge failed when looking for path=%q : %w", upperNode.RealPath, err) } - if lowerNode == nil { + if !lowerNode.HasFileNode() { // there is no existing Node... add parents and prepare to set if err := t.addParentPaths(upperNode.RealPath); err != nil { return fmt.Errorf("could not add parent paths to lower: %w", err) @@ -759,21 +826,21 @@ func (t *FileTree) merge(upper *FileTree) error { nodeCopy := *upperNode // keep original file references if the upper tree does not have them (only for the same file types) - if lowerNode != nil && lowerNode.Reference != nil && upperNode.Reference == nil && upperNode.FileType == lowerNode.FileType { - nodeCopy.Reference = lowerNode.Reference + if lowerNode.HasFileNode() && lowerNode.FileNode.Reference != nil && upperNode.Reference == nil && upperNode.FileType == lowerNode.FileNode.FileType { + nodeCopy.Reference = lowerNode.FileNode.Reference } - if lowerNode != nil && upperNode.FileType != file.TypeDir && lowerNode.FileType == file.TypeDir { + if lowerNode.HasFileNode() && upperNode.FileType != file.TypeDirectory && lowerNode.FileNode.FileType == file.TypeDirectory { // NOTE: both upperNode and lowerNode paths are the same, and does not have an effect // on removal of child paths err := t.RemoveChildPaths(upperNode.RealPath) if err != nil { - return fmt.Errorf("filetree merge failed to remove children for non-directory upper node (%s): %w", upperNode.RealPath, err) + return fmt.Errorf("filetree Merge failed to remove children for non-directory upper node (%s): %w", upperNode.RealPath, err) } } // graft a copy of the upper Node with potential lower information into the lower tree if err := t.setFileNode(&nodeCopy); err != nil { - return fmt.Errorf("filetree merge failed to set file Node (Node=%+v): %w", nodeCopy, err) + return fmt.Errorf("filetree Merge failed to set file Node (Node=%+v): %w", nodeCopy, err) } return nil @@ -782,10 +849,10 @@ func (t *FileTree) merge(upper *FileTree) error { // we are using the tree walker instead of the path walker to only look at an resolve merging of real files // with no consideration to virtual paths (paths that are valid in the filetree because constituent paths // contain symlinks). - return tree.NewDepthFirstWalkerWithConditions(upper.Reader(), visitor, conditions).WalkAll() + return tree.NewDepthFirstWalkerWithConditions(upper.TreeReader(), visitor, conditions).WalkAll() } -func (t *FileTree) hasOpaqueDirectory(directoryPath file.Path) bool { +func hasOpaqueDirectory(t Reader, directoryPath file.Path) bool { opaqueWhiteoutChild := file.Path(path.Join(string(directoryPath), file.OpaqueWhiteout)) return t.HasPath(opaqueWhiteoutChild) } diff --git a/pkg/filetree/filetree_test.go b/pkg/filetree/filetree_test.go index 0d3e0c27..e2d592cc 100644 --- a/pkg/filetree/filetree_test.go +++ b/pkg/filetree/filetree_test.go @@ -2,18 +2,20 @@ package filetree import ( "errors" - "fmt" - "github.com/stretchr/testify/require" + "github.com/scylladb/go-set/strset" "testing" - "github.com/anchore/stereoscope/internal" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/stretchr/testify/assert" ) func TestFileTree_AddPath(t *testing.T) { - tr := NewFileTree() + tr := New() path := file.Path("/home") fileNode, err := tr.AddFile(path) if err != nil { @@ -21,13 +23,13 @@ func TestFileTree_AddPath(t *testing.T) { } _, f, _ := tr.File(path) - if f != fileNode { + if f.Reference != fileNode { t.Fatal("expected pointer to the newly created fileNode") } } func TestFileTree_AddPathAndMissingAncestors(t *testing.T) { - tr := NewFileTree() + tr := New() path := file.Path("/home/wagoodman/awesome/file.txt") fileNode, err := tr.AddFile(path) if err != nil { @@ -35,7 +37,7 @@ func TestFileTree_AddPathAndMissingAncestors(t *testing.T) { } _, f, _ := tr.File(path) - if f != fileNode { + if f.Reference != fileNode { t.Fatal("expected pointer to the newly created fileNode") } @@ -46,7 +48,7 @@ func TestFileTree_AddPathAndMissingAncestors(t *testing.T) { if err != nil { t.Fatalf("could not get parent Node: %+v", err) } - children := tr.tree.Children(n) + children := tr.tree.Children(n.FileNode) if len(children) != 1 { t.Fatal("unexpected child count", len(children)) @@ -58,7 +60,7 @@ func TestFileTree_AddPathAndMissingAncestors(t *testing.T) { } func TestFileTree_RemovePath(t *testing.T) { - tr := NewFileTree() + tr := New() path := file.Path("/home/wagoodman/awesome/file.txt") _, err := tr.AddFile(path) if err != nil { @@ -85,8 +87,40 @@ func TestFileTree_RemovePath(t *testing.T) { } } +func TestFileTree_FilesByGlob_AncestorSymlink(t *testing.T) { + var err error + tr := New() + + _, err = tr.AddSymLink("/parent-link", "/parent") + require.NoError(t, err) + + _, err = tr.AddDir("/parent") + require.NoError(t, err) + + expectedRef, err := tr.AddFile("/parent/file.txt") + require.NoError(t, err) + + expected := []file.Resolution{ + { + RequestPath: "/parent-link/file.txt", + Reference: expectedRef, + LinkResolutions: nil, + }, + } + + requestGlob := "**/parent-link/file.txt" + linkOptions := []LinkResolutionOption{FollowBasenameLinks} + ref, err := tr.FilesByGlob(requestGlob, linkOptions...) + require.NoError(t, err) + + opt := cmp.AllowUnexported(file.Reference{}) + if d := cmp.Diff(expected, ref, opt); d != "" { + t.Errorf("unexpected file reference (-want +got):\n%s", d) + } +} + func TestFileTree_FilesByGlob(t *testing.T) { - tr := NewFileTree() + tr := New() paths := []string{ "/home/wagoodman/awesome/file.txt", @@ -301,22 +335,22 @@ func TestFileTree_FilesByGlob(t *testing.T) { return } - actualSet := internal.NewStringSet() - expectedSet := internal.NewStringSet() + actualSet := strset.New() + expectedSet := strset.New() for _, r := range actual { - actualSet.Add(string(r.MatchPath)) + actualSet.Add(string(r.RequestPath)) } for _, e := range test.expected { expectedSet.Add(e) - if !actualSet.Contains(e) { + if !actualSet.Has(e) { t.Errorf("missing search hit: %s", e) } } for _, r := range actual { - if !expectedSet.Contains(string(r.MatchPath)) { + if !expectedSet.Has(string(r.RequestPath)) { t.Errorf("extra search hit: %+v", r) } } @@ -327,14 +361,14 @@ func TestFileTree_FilesByGlob(t *testing.T) { } func TestFileTree_Merge(t *testing.T) { - tr1 := NewFileTree() + tr1 := New() tr1.AddFile("/home/wagoodman/awesome/file-1.txt") - tr2 := NewFileTree() + tr2 := New() tr2.AddFile("/home/wagoodman/awesome/file-2.txt") - if err := tr1.merge(tr2); err != nil { - t.Fatalf("error on merge : %+v", err) + if err := tr1.Merge(tr2); err != nil { + t.Fatalf("error on Merge : %+v", err) } for _, p := range []file.Path{"/home/wagoodman/awesome/file-1.txt", "/home/wagoodman/awesome/file-2.txt"} { @@ -345,32 +379,34 @@ func TestFileTree_Merge(t *testing.T) { } func TestFileTree_Merge_Overwrite(t *testing.T) { - tr1 := NewFileTree() + tr1 := New() tr1.AddFile("/home/wagoodman/awesome/file.txt") - tr2 := NewFileTree() + tr2 := New() newRef, _ := tr2.AddFile("/home/wagoodman/awesome/file.txt") - if err := tr1.merge(tr2); err != nil { - t.Fatalf("error on merge : %+v", err) + if err := tr1.Merge(tr2); err != nil { + t.Fatalf("error on Merge : %+v", err) } _, f, _ := tr1.File("/home/wagoodman/awesome/file.txt") if f.ID() != newRef.ID() { - t.Fatalf("did not overwrite paths on merge") + t.Fatalf("did not overwrite paths on Merge") } } func TestFileTree_Merge_OpaqueWhiteout(t *testing.T) { - tr1 := NewFileTree() - tr1.AddFile("/home/wagoodman/awesome/file.txt") + tr1 := New() + _, err := tr1.AddFile("/home/wagoodman/awesome/file.txt") + require.NoError(t, err) - tr2 := NewFileTree() - tr2.AddFile("/home/wagoodman/.wh..wh..opq") + tr2 := New() + _, err = tr2.AddFile("/home/wagoodman/.wh..wh..opq") + require.NoError(t, err) - if err := tr1.merge(tr2); err != nil { - t.Fatalf("error on merge : %+v", err) + if err := tr1.Merge(tr2); err != nil { + t.Fatalf("error on Merge : %+v", err) } for _, p := range []file.Path{"/home/wagoodman", "/home"} { @@ -388,14 +424,14 @@ func TestFileTree_Merge_OpaqueWhiteout(t *testing.T) { } func TestFileTree_Merge_OpaqueWhiteout_NoLowerDirectory(t *testing.T) { - tr1 := NewFileTree() + tr1 := New() tr1.AddFile("/home") - tr2 := NewFileTree() + tr2 := New() tr2.AddFile("/home/luhring/.wh..wh..opq") - if err := tr1.merge(tr2); err != nil { - t.Fatalf("error on merge : %+v", err) + if err := tr1.Merge(tr2); err != nil { + t.Fatalf("error on Merge : %+v", err) } for _, p := range []file.Path{"/home/luhring", "/home"} { @@ -406,14 +442,14 @@ func TestFileTree_Merge_OpaqueWhiteout_NoLowerDirectory(t *testing.T) { } func TestFileTree_Merge_Whiteout(t *testing.T) { - tr1 := NewFileTree() + tr1 := New() tr1.AddFile("/home/wagoodman/awesome/file.txt") - tr2 := NewFileTree() + tr2 := New() tr2.AddFile("/home/wagoodman/awesome/.wh.file.txt") - if err := tr1.merge(tr2); err != nil { - t.Fatalf("error on merge : %+v", err) + if err := tr1.Merge(tr2); err != nil { + t.Fatalf("error on Merge : %+v", err) } for _, p := range []file.Path{"/home/wagoodman/awesome", "/home/wagoodman", "/home"} { @@ -431,14 +467,14 @@ func TestFileTree_Merge_Whiteout(t *testing.T) { } func TestFileTree_Merge_DirOverride(t *testing.T) { - tr1 := NewFileTree() + tr1 := New() tr1.AddFile("/home/wagoodman/awesome/place") - tr2 := NewFileTree() + tr2 := New() tr2.AddFile("/home/wagoodman/awesome/place/thing.txt") - if err := tr1.merge(tr2); err != nil { - t.Fatalf("error on merge : %+v", err) + if err := tr1.Merge(tr2); err != nil { + t.Fatalf("error on Merge : %+v", err) } for _, p := range []file.Path{"/home/wagoodman/awesome/place", "/home/wagoodman/awesome/place/thing.txt"} { @@ -455,24 +491,24 @@ func TestFileTree_Merge_DirOverride(t *testing.T) { t.Fatalf("somehow override path does not exist?") } - if n.FileType != file.TypeDir { + if n.FileNode.FileType != file.TypeDirectory { t.Errorf("did not override to dir") } } func TestFileTree_Merge_RemoveChildPathsOnOverride(t *testing.T) { - lowerTree := NewFileTree() + lowerTree := New() // add a file in the lower tree, which implicitly adds "/home/wagoodman/awesome/place" as a directory type lowerTree.AddFile("/home/wagoodman/awesome/place/thing.txt") - upperTree := NewFileTree() + upperTree := New() // add "/home/wagoodman/awesome/place" as a file type in the upper treee upperTree.AddFile("/home/wagoodman/awesome/place") // merge the upper tree into the lower tree - if err := lowerTree.merge(upperTree); err != nil { - t.Fatalf("error on merge : %+v", err) + if err := lowerTree.Merge(upperTree); err != nil { + t.Fatalf("error on Merge : %+v", err) } // the directory should still exist @@ -494,90 +530,302 @@ func TestFileTree_Merge_RemoveChildPathsOnOverride(t *testing.T) { t.Fatalf("somehow override path does not exist?") } - if fileNode.FileType != file.TypeReg { + if fileNode.FileNode.FileType != file.TypeRegular { t.Errorf("did not override to dir") } } +func TestFileTree_File_MultiSymlink(t *testing.T) { + var err error + tr := New() + + _, err = tr.AddSymLink("/home", "/link-to-1/link-to-place") + require.NoError(t, err) + + _, err = tr.AddSymLink("/link-to-1", "/1") + require.NoError(t, err) + + _, err = tr.AddDir("/1") + require.NoError(t, err) + + _, err = tr.AddFile("/2/real-file.txt") + require.NoError(t, err) + + _, err = tr.AddSymLink("/1/file.txt", "/2/real-file.txt") + require.NoError(t, err) + + _, err = tr.AddSymLink("/1/link-to-place", "/place") + require.NoError(t, err) + + _, err = tr.AddSymLink("/place/wagoodman/file.txt", "/link-to-1/file.txt") + require.NoError(t, err) + + // this is the current state of the filetree + // . + // ├── 1 + // │ ├── file.txt -> 2/real-file.txt + // │ └── link-to-place -> place + // ├── 2 + // │ └── real-file.txt + // ├── home -> link-to-1/link-to-place + // ├── link-to-1 -> 1 + // └── place + // └── wagoodman + // └── file.txt -> link-to-1/file.txt + + // request: /home/wagoodman/file.txt + // reference: /2/real-file.txt + // ancestor resolution: + // - /home -> /link-to-1/link-to-place + // - /link-to-1 -> /1 + // - /1/link-to-place -> /place + // leaf resolution: + // - /place/wagoodman/file.txt -> /link-to-1/file.txt + // - /link-to-1 -> /1 + // - /1/file.txt -> /2/real-file.txt + // path: + // - home -> link-to-1/link-to-place -> place + // - place/wagoodman + // - place/wagoodman/file.txt -> link-to-1/file.txt -> 1/file.txt -> 2/real-file.txt + + expected := &file.Resolution{ + RequestPath: "/home/wagoodman/file.txt", + Reference: &file.Reference{RealPath: "/2/real-file.txt"}, + LinkResolutions: []file.Resolution{ + { + RequestPath: "/place/wagoodman/file.txt", + Reference: &file.Reference{RealPath: "/place/wagoodman/file.txt"}, + }, + { + RequestPath: "/1/file.txt", + Reference: &file.Reference{RealPath: "/1/file.txt"}, + }, + }, + } + + requestPath := "/home/wagoodman/file.txt" + linkOptions := []LinkResolutionOption{FollowBasenameLinks} + _, ref, err := tr.File(file.Path(requestPath), linkOptions...) + require.NoError(t, err) + + // compare the remaining expectations, ignoring any reference IDs + ignoreIDs := cmpopts.IgnoreUnexported(file.Reference{}) + if d := cmp.Diff(expected, ref, ignoreIDs); d != "" { + t.Errorf("unexpected file reference (-want +got):\n%s", d) + } + +} + +func TestFileTree_File_MultiSymlink_deadlink(t *testing.T) { + var err error + tr := New() + + _, err = tr.AddSymLink("/home", "/link-to-1/link-to-place") + require.NoError(t, err) + + _, err = tr.AddSymLink("/link-to-1", "/1") + require.NoError(t, err) + + _, err = tr.AddDir("/1") + require.NoError(t, err) + + // causes the dead link + //_, err = tr.AddFile("/2/real-file.txt") + //require.NoError(t, err) + + _, err = tr.AddSymLink("/1/file.txt", "/2/real-file.txt") + require.NoError(t, err) + + _, err = tr.AddSymLink("/1/link-to-place", "/place") + require.NoError(t, err) + + _, err = tr.AddSymLink("/place/wagoodman/file.txt", "/link-to-1/file.txt") + require.NoError(t, err) + + // this is the current state of the filetree + // . + // ├── 1 + // │ ├── file.txt -> 2/real-file.txt + // │ └── link-to-place -> place + // ├── home -> link-to-1/link-to-place + // ├── link-to-1 -> 1 + // └── place + // └── wagoodman + // └── file.txt -> link-to-1/file.txt + + // request: /home/wagoodman/file.txt + // reference: /2/real-file.txt + // ancestor resolution: + // - /home -> /link-to-1/link-to-place + // - /link-to-1 -> /1 + // - /1/link-to-place -> /place + // leaf resolution: + // - /place/wagoodman/file.txt -> /link-to-1/file.txt + // - /link-to-1 -> /1 + // - /1/file.txt -> /2/real-file.txt + // path: + // - home -> link-to-1/link-to-place -> place + // - place/wagoodman + // - place/wagoodman/file.txt -> link-to-1/file.txt -> 1/file.txt -> 2/real-file.txt + + expected := &file.Resolution{ + RequestPath: "/home/wagoodman/file.txt", + Reference: &file.Reference{RealPath: "/1/file.txt"}, + LinkResolutions: []file.Resolution{ + { + RequestPath: "/place/wagoodman/file.txt", + Reference: &file.Reference{RealPath: "/place/wagoodman/file.txt"}, + }, + { + RequestPath: "/1/file.txt", + Reference: &file.Reference{RealPath: "/1/file.txt"}, + }, + { + RequestPath: "/2/real-file.txt", + //Reference: &file.Reference{RealPath: "/2/real-file.txt"}, + }, + }, + } + + requestPath := "/home/wagoodman/file.txt" + + { + linkOptions := []LinkResolutionOption{FollowBasenameLinks} + _, ref, err := tr.File(file.Path(requestPath), linkOptions...) + require.Nil(t, ref) + require.NoError(t, err) + } + + { + linkOptions := []LinkResolutionOption{FollowBasenameLinks, DoNotFollowDeadBasenameLinks} + _, ref, err := tr.File(file.Path(requestPath), linkOptions...) + require.NoError(t, err) + + // compare the remaining expectations, ignoring any reference IDs + ignoreIDs := cmpopts.IgnoreUnexported(file.Reference{}) + if d := cmp.Diff(expected, ref, ignoreIDs); d != "" { + t.Errorf("unexpected file reference (-want +got):\n%s", d) + } + } + +} + func TestFileTree_File_Symlink(t *testing.T) { tests := []struct { - name string - buildLinkSource file.Path // ln -s DEST - buildLinkDest file.Path // ln -s SOURCE - buildRealPath file.Path // a real file that should exist (or not if "") - linkOptions []LinkResolutionOption - requestPath file.Path // the path to check against - expectedExists bool // if the request path should exist or not - expectedResolvedPath file.Path // the expected path for a request result - expectedErr bool // if an error is expected from the request - expectedRealRef bool // if the resolved reference should match the built reference from "buildRealPath" + name string + buildLinkSource file.Path // ln -s DEST + buildLinkDest file.Path // ln -s SOURCE + buildRealPath file.Path // a real file that should exist (or not if "") + linkOptions []LinkResolutionOption + requestPath file.Path // the path to check against + expectedExists bool // if the request path should exist or not + expectedErr bool // if an error is expected from the request + expectedRealRef bool // if the resolved reference should match the built reference from "buildRealPath" + expected *file.Resolution }{ - /////////////// + /////////////////// { - name: "request base is ABSOLUTE symlink", - buildLinkSource: "/home", - buildLinkDest: "/another/place", - buildRealPath: "/another/place", - linkOptions: []LinkResolutionOption{FollowBasenameLinks}, - requestPath: "/home", - expectedExists: true, - expectedResolvedPath: "/another/place", + name: "request base is ABSOLUTE symlink", + buildLinkSource: "/home", + buildLinkDest: "/another/place", + buildRealPath: "/another/place", + linkOptions: []LinkResolutionOption{FollowBasenameLinks}, + requestPath: "/home", // /another/place is the "real" reference that we followed, so we should expect the IDs to match upon lookup expectedRealRef: true, + expectedExists: true, + expected: &file.Resolution{ + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/another/place"}, + LinkResolutions: []file.Resolution{ + { + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/home"}, + }, + }, + }, }, { - name: "request base is ABSOLUTE symlink", - buildLinkSource: "/home", - buildLinkDest: "/another/place", - buildRealPath: "/another/place", - linkOptions: []LinkResolutionOption{}, - requestPath: "/home", - expectedExists: true, - expectedResolvedPath: "/home", - // /home is just a symlink, not the real file (which is at /another/place) + name: "request base is ABSOLUTE symlink, request no link resolution", + buildLinkSource: "/home", + buildLinkDest: "/another/place", + buildRealPath: "/another/place", + linkOptions: []LinkResolutionOption{}, + requestPath: "/home", + // /home is just a symlink, not the real file (which is at /another/place)... and we've provided no symlink resolution expectedRealRef: false, + expectedExists: true, + expected: &file.Resolution{ + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/home"}, + LinkResolutions: nil, + }, }, - /////////////// + ///////////////////// { - name: "request parent is ABSOLUTE symlink", - buildLinkSource: "/home", - buildLinkDest: "/another/place", - buildRealPath: "/another/place/wagoodman", - linkOptions: []LinkResolutionOption{FollowBasenameLinks}, // a nop for this case (note the expected path and ref) - requestPath: "/home/wagoodman", - expectedExists: true, - expectedResolvedPath: "/another/place/wagoodman", - expectedRealRef: true, + name: "request parent is ABSOLUTE symlink", + buildLinkSource: "/home", + buildLinkDest: "/another/place", + buildRealPath: "/another/place/wagoodman", + linkOptions: []LinkResolutionOption{FollowBasenameLinks}, // a nop for this case (note the expected path and ref) + requestPath: "/home/wagoodman", + expectedExists: true, + expectedRealRef: true, + expected: &file.Resolution{ + RequestPath: "/home/wagoodman", + Reference: &file.Reference{RealPath: "/another/place/wagoodman"}, + // note: the request is on the leaf, which is within a symlink, but is not a symlink itself. + // this means that all resolution is on the ancestors (thus not a link resolution on the leaf) + LinkResolutions: nil, + }, }, { - name: "request parent is ABSOLUTE symlink", - buildLinkSource: "/home", - buildLinkDest: "/another/place", - buildRealPath: "/another/place/wagoodman", - linkOptions: []LinkResolutionOption{}, // a nop for this case (note the expected path and ref) - requestPath: "/home/wagoodman", - expectedExists: true, - expectedResolvedPath: "/another/place/wagoodman", - expectedRealRef: true, + name: "request parent is ABSOLUTE symlink, request no link resolution", + buildLinkSource: "/home", + buildLinkDest: "/another/place", + buildRealPath: "/another/place/wagoodman", + linkOptions: []LinkResolutionOption{}, // a nop for this case (note the expected path and ref) + requestPath: "/home/wagoodman", + expectedExists: true, + expectedRealRef: true, + // why are we seeing a result that requires link resolution but we've requested no link resolution? + // because there is always ancestor link resolution by default, and this example is only via + // ancestors, thus the leaf is still resolved (since it doesn't have a link). + expected: &file.Resolution{ + RequestPath: "/home/wagoodman", + Reference: &file.Reference{RealPath: "/another/place/wagoodman"}, + // note: the request is on the leaf, which is within a symlink, but is not a symlink itself. + // this means that all resolution is on the ancestors (thus not a link resolution on the leaf) + LinkResolutions: nil, + }, }, - /////////////// + ///////////////// { - name: "request base is RELATIVE symlink", - buildLinkSource: "/home", - buildLinkDest: "../../another/place", - buildRealPath: "/another/place", - linkOptions: []LinkResolutionOption{FollowBasenameLinks}, - requestPath: "/home", - expectedExists: true, - expectedResolvedPath: "/another/place", - expectedRealRef: true, + name: "request base is RELATIVE symlink", + buildLinkSource: "/home", + buildLinkDest: "../../another/place", + buildRealPath: "/another/place", + linkOptions: []LinkResolutionOption{FollowBasenameLinks}, + requestPath: "/home", + expectedExists: true, + expectedRealRef: true, + expected: &file.Resolution{ + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/another/place"}, + LinkResolutions: []file.Resolution{ + { + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/home"}, + }, + }, + }, }, { - name: "request base is RELATIVE symlink", + name: "request base is RELATIVE symlink, no link resolution requested", buildLinkSource: "/home", buildLinkDest: "../../another/place/wagoodman", buildRealPath: "/another/place/wagoodman", @@ -585,42 +833,62 @@ func TestFileTree_File_Symlink(t *testing.T) { requestPath: "/home", expectedExists: true, // note that since the request matches the link source and we are NOT following, we get the link ref back - expectedResolvedPath: "/home", - expectedRealRef: false, + expectedRealRef: false, + expected: &file.Resolution{ + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/home"}, + LinkResolutions: nil, + }, }, - /////////////// + ///////////////// { - name: "request parent is RELATIVE symlink", - buildLinkSource: "/home", - buildLinkDest: "../../another/place", - buildRealPath: "/another/place/wagoodman", - linkOptions: []LinkResolutionOption{FollowBasenameLinks}, // this is a nop since the parent is a link - requestPath: "/home/wagoodman", - expectedExists: true, - expectedResolvedPath: "/another/place/wagoodman", - expectedRealRef: true, - }, - { - name: "request parent is RELATIVE symlink", - buildLinkSource: "/home", - buildLinkDest: "../../another/place", - buildRealPath: "/another/place/wagoodman", - linkOptions: []LinkResolutionOption{}, // this is a nop since the parent is a link - requestPath: "/home/wagoodman", - expectedExists: true, - expectedResolvedPath: "/another/place/wagoodman", - expectedRealRef: true, + name: "request parent is RELATIVE symlink", + buildLinkSource: "/home", + buildLinkDest: "../../another/place", + buildRealPath: "/another/place/wagoodman", + linkOptions: []LinkResolutionOption{FollowBasenameLinks}, // this is a nop since the parent is a link + requestPath: "/home/wagoodman", + expectedExists: true, + expectedRealRef: true, + expected: &file.Resolution{ + RequestPath: "/home/wagoodman", + Reference: &file.Reference{RealPath: "/another/place/wagoodman"}, + // note: the request is on the leaf, which is within a symlink, but is not a symlink itself. + // (the symlink is for an ancestor... so we don't show link resolutions) + LinkResolutions: nil, + }, + }, + { + name: "request parent is RELATIVE symlink, no link resolution requested", + buildLinkSource: "/home", + buildLinkDest: "../../another/place", + buildRealPath: "/another/place/wagoodman", + linkOptions: []LinkResolutionOption{}, // this is a nop since the parent is a link + requestPath: "/home/wagoodman", + expectedExists: true, + expectedRealRef: true, + expected: &file.Resolution{ + RequestPath: "/home/wagoodman", + Reference: &file.Reference{RealPath: "/another/place/wagoodman"}, + // note: the request is on the leaf, which is within a symlink, but is not a symlink itself. + // (the symlink is for an ancestor... so we don't show link resolutions) + LinkResolutions: nil, + }, }, /////////////// { - name: "request base is DEAD symlink", + name: "request base is DEAD symlink, request no link resolution", buildLinkSource: "/home", buildLinkDest: "/mwahaha/i/go/to/nowhere", linkOptions: []LinkResolutionOption{}, requestPath: "/home", // since we did not follow, the paths should exist to the symlink file - expectedResolvedPath: "/home", - expectedExists: true, + expectedExists: true, + expected: &file.Resolution{ + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/home"}, + LinkResolutions: nil, + }, }, { name: "request base is DEAD symlink", @@ -629,8 +897,8 @@ func TestFileTree_File_Symlink(t *testing.T) { linkOptions: []LinkResolutionOption{FollowBasenameLinks}, requestPath: "/home", // we are following the path, which goes to nowhere.... the first failed path is resolved and returned - expectedResolvedPath: "/mwahaha", - expectedExists: false, + expectedExists: false, + expected: nil, }, { name: "request base is DEAD symlink (which we don't follow)", @@ -639,38 +907,59 @@ func TestFileTree_File_Symlink(t *testing.T) { linkOptions: []LinkResolutionOption{FollowBasenameLinks, DoNotFollowDeadBasenameLinks}, requestPath: "/home", // we are following the path, which goes to nowhere.... the first failed path is resolved and returned - expectedResolvedPath: "/home", - expectedExists: true, + expectedExists: true, + expected: &file.Resolution{ + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/home"}, + LinkResolutions: []file.Resolution{ + { + RequestPath: "/home", + Reference: &file.Reference{RealPath: "/home"}, + }, + // this entry represents the dead symlink, note there is no file reference to fetch from the catalog + { + RequestPath: "/mwahaha/i/go/to/nowhere", + }, + }, + }, }, - /////////////// + ///////////////// // trying to resolve to above root { - name: "request parent is RELATIVE symlink to ABOVE root", - buildLinkSource: "/home", - buildLinkDest: "../../../../../../../../../../../../another/place", - buildRealPath: "/another/place/wagoodman", - linkOptions: []LinkResolutionOption{FollowBasenameLinks}, // this is a nop since the parent is a link - requestPath: "/home/wagoodman", - expectedExists: true, - expectedResolvedPath: "/another/place/wagoodman", - expectedRealRef: true, + name: "request parent is RELATIVE symlink to ABOVE root", + buildLinkSource: "/home", + buildLinkDest: "../../../../../../../../../../../../another/place", + buildRealPath: "/another/place/wagoodman", + linkOptions: []LinkResolutionOption{FollowBasenameLinks}, // this is a nop since the parent is a link + requestPath: "/home/wagoodman", + expectedExists: true, + expectedRealRef: true, + expected: &file.Resolution{ + RequestPath: "/home/wagoodman", + Reference: &file.Reference{RealPath: "/another/place/wagoodman"}, + LinkResolutions: nil, + }, }, { - name: "request parent is RELATIVE symlink to ABOVE root", - buildLinkSource: "/home", - buildLinkDest: "../../../../../../../../../../../../another/place", - buildRealPath: "/another/place/wagoodman", - linkOptions: []LinkResolutionOption{}, // this is a nop since the parent is a link - requestPath: "/home/wagoodman", - expectedExists: true, - expectedResolvedPath: "/another/place/wagoodman", - expectedRealRef: true, + name: "request parent is RELATIVE symlink to ABOVE root", + buildLinkSource: "/home", + buildLinkDest: "../../../../../../../../../../../../another/place", + buildRealPath: "/another/place/wagoodman", + linkOptions: []LinkResolutionOption{}, // this is a nop since the parent is a link + requestPath: "/home/wagoodman", + expectedExists: true, + expectedRealRef: true, + expected: &file.Resolution{ + RequestPath: "/home/wagoodman", + Reference: &file.Reference{RealPath: "/another/place/wagoodman"}, + LinkResolutions: nil, + }, }, } for _, test := range tests { - t.Run(fmt.Sprintf("%s (follow=%+v)", test.name, test.linkOptions), func(t *testing.T) { - tr := NewFileTree() + t.Run(test.name, func(t *testing.T) { + tr := New() _, err := tr.AddSymLink(test.buildLinkSource, test.buildLinkDest) if err != nil { t.Fatalf("unexpected an error on add link: %+v", err) @@ -700,29 +989,24 @@ func TestFileTree_File_Symlink(t *testing.T) { t.Fatalf("expected path to exist, but does NOT") } - // validate ref... - if realRef != nil && ref != nil { - // validate path... - if ref.RealPath != test.expectedResolvedPath { - t.Fatalf("unexpected path difference: %+v != %v", ref.RealPath, test.expectedResolvedPath) - } + // validate the resolved reference against the real reference added to the tree + if !test.expectedRealRef && ref.HasReference() && realRef != nil && ref.ID() == realRef.ID() { + t.Errorf("refs should not be the same: resolve(%+v) == reaal(%+v)", ref, realRef) + } else if test.expectedRealRef && ref.ID() != realRef.ID() { + t.Errorf("refs should be the same: resolve(%+v) != real(%+v)", ref, realRef) + } - if ref.ID() == realRef.ID() && !test.expectedRealRef { - t.Errorf("refs should not be the same: resolve(%+v) == reaal(%+v)", ref, realRef) - } else if ref.ID() != realRef.ID() && test.expectedRealRef { - t.Errorf("refs should be the same: resolve(%+v) != real(%+v)", ref, realRef) - } - } else { - if test.expectedRealRef { - t.Fatalf("expected to test a real reference, but could not") - } + // compare the remaining expectations, ignoring any reference IDs + ignoreIDs := cmpopts.IgnoreUnexported(file.Reference{}) + if d := cmp.Diff(test.expected, ref, ignoreIDs); d != "" { + t.Errorf("unexpected file reference (-want +got):\n%s", d) } }) } } func TestFileTree_File_MultipleIndirections(t *testing.T) { - tr := NewFileTree() + tr := New() // first indirection _, err := tr.AddSymLink("/home", "/another/place") if err != nil { @@ -763,7 +1047,7 @@ func TestFileTree_File_MultipleIndirections(t *testing.T) { } func TestFileTree_File_CycleDetection(t *testing.T) { - tr := NewFileTree() + tr := New() // first indirection _, err := tr.AddSymLink("/home", "/another/place") if err != nil { @@ -789,7 +1073,7 @@ func TestFileTree_File_CycleDetection(t *testing.T) { } func TestFileTree_File_DeadCycleDetection(t *testing.T) { - tr := NewFileTree() + tr := New() _, err := tr.AddSymLink("/somewhere/acorn", "noobaa-core/../acorn/bin/acorn") require.NoError(t, err) @@ -806,7 +1090,7 @@ func TestFileTree_File_DeadCycleDetection(t *testing.T) { } func TestFileTree_AllFiles(t *testing.T) { - tr := NewFileTree() + tr := New() paths := []string{ "/home/a-file.txt", @@ -816,30 +1100,26 @@ func TestFileTree_AllFiles(t *testing.T) { for _, p := range paths { _, err := tr.AddFile(file.Path(p)) - if err != nil { - t.Fatalf("failed to add path ('%s'): %+v", p, err) - } + require.NoError(t, err) } var err error + var f *file.Reference // dir - _, err = tr.AddDir("/home") - if err != nil { - t.Fatalf("could not setup dir: %+v", err) - } + f, err = tr.AddDir("/home") + require.NotNil(t, f) + require.NoError(t, err) // relative symlink - _, err = tr.AddSymLink("/home/symlink", "../../../sym-linked-dest") - if err != nil { - t.Fatalf("could not setup link: %+v", err) - } + f, err = tr.AddSymLink("/home/symlink", "../../../sym-linked-dest") + require.NotNil(t, f) + require.NoError(t, err) // hardlink - _, err = tr.AddHardLink("/home/hardlink", "/hard-linked-dest") - if err != nil { - t.Fatalf("could not setup link: %+v", err) - } + f, err = tr.AddHardLink("/home/hardlink", "/hard-linked-dest") + require.NotNil(t, f) + require.NoError(t, err) tests := []struct { name string @@ -853,7 +1133,7 @@ func TestFileTree_AllFiles(t *testing.T) { }, { name: "reg", - types: []file.Type{file.TypeReg}, + types: []file.Type{file.TypeRegular}, expected: []string{"/home/a-file.txt", "/sym-linked-dest/a-.gif", "/hard-linked-dest/b-.gif"}, }, { @@ -863,17 +1143,17 @@ func TestFileTree_AllFiles(t *testing.T) { }, { name: "symlink", - types: []file.Type{file.TypeSymlink}, + types: []file.Type{file.TypeSymLink}, expected: []string{"/home/symlink"}, }, { name: "multiple", - types: []file.Type{file.TypeReg, file.TypeSymlink}, + types: []file.Type{file.TypeRegular, file.TypeSymLink}, expected: []string{"/home/a-file.txt", "/sym-linked-dest/a-.gif", "/hard-linked-dest/b-.gif", "/home/symlink"}, }, { name: "dir", - types: []file.Type{file.TypeDir}, + types: []file.Type{file.TypeDirectory}, // note: only explicitly added directories exist in the catalog expected: []string{"/home"}, }, diff --git a/pkg/filetree/glob.go b/pkg/filetree/glob.go index 0a650c6f..a99ad154 100644 --- a/pkg/filetree/glob.go +++ b/pkg/filetree/glob.go @@ -19,13 +19,6 @@ var _ fs.FS = (*osAdapter)(nil) var _ fs.FileInfo = (*fileinfoAdapter)(nil) var _ fs.DirEntry = (*fileinfoAdapter)(nil) -type GlobResult struct { - MatchPath file.Path - RealPath file.Path - IsDeadLink bool - Reference file.Reference -} - // fileAdapter is an object meant to implement the doublestar.File for getting Lstat results for an entire directory. type fileAdapter struct { os *osAdapter @@ -52,14 +45,16 @@ func isInPathResolutionLoop(path string, ft *FileTree) (bool, error) { allPathSet := file.NewPathSet() allPaths := file.Path(path).AllPaths() for _, p := range allPaths { - fn, err := ft.node(p, linkResolutionStrategy{ + fna, err := ft.node(p, linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, }) if err != nil { return false, err } - allPathSet.Add(file.Path(fn.ID())) + if fna.HasFileNode() { + allPathSet.Add(file.Path(fna.FileNode.ID())) + } } // we want to allow for getting children out of the first iteration of a infinite path, but NOT allowing // beyond the second iteration down an infinite path. @@ -93,23 +88,23 @@ func (f *fileAdapter) ReadDir(n int) ([]fs.DirEntry, error) { return nil, os.ErrInvalid } var ret = make([]fs.DirEntry, 0) - fn, err := f.filetree.node(file.Path(f.name), linkResolutionStrategy{ + fna, err := f.filetree.node(file.Path(f.name), linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, }) if err != nil { return ret, err } - if fn == nil { + if !fna.HasFileNode() { return ret, nil } - isInPathResolutionLoop, err := isInPathResolutionLoop(f.name, f.filetree) - if err != nil || isInPathResolutionLoop { + isInLoop, err := isInPathResolutionLoop(f.name, f.filetree) + if err != nil || isInLoop { return ret, err } - for idx, child := range f.filetree.tree.Children(fn) { + for idx, child := range f.filetree.tree.Children(fna.FileNode) { if idx == n && n != -1 { break } @@ -132,23 +127,23 @@ type osAdapter struct { func (a *osAdapter) ReadDir(name string) ([]fs.DirEntry, error) { var ret = make([]fs.DirEntry, 0) - fn, err := a.filetree.node(file.Path(name), linkResolutionStrategy{ + fna, err := a.filetree.node(file.Path(name), linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, }) if err != nil { return ret, err } - if fn == nil { + if !fna.HasFileNode() { return ret, nil } - isInPathResolutionLoop, err := isInPathResolutionLoop(name, a.filetree) - if err != nil || isInPathResolutionLoop { + isInLoop, err := isInPathResolutionLoop(name, a.filetree) + if err != nil || isInLoop { return ret, err } - for _, child := range a.filetree.tree.Children(fn) { + for _, child := range a.filetree.tree.Children(fna.FileNode) { requestPath := path.Join(name, filepath.Base(string(child.ID()))) r, err := a.Lstat(requestPath) if err == nil { @@ -164,7 +159,7 @@ func (a *osAdapter) ReadDir(name string) ([]fs.DirEntry, error) { // Lstat returns a FileInfo describing the named file. If the file is a symbolic link, the returned // FileInfo describes the symbolic link. Lstat makes no attempt to follow the link. func (a *osAdapter) Lstat(name string) (fs.FileInfo, error) { - fn, err := a.filetree.node(file.Path(name), linkResolutionStrategy{ + fna, err := a.filetree.node(file.Path(name), linkResolutionStrategy{ FollowAncestorLinks: true, // Lstat by definition requires that basename symlinks are not followed FollowBasenameLinks: false, @@ -173,13 +168,13 @@ func (a *osAdapter) Lstat(name string) (fs.FileInfo, error) { if err != nil { return &fileinfoAdapter{}, err } - if fn == nil { + if !fna.HasFileNode() { return &fileinfoAdapter{}, os.ErrNotExist } return &fileinfoAdapter{ VirtualPath: file.Path(name), - Node: *fn, + Node: *fna.FileNode, }, nil } @@ -194,7 +189,7 @@ func (a *osAdapter) Open(name string) (fs.File, error) { // Stat returns a FileInfo describing the named file. func (a *osAdapter) Stat(name string) (fs.FileInfo, error) { - fn, err := a.filetree.node(file.Path(name), linkResolutionStrategy{ + fna, err := a.filetree.node(file.Path(name), linkResolutionStrategy{ FollowAncestorLinks: true, FollowBasenameLinks: true, DoNotFollowDeadBasenameLinks: a.doNotFollowDeadBasenameLinks, @@ -202,12 +197,12 @@ func (a *osAdapter) Stat(name string) (fs.FileInfo, error) { if err != nil { return &fileinfoAdapter{}, err } - if fn == nil { + if !fna.HasFileNode() { return &fileinfoAdapter{}, os.ErrNotExist } return &fileinfoAdapter{ VirtualPath: file.Path(name), - Node: *fn, + Node: *fna.FileNode, }, nil } @@ -248,7 +243,7 @@ func (a *fileinfoAdapter) Mode() os.FileMode { // the underlying implementation for symlinks and hardlinks share the same semantics in the tree implementation // (meaning resolution is required) where as in a real file system this is taken care of by the driver // by making the file point to the same inode as another --making the indirection transparent to applications. - if a.Node.FileType == file.TypeSymlink || a.Node.FileType == file.TypeHardLink { + if a.Node.FileType == file.TypeSymLink || a.Node.FileType == file.TypeHardLink { mode |= os.ModeSymlink } return mode @@ -261,7 +256,7 @@ func (a *fileinfoAdapter) ModTime() time.Time { // IsDir is an abbreviation for Mode().IsDir(). func (a *fileinfoAdapter) IsDir() bool { - return a.Node.FileType == file.TypeDir + return a.Node.FileType == file.TypeDirectory } // Sys contains underlying data source (nothing in this case). diff --git a/pkg/filetree/glob_parser.go b/pkg/filetree/glob_parser.go new file mode 100644 index 00000000..5f10acbd --- /dev/null +++ b/pkg/filetree/glob_parser.go @@ -0,0 +1,371 @@ +package filetree + +import ( + "regexp" + "strings" +) + +const ( + // searchByGlob is the default, unparsed/processed glob value searched directly against the filetree. + searchByGlob searchBasis = iota + + // searchByFullPath indicates that the given glob value is not a glob, thus a (simpler) path lookup against the filetree should be performed as the search. + searchByFullPath + + // searchByExtension indicates cases like "**/*.py" where the only specific glob element indicates the file or directory extension. + searchByExtension + + // searchByBasename indicates cases like "**/bin/python" where the only specific glob element indicates the file or directory basename (e.g. "python"). + searchByBasename + + // searchByBasenameGlob indicates cases like "**/bin/python*" where the search space is limited to the full set of all basenames that match the given glob. + searchByBasenameGlob + + // searchBySubDirectory indicates cases like "**/var/lib/dpkg/status.d/*" where we're interested in selecting all files within a directory (but not the directory itself). + searchBySubDirectory +) + +type searchBasis int + +func (s searchBasis) String() string { + switch s { + case searchByGlob: + return "glob" + case searchByFullPath: + return "full-path" + case searchByExtension: + return "extension" + case searchByBasename: + return "basename" + case searchByBasenameGlob: + return "basename-glob" + case searchBySubDirectory: + return "subdirectory" + } + return "unknown search basis" +} + +type searchRequest struct { + searchBasis + value string + requirement string +} + +func (s searchRequest) String() string { + value := s.searchBasis.String() + ": " + s.value + if s.requirement != "" { + value += " (requirement: " + s.requirement + ")" + } + return value +} + +func parseGlob(glob string) []searchRequest { + glob = cleanGlob(glob) + + if !strings.ContainsAny(glob, "*?[]{}") { + return []searchRequest{ + { + searchBasis: searchByFullPath, + value: glob, + }, + } + } + + beforeBasename, basename := splitAtBasename(glob) + + if basename == "*" { + _, nestedBasename := splitAtBasename(beforeBasename) + if !strings.ContainsAny(nestedBasename, "*?[]{}") { + // special case: glob is a parent glob + requests := []searchRequest{ + { + searchBasis: searchBySubDirectory, + value: nestedBasename, + requirement: beforeBasename, + }, + } + return requests + } + } + + requests := parseGlobBasename(basename) + for i := range requests { + applyRequirement(&requests[i], beforeBasename, glob) + } + + return requests +} + +func splitAtBasename(glob string) (string, string) { + // TODO: need to correctly avoid indexes within [] and {} groups + basenameSplitAt := strings.LastIndex(glob, "/") + + var basename string + var beforeBasename string + if basenameSplitAt == -1 { + // note: this has no glob path prefix, thus no requirement... + // this can only be a basename, basename glob, or extension + basename = glob + beforeBasename = "" + } else if basenameSplitAt < len(glob)-1 { + basename = glob[basenameSplitAt+1:] + } + + if basenameSplitAt >= 0 && basenameSplitAt < len(glob)-1 { + beforeBasename = glob[:basenameSplitAt] + } + + return beforeBasename, basename +} + +func applyRequirement(request *searchRequest, beforeBasename, glob string) { + var requirement string + + if beforeBasename != "" { + requirement = glob + switch beforeBasename { + case "**", request.requirement: + if request.searchBasis != searchByExtension { + requirement = "" + } + } + } else { + requirement = "" + } + + request.requirement = requirement + + if request.searchBasis == searchByGlob { + request.value = glob + if glob == request.requirement { + request.requirement = "" + } + } +} + +func parseGlobBasename(basenameInput string) []searchRequest { + if strings.ContainsAny(basenameInput, "[]{}") { + return parseBasenameAltAndClassGlobSections(basenameInput) + } + + extensionFields := strings.Split(basenameInput, "*.") + if len(extensionFields) == 2 && extensionFields[0] == "" { + possibleExtension := extensionFields[1] + if !strings.ContainsAny(possibleExtension, "*?") { + // special case, this is plain extension + return []searchRequest{ + { + searchBasis: searchByExtension, + value: "." + possibleExtension, + }, + } + } + } + + if !strings.ContainsAny(basenameInput, "*?") { + // special case, this is plain basename + return []searchRequest{ + { + searchBasis: searchByBasename, + value: basenameInput, + }, + } + } + + if strings.ReplaceAll(strings.ReplaceAll(basenameInput, "?", ""), "*", "") == "" { + // special case, this is a glob that is only asterisks... do not process! + return []searchRequest{ + { + searchBasis: searchByGlob, + // note: we let the parent caller attach the full glob value + }, + } + } + + return []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: basenameInput, + }, + } +} + +func parseBasenameAltAndClassGlobSections(basenameInput string) []searchRequest { + // TODO: process escape sequences + + altStartCount := strings.Count(basenameInput, "{") + altEndCount := strings.Count(basenameInput, "}") + classStartCount := strings.Count(basenameInput, "[") + classEndCount := strings.Count(basenameInput, "]") + + if altStartCount != altEndCount || classStartCount != classEndCount { + // imbalanced braces, this is not a valid glob relative to just the basename + return []searchRequest{ + { + searchBasis: searchByGlob, + // note: we let the parent caller attach the full glob value + }, + } + } + + if classStartCount > 0 { + // parsing this is not supported at this time + return []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: basenameInput, + }, + } + } + + // if the glob is the simplest list form, them allow for breaking into sub-searches + if altStartCount == 1 { + indexStartIsPrefix := strings.Index(basenameInput, "{") == 0 + indexEndIsSuffix := strings.Index(basenameInput, "}") == len(basenameInput)-1 + if indexStartIsPrefix && indexEndIsSuffix { + // this is a simple list, split it up + // e.g. {a,b,c} -> a, b, c + altSections := strings.Split(basenameInput[1:len(basenameInput)-1], ",") + if len(altSections) > 1 { + var requests []searchRequest + for _, altSection := range altSections { + basis := searchByBasename + if strings.ContainsAny(altSection, "*?") { + basis = searchByBasenameGlob + } + + requests = append(requests, searchRequest{ + searchBasis: basis, + value: altSection, + }) + } + return requests + } + } + } + + // there is some sort of alt usage, but it is not a simple list... just treat it as a glob + return []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: basenameInput, + }, + } +} + +func cleanGlob(glob string) string { + glob = strings.TrimSpace(glob) + glob = removeRedundantCountGlob(glob, '/', 1) + glob = removeRedundantCountGlob(glob, '*', 2) + if len(glob) > 1 { + // input case: / + // then preserve the slash + glob = strings.TrimRight(glob, "/") + } + // e.g. replace "/bar**/" with "/bar*/" + glob = simplifyMultipleGlobAsterisks(glob) + glob = simplifyGlobRecursion(glob) + return glob +} + +func simplifyMultipleGlobAsterisks(glob string) string { + // this will replace any recursive globs (**) that are not clearly indicating recursive tree searches with a single * + + var sb strings.Builder + var asteriskBuff strings.Builder + var withinRecursiveStreak bool + + for idx, c := range glob { + isAsterisk := c == '*' + isSlash := c == '/' + + // special case, this is the first character in the glob and it is an asterisk... + // treat this like a recursive streak + if idx == 0 && isAsterisk { + withinRecursiveStreak = true + asteriskBuff.WriteRune(c) + continue + } + + if isAsterisk { + asteriskBuff.WriteRune(c) + continue + } + + if isSlash { + if withinRecursiveStreak { + // this is a confirmed recursive streak + // keep all asterisks! + sb.WriteString(asteriskBuff.String()) + asteriskBuff.Reset() + } + + if asteriskBuff.Len() > 0 { + // this is NOT a recursive streak, but there are asterisks + // keep only one asterisk + sb.WriteRune('*') + asteriskBuff.Reset() + } + + // this is potentially a new streak... + withinRecursiveStreak = true + } else { + // ... and this is NOT a recursive streak + if asteriskBuff.Len() > 0 { + // ... keep only one asterisk, since it's not recursive + sb.WriteRune('*') + } + asteriskBuff.Reset() + withinRecursiveStreak = false + } + + sb.WriteRune(c) + } + + if asteriskBuff.Len() > 0 { + if withinRecursiveStreak { + sb.WriteString(asteriskBuff.String()) + } else { + sb.WriteRune('*') + } + } + + return sb.String() +} + +var globRecursionRightPattern = regexp.MustCompile(`(\*\*/?)+`) + +func simplifyGlobRecursion(glob string) string { + // this function assumes that all redundant asterisks have been removed (e.g. /****/ -> /**/) + // and that all seemingly recursive globs have been replaced with a single asterisk (e.g. /bar**/ -> /bar*/) + glob = globRecursionRightPattern.ReplaceAllString(glob, "**/") + glob = strings.ReplaceAll(glob, "//", "/") + if strings.HasPrefix(glob, "/**/") { + glob = strings.TrimPrefix(glob, "/") + } + if len(glob) > 1 { + // input case: /** + // then preserve the slash + glob = strings.TrimRight(glob, "/") + } + return glob +} + +func removeRedundantCountGlob(glob string, val rune, count int) string { + var sb strings.Builder + + var streak int + for _, c := range glob { + if c == val { + streak++ + if streak > count { + continue + } + } else { + streak = 0 + } + + sb.WriteRune(c) + } + return sb.String() +} diff --git a/pkg/filetree/glob_parser_test.go b/pkg/filetree/glob_parser_test.go new file mode 100644 index 00000000..64ea8380 --- /dev/null +++ b/pkg/filetree/glob_parser_test.go @@ -0,0 +1,663 @@ +package filetree + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_parseGlob(t *testing.T) { + + tests := []struct { + name string + glob string + want []searchRequest + }{ + { + name: "relative path", + glob: "foo/bar/basename.txt", + want: []searchRequest{ + { + searchBasis: searchByFullPath, + value: "foo/bar/basename.txt", + }, + }, + }, + { + name: "absolute path", + glob: "/foo/bar/basename.txt", + want: []searchRequest{ + { + searchBasis: searchByFullPath, + value: "/foo/bar/basename.txt", + }, + }, + }, + { + name: "extension", + glob: "*.txt", + want: []searchRequest{ + { + searchBasis: searchByExtension, + value: ".txt", + }, + }, + }, + { + name: "extension anywhere", + glob: "**/*.txt", + want: []searchRequest{ + { + searchBasis: searchByExtension, + value: ".txt", + requirement: "**/*.txt", + }, + }, + }, + { + name: "basename glob search with requirement", + glob: "bas*nam?.txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "bas*nam?.txt", + }, + }, + }, + { + name: "extension with path requirement", + glob: "foo/bar/**/*.txt", + want: []searchRequest{ + { + searchBasis: searchByExtension, + value: ".txt", + requirement: "foo/bar/**/*.txt", + }, + }, + }, + { + name: "basename but without a path prefix", + glob: "basename.txt", + want: []searchRequest{ + { + searchBasis: searchByFullPath, + value: "basename.txt", + }, + }, + }, + { + name: "basename anywhere", + glob: "**/basename.txt", + want: []searchRequest{ + { + searchBasis: searchByBasename, + value: "basename.txt", + }, + }, + }, + { + name: "basename with requirement", + glob: "foo/b*/basename.txt", + want: []searchRequest{ + { + searchBasis: searchByBasename, + value: "basename.txt", + requirement: "foo/b*/basename.txt", + }, + }, + }, + { + name: "basename glob", + glob: "basename.*", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "basename.*", + }, + }, + }, + { + name: "basename glob with requirement", + glob: "**/foo/bar/basename.*", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "basename.*", + requirement: "**/foo/bar/basename.*", + }, + }, + }, + { + name: "basename wildcard glob with requirement", + glob: "**/foo/bar/basenam?.txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "basenam?.txt", + requirement: "**/foo/bar/basenam?.txt", + }, + }, + }, + { + name: "glob classes within a basename", + glob: "**/foo/bar/basena[me][me].txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "basena[me][me].txt", + requirement: "**/foo/bar/basena[me][me].txt", + }, + }, + }, + { + name: "glob classes within a the path", + glob: "**/foo/[bB]ar/basena[me][me].txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "basena[me][me].txt", + requirement: "**/foo/[bB]ar/basena[me][me].txt", + }, + }, + }, + { + name: "alt clobbers basename extraction", + glob: "**/foo/bar/{nested/basena[me][me].txt,another.txt}", + want: []searchRequest{ + { + searchBasis: searchByGlob, + value: "**/foo/bar/{nested/basena[me][me].txt,another.txt}", + }, + }, + }, + { + name: "class clobbers basename extraction", + glob: "**/foo/bar/[me][m/e].txt,another.txt", + want: []searchRequest{ + { + searchBasis: searchByGlob, + value: "**/foo/bar/[me][m/e].txt,another.txt", + }, + }, + }, + { + name: "match alternative matches in the basename", + glob: "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}", + want: []searchRequest{ + { + searchBasis: searchByBasename, + value: "Packages", + requirement: "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}", + }, + { + searchBasis: searchByBasename, + value: "Packages.db", + requirement: "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}", + }, + { + searchBasis: searchByBasename, + value: "rpmdb.sqlite", + requirement: "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}", + }, + }, + }, + { + name: "match fallback to glob search on non-simple alternatives", + glob: "**/var/lib/rpm/{Packa{ges}{GES},Packages.db,rpmdb.sqlite}", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "{Packa{ges}{GES},Packages.db,rpmdb.sqlite}", + requirement: "**/var/lib/rpm/{Packa{ges}{GES},Packages.db,rpmdb.sqlite}", + }, + }, + }, + { + name: "dynamic extraction of basename and basename glob for alternatives", + glob: "**/var/lib/rpm/{Pack???s,Packages.db,rpm*.sqlite}", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "Pack???s", + requirement: "**/var/lib/rpm/{Pack???s,Packages.db,rpm*.sqlite}", + }, + { + searchBasis: searchByBasename, + value: "Packages.db", + requirement: "**/var/lib/rpm/{Pack???s,Packages.db,rpm*.sqlite}", + }, + { + searchBasis: searchByBasenameGlob, + value: "rpm*.sqlite", + requirement: "**/var/lib/rpm/{Pack???s,Packages.db,rpm*.sqlite}", + }, + }, + }, + { + name: "fallback to full glob search", + glob: "**/foo/bar/**?/**", + want: []searchRequest{ + { + searchBasis: searchByGlob, + value: "**/foo/bar/*?/**", + }, + }, + }, + { + name: "use parent basename for directory contents", + glob: "**/foo/bar/*", + want: []searchRequest{ + { + searchBasis: searchBySubDirectory, + value: "bar", + requirement: "**/foo/bar", + }, + }, + }, + // special cases + { + name: "empty string", + glob: "", + want: []searchRequest{ + { + searchBasis: searchByFullPath, + }, + }, + }, + { + name: "only a slash", + glob: "/", + want: []searchRequest{ + { + searchBasis: searchByFullPath, + value: "/", + }, + }, + }, + { + name: "cleanup to single slash", + glob: "///", + want: []searchRequest{ + { + searchBasis: searchByFullPath, + value: "/", + }, + }, + }, + { + name: "ends with slash", + glob: "/foo/b*r/", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "b*r", + requirement: "/foo/b*r", // note that the slash is removed since this should be a clean path + }, + }, + }, + { + name: "ends with *", + glob: "**/foo/b*r/*", + want: []searchRequest{ + { + searchBasis: searchByGlob, + value: "**/foo/b*r/*", + }, + }, + }, + { + name: "ends with ***", + glob: "**/foo/b*r/**", + want: []searchRequest{ + { + searchBasis: searchByGlob, + value: "**/foo/b*r/**", + }, + }, + }, + { + name: "spaces around everything", + glob: " /foo/b*r/ .txt ", + want: []searchRequest{ + { + searchBasis: searchByBasename, + value: " .txt", // note the space + requirement: "/foo/b*r/ .txt", // note the space in the middle, but otherwise clean on the front and back + }, + }, + }, + { + name: "fallback to full glob search", + glob: "**/foo/bar/***.*****.******", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "*.*.*", // note that the basename glob is cleaned up + requirement: "**/foo/bar/*.*.*", // note that the glob is cleaned up + }, + }, + }, + { + name: "odd glob input still honors basename searches", + glob: "**/foo/**.***.****bar/***thin*.txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "*thin*.txt", // note that the basename glob is cleaned up + requirement: "**/foo/*.*.*bar/*thin*.txt", // note that the glob is cleaned up + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, parseGlob(tt.glob), "parseGlob(%v)", tt.glob) + }) + } +} + +func Test_parseGlobBasename(t *testing.T) { + tests := []struct { + name string + input string + want []searchRequest + }{ + { + name: "empty string", + input: "", + want: []searchRequest{ + { + searchBasis: searchByBasename, + }, + }, + }, + { + name: "everything-ish", + input: "*?", + want: []searchRequest{ + { + searchBasis: searchByGlob, + }, + }, + }, + { + name: "everything recursive", + input: "**", + want: []searchRequest{ + { + searchBasis: searchByGlob, + }, + }, + }, + { + name: "simple basename", + input: "basename.txt", + want: []searchRequest{ + { + searchBasis: searchByBasename, + value: "basename.txt", + }, + }, + }, + { + name: "basename with prefix glob", + input: "*basename.txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "*basename.txt", + }, + }, + }, + { + name: "basename with pattern", + input: "bas*nam?.txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "bas*nam?.txt", + }, + }, + }, + { + name: "extension", + input: "*.txt", + want: []searchRequest{ + { + searchBasis: searchByExtension, + value: ".txt", + }, + }, + }, + { + name: "possible extension that should be searched by glob", + input: "*.*.txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "*.*.txt", + }, + }, + }, + { + name: "tricky basename", + input: ".txt", + want: []searchRequest{ + { + searchBasis: searchByBasename, + value: ".txt", + }, + }, + }, + { + name: "basename glob with extension", + input: "*thin*.txt", + want: []searchRequest{ + { + searchBasis: searchByBasenameGlob, + value: "*thin*.txt", + }, + }, + }, + { + name: "basename alternates", + input: "{Packages,Packages.db,rpmdb.sqlite}", + want: []searchRequest{ + { + searchBasis: searchByBasename, + value: "Packages", + }, + { + searchBasis: searchByBasename, + value: "Packages.db", + }, + { + searchBasis: searchByBasename, + value: "rpmdb.sqlite", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, parseGlobBasename(tt.input), "parseGlobBasename(%v)", tt.input) + }) + } +} + +func Test_cleanGlob(t *testing.T) { + tests := []struct { + name string + glob string + want string + }{ + { + name: "empty string", + glob: "", + want: "", + }, + { + name: "remove spaces from glob edges", + glob: " **/foo/ **/ bar.txt ", + want: "**/foo/ */ bar.txt", + }, + { + name: "simplify slashes", + glob: "///foo/////**///**////", + want: "/foo/**", + }, + { + name: "simplify larger recursive glob", + glob: "**/foo/**/*/***/*bar**/***.*****.******", + want: "**/foo/**/*/**/*bar*/*.*.*", + }, + { + name: "simplify glob prefix", + glob: "***/foo.txt", + want: "**/foo.txt", + }, + { + name: "simplify glob within multiple path", + glob: "bar**/ba**r*/***/**/bar***/**/foo.txt", + want: "bar*/ba*r*/**/bar*/**/foo.txt", + }, + { + name: "simplify prefix and suffix glob", + glob: "***/foo/**/****", + want: "**/foo/**", + }, + { + name: "simplify multiple recursive requests", + glob: "/**/**/foo/**/**", + want: "**/foo/**", + }, + { + name: "simplify slashes and asterisks", + glob: "/***/****///foo/**//****////", + want: "**/foo/**", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, cleanGlob(tt.glob), "cleanGlob(%v)", tt.glob) + }) + } +} + +func Test_removeRedundantCountGlob(t *testing.T) { + type args struct { + glob string + val rune + count int + } + tests := []struct { + name string + args args + want string + }{ + { + name: "empty string", + args: args{ + glob: "", + val: '*', + count: 1, + }, + want: "", + }, + { + name: "simplify on edges and body", + args: args{ + glob: "**/foo/***/****", + val: '*', + count: 2, + }, + want: "**/foo/**/**", + }, + { + name: "simplify slashes", + args: args{ + glob: "///something/**///here?/*/will//work///", + val: '/', + count: 1, + }, + want: "/something/**/here?/*/will/work/", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, removeRedundantCountGlob(tt.args.glob, tt.args.val, tt.args.count), "removeRedundantCountGlob(%v, %v, %v)", tt.args.glob, tt.args.val, tt.args.count) + }) + } +} + +func Test_simplifyMultipleGlobAsterisks(t *testing.T) { + tests := []struct { + name string + glob string + want string + }{ + { + name: "simplify glob suffix", + glob: "foo/.***", + want: "foo/.*", + }, + { + name: "simplify glob within path", + glob: "**/bar**/foo.txt", + want: "**/bar*/foo.txt", + }, + { + name: "simplify glob within multiple path", + glob: "bar**/ba**r*/**/**/bar**/**/foo.txt", + want: "bar*/ba*r*/**/**/bar*/**/foo.txt", + }, + { + name: "simplify glob within path prefix", + glob: "bar**/foo.txt", + want: "bar*/foo.txt", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, simplifyMultipleGlobAsterisks(tt.glob), "simplifyMultipleGlobAsterisks(%v)", tt.glob) + }) + } +} + +func Test_simplifyGlobRecursion(t *testing.T) { + tests := []struct { + name string + glob string + want string + }{ + { + name: "single instance with slash prefix", + glob: "/**", + want: "**", + }, + { + name: "single instance with slash suffix", + glob: "**/", + want: "**", + }, + { + name: "no slash prefix", + glob: "**/**/fo*o/**/**", + want: "**/fo*o/**", + }, + { + name: "within body", + glob: "/fo*o/**/**/bar", + want: "/fo*o/**/bar", + }, + { + name: "with slash prefix", + glob: "/**/**/foo/**/**", + want: "**/foo/**", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, simplifyGlobRecursion(tt.glob), "simplifyGlobRecursion(%v)", tt.glob) + }) + } +} diff --git a/pkg/filetree/glob_test.go b/pkg/filetree/glob_test.go index 6d63b1de..e5671561 100644 --- a/pkg/filetree/glob_test.go +++ b/pkg/filetree/glob_test.go @@ -10,7 +10,7 @@ import ( ) func TestFileInfoAdapter(t *testing.T) { - tr := NewFileTree() + tr := New() tr.AddFile("/home/thing.txt") tr.AddDir("/home/wagoodman") tr.AddSymLink("/home/thing", "./thing.txt") @@ -23,21 +23,21 @@ func TestFileInfoAdapter(t *testing.T) { VirtualPath: "/home/thing.txt", Node: filenode.FileNode{ RealPath: "/home/thing.txt", - FileType: file.TypeReg, + FileType: file.TypeRegular, }, }, "/home/wagoodman": { VirtualPath: "/home/wagoodman", Node: filenode.FileNode{ RealPath: "/home/wagoodman", - FileType: file.TypeDir, + FileType: file.TypeDirectory, }, }, "/home/thing": { VirtualPath: "/home/thing", Node: filenode.FileNode{ RealPath: "/home/thing", - FileType: file.TypeSymlink, + FileType: file.TypeSymLink, LinkPath: "./thing.txt", }, }, @@ -118,7 +118,7 @@ func TestFileInfoAdapter(t *testing.T) { } func TestOsAdapter_PreventInfiniteLoop(t *testing.T) { - tr := NewFileTree() + tr := New() tr.AddFile("/usr/bin/busybox") tr.AddSymLink("/usr/bin/X11", ".") @@ -167,7 +167,7 @@ func TestOsAdapter_PreventInfiniteLoop(t *testing.T) { } func TestFileInfoAdapter_PreventInfiniteLoop(t *testing.T) { - tr := NewFileTree() + tr := New() tr.AddFile("/usr/bin/busybox") tr.AddSymLink("/usr/bin/X11", ".") @@ -240,20 +240,20 @@ func TestOSAdapter_ReadDir(t *testing.T) { expected: []fileinfoAdapter{ { VirtualPath: "/home/thing.txt", - Node: filenode.FileNode{RealPath: "/home/thing.txt", FileType: 48}, + Node: filenode.FileNode{RealPath: "/home/thing.txt", FileType: file.TypeRegular}, }, { VirtualPath: "/home/wagoodman", - Node: filenode.FileNode{RealPath: "/home/wagoodman", FileType: 53}, + Node: filenode.FileNode{RealPath: "/home/wagoodman", FileType: file.TypeDirectory}, }, { VirtualPath: "/home/thing", - Node: filenode.FileNode{RealPath: "/home/thing", FileType: 50, LinkPath: "./thing.txt"}, + Node: filenode.FileNode{RealPath: "/home/thing", FileType: file.TypeSymLink, LinkPath: "./thing.txt"}, }, { VirtualPath: "/home/place", - Node: filenode.FileNode{RealPath: "/home/place", FileType: 49, LinkPath: "/somewhere-else"}, + Node: filenode.FileNode{RealPath: "/home/place", FileType: file.TypeHardLink, LinkPath: "/somewhere-else"}, }, }, shouldErr: false, @@ -312,7 +312,7 @@ func TestOSAdapter_Lstat(t *testing.T) { VirtualPath: "/home", Node: filenode.FileNode{ RealPath: "/home", - FileType: file.TypeDir, + FileType: file.TypeDirectory, }, }, }, @@ -324,7 +324,7 @@ func TestOSAdapter_Lstat(t *testing.T) { VirtualPath: "/home/thing", Node: filenode.FileNode{ RealPath: "/home/thing", - FileType: file.TypeSymlink, + FileType: file.TypeSymLink, LinkPath: "./thing.txt", }, }, @@ -400,7 +400,7 @@ func TestOSAdapter_Stat(t *testing.T) { VirtualPath: "/home", Node: filenode.FileNode{ RealPath: "/home", - FileType: file.TypeDir, + FileType: file.TypeDirectory, }, }, }, @@ -413,7 +413,7 @@ func TestOSAdapter_Stat(t *testing.T) { VirtualPath: "/home/thing", Node: filenode.FileNode{ RealPath: "/home/thing.txt", - FileType: file.TypeReg, + FileType: file.TypeRegular, }, }, }, @@ -470,7 +470,7 @@ func TestOSAdapter_Stat(t *testing.T) { } func newHelperTree() *FileTree { - tr := NewFileTree() + tr := New() tr.AddFile("/home/thing.txt") tr.AddDir("/home/wagoodman") tr.AddSymLink("/home/thing", "./thing.txt") diff --git a/pkg/filetree/index.go b/pkg/filetree/index.go new file mode 100644 index 00000000..a7433d48 --- /dev/null +++ b/pkg/filetree/index.go @@ -0,0 +1,297 @@ +package filetree + +import ( + "fmt" + "os" + "path" + "sort" + "strings" + "sync" + + "github.com/anchore/stereoscope/internal/log" + + "github.com/anchore/stereoscope/pkg/file" + "github.com/becheran/wildmatch-go" + "github.com/scylladb/go-set/strset" +) + +type Index interface { + IndexReader + IndexWriter +} + +type IndexReader interface { + Exists(f file.Reference) bool + Get(f file.Reference) (IndexEntry, error) + GetByMIMEType(mTypes ...string) ([]IndexEntry, error) + GetByFileType(fTypes ...file.Type) ([]IndexEntry, error) + GetByExtension(extensions ...string) ([]IndexEntry, error) + GetByBasename(basenames ...string) ([]IndexEntry, error) + GetByBasenameGlob(globs ...string) ([]IndexEntry, error) + Basenames() []string +} + +type IndexWriter interface { + Add(f file.Reference, m file.Metadata) +} + +// Index represents all file metadata and source tracing for all files contained within the image layer +// blobs (i.e. everything except for the image index/manifest/metadata files). +type index struct { + *sync.RWMutex + index map[file.ID]IndexEntry + byFileType map[file.Type]file.IDSet + byMIMEType map[string]file.IDSet + byExtension map[string]file.IDSet + byBasename map[string]file.IDSet + basenames *strset.Set +} + +// NewIndex returns an empty Index. +func NewIndex() Index { + return &index{ + RWMutex: &sync.RWMutex{}, + index: make(map[file.ID]IndexEntry), + byFileType: make(map[file.Type]file.IDSet), + byMIMEType: make(map[string]file.IDSet), + byExtension: make(map[string]file.IDSet), + byBasename: make(map[string]file.IDSet), + basenames: strset.New(), + } +} + +// IndexEntry represents all stored metadata for a single file reference. +type IndexEntry struct { + file.Reference + file.Metadata +} + +// Add creates a new IndexEntry for the given file reference and metadata, cataloged by the ID of the +// file reference (overwriting any existing entries without warning). +func (c *index) Add(f file.Reference, m file.Metadata) { + c.Lock() + defer c.Unlock() + + id := f.ID() + + if _, ok := c.index[id]; ok { + log.WithFields("id", id, "path", f.RealPath).Debug("overwriting existing file index entry") + } + + if m.MIMEType != "" { + if _, ok := c.byMIMEType[m.MIMEType]; !ok { + c.byMIMEType[m.MIMEType] = file.NewIDSet() + } + // an empty MIME type means that we didn't have the contents of the file to determine the MIME type. If we have + // the contents and the MIME type could not be determined then the default value is application/octet-stream. + c.byMIMEType[m.MIMEType].Add(id) + } + + basename := path.Base(string(f.RealPath)) + + if _, ok := c.byBasename[basename]; !ok { + c.byBasename[basename] = file.NewIDSet() + } + + c.byBasename[basename].Add(id) + c.basenames.Add(basename) + + for _, ext := range fileExtensions(string(f.RealPath)) { + if _, ok := c.byExtension[ext]; !ok { + c.byExtension[ext] = file.NewIDSet() + } + c.byExtension[ext].Add(id) + } + + if _, ok := c.byFileType[m.Type]; !ok { + c.byFileType[m.Type] = file.NewIDSet() + } + c.byFileType[m.Type].Add(id) + + c.index[id] = IndexEntry{ + Reference: f, + Metadata: m, + } +} + +// Exists indicates if the given file reference exists in the index. +func (c *index) Exists(f file.Reference) bool { + c.RLock() + defer c.RUnlock() + _, ok := c.index[f.ID()] + return ok +} + +// Get fetches a IndexEntry for the given file reference, or returns an error if the file reference has not +// been added to the index. +func (c *index) Get(f file.Reference) (IndexEntry, error) { + c.RLock() + defer c.RUnlock() + value, ok := c.index[f.ID()] + if !ok { + return IndexEntry{}, os.ErrNotExist + } + return value, nil +} + +func (c *index) Basenames() []string { + c.RLock() + defer c.RUnlock() + + bns := c.basenames.List() + sort.Strings(bns) + + return bns +} + +func (c *index) GetByFileType(fTypes ...file.Type) ([]IndexEntry, error) { + c.RLock() + defer c.RUnlock() + + var entries []IndexEntry + + for _, fType := range fTypes { + fileIDs, ok := c.byFileType[fType] + if !ok { + continue + } + + for _, id := range fileIDs.Sorted() { + entry, ok := c.index[id] + if !ok { + return nil, os.ErrNotExist + } + entries = append(entries, entry) + } + } + + return entries, nil +} + +func (c *index) GetByMIMEType(mTypes ...string) ([]IndexEntry, error) { + c.RLock() + defer c.RUnlock() + + var entries []IndexEntry + + for _, mType := range mTypes { + fileIDs, ok := c.byMIMEType[mType] + if !ok { + continue + } + + for _, id := range fileIDs.Sorted() { + entry, ok := c.index[id] + if !ok { + return nil, os.ErrNotExist + } + entries = append(entries, entry) + } + } + + return entries, nil +} + +func (c *index) GetByExtension(extensions ...string) ([]IndexEntry, error) { + c.RLock() + defer c.RUnlock() + + var entries []IndexEntry + + for _, extension := range extensions { + fileIDs, ok := c.byExtension[extension] + if !ok { + continue + } + + for _, id := range fileIDs.Sorted() { + entry, ok := c.index[id] + if !ok { + return nil, os.ErrNotExist + } + entries = append(entries, entry) + } + } + + return entries, nil +} + +func (c *index) GetByBasename(basenames ...string) ([]IndexEntry, error) { + c.RLock() + defer c.RUnlock() + + var entries []IndexEntry + + for _, basename := range basenames { + if strings.Contains(basename, "/") { + return nil, fmt.Errorf("found directory separator in a basename") + } + + fileIDs, ok := c.byBasename[basename] + if !ok { + continue + } + + for _, id := range fileIDs.Sorted() { + entry, ok := c.index[id] + if !ok { + return nil, os.ErrNotExist + } + entries = append(entries, entry) + } + } + + return entries, nil +} + +func (c *index) GetByBasenameGlob(globs ...string) ([]IndexEntry, error) { + c.RLock() + defer c.RUnlock() + + var entries []IndexEntry + for _, glob := range globs { + if strings.Contains(glob, "**") { + return nil, fmt.Errorf("basename glob patterns with '**' are not supported") + } + if strings.Contains(glob, "/") { + return nil, fmt.Errorf("found directory separator in a basename") + } + + patternObj := wildmatch.NewWildMatch(glob) + for _, b := range c.Basenames() { + if patternObj.IsMatch(b) { + bns, err := c.GetByBasename(b) + if err != nil { + return nil, fmt.Errorf("unable to fetch file references by basename (%q): %w", b, err) + } + entries = append(entries, bns...) + } + } + } + + return entries, nil +} + +func fileExtensions(p string) []string { + var exts []string + p = strings.TrimSpace(p) + + // ignore oddities + if strings.HasSuffix(p, ".") { + return exts + } + + // ignore directories + if strings.HasSuffix(p, "/") { + return exts + } + + // ignore . which indicate a hidden file + p = strings.TrimLeft(path.Base(p), ".") + for i := len(p) - 1; i >= 0; i-- { + if p[i] == '.' { + exts = append(exts, p[i:]) + } + } + return exts +} diff --git a/pkg/filetree/index_test.go b/pkg/filetree/index_test.go new file mode 100644 index 00000000..d8cd6b91 --- /dev/null +++ b/pkg/filetree/index_test.go @@ -0,0 +1,776 @@ +//go:build !windows +// +build !windows + +package filetree + +import ( + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" + + "github.com/anchore/stereoscope/pkg/file" +) + +func commonIndexFixture(t *testing.T) Index { + t.Helper() + + tree := New() + idx := NewIndex() + + addDir := func(path file.Path) { + ref, err := tree.AddDir(path) + require.NoError(t, err, "failed to add DIR reference to index") + require.NotNil(t, ref, "failed to add DIR reference to index (nil ref") + idx.Add(*ref, file.Metadata{Path: string(path), Type: file.TypeDirectory, IsDir: true}) + } + + addFile := func(path file.Path) { + ref, err := tree.AddFile(path) + require.NoError(t, err, "failed to add FILE reference to index") + require.NotNil(t, ref, "failed to add FILE reference to index (nil ref") + idx.Add(*ref, file.Metadata{Path: string(path), Type: file.TypeRegular, MIMEType: "text/plain"}) + } + + addLink := func(from, to file.Path) { + ref, err := tree.AddSymLink(from, to) + require.NoError(t, err, "failed to add LINK reference to index") + require.NotNil(t, ref, "failed to add LINK reference to index (nil ref") + idx.Add(*ref, file.Metadata{Path: string(from), LinkDestination: string(to), Type: file.TypeSymLink}) + } + + // mkdir -p path/branch.d/one + // mkdir -p path/branch.d/two + // mkdir -p path/common + + // note: we need to add all paths explicitly to the index + addDir("/path") + addDir("/path/branch.d") + addDir("/path/branch.d/one") + addDir("/path/branch.d/two") + addDir("/path/common") + + // echo "first file" > path/branch.d/one/file-1.txt + // echo "forth file" > path/branch.d/one/file-4.d + // echo "multi ext file" > path/branch.d/one/file-4.tar.gz + // echo "hidden file" > path/branch.d/one/.file-4.tar.gz + + addFile("/path/branch.d/one/file-1.txt") + addFile("/path/branch.d/one/file-4.d") + addFile("/path/branch.d/one/file-4.tar.gz") + addFile("/path/branch.d/one/.file-4.tar.gz") + + // ln -s path/branch.d path/common/branch.d + // ln -s path/branch.d path/common/branch + // ln -s path/branch.d/one/file-4.d path/common/file-4 + // ln -s path/branch.d/one/file-1.txt path/common/file-1.d + + addLink("/path/common/branch.d", "path/branch.d") + addLink("/path/common/branch", "path/branch.d") + addLink("/path/common/file-4", "path/branch.d/one/file-4.d") + addLink("/path/common/file-1.d", "path/branch.d/one/file-1.txt") + + // echo "second file" > path/branch.d/two/file-2.txt + // echo "third file" > path/file-3.txt + + addFile("/path/branch.d/two/file-2.txt") + addFile("/path/file-3.txt") + + return idx +} + +func Test_fileExtensions(t *testing.T) { + tests := []struct { + name string + path string + want []string + }{ + { + name: "empty", + path: "", + }, + { + name: "directory", + path: "/somewhere/to/nowhere/", + }, + { + name: "directory with ext", + path: "/somewhere/to/nowhere.d/", + }, + { + name: "single extension", + path: "/somewhere/to/my.tar", + want: []string{".tar"}, + }, + { + name: "multiple extensions", + path: "/somewhere/to/my.tar.gz", + want: []string{".gz", ".tar.gz"}, + }, + { + name: "ignore . prefix", + path: "/somewhere/to/.my.tar.gz", + want: []string{".gz", ".tar.gz"}, + }, + { + name: "ignore more . prefixes", + path: "/somewhere/to/...my.tar.gz", + want: []string{".gz", ".tar.gz"}, + }, + { + name: "ignore . suffixes", + path: "/somewhere/to/my.tar.gz...", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, fileExtensions(tt.path)) + }) + } +} + +func TestFileCatalog_GetByFileType(t *testing.T) { + fileIndex := commonIndexFixture(t) + + tests := []struct { + name string + input []file.Type + want []IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get real file", + input: []file.Type{file.TypeRegular}, + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.d", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/.file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/.file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + + Reference: file.Reference{RealPath: "/path/branch.d/two/file-2.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/two/file-2.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/file-3.txt"}, + Metadata: file.Metadata{ + Path: "/path/file-3.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get directories", + input: []file.Type{file.TypeDirectory}, + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path"}, + Metadata: file.Metadata{ + Path: "/path", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + + Reference: file.Reference{RealPath: "/path/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/two"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/two", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common"}, + Metadata: file.Metadata{ + Path: "/path/common", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + }, + }, + { + name: "get links", + input: []file.Type{file.TypeHardLink, file.TypeSymLink}, + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/common/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/common/branch.d", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common/branch"}, + Metadata: file.Metadata{ + Path: "/path/common/branch", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common/file-4"}, + Metadata: file.Metadata{ + Path: "/path/common/file-4", + LinkDestination: "path/branch.d/one/file-4.d", + Type: file.TypeSymLink, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get non-existent types", + input: []file.Type{file.TypeBlockDevice, file.TypeCharacterDevice, file.TypeFIFO, file.TypeSocket, file.TypeIrregular}, + want: []IndexEntry{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileIndex.GetByFileType(tt.input...) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetByExtension(t *testing.T) { + fileIndex := commonIndexFixture(t) + + tests := []struct { + name string + input string + want []IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get simple extension", + input: ".txt", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + + Reference: file.Reference{RealPath: "/path/branch.d/two/file-2.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/two/file-2.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/file-3.txt"}, + Metadata: file.Metadata{ + Path: "/path/file-3.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get mixed type extension", + input: ".d", + want: []IndexEntry{ + { + + Reference: file.Reference{RealPath: "/path/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.d", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + + { + + Reference: file.Reference{RealPath: "/path/common/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/common/branch.d", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + { + + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get long extension", + input: ".tar.gz", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/.file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/.file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get short extension", + input: ".gz", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/.file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/.file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existent extension", + input: ".blerg-123", + want: []IndexEntry{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileIndex.GetByExtension(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetByBasename(t *testing.T) { + fileIndex := commonIndexFixture(t) + + tests := []struct { + name string + input string + want []IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get existing file name", + input: "file-1.txt", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existing name", + input: "file-11.txt", + want: []IndexEntry{}, + }, + { + name: "get directory name", + input: "branch.d", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/common/branch.d", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get symlink name", + input: "file-1.d", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get basename with path expression", + input: "somewhere/file-1.d", + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileIndex.GetByBasename(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetByBasenameGlob(t *testing.T) { + fileIndex := commonIndexFixture(t) + + tests := []struct { + name string + input string + want []IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get existing file name", + input: "file-1.*", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existing name", + input: "blerg-*.txt", + want: []IndexEntry{}, + }, + { + name: "get directory name", + input: "bran*.d", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/common/branch.d", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get symlink name", + input: "file?1.d", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get basename with path expression", + input: "somewhere/file?1.d", + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileIndex.GetByBasenameGlob(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetByMimeType(t *testing.T) { + fileIndex := commonIndexFixture(t) + + tests := []struct { + name string + input string + want []IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get existing file mimetype", + input: "text/plain", + want: []IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.d", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/.file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/.file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/two/file-2.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/two/file-2.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/file-3.txt"}, + Metadata: file.Metadata{ + Path: "/path/file-3.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existing mimetype", + input: "text/bogus", + want: []IndexEntry{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileIndex.GetByMIMEType(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetBasenames(t *testing.T) { + fileIndex := commonIndexFixture(t) + + tests := []struct { + name string + want []string + }{ + { + name: "go case", + want: []string{ + ".file-4.tar.gz", + "branch", + "branch.d", + "common", + "file-1.d", + "file-1.txt", + "file-2.txt", + "file-3.txt", + "file-4", + "file-4.d", + "file-4.tar.gz", + "one", + "path", + "two", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := fileIndex.Basenames() + assert.ElementsMatchf(t, tt.want, actual, "diff: %s", cmp.Diff(tt.want, actual)) + }) + } +} diff --git a/pkg/filetree/interfaces.go b/pkg/filetree/interfaces.go new file mode 100644 index 00000000..9d813fc4 --- /dev/null +++ b/pkg/filetree/interfaces.go @@ -0,0 +1,45 @@ +package filetree + +import ( + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree/filenode" + "github.com/anchore/stereoscope/pkg/tree" +) + +type ReadWriter interface { + Reader + Writer +} + +type Reader interface { + AllFiles(types ...file.Type) []file.Reference + TreeReader() tree.Reader + PathReader + Walker + Copier +} + +type PathReader interface { + File(path file.Path, options ...LinkResolutionOption) (bool, *file.Resolution, error) + FilesByGlob(query string, options ...LinkResolutionOption) ([]file.Resolution, error) + AllRealPaths() []file.Path + ListPaths(dir file.Path) ([]file.Path, error) + HasPath(path file.Path, options ...LinkResolutionOption) bool +} + +type Copier interface { + Copy() (ReadWriter, error) +} + +type Walker interface { + Walk(fn func(path file.Path, f filenode.FileNode) error, conditions *WalkConditions) error +} + +type Writer interface { + AddFile(realPath file.Path) (*file.Reference, error) + AddSymLink(realPath file.Path, linkPath file.Path) (*file.Reference, error) + AddHardLink(realPath file.Path, linkPath file.Path) (*file.Reference, error) + AddDir(realPath file.Path) (*file.Reference, error) + RemovePath(path file.Path) error + Merge(upper Reader) error +} diff --git a/pkg/filetree/node_access.go b/pkg/filetree/node_access.go new file mode 100644 index 00000000..dda333bd --- /dev/null +++ b/pkg/filetree/node_access.go @@ -0,0 +1,50 @@ +package filetree + +import ( + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree/filenode" +) + +// nodeAccess represents a request into the tree for a specific path and the resulting node, which may have a different path. +type nodeAccess struct { + RequestPath file.Path + FileNode *filenode.FileNode // note: it is important that nodeAccess does not behave like FileNode (then it can be added to the tree directly) + LeafLinkResolution []nodeAccess +} + +func (na *nodeAccess) HasFileNode() bool { + if na == nil { + return false + } + return na.FileNode != nil +} + +func (na *nodeAccess) FileResolution() *file.Resolution { + if !na.HasFileNode() { + return nil + } + return file.NewResolution( + na.RequestPath, + na.FileNode.Reference, + newResolutions(na.LeafLinkResolution), + ) +} + +func (na *nodeAccess) References() []file.Reference { + if !na.HasFileNode() { + return nil + } + var refs []file.Reference + + if na.FileNode.Reference != nil { + refs = append(refs, *na.FileNode.Reference) + } + + for _, l := range na.LeafLinkResolution { + if l.HasFileNode() && l.FileNode.Reference != nil { + refs = append(refs, *l.FileNode.Reference) + } + } + + return refs +} diff --git a/pkg/filetree/search.go b/pkg/filetree/search.go new file mode 100644 index 00000000..e5152ab7 --- /dev/null +++ b/pkg/filetree/search.go @@ -0,0 +1,477 @@ +package filetree + +import ( + "fmt" + "path" + "sort" + + "github.com/anchore/stereoscope/internal/log" + "github.com/anchore/stereoscope/pkg/filetree/filenode" + "github.com/anchore/stereoscope/pkg/tree/node" + + "github.com/anchore/stereoscope/pkg/file" + "github.com/bmatcuk/doublestar/v4" +) + +// Searcher is a facade for searching a file tree with optional indexing support. +type Searcher interface { + SearchByPath(path string, options ...LinkResolutionOption) (*file.Resolution, error) + SearchByGlob(patterns string, options ...LinkResolutionOption) ([]file.Resolution, error) + SearchByMIMEType(mimeTypes ...string) ([]file.Resolution, error) +} + +type searchContext struct { + tree *FileTree // this is the tree which all index search results are filtered against + index IndexReader // this index is relative to one or more trees, not just necessarily one + + // the following enables correct link resolution when searching via the index + linkBackwardRefs map[node.ID]node.IDSet // {link-destination-node-id: str([link-node-id, ...])} +} + +func NewSearchContext(tree Reader, index IndexReader) Searcher { + c := &searchContext{ + tree: tree.(*FileTree), + index: index, + linkBackwardRefs: make(map[node.ID]node.IDSet), + } + + if err := c.buildLinkResolutionIndex(); err != nil { + log.WithFields("error", err).Warn("unable to build link resolution index for filetree search context") + } + + return c +} + +func (sc *searchContext) buildLinkResolutionIndex() error { + entries, err := sc.index.GetByFileType(file.TypeSymLink, file.TypeHardLink) + if err != nil { + return err + } + + // filter the results relative to the tree + nodes, err := sc.fileNodesInTree(entries) + if err != nil { + return err + } + + // note: the remaining references are all links that exist in the tree + + for _, fn := range nodes { + destinationFna, err := sc.tree.file(fn.RenderLinkDestination()) + if err != nil { + return fmt.Errorf("unable to get node for path=%q: %w", fn.RealPath, err) + } + + if !destinationFna.HasFileNode() { + // we were unable to resolve the link destination, this could be due to the fact that the destination simply + continue + } + + linkID := fn.ID() + destinationID := destinationFna.FileNode.ID() + + // add backward reference... + if _, ok := sc.linkBackwardRefs[destinationID]; !ok { + sc.linkBackwardRefs[destinationID] = node.NewIDSet() + } + sc.linkBackwardRefs[destinationID].Add(linkID) + } + + return nil +} + +func (sc searchContext) SearchByPath(path string, options ...LinkResolutionOption) (*file.Resolution, error) { + // TODO: one day this could leverage indexes outside of the tree, but today this is not implemented + log.WithFields("path", path).Trace("searching filetree by path") + + options = append(options, FollowBasenameLinks) + _, ref, err := sc.tree.File(file.Path(path), options...) + return ref, err +} + +func (sc searchContext) SearchByMIMEType(mimeTypes ...string) ([]file.Resolution, error) { + log.WithFields("types", mimeTypes).Trace("searching filetree by MIME types") + + var fileEntries []IndexEntry + + for _, mType := range mimeTypes { + entries, err := sc.index.GetByMIMEType(mType) + if err != nil { + return nil, fmt.Errorf("unable to fetch file references by MIME type (%q): %w", mType, err) + } + fileEntries = append(fileEntries, entries...) + } + + refs, err := sc.referencesInTree(fileEntries) + if err != nil { + return nil, err + } + + sort.Sort(file.Resolutions(refs)) + + return refs, nil +} + +// add case for status.d/* like things that hook up directly into filetree.ListPaths() + +func (sc searchContext) SearchByGlob(pattern string, options ...LinkResolutionOption) ([]file.Resolution, error) { + log.WithFields("glob", pattern).Trace("searching filetree by glob") + + if sc.index == nil { + options = append(options, FollowBasenameLinks) + refs, err := sc.tree.FilesByGlob(pattern, options...) + if err != nil { + return nil, fmt.Errorf("unable to search by glob=%q: %w", pattern, err) + } + sort.Sort(file.Resolutions(refs)) + return refs, nil + } + + var allRefs []file.Resolution + for _, request := range parseGlob(pattern) { + refs, err := sc.searchByRequest(request, options...) + if err != nil { + return nil, fmt.Errorf("unable to search by glob=%q: %w", pattern, err) + } + allRefs = append(allRefs, refs...) + } + + sort.Sort(file.Resolutions(allRefs)) + + return allRefs, nil +} + +func (sc searchContext) searchByRequest(request searchRequest, options ...LinkResolutionOption) ([]file.Resolution, error) { + switch request.searchBasis { + case searchByFullPath: + options = append(options, FollowBasenameLinks) + ref, err := sc.SearchByPath(request.value, options...) + if err != nil { + return nil, err + } + if ref == nil { + return nil, nil + } + return []file.Resolution{*ref}, nil + case searchByBasename: + indexes, err := sc.index.GetByBasename(request.value) + if err != nil { + return nil, fmt.Errorf("unable to search by basename=%q: %w", request.value, err) + } + refs, err := sc.referencesWithRequirement(request.requirement, indexes) + if err != nil { + return nil, err + } + return refs, nil + case searchByBasenameGlob: + indexes, err := sc.index.GetByBasenameGlob(request.value) + if err != nil { + return nil, fmt.Errorf("unable to search by basename-glob=%q: %w", request.value, err) + } + refs, err := sc.referencesWithRequirement(request.requirement, indexes) + if err != nil { + return nil, err + } + return refs, nil + case searchByExtension: + indexes, err := sc.index.GetByExtension(request.value) + if err != nil { + return nil, fmt.Errorf("unable to search by extension=%q: %w", request.value, err) + } + refs, err := sc.referencesWithRequirement(request.requirement, indexes) + if err != nil { + return nil, err + } + return refs, nil + case searchBySubDirectory: + return sc.searchByParentBasename(request) + + case searchByGlob: + log.WithFields("glob", request.value).Trace("glob provided is an expensive search, consider using a more specific indexed search") + + options = append(options, FollowBasenameLinks) + return sc.tree.FilesByGlob(request.value, options...) + } + + return nil, fmt.Errorf("invalid search request: %+v", request.searchBasis) +} + +func (sc searchContext) searchByParentBasename(request searchRequest) ([]file.Resolution, error) { + indexes, err := sc.index.GetByBasename(request.value) + if err != nil { + return nil, fmt.Errorf("unable to search by extension=%q: %w", request.value, err) + } + refs, err := sc.referencesWithRequirement(request.requirement, indexes) + if err != nil { + return nil, err + } + + var results []file.Resolution + for _, ref := range refs { + paths, err := sc.tree.ListPaths(ref.RequestPath) + if err != nil { + // this may not be a directory, that's alright, just continue... + continue + } + for _, p := range paths { + _, nestedRef, err := sc.tree.File(p, FollowBasenameLinks) + if err != nil { + return nil, fmt.Errorf("unable to fetch file reference from parent path %q for path=%q: %w", ref.RequestPath, p, err) + } + if !nestedRef.HasReference() { + continue + } + // note: the requirement was written for the parent, so we need to consider the new + // child path by adding /* to match all children + matches, err := matchesRequirement(*nestedRef, request.requirement+"/*") + if err != nil { + return nil, err + } + if matches { + results = append(results, *nestedRef) + } + } + } + + return results, nil +} + +func (sc searchContext) referencesWithRequirement(requirement string, entries []IndexEntry) ([]file.Resolution, error) { + refs, err := sc.referencesInTree(entries) + if err != nil { + return nil, err + } + + if requirement == "" { + return refs, nil + } + + var results []file.Resolution + for _, ref := range refs { + matches, err := matchesRequirement(ref, requirement) + if err != nil { + return nil, err + } + if matches { + results = append(results, ref) + } + } + + return results, nil +} + +func matchesRequirement(ref file.Resolution, requirement string) (bool, error) { + allRefPaths := ref.AllRequestPaths() + for _, p := range allRefPaths { + matched, err := doublestar.Match(requirement, string(p)) + if err != nil { + return false, fmt.Errorf("unable to match glob pattern=%q to path=%q: %w", requirement, p, err) + } + if matched { + return true, nil + } + } + return false, nil +} + +type cacheRequest struct { + RealPath file.Path +} + +type cacheResult struct { + Paths file.PathSet + Error error +} + +func (sc searchContext) allPathsToNode(fn *filenode.FileNode) ([]file.Path, error) { + if fn == nil { + return nil, nil + } + + observedPaths := file.NewPathSet() + + cache := map[cacheRequest]cacheResult{} + + paths, err := sc.pathsToNode(fn, observedPaths, cache) + if err != nil { + return nil, err + } + + pathsList := paths.List() + sort.Sort(file.Paths(pathsList)) + + // TODO: filter to only paths that exist in the tree + + return pathsList, nil +} + +func (sc searchContext) pathsToNode(fn *filenode.FileNode, observedPaths file.PathSet, cache map[cacheRequest]cacheResult) (file.PathSet, error) { + req := cacheRequest{ + RealPath: fn.RealPath, + } + + if result, ok := cache[req]; ok { + return result.Paths, result.Error + } + + paths, err := sc._pathsToNode(fn, observedPaths, cache) + + cache[req] = cacheResult{ + Paths: paths, + Error: err, + } + + return paths, err +} + +// nolint: funlen +func (sc searchContext) _pathsToNode(fn *filenode.FileNode, observedPaths file.PathSet, cache map[cacheRequest]cacheResult) (file.PathSet, error) { + if fn == nil { + return nil, nil + } + + paths := file.NewPathSet() + paths.Add(fn.RealPath) + + if observedPaths != nil { + if observedPaths.Contains(fn.RealPath) { + // we've already observed this path, so we can stop here + return nil, nil + } + observedPaths.Add(fn.RealPath) + } + + nodeID := fn.ID() + + addPath := func(suffix string, ps ...file.Path) { + for _, p := range ps { + if suffix != "" { + p = file.Path(path.Join(string(p), suffix)) + } + paths.Add(p) + } + } + + // add all paths to the node that are linked to it + for _, linkSrcID := range sc.linkBackwardRefs[nodeID].List() { + pfn := sc.tree.tree.Node(linkSrcID) + if pfn == nil { + log.WithFields("id", nodeID, "parent", linkSrcID).Warn("found non-existent parent link") + continue + } + linkSrcPaths, err := sc.pathsToNode(pfn.(*filenode.FileNode), observedPaths, cache) + if err != nil { + return nil, err + } + + addPath("", linkSrcPaths.List()...) + } + + // crawl up the tree to find all paths to our parent and repeat + for _, p := range paths.List() { + nextNestedSuffix := p.Basename() + allParentPaths := p.ConstituentPaths() + sort.Sort(sort.Reverse(file.Paths(allParentPaths))) + + for _, pp := range allParentPaths { + if pp == "/" { + break + } + + nestedSuffix := nextNestedSuffix + nextNestedSuffix = path.Join(pp.Basename(), nestedSuffix) + + pna, err := sc.tree.node(pp, linkResolutionStrategy{ + FollowAncestorLinks: true, + FollowBasenameLinks: false, + }) + if err != nil { + return nil, fmt.Errorf("unable to get parent node for path=%q: %w", pp, err) + } + + if !pna.HasFileNode() { + continue + } + + parentLinkPaths, err := sc.pathsToNode(pna.FileNode, observedPaths, cache) + if err != nil { + return nil, err + } + addPath(nestedSuffix, parentLinkPaths.List()...) + } + } + observedPaths.Remove(fn.RealPath) + + return paths, nil +} + +func (sc searchContext) fileNodesInTree(fileEntries []IndexEntry) ([]*filenode.FileNode, error) { + var nodes []*filenode.FileNode +allFileEntries: + for _, entry := range fileEntries { + // note: it is important that we don't enable any basename link resolution + na, err := sc.tree.file(entry.Reference.RealPath) + if err != nil { + return nil, fmt.Errorf("unable to get ref for path=%q: %w", entry.Reference.RealPath, err) + } + + if !na.HasFileNode() { + continue + } + + // only check the resolved node matches the index entries reference, not via link resolutions... + if na.FileNode.Reference != nil && na.FileNode.Reference.ID() == entry.Reference.ID() { + nodes = append(nodes, na.FileNode) + continue allFileEntries + } + + // we did not find a matching file ID in the tree, so drop this entry + } + return nodes, nil +} + +// referencesInTree does two things relative to the index entries given: +// 1) it expands the index entries to include all possible access paths to the file node (by considering all possible link resolutions) +// 2) it filters the index entries to only include those that exist in the tree +func (sc searchContext) referencesInTree(fileEntries []IndexEntry) ([]file.Resolution, error) { + var refs []file.Resolution + + for _, entry := range fileEntries { + na, err := sc.tree.file(entry.Reference.RealPath, FollowBasenameLinks) + if err != nil { + return nil, fmt.Errorf("unable to get ref for path=%q: %w", entry.Reference.RealPath, err) + } + + // this filters out any index entries that do not exist in the tree + if !na.HasFileNode() { + continue + } + + // expand the index results with more possible access paths from the link resolution cache + var expandedRefs []file.Resolution + allPathsToNode, err := sc.allPathsToNode(na.FileNode) + if err != nil { + return nil, fmt.Errorf("unable to get all paths to node for path=%q: %w", entry.Reference.RealPath, err) + } + for _, p := range allPathsToNode { + _, ref, err := sc.tree.File(p, FollowBasenameLinks) + if err != nil { + return nil, fmt.Errorf("unable to get ref for path=%q: %w", p, err) + } + if !ref.HasReference() { + continue + } + expandedRefs = append(expandedRefs, *ref) + } + + for _, ref := range expandedRefs { + for _, accessRef := range ref.References() { + if accessRef.ID() == entry.Reference.ID() { + // we know this entry exists in the tree, keep track of the reference for this file + refs = append(refs, ref) + } + } + } + } + return refs, nil +} diff --git a/pkg/filetree/search_test.go b/pkg/filetree/search_test.go new file mode 100644 index 00000000..6033646c --- /dev/null +++ b/pkg/filetree/search_test.go @@ -0,0 +1,1015 @@ +package filetree + +import ( + "fmt" + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree/filenode" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func Test_searchContext_SearchByPath(t *testing.T) { + type fields struct { + tree *FileTree + index Index + } + type args struct { + path string + options []LinkResolutionOption + } + + tree := New() + ref, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, ref) + + idx := NewIndex() + idx.Add(*ref, file.Metadata{MIMEType: "plain/text"}) + + defaultFields := fields{ + tree: tree, + index: idx, + } + + tests := []struct { + name string + fields fields + args args + want *file.Resolution + wantErr require.ErrorAssertionFunc + }{ + { + name: "path exists", + fields: defaultFields, + args: args{ + path: "/path/to/file.txt", + }, + want: &file.Resolution{ + RequestPath: "/path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + { + name: "path does not exists", + fields: defaultFields, + args: args{ + path: "/NOT/path/to/file.txt", + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + i := searchContext{ + tree: tt.fields.tree, + index: tt.fields.index, + } + got, err := i.SearchByPath(tt.args.path, tt.args.options...) + tt.wantErr(t, err, fmt.Sprintf("SearchByPath(%v, %v)", tt.args.path, tt.args.options)) + if err != nil { + return + } + + opts := []cmp.Option{ + cmpopts.IgnoreFields(file.Reference{}, "id"), + } + + if d := cmp.Diff(tt.want, got, opts...); d != "" { + t.Errorf("SearchByPath() mismatch (-want +got):\n%s", d) + } + }) + } +} + +func Test_searchContext_SearchByGlob(t *testing.T) { + type fields struct { + tree *FileTree + index Index + } + type args struct { + glob string + options []LinkResolutionOption + } + + tree := New() + doubleLinkToPathRef, err := tree.AddSymLink("/double-link-to-path", "/link-to-path") + require.NoError(t, err) + require.NotNil(t, doubleLinkToPathRef) + + linkToPathRef, err := tree.AddSymLink("/link-to-path", "/path") + require.NoError(t, err) + require.NotNil(t, linkToPathRef) + + linkToFileRef, err := tree.AddSymLink("/link-to-file", "/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, linkToFileRef) + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + toRef, err := tree.AddDir("/path/to") + require.NoError(t, err) + require.NotNil(t, toRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToFileRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*linkToPathRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*doubleLinkToPathRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*toRef, file.Metadata{Type: file.TypeDirectory}) + + defaultFields := fields{ + tree: tree, + index: idx, + } + + tests := []struct { + name string + fields fields + args args + want []file.Resolution + wantErr require.ErrorAssertionFunc + }{ + { + name: "path exists", + fields: defaultFields, + args: args{ + glob: "/**/t?/fil?.txt", + }, + // note: result "/link-to-file" resolves to the file but does not show up since the request path + // does not match the requirement glob + want: []file.Resolution{ + { + + RequestPath: "/path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + { + + RequestPath: "/double-link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + { + + RequestPath: "/link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "ancestor access path exists", + fields: defaultFields, + args: args{ + // note: this is a glob through a symlink (ancestor). If not using the index, this will work + // just fine, since we do a full tree search. However, if using the index, this shortcut will + // dodge any ancestor symlink and will not find the file. + glob: "**/link-to-path/to/file.txt", + }, + want: []file.Resolution{ + { + RequestPath: "/link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "access all children", + fields: defaultFields, + args: args{ + glob: "**/path/to/*", + }, + want: []file.Resolution{ + { + RequestPath: "/path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "access all children as path", + fields: defaultFields, + args: args{ + glob: "/path/to/*", + }, + want: []file.Resolution{ + { + RequestPath: "/path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "access via symlink for all children", + fields: defaultFields, + args: args{ + glob: "**/link-to-path/to/*", + }, + want: []file.Resolution{ + { + RequestPath: "/link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "multi ancestor access path exists", + fields: defaultFields, + args: args{ + // note: this is a glob through a symlink (ancestor). If not using the index, this will work + // just fine, since we do a full tree search. However, if using the index, this shortcut will + // dodge any ancestor symlink and will not find the file. + glob: "**/double-link-to-path/to/file.txt", + }, + want: []file.Resolution{ + { + RequestPath: "/double-link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "leaf access path exists", + fields: defaultFields, + args: args{ + glob: "**/link-to-file", + }, + want: []file.Resolution{ + { + RequestPath: "/link-to-file", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + LinkResolutions: []file.Resolution{ + { + RequestPath: "/link-to-file", + Reference: &file.Reference{ + RealPath: "/link-to-file", + }, + }, + }, + }, + }, + }, + { + name: "ancestor access path exists", + fields: defaultFields, + args: args{ + // note: this is a glob through a symlink (ancestor). If not using the index, this will work + // just fine, since we do a full tree search. However, if using the index, this shortcut will + // dodge any ancestor symlink and will not find the file. + glob: "**/link-to-path/to/file.txt", + }, + want: []file.Resolution{ + { + RequestPath: "/link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "by extension", + fields: defaultFields, + args: args{ + // note: this is a glob through a symlink (ancestor). If not using the index, this will work + // just fine, since we do a full tree search. However, if using the index, this shortcut will + // dodge any ancestor symlink and will not find the file. + glob: "**/*.txt", + }, + want: []file.Resolution{ + { + RequestPath: "/path/to/file.txt", + Reference: &file.Reference{RealPath: "/path/to/file.txt"}, + }, + { + RequestPath: "/double-link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + // note: this is NOT expected since the input glob does not match against the request path + //{ + // Resolution: file.Resolution{ + // RequestPath: "/link-to-file", + // Reference: &file.Reference{ + // RealPath: "/path/to/file.txt", + // }, + // }, + // LinkResolutions: []file.Resolution{ + // { + // RequestPath: "/link-to-file", + // Reference: &file.Reference{RealPath: "/link-to-file"}, + // }, + // }, + //}, + { + RequestPath: "/link-to-path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "path does not exists", + fields: defaultFields, + args: args{ + glob: "/NOT/**/file", + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + sc := NewSearchContext(tt.fields.tree, tt.fields.index) + got, err := sc.SearchByGlob(tt.args.glob, tt.args.options...) + tt.wantErr(t, err, fmt.Sprintf("SearchByGlob(%v, %v)", tt.args.glob, tt.args.options)) + if err != nil { + return + } + + opts := []cmp.Option{ + cmpopts.IgnoreFields(file.Reference{}, "id"), + } + + if d := cmp.Diff(tt.want, got, opts...); d != "" { + t.Errorf("SearchByGlob() mismatch (-want +got):\n%s", d) + } + + expected, err := tt.fields.tree.FilesByGlob(tt.args.glob, tt.args.options...) + require.NoError(t, err) + + if d := cmp.Diff(expected, got, opts...); d != "" { + t.Errorf("Difference relative to tree results mismatch (-want +got):\n%s", d) + } + }) + } +} + +func Test_searchContext_SearchByMIMEType(t *testing.T) { + type fields struct { + tree *FileTree + index Index + } + type args struct { + mimeTypes string + } + + tree := New() + ref, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, ref) + + idx := NewIndex() + idx.Add(*ref, file.Metadata{MIMEType: "plain/text"}) + + defaultFields := fields{ + tree: tree, + index: idx, + } + + tests := []struct { + name string + fields fields + args args + want []file.Resolution + wantErr require.ErrorAssertionFunc + }{ + { + name: "types exists", + fields: defaultFields, + args: args{ + mimeTypes: "plain/text", + }, + want: []file.Resolution{ + { + RequestPath: "/path/to/file.txt", + Reference: &file.Reference{ + RealPath: "/path/to/file.txt", + }, + }, + }, + }, + { + name: "types do not exists", + fields: defaultFields, + args: args{ + mimeTypes: "octetstream", + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + i := searchContext{ + tree: tt.fields.tree, + index: tt.fields.index, + } + got, err := i.SearchByMIMEType(tt.args.mimeTypes) + tt.wantErr(t, err, fmt.Sprintf("SearchByMIMEType(%v)", tt.args.mimeTypes)) + if err != nil { + return + } + + opts := []cmp.Option{ + cmpopts.IgnoreFields(file.Reference{}, "id"), + } + + if d := cmp.Diff(tt.want, got, opts...); d != "" { + t.Errorf("SearchByMIMEType() mismatch (-want +got):\n%s", d) + } + }) + } +} + +func Test_searchContext_allPathsToNode(t *testing.T) { + type input struct { + query *filenode.FileNode + sc *searchContext + } + + tests := []struct { + name string + input input + want []file.Path + wantErr require.ErrorAssertionFunc + }{ + { + name: "simple dir", + want: []file.Path{ + "/path/to", + }, + input: func() input { + tree := New() + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + + na, err := tree.node("/path/to", linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equal(t, file.Path("/path/to"), na.FileNode.RealPath) + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "dead symlink", + want: []file.Path{ + "/path/to/file.txt", + }, + input: func() input { + tree := New() + + deafLinkRef, err := tree.AddSymLink("/link-to-file", "/path/to/dead/file.txt") + require.NoError(t, err) + require.NotNil(t, deafLinkRef) + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*deafLinkRef, file.Metadata{Type: file.TypeSymLink}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "symlink triangle cycle", + want: []file.Path{ + "/1", + "/2", + "/3", + }, + input: func() input { + tree := New() + + link1, err := tree.AddSymLink("/1", "/2") + require.NoError(t, err) + require.NotNil(t, link1) + + link2, err := tree.AddSymLink("/2", "/3") + require.NoError(t, err) + require.NotNil(t, link2) + + link3, err := tree.AddSymLink("/3", "/1") + require.NoError(t, err) + require.NotNil(t, link3) + + idx := NewIndex() + idx.Add(*link1, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*link2, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*link3, file.Metadata{Type: file.TypeSymLink}) + + na, err := tree.node(link1.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, link1.ID(), na.FileNode.Reference.ID(), "query node should be the same as the first link") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + // note: this isn't a real link cycle, but it does look like one while resolving from a leaf to the root + name: "reverse symlink cycle", + want: []file.Path{ + "/bin/ttyd", + "/usr/bin/X11/ttyd", + "/usr/bin/ttyd", + }, + input: func() input { + tree := New() + + usrRef, err := tree.AddDir("/usr") + require.NoError(t, err) + require.NotNil(t, usrRef) + + usrBinRef, err := tree.AddDir("/usr/bin") + require.NoError(t, err) + require.NotNil(t, usrBinRef) + + ttydRef, err := tree.AddFile("/usr/bin/ttyd") + require.NoError(t, err) + require.NotNil(t, ttydRef) + + binLinkRef, err := tree.AddSymLink("/bin", "usr/bin") + require.NoError(t, err) + require.NotNil(t, binLinkRef) + + x11LinkRef, err := tree.AddSymLink("/usr/bin/X11", ".") + require.NoError(t, err) + require.NotNil(t, x11LinkRef) + + idx := NewIndex() + idx.Add(*usrRef, file.Metadata{Type: file.TypeDirectory}) + idx.Add(*usrBinRef, file.Metadata{Type: file.TypeDirectory}) + idx.Add(*binLinkRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*x11LinkRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*ttydRef, file.Metadata{Type: file.TypeRegular}) + + na, err := tree.node(ttydRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, ttydRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as usr/bin/ttyd binary") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "single leaf symlink", + want: []file.Path{ + "/link-to-file", + "/path/to/file.txt", + }, + input: func() input { + tree := New() + + linkToFileRef, err := tree.AddSymLink("/link-to-file", "/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, linkToFileRef) + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToFileRef, file.Metadata{Type: file.TypeSymLink}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "2 deep leaf symlink", + want: []file.Path{ + "/double-link-to-file", + "/link-to-file", + "/path/to/file.txt", + }, + input: func() input { + tree := New() + + doubleLinkToFileRef, err := tree.AddSymLink("/double-link-to-file", "/link-to-file") + require.NoError(t, err) + require.NotNil(t, doubleLinkToFileRef) + + linkToFileRef, err := tree.AddSymLink("/link-to-file", "/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, linkToFileRef) + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToFileRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*doubleLinkToFileRef, file.Metadata{Type: file.TypeSymLink}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "single ancestor symlink", + want: []file.Path{ + "/link-to-to/file.txt", + "/path/to/file.txt", + }, + input: func() input { + tree := New() + + dirTo, err := tree.AddDir("/path/to") + require.NoError(t, err) + require.NotNil(t, dirTo) + + linkToToRef, err := tree.AddSymLink("/link-to-to", "/path/to") + require.NoError(t, err) + require.NotNil(t, linkToToRef) + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToToRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*dirTo, file.Metadata{Type: file.TypeDirectory}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "2 deep, single sibling ancestor symlink", + want: []file.Path{ + "/link-to-path/to/file.txt", + "/link-to-to/file.txt", + "/path/to/file.txt", + }, + input: func() input { + tree := New() + + dirTo, err := tree.AddDir("/path/to") + require.NoError(t, err) + require.NotNil(t, dirTo) + + linkToPathRef, err := tree.AddSymLink("/link-to-path", "/path") + require.NoError(t, err) + require.NotNil(t, linkToPathRef) + + linkToToRef, err := tree.AddSymLink("/link-to-to", "/path/to") + require.NoError(t, err) + require.NotNil(t, linkToToRef) + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToToRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*linkToPathRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*dirTo, file.Metadata{Type: file.TypeDirectory}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "2 deep, multiple sibling ancestor symlink", + want: []file.Path{ + "/another-link-to-path/to/file.txt", + "/another-link-to-to/file.txt", + "/link-to-path/to/file.txt", + "/link-to-to/file.txt", + "/path/to/file.txt", + }, + input: func() input { + tree := New() + + dirTo, err := tree.AddDir("/path/to") + require.NoError(t, err) + require.NotNil(t, dirTo) + + linkToPathRef, err := tree.AddSymLink("/link-to-path", "/path") + require.NoError(t, err) + require.NotNil(t, linkToPathRef) + + anotherLinkToPathRef, err := tree.AddSymLink("/another-link-to-path", "/path") + require.NoError(t, err) + require.NotNil(t, anotherLinkToPathRef) + + linkToToRef, err := tree.AddSymLink("/link-to-to", "/path/to") + require.NoError(t, err) + require.NotNil(t, linkToToRef) + + anotherLinkToToRef, err := tree.AddSymLink("/another-link-to-to", "/path/to") + require.NoError(t, err) + require.NotNil(t, anotherLinkToToRef) + + fileRef, err := tree.AddFile("/path/to/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToToRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*linkToPathRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*anotherLinkToPathRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*anotherLinkToToRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*dirTo, file.Metadata{Type: file.TypeDirectory}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "2 deep, multiple nested ancestor symlink", + want: []file.Path{ + "/link-to-path/link-to-another/file.txt", + "/link-to-path/to/another/file.txt", + "/link-to-path/to/link-to-file", + "/link-to-to/another/file.txt", + "/link-to-to/link-to-file", + "/path/link-to-another/file.txt", + "/path/to/another/file.txt", + "/path/to/link-to-file", + }, + input: func() input { + tree := New() + + linkToAnotherViaLinkRef, err := tree.AddSymLink("/path/link-to-another", "/link-to-to/another") + require.NoError(t, err) + require.NotNil(t, linkToAnotherViaLinkRef) + + linkToPathRef, err := tree.AddSymLink("/link-to-path", "/path") + require.NoError(t, err) + require.NotNil(t, linkToPathRef) + + linkToToRef, err := tree.AddSymLink("/link-to-to", "/path/to") + require.NoError(t, err) + require.NotNil(t, linkToToRef) + + pathToLinkToFileRef, err := tree.AddSymLink("/path/to/link-to-file", "/path/to/another/file.txt") + require.NoError(t, err) + require.NotNil(t, pathToLinkToFileRef) + + dirTo, err := tree.AddDir("/path/to") + require.NoError(t, err) + require.NotNil(t, dirTo) + + dirAnother, err := tree.AddDir("/path/to/another") + require.NoError(t, err) + require.NotNil(t, dirAnother) + + fileRef, err := tree.AddFile("/path/to/another/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToAnotherViaLinkRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*linkToPathRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*linkToToRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*pathToLinkToFileRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*dirTo, file.Metadata{Type: file.TypeDirectory}) + idx.Add(*dirAnother, file.Metadata{Type: file.TypeDirectory}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + { + name: "relative, 2 deep, multiple nested ancestor symlink", + want: []file.Path{ + "/link-to-path/link-to-another/file.txt", + "/link-to-path/to/another/file.txt", + "/link-to-path/to/link-to-file", + "/link-to-to/another/file.txt", + "/link-to-to/link-to-file", + "/path/link-to-another/file.txt", + "/path/to/another/file.txt", + "/path/to/link-to-file", + }, + input: func() input { + tree := New() + + linkToAnotherViaLinkRef, err := tree.AddSymLink("/path/link-to-another", "../link-to-to/another") + require.NoError(t, err) + require.NotNil(t, linkToAnotherViaLinkRef) + + linkToPathRef, err := tree.AddSymLink("/link-to-path", "./path") + require.NoError(t, err) + require.NotNil(t, linkToPathRef) + + linkToToRef, err := tree.AddSymLink("/link-to-to", "./path/to") + require.NoError(t, err) + require.NotNil(t, linkToToRef) + + pathToLinkToFileRef, err := tree.AddSymLink("/path/to/link-to-file", "../to/another/file.txt") + require.NoError(t, err) + require.NotNil(t, pathToLinkToFileRef) + + dirTo, err := tree.AddDir("/path/to") + require.NoError(t, err) + require.NotNil(t, dirTo) + + dirAnother, err := tree.AddDir("/path/to/another") + require.NoError(t, err) + require.NotNil(t, dirAnother) + + fileRef, err := tree.AddFile("/path/to/another/file.txt") + require.NoError(t, err) + require.NotNil(t, fileRef) + + idx := NewIndex() + idx.Add(*fileRef, file.Metadata{MIMEType: "plain/text", Type: file.TypeRegular}) + idx.Add(*linkToAnotherViaLinkRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*linkToPathRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*linkToToRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*pathToLinkToFileRef, file.Metadata{Type: file.TypeSymLink}) + idx.Add(*dirTo, file.Metadata{Type: file.TypeDirectory}) + idx.Add(*dirAnother, file.Metadata{Type: file.TypeDirectory}) + + na, err := tree.node(fileRef.RealPath, linkResolutionStrategy{ + FollowAncestorLinks: false, + FollowBasenameLinks: false, + DoNotFollowDeadBasenameLinks: false, + }) + require.NoError(t, err) + require.NotNil(t, na) + require.NotNil(t, na.FileNode) + require.Equalf(t, fileRef.ID(), na.FileNode.Reference.ID(), "query node should be the same as the file node") + + return input{ + query: na.FileNode, + sc: NewSearchContext(tree, idx).(*searchContext), + } + }(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + + got, err := tt.input.sc.allPathsToNode(tt.input.query) + tt.wantErr(t, err, fmt.Sprintf("allPathsToNode(%v)", tt.input.query)) + if err != nil { + return + } + + assert.ElementsMatchf(t, tt.want, got, cmp.Diff(tt.want, got), "expected and actual paths should match") + }) + } +} diff --git a/pkg/filetree/union_filetree.go b/pkg/filetree/union_filetree.go index 5e30c0a9..c1606a42 100644 --- a/pkg/filetree/union_filetree.go +++ b/pkg/filetree/union_filetree.go @@ -3,28 +3,28 @@ package filetree import "fmt" type UnionFileTree struct { - trees []*FileTree + trees []ReadWriter } func NewUnionFileTree() *UnionFileTree { return &UnionFileTree{ - trees: make([]*FileTree, 0), + trees: make([]ReadWriter, 0), } } -func (u *UnionFileTree) PushTree(t *FileTree) { +func (u *UnionFileTree) PushTree(t ReadWriter) { u.trees = append(u.trees, t) } -func (u *UnionFileTree) Squash() (*FileTree, error) { +func (u *UnionFileTree) Squash() (ReadWriter, error) { switch len(u.trees) { case 0: - return NewFileTree(), nil + return New(), nil case 1: return u.trees[0].Copy() } - var squashedTree *FileTree + var squashedTree ReadWriter var err error for layerIdx, refTree := range u.trees { if layerIdx == 0 { @@ -35,7 +35,7 @@ func (u *UnionFileTree) Squash() (*FileTree, error) { continue } - if err = squashedTree.merge(refTree); err != nil { + if err = squashedTree.Merge(refTree); err != nil { return nil, fmt.Errorf("unable to squash layer=%d : %w", layerIdx, err) } } diff --git a/pkg/filetree/union_filetree_test.go b/pkg/filetree/union_filetree_test.go index caeacc14..adbd6103 100644 --- a/pkg/filetree/union_filetree_test.go +++ b/pkg/filetree/union_filetree_test.go @@ -8,7 +8,7 @@ import ( func TestUnionFileTree_Squash(t *testing.T) { ut := NewUnionFileTree() - base := NewFileTree() + base := New() base.AddFile("/home/wagoodman/some/stuff-1.txt") originalNode, _ := base.AddFile("/home/wagoodman/some/stuff-2-overlap.txt") @@ -16,7 +16,7 @@ func TestUnionFileTree_Squash(t *testing.T) { originalMore, _ := base.AddFile("/home/wagoodman/more") originalMoreDir, _ := base.AddDir("/home/wagoodman/moredir") - top := NewFileTree() + top := New() top.AddFile("/etc/redhat-release") // note: override /home/wagoodman/more (a file) as a directory top.AddFile("/home/wagoodman/more/things.txt") @@ -65,7 +65,7 @@ func TestUnionFileTree_Squash(t *testing.T) { } _, f, _ = base.File("/home/wagoodman/more") - if f == nil { + if f == nil || f.Reference == nil { t.Fatal("base was never created") } @@ -74,17 +74,17 @@ func TestUnionFileTree_Squash(t *testing.T) { } _, f, _ = top.File("/home/wagoodman/more") - if f != nil { + if f.Reference != nil { t.Fatal("top file should have been implicitly nil but wasn't") } _, f, _ = squashed.File("/home/wagoodman/more") - if f != nil { + if f.Reference != nil { t.Fatal("file override to a dir has original properties") } _, f, _ = squashed.File("/home/wagoodman/moredir") - if f == nil { + if f == nil || f.Reference == nil { t.Fatal("dir override to a dir is missing original properties") } if originalMoreDir.ID() != f.ID() { @@ -95,13 +95,13 @@ func TestUnionFileTree_Squash(t *testing.T) { func TestUnionFileTree_Squash_whiteout(t *testing.T) { ut := NewUnionFileTree() - base := NewFileTree() + base := New() base.AddFile("/some/stuff-1.txt") base.AddFile("/some/stuff-2.txt") base.AddFile("/other/things-1.txt") - top := NewFileTree() + top := New() top.AddFile("/some/" + file.OpaqueWhiteout) top.AddFile("/other/" + file.WhiteoutPrefix + "things-1.txt") diff --git a/pkg/image/content_helpers.go b/pkg/image/content_helpers.go index 50709085..b38f811a 100644 --- a/pkg/image/content_helpers.go +++ b/pkg/image/content_helpers.go @@ -8,43 +8,20 @@ import ( "github.com/anchore/stereoscope/pkg/filetree" ) -// fetchFileContentsByPath is a common helper function for resolving the file contents for a path from the file +// fetchReaderByPath is a common helper function for resolving the file contents for a path from the file // catalog relative to the given tree. -func fetchFileContentsByPath(ft *filetree.FileTree, fileCatalog *FileCatalog, path file.Path) (io.ReadCloser, error) { - exists, fileReference, err := ft.File(path, filetree.FollowBasenameLinks) +func fetchReaderByPath(ft filetree.Reader, fileCatalog FileCatalogReader, path file.Path) (io.ReadCloser, error) { + exists, refVia, err := ft.File(path, filetree.FollowBasenameLinks) if err != nil { return nil, err } - if !exists && fileReference == nil { + if !exists && refVia == nil || refVia.Reference == nil { return nil, fmt.Errorf("could not find file path in Tree: %s", path) } - reader, err := fileCatalog.FileContents(*fileReference) + reader, err := fileCatalog.Open(*refVia.Reference) if err != nil { return nil, err } return reader, nil } - -// fetchFileContentsByPath is a common helper function for resolving file references for a MIME type from the file -// catalog relative to the given tree. -func fetchFilesByMIMEType(ft *filetree.FileTree, fileCatalog *FileCatalog, mType string) ([]file.Reference, error) { - fileEntries, err := fileCatalog.GetByMIMEType(mType) - if err != nil { - return nil, fmt.Errorf("unable to fetch file references by MIME type: %w", err) - } - - var refs []file.Reference - for _, entry := range fileEntries { - _, ref, err := ft.File(entry.File.RealPath, filetree.FollowBasenameLinks) - if err != nil { - return nil, fmt.Errorf("unable to get ref for path=%q: %w", entry.File.RealPath, err) - } - - // we know this entry exists in the tree, keep track of the reference for this file - if ref != nil && ref.ID() == entry.File.ID() { - refs = append(refs, *ref) - } - } - return refs, nil -} diff --git a/pkg/image/docker/tarball_provider.go b/pkg/image/docker/tarball_provider.go index 6eb5ce73..663f809e 100644 --- a/pkg/image/docker/tarball_provider.go +++ b/pkg/image/docker/tarball_provider.go @@ -82,5 +82,5 @@ func (p *TarballImageProvider) Provide(_ context.Context, userMetadata ...image. return nil, err } - return image.NewImage(img, contentTempDir, metadata...), nil + return image.New(img, contentTempDir, metadata...), nil } diff --git a/pkg/image/file_catalog.go b/pkg/image/file_catalog.go index 3f62ed97..e8ec637a 100644 --- a/pkg/image/file_catalog.go +++ b/pkg/image/file_catalog.go @@ -6,104 +6,69 @@ import ( "sync" "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree" ) -var ErrFileNotFound = fmt.Errorf("could not find file") +type FileCatalogReader interface { + Layer(file.Reference) *Layer + Open(file.Reference) (io.ReadCloser, error) + filetree.IndexReader +} // FileCatalog represents all file metadata and source tracing for all files contained within the image layer // blobs (i.e. everything except for the image index/manifest/metadata files). type FileCatalog struct { - sync.RWMutex - catalog map[file.ID]FileCatalogEntry - byMIMEType map[string][]file.ID -} - -// FileCatalogEntry represents all stored metadata for a single file reference. -type FileCatalogEntry struct { - File file.Reference - Metadata file.Metadata - Layer *Layer - Contents file.Opener + *sync.RWMutex + filetree.Index + layerByID map[file.ID]*Layer + openerByID map[file.ID]file.Opener } // NewFileCatalog returns an empty FileCatalog. -func NewFileCatalog() FileCatalog { - return FileCatalog{ - catalog: make(map[file.ID]FileCatalogEntry), - byMIMEType: make(map[string][]file.ID), +func NewFileCatalog() *FileCatalog { + return &FileCatalog{ + RWMutex: &sync.RWMutex{}, + Index: filetree.NewIndex(), + layerByID: make(map[file.ID]*Layer), + openerByID: make(map[file.ID]file.Opener), } } // Add creates a new FileCatalogEntry for the given file reference and metadata, cataloged by the ID of the // file reference (overwriting any existing entries without warning). func (c *FileCatalog) Add(f file.Reference, m file.Metadata, l *Layer, opener file.Opener) { + c.Index.Add(f, m) // note: the index is already thread-safe + c.addImageReferences(f.ID(), l, opener) +} + +func (c *FileCatalog) addImageReferences(id file.ID, l *Layer, opener file.Opener) { c.Lock() defer c.Unlock() - if m.MIMEType != "" { - // an empty MIME type means that we didn't have the contents of the file to determine the MIME type. If we have - // the contents and the MIME type could not be determined then the default value is application/octet-stream. - c.byMIMEType[m.MIMEType] = append(c.byMIMEType[m.MIMEType], f.ID()) - } - c.catalog[f.ID()] = FileCatalogEntry{ - File: f, - Metadata: m, - Layer: l, - Contents: opener, - } + c.layerByID[id] = l + c.openerByID[id] = opener } -// Exists indicates if the given file reference exists in the catalog. -func (c *FileCatalog) Exists(f file.Reference) bool { +func (c *FileCatalog) Layer(f file.Reference) *Layer { c.RLock() defer c.RUnlock() - _, ok := c.catalog[f.ID()] - return ok -} -// Get fetches a FileCatalogEntry for the given file reference, or returns an error if the file reference has not -// been added to the catalog. -func (c *FileCatalog) Get(f file.Reference) (FileCatalogEntry, error) { - c.RLock() - defer c.RUnlock() - value, ok := c.catalog[f.ID()] - if !ok { - return FileCatalogEntry{}, ErrFileNotFound - } - return value, nil + return c.layerByID[f.ID()] } -func (c *FileCatalog) GetByMIMEType(mType string) ([]FileCatalogEntry, error) { +// Open returns a io.ReadCloser for the given file reference. The underlying io.ReadCloser will not attempt to +// allocate resources until the first read is performed. +func (c *FileCatalog) Open(f file.Reference) (io.ReadCloser, error) { c.RLock() defer c.RUnlock() - fileIDs, ok := c.byMIMEType[mType] - if !ok { - return nil, nil - } - var entries []FileCatalogEntry - for _, id := range fileIDs { - entry, ok := c.catalog[id] - if !ok { - return nil, fmt.Errorf("could not find file: %+v", id) - } - entries = append(entries, entry) - } - return entries, nil -} - -// FetchContents reads the file contents for the given file reference from the underlying image/layer blob. An error -// is returned if there is no file at the given path and layer or the read operation cannot continue. -func (c *FileCatalog) FileContents(f file.Reference) (io.ReadCloser, error) { - c.RLock() - defer c.RUnlock() - catalogEntry, ok := c.catalog[f.ID()] + opener, ok := c.openerByID[f.ID()] if !ok { return nil, fmt.Errorf("could not find file: %+v", f.RealPath) } - if catalogEntry.Contents == nil { + if opener == nil { return nil, fmt.Errorf("no contents available for file: %+v", f.RealPath) } - return catalogEntry.Contents(), nil + return opener(), nil } diff --git a/pkg/image/file_catalog_test.go b/pkg/image/file_catalog_test.go index b7211a7b..a6100156 100644 --- a/pkg/image/file_catalog_test.go +++ b/pkg/image/file_catalog_test.go @@ -6,11 +6,17 @@ package image import ( "crypto/sha256" "fmt" + "github.com/anchore/stereoscope/pkg/filetree" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "io" "os" "os/exec" "path" "path/filepath" + "strings" "testing" "github.com/go-test/deep" @@ -29,93 +35,18 @@ var ( tarCachePath = path.Join(fixturesPath, "tar-cache") ) -func getTarFixture(t *testing.T, name string) (*os.File, func()) { - generatorScriptName := name + ".sh" - generatorScriptPath := path.Join(fixturesGeneratorsPath, generatorScriptName) - if !fileExists(t, generatorScriptPath) { - t.Fatalf("no tar generator script for fixture '%s'", generatorScriptPath) - } - - version := fixtureVersion(t, generatorScriptPath) - tarName := name + ":" + version + ".tar" - tarFixturePath := path.Join(tarCachePath, tarName) - - if !fileExists(t, tarFixturePath) { - t.Logf("Creating tar fixture: %s", tarFixturePath) - - fullPath, err := filepath.Abs(tarFixturePath) - if err != nil { - t.Fatal(err) - } - - cmd := exec.Command("./"+generatorScriptName, fullPath) - cmd.Env = os.Environ() - cmd.Dir = fixturesGeneratorsPath - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - - err = cmd.Run() - if err != nil { - panic(err) - } - } - - fh, err := os.Open(tarFixturePath) - if err != nil { - t.Fatalf("could not open tar fixture '%s'", tarFixturePath) - } - - return fh, func() { - fh.Close() - } -} - -func fixtureVersion(t *testing.T, path string) string { - t.Helper() - f, err := os.Open(path) - if err != nil { - t.Fatal(err) - } - defer func() { - err := f.Close() - if err != nil { - t.Fatal(err) - } - }() - - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - t.Fatal(err) - } - - return fmt.Sprintf("%x", h.Sum(nil)) -} - -func fileExists(t *testing.T, filename string) bool { - t.Helper() - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } else if err != nil { - t.Fatal(err) - } - return !info.IsDir() -} - func TestFileCatalog_Add(t *testing.T) { ref := file.NewFileReference("/somepath") metadata := file.Metadata{ - Path: "a", - TarHeaderName: "b", - Linkname: "c", - Size: 1, - UserID: 2, - GroupID: 3, - TypeFlag: 4, - IsDir: true, - Mode: 5, + Path: "a", + LinkDestination: "c", + Size: 1, + UserID: 2, + GroupID: 3, + Type: 4, + IsDir: true, + Mode: 5, } layer := &Layer{ @@ -134,10 +65,9 @@ func TestFileCatalog_Add(t *testing.T) { catalog := NewFileCatalog() catalog.Add(*ref, metadata, layer, nil) - expected := FileCatalogEntry{ - File: *ref, - Metadata: metadata, - Layer: layer, + expected := filetree.IndexEntry{ + Reference: *ref, + Metadata: metadata, } actual, err := catalog.Get(*ref) @@ -148,6 +78,8 @@ func TestFileCatalog_Add(t *testing.T) { for d := range deep.Equal(expected, actual) { t.Errorf("diff: %+v", d) } + + assert.Equal(t, layer, catalog.Layer(*ref)) } type testLayerContent struct { @@ -177,9 +109,8 @@ func (t *testLayerContent) MediaType() (types.MediaType, error) { panic("not implemented") } -func TestFileCatalog_FileContents(t *testing.T) { - fixtureFile, cleanup := getTarFixture(t, "fixture-1") - defer cleanup() +func TestFileCatalog_Open(t *testing.T) { + fixtureFile := getTarFixture(t, "fixture-1") // a real path & contents from the fixture p := "path/branch/one/file-1.txt" @@ -187,27 +118,21 @@ func TestFileCatalog_FileContents(t *testing.T) { expected := "first file\n" metadata := file.Metadata{ - Path: p, - TarHeaderName: p, + Path: p, } tr, err := file.NewTarIndex(fixtureFile.Name(), nil) - if err != nil { - t.Fatalf("unable to get indexed reader") - } + require.NoError(t, err) + layer := &Layer{ layer: &testLayerContent{}, indexedContent: tr, } entries, err := tr.EntriesByName(p) - if err != nil { - t.Fatalf("unable to get entryies: %+v", err) - } + require.NoError(t, err) - if len(entries) != 1 { - t.Fatalf("bad entries len: %d", len(entries)) - } + require.Len(t, entries, 1) opener := func() io.ReadCloser { return io.NopCloser(entries[0].Reader) @@ -216,17 +141,687 @@ func TestFileCatalog_FileContents(t *testing.T) { catalog := NewFileCatalog() catalog.Add(*ref, metadata, layer, opener) - reader, err := catalog.FileContents(*ref) + reader, err := catalog.Open(*ref) + require.NoError(t, err) + + actual, err := io.ReadAll(reader) + require.NoError(t, err) + + for _, d := range deep.Equal([]byte(expected), actual) { + t.Errorf("diff: %+v", d) + } +} + +func Test_fileExtensions(t *testing.T) { + tests := []struct { + name string + path string + want []string + }{ + { + name: "empty", + path: "", + }, + { + name: "directory", + path: "/somewhere/to/nowhere/", + }, + { + name: "directory with ext", + path: "/somewhere/to/nowhere.d/", + }, + { + name: "single extension", + path: "/somewhere/to/my.tar", + want: []string{".tar"}, + }, + { + name: "multiple extensions", + path: "/somewhere/to/my.tar.gz", + want: []string{".gz", ".tar.gz"}, + }, + { + name: "ignore . prefix", + path: "/somewhere/to/.my.tar.gz", + want: []string{".gz", ".tar.gz"}, + }, + { + name: "ignore more . prefixes", + path: "/somewhere/to/...my.tar.gz", + want: []string{".gz", ".tar.gz"}, + }, + { + name: "ignore . suffixes", + path: "/somewhere/to/my.tar.gz...", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, fileExtensions(tt.path)) + }) + } +} + +func TestFileCatalog_GetByExtension(t *testing.T) { + fixtureTarFile := getTarFixture(t, "fixture-2") + + ft := filetree.New() + fileCatalog := NewFileCatalog() + var size int64 + + // we don't need the index itself, just the side effect on the file catalog after indexing + _, err := file.NewTarIndex( + fixtureTarFile.Name(), + layerTarIndexer(ft, fileCatalog, &size, nil, nil), + ) + require.NoError(t, err) + + tests := []struct { + name string + input string + want []filetree.IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get simple extension", + input: ".txt", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + + Reference: file.Reference{RealPath: "/path/branch.d/two/file-2.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/two/file-2.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/file-3.txt"}, + Metadata: file.Metadata{ + Path: "/path/file-3.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get mixed type extension", + input: ".d", + want: []filetree.IndexEntry{ + { + + Reference: file.Reference{RealPath: "/path/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.d", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + + { + + Reference: file.Reference{RealPath: "/path/common/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/common/branch.d", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + { + + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get long extension", + input: ".tar.gz", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/.file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/.file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get short extension", + input: ".gz", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/.file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/.file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existent extension", + input: ".blerg-123", + want: []filetree.IndexEntry{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileCatalog.GetByExtension(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetByBasename(t *testing.T) { + fixtureTarFile := getTarFixture(t, "fixture-2") + + ft := filetree.New() + fileCatalog := NewFileCatalog() + var size int64 + + // we don't need the index itself, just the side effect on the file catalog after indexing + _, err := file.NewTarIndex( + fixtureTarFile.Name(), + layerTarIndexer(ft, fileCatalog, &size, nil, nil), + ) + require.NoError(t, err) + + tests := []struct { + name string + input string + want []filetree.IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get existing file name", + input: "file-1.txt", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existing name", + input: "file-11.txt", + want: []filetree.IndexEntry{}, + }, + { + name: "get directory name", + input: "branch.d", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/common/branch.d", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get symlink name", + input: "file-1.d", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get basename with path expression", + input: "somewhere/file-1.d", + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileCatalog.GetByBasename(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetByBasenameGlob(t *testing.T) { + fixtureTarFile := getTarFixture(t, "fixture-2") + + ft := filetree.New() + fileCatalog := NewFileCatalog() + var size int64 + + // we don't need the index itself, just the side effect on the file catalog after indexing + _, err := file.NewTarIndex( + fixtureTarFile.Name(), + layerTarIndexer(ft, fileCatalog, &size, nil, nil), + ) + require.NoError(t, err) + + tests := []struct { + name string + input string + want []filetree.IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get existing file name", + input: "file-1.*", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existing name", + input: "blerg-*.txt", + want: []filetree.IndexEntry{}, + }, + { + name: "get directory name", + input: "bran*.d", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d", + Type: file.TypeDirectory, + IsDir: true, + }, + }, + { + Reference: file.Reference{RealPath: "/path/common/branch.d"}, + Metadata: file.Metadata{ + Path: "/path/common/branch.d", + LinkDestination: "path/branch.d", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get symlink name", + input: "file?1.d", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/common/file-1.d"}, + Metadata: file.Metadata{ + Path: "/path/common/file-1.d", + LinkDestination: "path/branch.d/one/file-1.txt", + Type: file.TypeSymLink, + }, + }, + }, + }, + { + name: "get basename with path expression", + input: "somewhere/file?1.d", + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileCatalog.GetByBasenameGlob(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetByMimeType(t *testing.T) { + fixtureTarFile := getTarFixture(t, "fixture-2") + + ft := filetree.New() + fileCatalog := NewFileCatalog() + var size int64 + + // we don't need the index itself, just the side effect on the file catalog after indexing + _, err := file.NewTarIndex( + fixtureTarFile.Name(), + layerTarIndexer(ft, fileCatalog, &size, nil, nil), + ) + require.NoError(t, err) + + tests := []struct { + name string + input string + want []filetree.IndexEntry + wantErr require.ErrorAssertionFunc + }{ + { + name: "get existing file mimetype", + input: "text/plain", + want: []filetree.IndexEntry{ + { + Reference: file.Reference{RealPath: "/path/branch.d/one/.file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/.file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-1.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-1.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.d"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.d", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/one/file-4.tar.gz"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/one/file-4.tar.gz", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/branch.d/two/file-2.txt"}, + Metadata: file.Metadata{ + Path: "/path/branch.d/two/file-2.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + { + Reference: file.Reference{RealPath: "/path/file-3.txt"}, + Metadata: file.Metadata{ + Path: "/path/file-3.txt", + Type: file.TypeRegular, + MIMEType: "text/plain", + }, + }, + }, + }, + { + name: "get non-existing mimetype", + input: "text/bogus", + want: []filetree.IndexEntry{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + actual, err := fileCatalog.GetByMIMEType(tt.input) + tt.wantErr(t, err) + if err != nil { + return + } + if d := cmp.Diff(tt.want, actual, + cmpopts.EquateEmpty(), + cmpopts.IgnoreUnexported(file.Reference{}), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + ); d != "" { + t.Errorf("diff: %s", d) + } + }) + } +} + +func TestFileCatalog_GetBasenames(t *testing.T) { + fixtureTarFile := getTarFixture(t, "fixture-2") + + ft := filetree.New() + fileCatalog := NewFileCatalog() + var size int64 + + // we don't need the index itself, just the side effect on the file catalog after indexing + _, err := file.NewTarIndex( + fixtureTarFile.Name(), + layerTarIndexer(ft, fileCatalog, &size, nil, nil), + ) + require.NoError(t, err) + + tests := []struct { + name string + want []string + }{ + { + name: "go case", + want: []string{ + ".file-4.tar.gz", + "branch", + "branch.d", + "common", + "file-1.d", + "file-1.txt", + "file-2.txt", + "file-3.txt", + "file-4", + "file-4.d", + "file-4.tar.gz", + "one", + "path", + "two", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := fileCatalog.Basenames() + assert.ElementsMatchf(t, tt.want, actual, "diff: %s", cmp.Diff(tt.want, actual)) + }) + } +} + +func getTarFixture(t *testing.T, name string) *os.File { + generatorScriptName := name + ".sh" + generatorScriptPath := path.Join(fixturesGeneratorsPath, generatorScriptName) + if !fileExists(t, generatorScriptPath) { + t.Fatalf("no tar generator script for fixture '%s'", generatorScriptPath) + } + + version := fixtureVersion(t, generatorScriptPath) + tarName := name + ":" + version + ".tar" + tarFixturePath := path.Join(tarCachePath, tarName) + + if !fileExists(t, tarFixturePath) { + t.Logf("Creating tar fixture: %s", tarFixturePath) + + fullPath, err := filepath.Abs(tarFixturePath) + if err != nil { + t.Fatal(err) + } + + cmd := exec.Command("./"+generatorScriptName, fullPath) + cmd.Env = os.Environ() + cmd.Dir = fixturesGeneratorsPath + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + err = cmd.Run() + if err != nil { + panic(err) + } + } + + fh, err := os.Open(tarFixturePath) if err != nil { - t.Fatalf("could not get contents by ref: %+v", err) + t.Fatalf("could not open tar fixture '%s'", tarFixturePath) } - actual, err := io.ReadAll(reader) + t.Cleanup(func() { + require.NoError(t, fh.Close()) + }) + + return fh +} + +func fixtureVersion(t *testing.T, path string) string { + t.Helper() + f, err := os.Open(path) if err != nil { - t.Fatalf("could not read content reader: %+v", err) + t.Fatal(err) } + defer func() { + err := f.Close() + if err != nil { + t.Fatal(err) + } + }() - for _, d := range deep.Equal([]byte(expected), actual) { - t.Errorf("diff: %+v", d) + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + t.Fatal(err) + } + + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func fileExists(t *testing.T, filename string) bool { + t.Helper() + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } else if err != nil { + t.Fatal(err) + } + return !info.IsDir() +} + +func fileExtensions(p string) []string { + var exts []string + p = strings.TrimSpace(p) + + // ignore oddities + if strings.HasSuffix(p, ".") { + return exts + } + + // ignore directories + if strings.HasSuffix(p, "/") { + return exts + } + + // ignore . which indicate a hidden file + p = strings.TrimLeft(path.Base(p), ".") + for i := len(p) - 1; i >= 0; i-- { + if p[i] == '.' { + exts = append(exts, p[i:]) + } } + return exts } diff --git a/pkg/image/image.go b/pkg/image/image.go index 0748c2c4..9798197b 100644 --- a/pkg/image/image.go +++ b/pkg/image/image.go @@ -30,7 +30,9 @@ type Image struct { // Layers contains the rich layer objects in build order Layers []*Layer // FileCatalog contains all file metadata for all files in all layers - FileCatalog FileCatalog + FileCatalog FileCatalogReader + + SquashedSearchContext filetree.Searcher overrideMetadata []AdditionalMetadata } @@ -127,12 +129,17 @@ func WithOS(o string) AdditionalMetadata { } } -// NewImage provides a new, unread image object. +// NewImage provides a new (unread) image object. +// Deprecated: use New() instead func NewImage(image v1.Image, contentCacheDir string, additionalMetadata ...AdditionalMetadata) *Image { + return New(image, contentCacheDir, additionalMetadata...) +} + +// New provides a new (unread) image object. +func New(image v1.Image, contentCacheDir string, additionalMetadata ...AdditionalMetadata) *Image { imgObj := &Image{ image: image, contentCacheDir: contentCacheDir, - FileCatalog: NewFileCatalog(), overrideMetadata: additionalMetadata, } return imgObj @@ -199,9 +206,11 @@ func (i *Image) Read() error { // let consumers know of a monitorable event (image save + copy stages) readProg := i.trackReadProgress(i.Metadata) + fileCatalog := NewFileCatalog() + for idx, v1Layer := range v1Layers { layer := NewLayer(v1Layer) - err := layer.Read(&i.FileCatalog, i.Metadata, idx, i.contentCacheDir) + err := layer.Read(fileCatalog, i.Metadata, idx, i.contentCacheDir) if err != nil { return err } @@ -214,24 +223,30 @@ func (i *Image) Read() error { i.Layers = layers // in order to resolve symlinks all squashed trees must be available - return i.squash(readProg) + err = i.squash(readProg) + + i.FileCatalog = fileCatalog + i.SquashedSearchContext = filetree.NewSearchContext(i.SquashedTree(), i.FileCatalog) + + return err } // squash generates a squash tree for each layer in the image. For instance, layer 2 squash = // squash(layer 0, layer 1, layer 2), layer 3 squash = squash(layer 0, layer 1, layer 2, layer 3), and so on. func (i *Image) squash(prog *progress.Manual) error { - var lastSquashTree *filetree.FileTree + var lastSquashTree filetree.ReadWriter for idx, layer := range i.Layers { if idx == 0 { - lastSquashTree = layer.Tree + lastSquashTree = layer.Tree.(filetree.ReadWriter) layer.SquashedTree = layer.Tree + layer.SquashedSearchContext = filetree.NewSearchContext(layer.SquashedTree, layer.fileCatalog.Index) continue } var unionTree = filetree.NewUnionFileTree() unionTree.PushTree(lastSquashTree) - unionTree.PushTree(layer.Tree) + unionTree.PushTree(layer.Tree.(filetree.ReadWriter)) squashedTree, err := unionTree.Squash() if err != nil { @@ -239,6 +254,7 @@ func (i *Image) squash(prog *progress.Manual) error { } layer.SquashedTree = squashedTree + layer.SquashedSearchContext = filetree.NewSearchContext(layer.SquashedTree, layer.fileCatalog.Index) lastSquashTree = squashedTree prog.N++ @@ -250,46 +266,63 @@ func (i *Image) squash(prog *progress.Manual) error { } // SquashedTree returns the pre-computed image squash file tree. -func (i *Image) SquashedTree() *filetree.FileTree { +func (i *Image) SquashedTree() filetree.Reader { layerCount := len(i.Layers) if layerCount == 0 { - return filetree.NewFileTree() + return filetree.New() } topLayer := i.Layers[layerCount-1] return topLayer.SquashedTree } +// OpenPathFromSquash fetches file contents for a single path, relative to the image squash tree. +// If the path does not exist an error is returned. +func (i *Image) OpenPathFromSquash(path file.Path) (io.ReadCloser, error) { + return fetchReaderByPath(i.SquashedTree(), i.FileCatalog, path) +} + // FileContentsFromSquash fetches file contents for a single path, relative to the image squash tree. // If the path does not exist an error is returned. +// Deprecated: use OpenPathFromSquash() instead. func (i *Image) FileContentsFromSquash(path file.Path) (io.ReadCloser, error) { - return fetchFileContentsByPath(i.SquashedTree(), &i.FileCatalog, path) + return fetchReaderByPath(i.SquashedTree(), i.FileCatalog, path) } // FilesByMIMETypeFromSquash returns file references for files that match at least one of the given MIME types. +// Deprecated: please use SquashedSearchContext().SearchByMIMEType() instead. func (i *Image) FilesByMIMETypeFromSquash(mimeTypes ...string) ([]file.Reference, error) { var refs []file.Reference - for _, ty := range mimeTypes { - refsForType, err := fetchFilesByMIMEType(i.SquashedTree(), &i.FileCatalog, ty) - if err != nil { - return nil, err + refVias, err := i.SquashedSearchContext.SearchByMIMEType(mimeTypes...) + if err != nil { + return nil, err + } + for _, refVia := range refVias { + if refVia.HasReference() { + refs = append(refs, *refVia.Reference) } - refs = append(refs, refsForType...) } return refs, nil } -// FileContentsByRef fetches file contents for a single file reference, irregardless of the source layer. +// OpenReference fetches file contents for a single file reference, regardless of the source layer. +// If the path does not exist an error is returned. +func (i *Image) OpenReference(ref file.Reference) (io.ReadCloser, error) { + return i.FileCatalog.Open(ref) +} + +// FileContentsByRef fetches file contents for a single file reference, regardless of the source layer. // If the path does not exist an error is returned. +// Deprecated: please use OpenReference() instead. func (i *Image) FileContentsByRef(ref file.Reference) (io.ReadCloser, error) { - return i.FileCatalog.FileContents(ref) + return i.FileCatalog.Open(ref) } // ResolveLinkByLayerSquash resolves a symlink or hardlink for the given file reference relative to the result from // the layer squash of the given layer index argument. // If the given file reference is not a link type, or is a unresolvable (dead) link, then the given file reference is returned. -func (i *Image) ResolveLinkByLayerSquash(ref file.Reference, layer int, options ...filetree.LinkResolutionOption) (*file.Reference, error) { +func (i *Image) ResolveLinkByLayerSquash(ref file.Reference, layer int, options ...filetree.LinkResolutionOption) (*file.Resolution, error) { allOptions := append([]filetree.LinkResolutionOption{filetree.FollowBasenameLinks}, options...) _, resolvedRef, err := i.Layers[layer].SquashedTree.File(ref.RealPath, allOptions...) return resolvedRef, err @@ -297,7 +330,7 @@ func (i *Image) ResolveLinkByLayerSquash(ref file.Reference, layer int, options // ResolveLinkByImageSquash resolves a symlink or hardlink for the given file reference relative to the result from the image squash. // If the given file reference is not a link type, or is a unresolvable (dead) link, then the given file reference is returned. -func (i *Image) ResolveLinkByImageSquash(ref file.Reference, options ...filetree.LinkResolutionOption) (*file.Reference, error) { +func (i *Image) ResolveLinkByImageSquash(ref file.Reference, options ...filetree.LinkResolutionOption) (*file.Resolution, error) { allOptions := append([]filetree.LinkResolutionOption{filetree.FollowBasenameLinks}, options...) _, resolvedRef, err := i.Layers[len(i.Layers)-1].SquashedTree.File(ref.RealPath, allOptions...) return resolvedRef, err diff --git a/pkg/image/image_test.go b/pkg/image/image_test.go index cd38df1b..3a44a626 100644 --- a/pkg/image/image_test.go +++ b/pkg/image/image_test.go @@ -3,10 +3,11 @@ package image import ( "crypto/sha256" "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "os" "testing" - "github.com/go-test/deep" "github.com/google/go-containerregistry/pkg/name" ) @@ -97,13 +98,17 @@ func TestImageAdditionalMetadata(t *testing.T) { os.Remove(tempFile.Name()) }) - img := NewImage(nil, tempFile.Name(), test.options...) + img := New(nil, tempFile.Name(), test.options...) err = img.applyOverrideMetadata() if err != nil { t.Fatalf("could not create image: %+v", err) } - for _, d := range deep.Equal(img, &test.image) { + if d := cmp.Diff(img, &test.image, + cmpopts.IgnoreFields(Image{}, "FileCatalog"), + cmpopts.IgnoreUnexported(Image{}), + cmp.AllowUnexported(name.Tag{}, name.Repository{}, name.Registry{}), + ); d != "" { t.Errorf("diff: %+v", d) } }) diff --git a/pkg/image/layer.go b/pkg/image/layer.go index 96fe14c3..f0b1267d 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -1,7 +1,6 @@ package image import ( - "archive/tar" "bytes" "errors" "fmt" @@ -33,12 +32,14 @@ type Layer struct { // Metadata contains select layer attributes Metadata LayerMetadata // Tree is a filetree that represents the structure of the layer tar contents ("diff tree") - Tree *filetree.FileTree + Tree filetree.Reader // SquashedTree is a filetree that represents the combination of this layers diff tree and all diff trees // in lower layers relative to this one. - SquashedTree *filetree.FileTree + SquashedTree filetree.Reader // fileCatalog contains all file metadata for all files in all layers (not just this layer) - fileCatalog *FileCatalog + fileCatalog *FileCatalog + SquashedSearchContext filetree.Searcher + SearchContext filetree.Searcher } // NewLayer provides a new, unread layer object. @@ -80,7 +81,8 @@ func (l *Layer) uncompressedTarCache(uncompressedLayersCacheDir string) (string, // file tree, and the layer squash tree. func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncompressedLayersCacheDir string) error { var err error - l.Tree = filetree.NewFileTree() + tree := filetree.New() + l.Tree = tree l.fileCatalog = catalog l.Metadata, err = newLayerMetadata(imgMetadata, l.layer, idx) if err != nil { @@ -108,7 +110,10 @@ func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncomp return err } - l.indexedContent, err = file.NewTarIndex(tarFilePath, l.indexer(monitor)) + l.indexedContent, err = file.NewTarIndex( + tarFilePath, + layerTarIndexer(tree, l.fileCatalog, &l.Metadata.Size, l, monitor), + ) if err != nil { return fmt.Errorf("failed to read layer=%q tar : %w", l.Metadata.Digest, err) } @@ -122,9 +127,9 @@ func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncomp // Walk the more efficient walk if we're blessed with an io.ReaderAt. if ra, ok := r.(io.ReaderAt); ok { - err = file.WalkSquashFS(ra, l.squashfsVisitor(monitor)) + err = file.WalkSquashFS(ra, squashfsVisitor(tree, l.fileCatalog, &l.Metadata.Size, l, monitor)) } else { - err = file.WalkSquashFSFromReader(r, l.squashfsVisitor(monitor)) + err = file.WalkSquashFSFromReader(r, squashfsVisitor(tree, l.fileCatalog, &l.Metadata.Size, l, monitor)) } if err != nil { return fmt.Errorf("failed to walk layer=%q: %w", l.Metadata.Digest, err) @@ -134,50 +139,74 @@ func (l *Layer) Read(catalog *FileCatalog, imgMetadata Metadata, idx int, uncomp return fmt.Errorf("unknown layer media type: %+v", l.Metadata.MediaType) } + l.SearchContext = filetree.NewSearchContext(l.Tree, l.fileCatalog.Index) + monitor.SetCompleted() return nil } -// FetchContents reads the file contents for the given path from the underlying layer blob, relative to the layers "diff tree". +// OpenPath reads the file contents for the given path from the underlying layer blob, relative to the layers "diff tree". +// An error is returned if there is no file at the given path and layer or the read operation cannot continue. +func (l *Layer) OpenPath(path file.Path) (io.ReadCloser, error) { + return fetchReaderByPath(l.Tree, l.fileCatalog, path) +} + +// OpenPathFromSquash reads the file contents for the given path from the underlying layer blob, relative to the layers squashed file tree. +// An error is returned if there is no file at the given path and layer or the read operation cannot continue. +func (l *Layer) OpenPathFromSquash(path file.Path) (io.ReadCloser, error) { + return fetchReaderByPath(l.SquashedTree, l.fileCatalog, path) +} + +// FileContents reads the file contents for the given path from the underlying layer blob, relative to the layers "diff tree". // An error is returned if there is no file at the given path and layer or the read operation cannot continue. +// Deprecated: use OpenPath() instead. func (l *Layer) FileContents(path file.Path) (io.ReadCloser, error) { - return fetchFileContentsByPath(l.Tree, l.fileCatalog, path) + return fetchReaderByPath(l.Tree, l.fileCatalog, path) } // FileContentsFromSquash reads the file contents for the given path from the underlying layer blob, relative to the layers squashed file tree. // An error is returned if there is no file at the given path and layer or the read operation cannot continue. +// Deprecated: use OpenPathFromSquash() instead. func (l *Layer) FileContentsFromSquash(path file.Path) (io.ReadCloser, error) { - return fetchFileContentsByPath(l.SquashedTree, l.fileCatalog, path) + return fetchReaderByPath(l.SquashedTree, l.fileCatalog, path) } // FilesByMIMEType returns file references for files that match at least one of the given MIME types relative to each layer tree. +// Deprecated: use SearchContext().SearchByMIMEType() instead. func (l *Layer) FilesByMIMEType(mimeTypes ...string) ([]file.Reference, error) { var refs []file.Reference - for _, ty := range mimeTypes { - refsForType, err := fetchFilesByMIMEType(l.Tree, l.fileCatalog, ty) - if err != nil { - return nil, err + refVias, err := l.SearchContext.SearchByMIMEType(mimeTypes...) + if err != nil { + return nil, err + } + for _, refVia := range refVias { + if refVia.HasReference() { + refs = append(refs, *refVia.Reference) } - refs = append(refs, refsForType...) } return refs, nil } // FilesByMIMETypeFromSquash returns file references for files that match at least one of the given MIME types relative to the squashed file tree representation. +// Deprecated: use SquashedSearchContext().SearchByMIMEType() instead. func (l *Layer) FilesByMIMETypeFromSquash(mimeTypes ...string) ([]file.Reference, error) { var refs []file.Reference - for _, ty := range mimeTypes { - refsForType, err := fetchFilesByMIMEType(l.SquashedTree, l.fileCatalog, ty) - if err != nil { - return nil, err + refVias, err := l.SquashedSearchContext.SearchByMIMEType(mimeTypes...) + if err != nil { + return nil, err + } + for _, refVia := range refVias { + if refVia.HasReference() { + refs = append(refs, *refVia.Reference) } - refs = append(refs, refsForType...) } return refs, nil } -func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { +func layerTarIndexer(ft filetree.Writer, fileCatalog *FileCatalog, size *int64, layerRef *Layer, monitor *progress.Manual) file.TarIndexVisitor { + builder := filetree.NewBuilder(ft, fileCatalog.Index) + return func(index file.TarIndexEntry) error { var err error var entry = index.ToTarFileEntry() @@ -188,7 +217,7 @@ func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { log.Warnf("unable to close file while indexing layer: %+v", err) } }() - metadata := file.NewMetadata(entry.Header, entry.Sequence, contents) + metadata := file.NewMetadata(entry.Header, contents) // note: the tar header name is independent of surrounding structure, for example, there may be a tar header entry // for /some/path/to/file.txt without any entries to constituent paths (/some, /some/path, /some/path/to ). @@ -200,42 +229,26 @@ func (l *Layer) indexer(monitor *progress.Manual) file.TarIndexVisitor { // // In summary: the set of all FileTrees can have NON-leaf nodes that don't exist in the FileCatalog, but // the FileCatalog should NEVER have entries that don't appear in one (or more) FileTree(s). - var fileReference *file.Reference - switch metadata.TypeFlag { - case tar.TypeSymlink: - fileReference, err = l.Tree.AddSymLink(file.Path(metadata.Path), file.Path(metadata.Linkname)) - if err != nil { - return err - } - case tar.TypeLink: - fileReference, err = l.Tree.AddHardLink(file.Path(metadata.Path), file.Path(metadata.Linkname)) - if err != nil { - return err - } - case tar.TypeDir: - fileReference, err = l.Tree.AddDir(file.Path(metadata.Path)) - if err != nil { - return err - } - default: - fileReference, err = l.Tree.AddFile(file.Path(metadata.Path)) - if err != nil { - return err - } - } - if fileReference == nil { - return fmt.Errorf("could not add path=%q link=%q during tar iteration", metadata.Path, metadata.Linkname) + ref, err := builder.Add(metadata) + if err != nil { + return err } - l.Metadata.Size += metadata.Size - l.fileCatalog.Add(*fileReference, metadata, l, index.Open) + if size != nil { + *(size) += metadata.Size + } + fileCatalog.addImageReferences(ref.ID(), layerRef, index.Open) - monitor.N++ + if monitor != nil { + monitor.N++ + } return nil } } -func (l *Layer) squashfsVisitor(monitor *progress.Manual) file.SquashFSVisitor { +func squashfsVisitor(ft filetree.Writer, fileCatalog *FileCatalog, size *int64, layerRef *Layer, monitor *progress.Manual) file.SquashFSVisitor { + builder := filetree.NewBuilder(ft, fileCatalog.Index) + return func(fsys fs.FS, path string, d fs.DirEntry) error { ff, err := fsys.Open(path) if err != nil { @@ -245,7 +258,7 @@ func (l *Layer) squashfsVisitor(monitor *progress.Manual) file.SquashFSVisitor { f, ok := ff.(*squashfs.File) if !ok { - return errors.New("unexpected file type") + return errors.New("unexpected file type from squashfs") } metadata, err := file.NewMetadataFromSquashFSFile(path, f) @@ -253,32 +266,15 @@ func (l *Layer) squashfsVisitor(monitor *progress.Manual) file.SquashFSVisitor { return err } - var fileReference *file.Reference - - switch { - case f.IsSymlink(): - fileReference, err = l.Tree.AddSymLink(file.Path(metadata.Path), file.Path(metadata.Linkname)) - if err != nil { - return err - } - case f.IsDir(): - fileReference, err = l.Tree.AddDir(file.Path(metadata.Path)) - if err != nil { - return err - } - default: - fileReference, err = l.Tree.AddFile(file.Path(metadata.Path)) - if err != nil { - return err - } + fileReference, err := builder.Add(metadata) + if err != nil { + return err } - if fileReference == nil { - return fmt.Errorf("could not add path=%q link=%q during squashfs iteration", metadata.Path, metadata.Linkname) + if size != nil { + *(size) += metadata.Size } - - l.Metadata.Size += metadata.Size - l.fileCatalog.Add(*fileReference, metadata, l, func() io.ReadCloser { + fileCatalog.addImageReferences(fileReference.ID(), layerRef, func() io.ReadCloser { r, err := fsys.Open(path) if err != nil { // The file.Opener interface doesn't give us a way to return an error, and callers diff --git a/pkg/image/oci/directory_provider.go b/pkg/image/oci/directory_provider.go index 643b6cb1..08220cb5 100644 --- a/pkg/image/oci/directory_provider.go +++ b/pkg/image/oci/directory_provider.go @@ -69,5 +69,5 @@ func (p *DirectoryImageProvider) Provide(_ context.Context, userMetadata ...imag return nil, err } - return image.NewImage(img, contentTempDir, metadata...), nil + return image.New(img, contentTempDir, metadata...), nil } diff --git a/pkg/image/oci/registry_provider.go b/pkg/image/oci/registry_provider.go index 7768a407..52128a62 100644 --- a/pkg/image/oci/registry_provider.go +++ b/pkg/image/oci/registry_provider.go @@ -80,7 +80,7 @@ func (p *RegistryImageProvider) Provide(ctx context.Context, userMetadata ...ima // apply user-supplied metadata last to override any default behavior metadata = append(metadata, userMetadata...) - return image.NewImage(img, imageTempDir, metadata...), nil + return image.New(img, imageTempDir, metadata...), nil } func prepareReferenceOptions(registryOptions image.RegistryOptions) []name.Option { diff --git a/pkg/image/sif/provider.go b/pkg/image/sif/provider.go index 6f6a6738..e30f2033 100644 --- a/pkg/image/sif/provider.go +++ b/pkg/image/sif/provider.go @@ -51,5 +51,5 @@ func (p *SingularityImageProvider) Provide(ctx context.Context, userMetadata ... } metadata = append(metadata, userMetadata...) - return image.NewImage(ui, contentCacheDir, metadata...), nil + return image.New(ui, contentCacheDir, metadata...), nil } diff --git a/pkg/image/test-fixtures/generators/fixture-2.sh b/pkg/image/test-fixtures/generators/fixture-2.sh new file mode 100755 index 00000000..0c7b2f19 --- /dev/null +++ b/pkg/image/test-fixtures/generators/fixture-2.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -ue + +realpath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} + +FIXTURE_TAR_PATH=$1 +FIXTURE_NAME=$(basename $FIXTURE_TAR_PATH) +FIXTURE_DIR=$(realpath $(dirname $FIXTURE_TAR_PATH)) + +# note: since tar --sort is not an option on mac, and we want these generation scripts to be generally portable, we've +# elected to use docker to generate the tar +docker run --rm -i \ + -u $(id -u):$(id -g) \ + -v ${FIXTURE_DIR}:/scratch \ + -w /scratch \ + ubuntu:latest \ + /bin/bash -xs < path/branch.d/one/file-1.txt + echo "forth file" > path/branch.d/one/file-4.d + echo "multi ext file" > path/branch.d/one/file-4.tar.gz + echo "hidden file" > path/branch.d/one/.file-4.tar.gz + + ln -s path/branch.d path/common/branch.d + ln -s path/branch.d path/common/branch + ln -s path/branch.d/one/file-4.d path/common/file-4 + ln -s path/branch.d/one/file-1.txt path/common/file-1.d + + echo "second file" > path/branch.d/two/file-2.txt + + echo "third file" > path/file-3.txt + + # permissions + chmod -R 755 path + chmod -R 700 path/branch/one/ + chmod 664 path/file-3.txt + + # tar + owner + # note: sort by name is important for test file header entry ordering + tar --sort=name --owner=1337 --group=5432 -cvf "/scratch/${FIXTURE_NAME}" path/ + +popd +EOF diff --git a/pkg/tree/depth_first_walker.go b/pkg/tree/depth_first_walker.go index 6870de1d..0efd677d 100644 --- a/pkg/tree/depth_first_walker.go +++ b/pkg/tree/depth_first_walker.go @@ -27,7 +27,7 @@ type DepthFirstWalker struct { visitor NodeVisitor tree Reader stack node.Stack - visited node.Set + visited node.IDSet conditions WalkConditions } diff --git a/pkg/tree/node/id.go b/pkg/tree/node/id.go index b84d2c09..b023c733 100644 --- a/pkg/tree/node/id.go +++ b/pkg/tree/node/id.go @@ -1,22 +1,76 @@ package node +import "sort" + type ID string -type Set map[ID]struct{} +type IDSet map[ID]struct{} + +func NewIDSet(is ...ID) IDSet { + // TODO: replace with single generic implementation that also incorporates other set implementations + s := make(IDSet) + s.Add(is...) + return s +} + +func (s IDSet) Size() int { + return len(s) +} -func NewIDSet() Set { - return make(Set) +func (s IDSet) Merge(other IDSet) { + for _, i := range other.List() { + s.Add(i) + } } -func (s Set) Add(i ID) { - s[i] = struct{}{} +func (s IDSet) Add(ids ...ID) { + for _, i := range ids { + s[i] = struct{}{} + } } -func (s Set) Remove(i ID) { - delete(s, i) +func (s IDSet) Remove(ids ...ID) { + for _, i := range ids { + delete(s, i) + } } -func (s Set) Contains(i ID) bool { +func (s IDSet) Contains(i ID) bool { _, ok := s[i] return ok } + +func (s IDSet) Clear() { + // TODO: replace this with the new 'clear' keyword when it's available in go 1.20 or 1.21 + for i := range s { + delete(s, i) + } +} + +func (s IDSet) List() []ID { + ret := make([]ID, 0, len(s)) + for i := range s { + ret = append(ret, i) + } + return ret +} + +func (s IDSet) Sorted() []ID { + ids := s.List() + + sort.Slice(ids, func(i, j int) bool { + return ids[i] < ids[j] + }) + + return ids +} + +func (s IDSet) ContainsAny(ids ...ID) bool { + for _, i := range ids { + _, ok := s[i] + if ok { + return true + } + } + return false +} diff --git a/pkg/tree/node/id_test.go b/pkg/tree/node/id_test.go new file mode 100644 index 00000000..222981bc --- /dev/null +++ b/pkg/tree/node/id_test.go @@ -0,0 +1,226 @@ +package node + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestIDSet_Size(t *testing.T) { + type testCase struct { + name string + s IDSet + want int + } + tests := []testCase{ + { + name: "empty set", + s: NewIDSet(), + want: 0, + }, + { + name: "non-empty set", + s: NewIDSet("items", "in", "set"), + want: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Size(); got != tt.want { + t.Errorf("Size() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIDSet_Add(t *testing.T) { + type args struct { + ids []ID + } + type testCase struct { + name string + s IDSet + args args + } + tests := []testCase{ + { + name: "add multiple", + s: NewIDSet(), + args: args{ids: []ID{"a", "b", "c"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Add(tt.args.ids...) + for _, id := range tt.args.ids { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestIDSet_Remove(t *testing.T) { + type args struct { + ids []ID + } + type testCase struct { + name string + s IDSet + args args + expected []ID + } + tests := []testCase{ + { + name: "remove multiple", + s: NewIDSet("a", "b", "c"), + args: args{ids: []ID{"a", "b"}}, + expected: []ID{"c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Remove(tt.args.ids...) + for _, id := range tt.args.ids { + if tt.s.Contains(id) { + t.Errorf("expected set to NOT contain %q", id) + } + } + for _, id := range tt.expected { + if !tt.s.Contains(id) { + t.Errorf("expected set to contain %q", id) + } + } + }) + } +} + +func TestIDSet_Contains(t *testing.T) { + type args struct { + i ID + } + type testCase struct { + name string + s IDSet + args args + want bool + } + tests := []testCase{ + { + name: "contains", + s: NewIDSet("a", "b", "c"), + args: args{i: "a"}, + want: true, + }, + { + name: "not contains", + s: NewIDSet("a", "b", "c"), + args: args{i: "x"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Contains(tt.args.i); got != tt.want { + t.Errorf("Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIDSet_Clear(t *testing.T) { + type testCase struct { + name string + s IDSet + } + tests := []testCase{ + { + name: "go case", + s: NewIDSet("a", "b", "c"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Clear() + assert.Equal(t, 0, tt.s.Size()) + }) + } +} + +func TestIDSet_List(t *testing.T) { + type testCase struct { + name string + s IDSet + want []ID + } + tests := []testCase{ + { + name: "go case", + s: NewIDSet("a", "b", "c"), + want: []ID{"a", "b", "c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, tt.want, tt.s.List(), "List()") + }) + } +} + +func TestIDSet_Sorted(t *testing.T) { + type testCase struct { + name string + s IDSet + want []ID + } + tests := []testCase{ + { + name: "go case", + s: NewIDSet("a", "b", "c"), + want: []ID{"a", "b", "c"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.s.Sorted(), "Sorted()") + }) + } +} + +func TestIDSet_ContainsAny(t *testing.T) { + type args struct { + ids []ID + } + type testCase struct { + name string + s IDSet + args args + want bool + } + tests := []testCase{ + { + name: "contains one", + s: NewIDSet("a", "b", "c"), + args: args{ids: []ID{"a", "x"}}, + want: true, + }, + { + name: "contains all", + s: NewIDSet("a", "b", "c"), + args: args{ids: []ID{"a", "b"}}, + want: true, + }, + { + name: "contains none", + s: NewIDSet("a", "b", "c"), + args: args{ids: []ID{"x", "y"}}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.s.ContainsAny(tt.args.ids...), fmt.Sprintf("ContainsAny(%v)", tt.args.ids)) + }) + } +} diff --git a/pkg/tree/tree.go b/pkg/tree/tree.go index 1b5079fd..0dc04557 100644 --- a/pkg/tree/tree.go +++ b/pkg/tree/tree.go @@ -8,9 +8,9 @@ import ( // Tree represents a simple Tree data structure. type Tree struct { - nodes map[node.ID]node.Node - children map[node.ID]map[node.ID]node.Node - parent map[node.ID]node.Node + nodes map[node.ID]node.Node // {node-id: node} + children map[node.ID]map[node.ID]node.Node // {parent-id: {child-id: child-node} + parent map[node.ID]node.Node // {child-id: parent-node} } // NewTree returns an instance of a Tree. diff --git a/test/integration/fixture_image_opaque_directory_test.go b/test/integration/fixture_image_opaque_directory_test.go index cccc4207..2e58fe2b 100644 --- a/test/integration/fixture_image_opaque_directory_test.go +++ b/test/integration/fixture_image_opaque_directory_test.go @@ -17,7 +17,7 @@ func TestImage_SquashedTree_OpaqueDirectoryExistsInFileCatalog(t *testing.T) { t.Fatalf("unable to get file=%q : %+v", path, err) } - _, err = image.FileCatalog.Get(*ref) + _, err = image.FileCatalog.Get(*ref.Reference) if err != nil { t.Fatal(err) } diff --git a/test/integration/fixture_image_simple_test.go b/test/integration/fixture_image_simple_test.go index 24a44c9f..2a378015 100644 --- a/test/integration/fixture_image_simple_test.go +++ b/test/integration/fixture_image_simple_test.go @@ -177,11 +177,11 @@ func BenchmarkSimpleImage_FetchSquashedContents(b *testing.B) { b.Run(c.source, func(b *testing.B) { for i := 0; i < b.N; i++ { for _, ref := range paths { - f, err := img.FileCatalog.Get(ref) + f, err := img.FileCatalog.Open(ref) if err != nil { b.Fatalf("unable to read: %+v", err) } - _, err = io.ReadAll(f.Contents()) + _, err = io.ReadAll(f) } } }) @@ -191,6 +191,7 @@ func BenchmarkSimpleImage_FetchSquashedContents(b *testing.B) { func assertImageSimpleMetadata(t *testing.T, i *image.Image, expectedValues testCase) { t.Helper() t.Log("Asserting metadata...") + if i.Metadata.MediaType != expectedValues.imageMediaType { t.Errorf("unexpected image media type: %+v", i.Metadata.MediaType) } @@ -226,21 +227,23 @@ func assertImageSimpleMetadata(t *testing.T, i *image.Image, expectedValues test } func assertImageSimpleSquashedTrees(t *testing.T, i *image.Image) { + t.Helper() t.Log("Asserting squashed trees...") - one := filetree.NewFileTree() + + one := filetree.New() one.AddFile("/somefile-1.txt") - two := filetree.NewFileTree() + two := filetree.New() two.AddFile("/somefile-1.txt") two.AddFile("/somefile-2.txt") - three := filetree.NewFileTree() + three := filetree.New() three.AddFile("/somefile-1.txt") three.AddFile("/somefile-2.txt") three.AddFile("/really/.wh..wh..opq") three.AddFile("/really/nested/file-3.txt") - expectedTrees := map[uint]*filetree.FileTree{ + expectedTrees := map[uint]filetree.Reader{ 0: one, 1: two, 2: three, @@ -254,7 +257,7 @@ func assertImageSimpleSquashedTrees(t *testing.T, i *image.Image) { compareLayerSquashTrees(t, expectedTrees, i, ignorePaths) - squashed := filetree.NewFileTree() + squashed := filetree.New() squashed.AddFile("/somefile-1.txt") squashed.AddFile("/somefile-2.txt") squashed.AddFile("/really/nested/file-3.txt") @@ -263,18 +266,20 @@ func assertImageSimpleSquashedTrees(t *testing.T, i *image.Image) { } func assertImageSimpleTrees(t *testing.T, i *image.Image) { + t.Helper() t.Log("Asserting trees...") - one := filetree.NewFileTree() + + one := filetree.New() one.AddFile("/somefile-1.txt") - two := filetree.NewFileTree() + two := filetree.New() two.AddFile("/somefile-2.txt") - three := filetree.NewFileTree() + three := filetree.New() three.AddFile("/really/.wh..wh..opq") three.AddFile("/really/nested/file-3.txt") - expectedTrees := map[uint]*filetree.FileTree{ + expectedTrees := map[uint]filetree.Reader{ 0: one, 1: two, 2: three, @@ -290,6 +295,7 @@ func assertImageSimpleTrees(t *testing.T, i *image.Image) { } func assertImageSimpleContents(t *testing.T, i *image.Image) { + t.Helper() t.Log("Asserting contents...") expectedContents := map[string]string{ @@ -300,7 +306,7 @@ func assertImageSimpleContents(t *testing.T, i *image.Image) { actualContents := make(map[string]io.Reader) for path := range expectedContents { - reader, err := i.FileContentsFromSquash(file.Path(path)) + reader, err := i.OpenPathFromSquash(file.Path(path)) if err != nil { t.Fatal("unable to fetch multiple contents", err) } diff --git a/test/integration/fixture_image_symlinks_test.go b/test/integration/fixture_image_symlinks_test.go index 1c2280da..684af08f 100644 --- a/test/integration/fixture_image_symlinks_test.go +++ b/test/integration/fixture_image_symlinks_test.go @@ -5,6 +5,7 @@ package integration import ( "fmt" + "github.com/stretchr/testify/require" "io" "testing" @@ -92,58 +93,45 @@ func assertMatch(t *testing.T, i *image.Image, cfg linkFetchConfig, expectedReso if actualResolve.ID() != expectedResolve.ID() { var exLayer = -1 var acLayer = -1 - var exType byte - var acType byte + var exType file.Type + var acType file.Type eM, err := i.FileCatalog.Get(*expectedResolve) if err == nil { - exLayer = int(eM.Layer.Metadata.Index) - exType = eM.Metadata.TypeFlag + exLayer = int(i.FileCatalog.Layer(*expectedResolve).Metadata.Index) + exType = eM.Metadata.Type } aM, err := i.FileCatalog.Get(*actualResolve) if err == nil { - acLayer = int(aM.Layer.Metadata.Index) - acType = aM.Metadata.TypeFlag + acLayer = int(i.FileCatalog.Layer(*actualResolve).Metadata.Index) + acType = aM.Metadata.Type } - t.Fatalf("mismatched link resolution link=%+v: '%+v (layer=%d type=%+v)'!='%+v (layer=%d type=%+v linkName=%s)'", cfg.linkPath, expectedResolve, exLayer, exType, actualResolve, acLayer, acType, aM.Metadata.Linkname) + t.Fatalf("mismatched link resolution link=%+v: <%+v (layer=%d type=%+v)> != <%+v (layer=%d type=%+v linkName=%s)>", cfg.linkPath, expectedResolve, exLayer, exType, actualResolve, acLayer, acType, aM.Metadata.LinkDestination) } } func fetchRefs(t *testing.T, i *image.Image, cfg linkFetchConfig) (*file.Reference, *file.Reference) { _, link, err := i.Layers[cfg.linkLayer].Tree.File(file.Path(cfg.linkPath), cfg.linkOptions...) - if err != nil { - t.Fatalf("unable to get link: %+v", err) - } - if link == nil { - t.Fatalf("missing expected link: %s", cfg.linkPath) - } + require.NoError(t, err) + require.NotNil(t, link) _, expectedResolve, err := i.Layers[cfg.resolveLayer].Tree.File(file.Path(cfg.expectedPath), cfg.linkOptions...) - if err != nil { - t.Fatalf("unable to get resolved link: %+v", err) - } - if expectedResolve == nil { - t.Fatalf("missing expected path: %s", expectedResolve) - } + require.NoError(t, err) + require.NotNil(t, expectedResolve) - actualResolve, err := i.ResolveLinkByLayerSquash(*link, cfg.perspectiveLayer, cfg.linkOptions...) - if err != nil { - t.Fatalf("failed to resolve link=%+v: %+v", link, err) - } - return expectedResolve, actualResolve + actualResolve, err := i.ResolveLinkByLayerSquash(*link.Reference, cfg.perspectiveLayer, cfg.linkOptions...) + require.NoError(t, err) + return expectedResolve.Reference, actualResolve.Reference } func fetchContents(t *testing.T, i *image.Image, cfg linkFetchConfig) string { - contents, err := i.Layers[cfg.perspectiveLayer].FileContentsFromSquash(file.Path(cfg.linkPath)) - if err != nil { - t.Fatalf("could not fetch contents of %+v: %+v", cfg.linkPath, err) - } + contents, err := i.Layers[cfg.perspectiveLayer].OpenPathFromSquash(file.Path(cfg.linkPath)) + require.NoError(t, err) + b, err := io.ReadAll(contents) - if err != nil { - t.Fatalf("unable to fetch contents for %+v : %+v", cfg, err) - } + require.NoError(t, err) return string(b) } diff --git a/test/integration/mime_type_detection_test.go b/test/integration/mime_type_detection_test.go index f905ad34..eb43edd7 100644 --- a/test/integration/mime_type_detection_test.go +++ b/test/integration/mime_type_detection_test.go @@ -22,7 +22,7 @@ func TestContentMIMETypeDetection(t *testing.T) { } for mimeType, paths := range pathsByMIMEType { - refs, err := img.FilesByMIMETypeFromSquash(mimeType) + refs, err := img.SquashedSearchContext.SearchByMIMEType(mimeType) assert.NoError(t, err) assert.NotZero(t, len(refs), "found no refs for type=%q", mimeType) for _, ref := range refs { diff --git a/test/integration/utils_test.go b/test/integration/utils_test.go index 6ca7b53a..4f9fa268 100644 --- a/test/integration/utils_test.go +++ b/test/integration/utils_test.go @@ -9,13 +9,13 @@ import ( "github.com/anchore/stereoscope/pkg/image" ) -func compareLayerSquashTrees(t *testing.T, expected map[uint]*filetree.FileTree, i *image.Image, ignorePaths []file.Path) { +func compareLayerSquashTrees(t *testing.T, expected map[uint]filetree.Reader, i *image.Image, ignorePaths []file.Path) { t.Helper() if len(expected) != len(i.Layers) { t.Fatalf("mismatched layers (%d!=%d)", len(expected), len(i.Layers)) } - var actual = make([]*filetree.FileTree, 0) + var actual = make([]filetree.Reader, 0) for _, l := range i.Layers { actual = append(actual, l.SquashedTree) } @@ -23,13 +23,13 @@ func compareLayerSquashTrees(t *testing.T, expected map[uint]*filetree.FileTree, compareTrees(t, expected, actual, ignorePaths) } -func compareLayerTrees(t *testing.T, expected map[uint]*filetree.FileTree, i *image.Image, ignorePaths []file.Path) { +func compareLayerTrees(t *testing.T, expected map[uint]filetree.Reader, i *image.Image, ignorePaths []file.Path) { t.Helper() if len(expected) != len(i.Layers) { t.Fatalf("mismatched layers (%d!=%d)", len(expected), len(i.Layers)) } - var actual = make([]*filetree.FileTree, 0) + var actual = make([]filetree.Reader, 0) for _, l := range i.Layers { actual = append(actual, l.Tree) } @@ -37,13 +37,13 @@ func compareLayerTrees(t *testing.T, expected map[uint]*filetree.FileTree, i *im compareTrees(t, expected, actual, ignorePaths) } -func compareTrees(t *testing.T, expected map[uint]*filetree.FileTree, actual []*filetree.FileTree, ignorePaths []file.Path) { +func compareTrees(t *testing.T, expected map[uint]filetree.Reader, actual []filetree.Reader, ignorePaths []file.Path) { t.Helper() - for idx, expected := range expected { - actual := actual[idx] - if !expected.Equal(actual) { - extra, missing := expected.PathDiff(actual) + for idx, e := range expected { + a := actual[idx] + if !e.(*filetree.FileTree).Equal(a.(*filetree.FileTree)) { + extra, missing := e.(*filetree.FileTree).PathDiff(a.(*filetree.FileTree)) nonIgnoredPaths := 0 for _, p := range extra { @@ -82,11 +82,11 @@ func compareTrees(t *testing.T, expected map[uint]*filetree.FileTree, actual []* } } -func compareSquashTree(t *testing.T, expected *filetree.FileTree, i *image.Image) { +func compareSquashTree(t *testing.T, expected filetree.Reader, i *image.Image) { t.Helper() actual := i.SquashedTree() - if !expected.Equal(actual) { + if !expected.(*filetree.FileTree).Equal(actual.(*filetree.FileTree)) { t.Log("Walking expected squashed tree:") err := expected.Walk(func(p file.Path, _ filenode.FileNode) error { t.Log(" ", p) @@ -105,7 +105,7 @@ func compareSquashTree(t *testing.T, expected *filetree.FileTree, i *image.Image t.Fatalf("failed to walk tree: %+v", err) } - extra, missing := expected.PathDiff(actual) + extra, missing := expected.(*filetree.FileTree).PathDiff(actual.(*filetree.FileTree)) t.Errorf("path differences: extra=%+v missing=%+v", extra, missing) t.Errorf("mismatched squashed trees") } From 38cc5e5029753ad77e737c590f07b55c5bb71061 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Feb 2023 09:33:38 -0500 Subject: [PATCH 07/23] Bump github.com/containerd/containerd from 1.6.12 to 1.6.18 (#156) --- go.mod | 4 ++-- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 9cec3854..fc28b246 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 github.com/becheran/wildmatch-go v1.0.0 github.com/bmatcuk/doublestar/v4 v4.0.2 - github.com/containerd/containerd v1.6.12 + github.com/containerd/containerd v1.6.18 github.com/docker/cli v20.10.12+incompatible github.com/docker/docker v20.10.12+incompatible github.com/gabriel-vasile/mimetype v1.4.0 @@ -19,7 +19,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 github.com/mitchellh/go-homedir v1.1.0 - github.com/pelletier/go-toml v1.9.3 + github.com/pelletier/go-toml v1.9.5 github.com/pkg/errors v0.9.1 // pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e diff --git a/go.sum b/go.sum index 33eeee5a..fc5507c2 100644 --- a/go.sum +++ b/go.sum @@ -207,8 +207,8 @@ github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09Zvgq github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.6.12 h1:kJ9b3mOFKf8yqo05Ob+tMoxvt1pbVWhnB0re9Y+k+8c= -github.com/containerd/containerd v1.6.12/go.mod h1:K4Bw7gjgh4TnkmQY+py/PYQGp4e7xgnHAeg87VeWb3A= +github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= +github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -655,8 +655,9 @@ github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xA github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= From bd07bb5a244ae3871cdccab24f4f1237c636aa6e Mon Sep 17 00:00:00 2001 From: Weston Steimel Date: Tue, 21 Feb 2023 19:46:33 +0000 Subject: [PATCH 08/23] fix: bump golang.org/x/net to v0.7.0 (#157) Signed-off-by: Weston Steimel --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index fc28b246..e50a4d1c 100644 --- a/go.mod +++ b/go.mod @@ -72,12 +72,12 @@ require ( github.com/therootcompany/xz v1.0.1 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/vbatts/tar-split v0.11.2 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect google.golang.org/grpc v1.47.0 // indirect diff --git a/go.sum b/go.sum index fc5507c2..fbbc8b4a 100644 --- a/go.sum +++ b/go.sum @@ -943,8 +943,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1063,11 +1063,11 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1076,8 +1076,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 66b650a44f6a40b60be0b3d0d7c7c983f09df29f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 21 Feb 2023 17:49:19 -0500 Subject: [PATCH 09/23] Fix link cycle detection (#158) * test: add failing test for cycle case Signed-off-by: Christopher Phillips * test: test updates sym links Signed-off-by: Christopher Phillips * change the filetree recursive pathset to represent open calls Signed-off-by: Alex Goodman * add another cycle test Signed-off-by: Alex Goodman * change filetree attempting path set to counters Signed-off-by: Alex Goodman * remove comment Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman --------- Signed-off-by: Christopher Phillips Signed-off-by: Alex Goodman Co-authored-by: Christopher Phillips --- pkg/file/path_set.go | 36 ++++++++++++++++++++++++ pkg/file/path_set_test.go | 42 ++++++++++++++++++++++++++++ pkg/filetree/filetree.go | 35 +++++++++++++---------- pkg/filetree/filetree_test.go | 52 +++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 14 deletions(-) diff --git a/pkg/file/path_set.go b/pkg/file/path_set.go index a46f342a..f4a8084d 100644 --- a/pkg/file/path_set.go +++ b/pkg/file/path_set.go @@ -75,3 +75,39 @@ func (s PathSet) ContainsAny(ids ...Path) bool { } return false } + +type PathCountSet map[Path]int + +func NewPathCountSet(is ...Path) PathCountSet { + s := make(PathCountSet) + s.Add(is...) + return s +} + +func (s PathCountSet) Add(ids ...Path) { + for _, i := range ids { + if _, ok := s[i]; !ok { + s[i] = 1 + continue + } + s[i]++ + } +} + +func (s PathCountSet) Remove(ids ...Path) { + for _, i := range ids { + if _, ok := s[i]; !ok { + continue + } + + s[i]-- + if s[i] <= 0 { + delete(s, i) + } + } +} + +func (s PathCountSet) Contains(i Path) bool { + count, ok := s[i] + return ok && count > 0 +} diff --git a/pkg/file/path_set_test.go b/pkg/file/path_set_test.go index 5d296649..1e578911 100644 --- a/pkg/file/path_set_test.go +++ b/pkg/file/path_set_test.go @@ -224,3 +224,45 @@ func TestPathSet_ContainsAny(t *testing.T) { }) } } + +func TestPathCountSet(t *testing.T) { + s := NewPathCountSet() + + s.Add("a", "b") // {a: 1, b: 1} + assert.True(t, s.Contains("a")) + assert.True(t, s.Contains("b")) + assert.False(t, s.Contains("c")) + + s.Add("a", "c") // {a: 2, b: 1, c: 1} + assert.True(t, s.Contains("a")) + assert.True(t, s.Contains("b")) + assert.True(t, s.Contains("c")) + + s.Remove("a") // {a: 1, b: 1, c: 1} + assert.True(t, s.Contains("a")) + assert.True(t, s.Contains("b")) + assert.True(t, s.Contains("c")) + + s.Remove("a", "b") // {c: 1} + assert.False(t, s.Contains("a")) + assert.False(t, s.Contains("b")) + assert.True(t, s.Contains("c")) + + s.Remove("a", "b", "v", "c") // {} + assert.False(t, s.Contains("a")) + assert.False(t, s.Contains("b")) + assert.False(t, s.Contains("c")) + + s.Add("a", "a", "a", "a") // {a: 4} + assert.True(t, s.Contains("a")) + assert.Equal(t, 4, s["a"]) + + s.Remove("a", "a", "a") // {a: 1} + assert.True(t, s.Contains("a")) + + s.Remove("a", "a", "a", "a") // {} + assert.False(t, s.Contains("a")) + + s.Remove("a", "a", "a", "a", "a", "a", "a", "a") // {} + assert.False(t, s.Contains("a")) +} diff --git a/pkg/filetree/filetree.go b/pkg/filetree/filetree.go index 9352e8d9..141040c8 100644 --- a/pkg/filetree/filetree.go +++ b/pkg/filetree/filetree.go @@ -250,7 +250,7 @@ func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*nodeAcce // return FileNode of the basename in the given path (no resolution is done at or past the basename). Note: it is // assumed that the given path has already been normalized. -func (t *FileTree) resolveAncestorLinks(path file.Path, attemptedPaths file.PathSet) (*nodeAccess, error) { +func (t *FileTree) resolveAncestorLinks(path file.Path, currentlyResolvingLinkPaths file.PathCountSet) (*nodeAccess, error) { // performance optimization... see if there is a node at the path (as if it is a real path). If so, // use it, otherwise, continue with ancestor resolution currentNodeAccess, err := t.node(path, linkResolutionStrategy{}) @@ -306,7 +306,7 @@ func (t *FileTree) resolveAncestorLinks(path file.Path, attemptedPaths file.Path // links until the next Node is resolved (or not). isLastPart := idx == len(pathParts)-1 if !isLastPart && currentNodeAccess.FileNode.IsLink() { - currentNodeAccess, err = t.resolveNodeLinks(currentNodeAccess, true, attemptedPaths) + currentNodeAccess, err = t.resolveNodeLinks(currentNodeAccess, true, currentlyResolvingLinkPaths) if err != nil { // only expected to happen on cycles return currentNodeAccess, err @@ -325,14 +325,16 @@ func (t *FileTree) resolveAncestorLinks(path file.Path, attemptedPaths file.Path // resolveNodeLinks takes the given FileNode and resolves all links at the base of the real path for the node (this implies // that NO ancestors are considered). // nolint: funlen -func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, attemptedPaths file.PathSet) (*nodeAccess, error) { +func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, currentlyResolvingLinkPaths file.PathCountSet) (*nodeAccess, error) { if n == nil { return nil, fmt.Errorf("cannot resolve links with nil Node given") } - // we need to short-circuit link resolution that never resolves (cycles) due to a cycle referencing nodes that do not exist - if attemptedPaths == nil { - attemptedPaths = file.NewPathSet() + // we need to short-circuit link resolution that never resolves (cycles) due to a cycle referencing nodes that do not exist. + // this represents current link resolution requests that are in progress. This set is pruned once the resolution + // has been completed. + if currentlyResolvingLinkPaths == nil { + currentlyResolvingLinkPaths = file.NewPathCountSet() } // note: this assumes that callers are passing paths in which the constituent parts are NOT symlinks @@ -342,8 +344,10 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, currentNodeAccess := n - // keep resolving links until a regular file or directory is found - alreadySeen := strset.New() + // keep resolving links until a regular file or directory is found. + // Note: this is NOT redundant relative to the 'currentlyResolvingLinkPaths' set. This set is used to short-circuit + // real paths that have been revisited through potentially different links (or really anyway). + realPathsVisited := strset.New() var err error for { nodePath = append(nodePath, *currentNodeAccess) @@ -357,7 +361,7 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, break } - if alreadySeen.Has(string(currentNodeAccess.FileNode.RealPath)) { + if realPathsVisited.Has(string(currentNodeAccess.FileNode.RealPath)) { return nil, ErrLinkCycleDetected } @@ -368,7 +372,8 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, } // prepare for the next iteration - alreadySeen.Add(string(currentNodeAccess.FileNode.RealPath)) + // already seen is important for the context of this loop + realPathsVisited.Add(string(currentNodeAccess.FileNode.RealPath)) nextPath = currentNodeAccess.FileNode.RenderLinkDestination() @@ -381,13 +386,14 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, lastNode = currentNodeAccess // break any cycles with non-existent paths (before attempting to look the path up again) - if attemptedPaths.Contains(nextPath) { + if currentlyResolvingLinkPaths.Contains(nextPath) { return nil, ErrLinkCycleDetected } - // get the next Node (based on the next path) - attemptedPaths.Add(nextPath) - currentNodeAccess, err = t.resolveAncestorLinks(nextPath, attemptedPaths) + // get the next Node (based on the next path)a + // attempted paths maintains state across calls to resolveAncestorLinks + currentlyResolvingLinkPaths.Add(nextPath) + currentNodeAccess, err = t.resolveAncestorLinks(nextPath, currentlyResolvingLinkPaths) if err != nil { if currentNodeAccess != nil { currentNodeAccess.LeafLinkResolution = append(currentNodeAccess.LeafLinkResolution, nodePath...) @@ -396,6 +402,7 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, // only expected to occur upon cycle detection return currentNodeAccess, err } + currentlyResolvingLinkPaths.Remove(nextPath) } if !currentNodeAccess.HasFileNode() && !followDeadBasenameLinks { diff --git a/pkg/filetree/filetree_test.go b/pkg/filetree/filetree_test.go index e2d592cc..fff09810 100644 --- a/pkg/filetree/filetree_test.go +++ b/pkg/filetree/filetree_test.go @@ -1079,6 +1079,8 @@ func TestFileTree_File_DeadCycleDetection(t *testing.T) { // the test.... do we stop when a cycle is detected? exists, _, err := tr.File("/somewhere/acorn", FollowBasenameLinks) + require.Error(t, err, "should have gotten an error on resolution of a dead cycle") + // TODO: check this case if err != ErrLinkCycleDetected { t.Fatalf("should have gotten an error on resolving a file") } @@ -1089,6 +1091,56 @@ func TestFileTree_File_DeadCycleDetection(t *testing.T) { } +func TestFileTree_File_ShortCircuitDeadBasenameLinkCycles(t *testing.T) { + tr := New() + _, err := tr.AddFile("/usr/bin/ksh93") + require.NoError(t, err) + + linkPath, err := tr.AddSymLink("/usr/local/bin/ksh", "/bin/ksh") + require.NoError(t, err) + + _, err = tr.AddSymLink("/bin", "/usr/bin/ksh93") + require.NoError(t, err) + + // note: we follow dead basename links + exists, resolution, err := tr.File("/usr/local/bin/ksh", FollowBasenameLinks) + require.NoError(t, err) + assert.False(t, exists) + assert.False(t, resolution.HasReference()) + + // note: we don't follow dead basename links + exists, resolution, err = tr.File("/usr/local/bin/ksh", FollowBasenameLinks, DoNotFollowDeadBasenameLinks) + require.NoError(t, err) + assert.True(t, exists) + assert.True(t, resolution.HasReference()) + assert.Equal(t, *linkPath, *resolution.Reference) +} + +// regression: Syft issue https://github.com/anchore/syft/issues/1586 +func TestFileTree_File_ResolutionWithMultipleAncestorResolutionsForSameNode(t *testing.T) { + tr := New() + actualRef, err := tr.AddFile("/usr/bin/ksh93") + require.NoError(t, err) + + _, err = tr.AddSymLink("/usr/local/bin/ksh", "/bin/ksh") + require.NoError(t, err) + + _, err = tr.AddSymLink("/bin", "/usr/bin") + require.NoError(t, err) + + _, err = tr.AddSymLink("/etc/alternatives/ksh", "/bin/ksh93") + require.NoError(t, err) + + _, err = tr.AddSymLink("/usr/bin/ksh", "/etc/alternatives/ksh") + require.NoError(t, err) + + exists, resolution, err := tr.File("/usr/local/bin/ksh", FollowBasenameLinks) + require.NoError(t, err) + assert.True(t, exists) + assert.True(t, resolution.HasReference()) + assert.Equal(t, *actualRef, *resolution.Reference) +} + func TestFileTree_AllFiles(t *testing.T) { tr := New() From c032eae2efe5947117bcd4351cbd4af8e03c438e Mon Sep 17 00:00:00 2001 From: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Date: Wed, 22 Feb 2023 13:59:48 -0500 Subject: [PATCH 10/23] Link Detection Stack Depth FailSafe (#159) * test: add failing test for cycle case Signed-off-by: Christopher Phillips * test: test updates sym links Signed-off-by: Christopher Phillips * change the filetree recursive pathset to represent open calls Signed-off-by: Alex Goodman * add another cycle test Signed-off-by: Alex Goodman * change filetree attempting path set to counters Signed-off-by: Alex Goodman * remove comment Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman * feat: decrement stack depth Signed-off-by: Christopher Phillips * test: remove old wip test name Signed-off-by: Christopher Phillips * chore: style updates Signed-off-by: Christopher Phillips * feat: move maxLinkDepth decrement to inside ancestor loop Signed-off-by: Christopher Phillips * feat: move maxLinkDepth decrement into resolveNodeLinks loop Signed-off-by: Christopher Phillips * feat: move detection to top and write minimal test case Signed-off-by: Christopher Phillips * test: update linkResolution test to use internal value Signed-off-by: Christopher Phillips --------- Signed-off-by: Christopher Phillips Signed-off-by: Alex Goodman Co-authored-by: Alex Goodman --- pkg/filetree/filetree.go | 24 +++++++---- pkg/filetree/filetree_test.go | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/pkg/filetree/filetree.go b/pkg/filetree/filetree.go index 141040c8..e2ea64a5 100644 --- a/pkg/filetree/filetree.go +++ b/pkg/filetree/filetree.go @@ -20,6 +20,8 @@ import ( var ErrRemovingRoot = errors.New("cannot remove the root path (`/`) from the FileTree") var ErrLinkCycleDetected = errors.New("cycle during symlink resolution") +var ErrLinkResolutionDepth = errors.New("maximum link resolution stack depth exceeded") +var maxLinkResolutionDepth = 100 // FileTree represents a file/directory Tree type FileTree struct { @@ -213,7 +215,7 @@ func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*nodeAcce var currentNode *nodeAccess var err error if strategy.FollowAncestorLinks { - currentNode, err = t.resolveAncestorLinks(normalizedPath, nil) + currentNode, err = t.resolveAncestorLinks(normalizedPath, nil, maxLinkResolutionDepth) if err != nil { if currentNode != nil { currentNode.RequestPath = normalizedPath @@ -239,7 +241,7 @@ func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*nodeAcce } if strategy.FollowBasenameLinks { - currentNode, err = t.resolveNodeLinks(currentNode, !strategy.DoNotFollowDeadBasenameLinks, nil) + currentNode, err = t.resolveNodeLinks(currentNode, !strategy.DoNotFollowDeadBasenameLinks, nil, maxLinkResolutionDepth) } if currentNode != nil { currentNode.RequestPath = normalizedPath @@ -250,7 +252,7 @@ func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*nodeAcce // return FileNode of the basename in the given path (no resolution is done at or past the basename). Note: it is // assumed that the given path has already been normalized. -func (t *FileTree) resolveAncestorLinks(path file.Path, currentlyResolvingLinkPaths file.PathCountSet) (*nodeAccess, error) { +func (t *FileTree) resolveAncestorLinks(path file.Path, currentlyResolvingLinkPaths file.PathCountSet, maxLinkDepth int) (*nodeAccess, error) { // performance optimization... see if there is a node at the path (as if it is a real path). If so, // use it, otherwise, continue with ancestor resolution currentNodeAccess, err := t.node(path, linkResolutionStrategy{}) @@ -306,7 +308,7 @@ func (t *FileTree) resolveAncestorLinks(path file.Path, currentlyResolvingLinkPa // links until the next Node is resolved (or not). isLastPart := idx == len(pathParts)-1 if !isLastPart && currentNodeAccess.FileNode.IsLink() { - currentNodeAccess, err = t.resolveNodeLinks(currentNodeAccess, true, currentlyResolvingLinkPaths) + currentNodeAccess, err = t.resolveNodeLinks(currentNodeAccess, true, currentlyResolvingLinkPaths, maxLinkDepth) if err != nil { // only expected to happen on cycles return currentNodeAccess, err @@ -325,7 +327,7 @@ func (t *FileTree) resolveAncestorLinks(path file.Path, currentlyResolvingLinkPa // resolveNodeLinks takes the given FileNode and resolves all links at the base of the real path for the node (this implies // that NO ancestors are considered). // nolint: funlen -func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, currentlyResolvingLinkPaths file.PathCountSet) (*nodeAccess, error) { +func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, currentlyResolvingLinkPaths file.PathCountSet, maxLinkDepth int) (*nodeAccess, error) { if n == nil { return nil, fmt.Errorf("cannot resolve links with nil Node given") } @@ -341,7 +343,6 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, var lastNode *nodeAccess var nodePath []nodeAccess var nextPath file.Path - currentNodeAccess := n // keep resolving links until a regular file or directory is found. @@ -350,6 +351,13 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, realPathsVisited := strset.New() var err error for { + // we need to short-circuit link resolution that never resolves (depth) due to a cycle referencing + // maxLinkDepth is counted across all calls to resolveAncestorLinks and resolveNodeLinks + maxLinkDepth-- + if maxLinkDepth < 1 { + return nil, ErrLinkResolutionDepth + } + nodePath = append(nodePath, *currentNodeAccess) // if there is no next path, return this reference (dead link) @@ -390,10 +398,10 @@ func (t *FileTree) resolveNodeLinks(n *nodeAccess, followDeadBasenameLinks bool, return nil, ErrLinkCycleDetected } - // get the next Node (based on the next path)a + // get the next Node (based on the next path) // attempted paths maintains state across calls to resolveAncestorLinks currentlyResolvingLinkPaths.Add(nextPath) - currentNodeAccess, err = t.resolveAncestorLinks(nextPath, currentlyResolvingLinkPaths) + currentNodeAccess, err = t.resolveAncestorLinks(nextPath, currentlyResolvingLinkPaths, maxLinkDepth) if err != nil { if currentNodeAccess != nil { currentNodeAccess.LeafLinkResolution = append(currentNodeAccess.LeafLinkResolution, nodePath...) diff --git a/pkg/filetree/filetree_test.go b/pkg/filetree/filetree_test.go index fff09810..a6aac4a7 100644 --- a/pkg/filetree/filetree_test.go +++ b/pkg/filetree/filetree_test.go @@ -2,6 +2,7 @@ package filetree import ( "errors" + "fmt" "github.com/scylladb/go-set/strset" "testing" @@ -1136,11 +1137,85 @@ func TestFileTree_File_ResolutionWithMultipleAncestorResolutionsForSameNode(t *t exists, resolution, err := tr.File("/usr/local/bin/ksh", FollowBasenameLinks) require.NoError(t, err) + /* + /usr/bin/ksh93 <-- Real file + /bin -> /usr/bin + /usr/bin/ksh -> /etc/alternatives/ksh + /etc/alternatives/ksh -> /bin/ksh93 + */ + + // ls -al /usr/local/bin/ksh + // /usr/local/bin/ksh -> /bin/ksh + + // ls -al /bin/ksh + // /bin/ksh -> /etc/alternatives/ksh + + // ls -al /etc/alternatives/ksh + // /etc/alternatives/ksh -> /bin/ksh93 + + // the test.... we should not stop when a small cycle for /usr/bin is done more than once + _, _, err = tr.File("/usr/local/bin/ksh", FollowBasenameLinks) + require.NoError(t, err) assert.True(t, exists) assert.True(t, resolution.HasReference()) assert.Equal(t, *actualRef, *resolution.Reference) } +func TestFileTreeMaxLinkDepth(t *testing.T) { + tr := New() + _, err := tr.AddFile("/usr/bin/ksh93") + require.NoError(t, err) + + _, err = tr.AddSymLink("/usr/local/bin/ksh", "/bin/ksh") + require.NoError(t, err) + + _, err = tr.AddSymLink("/bin", "/usr/bin") + require.NoError(t, err) + + _, err = tr.AddSymLink("/etc/alternatives/ksh", "/bin/ksh93") + require.NoError(t, err) + + _, err = tr.AddSymLink("/usr/bin/ksh", "/etc/alternatives/ksh") + require.NoError(t, err) + rs := linkResolutionStrategy{} + + currentNode, err := tr.node("/usr/local/bin/ksh", rs) + require.NoError(t, err) + + _, err = tr.resolveNodeLinks(currentNode, !rs.DoNotFollowDeadBasenameLinks, nil, 2) + require.Error(t, err, "should have gotten an error on resolution of a dead cycle") + // require certain error + if err != ErrLinkResolutionDepth { + t.Fatalf("should have gotten an ErrLinkResolutionDepth resolving a file") + } +} + +func TestFileTree_MaximumLinkResolutionExceeded(t *testing.T) { + tr := New() + ref, err := tr.AddFile("/usr/bin/ksh") + + // we hard code this in filetree to 100 + for i := 0; i < maxLinkResolutionDepth; i++ { + _, err = tr.AddSymLink( + file.Path(fmt.Sprintf("/usr/bin/ksh%d", i)), + file.Path(fmt.Sprintf("/usr/bin/ksh%d", i+1)), + ) + require.NoError(t, err) + } + + // explicitly link 10 to ksh + _, err = tr.AddSymLink("/usr/bin/ksh100", "/usr/bin/ksh") + + // we should be short-circuiting the resolution of multiple SymLinks > 100 + _, _, err = tr.File("/usr/bin/ksh0", FollowBasenameLinks) + require.Error(t, err) + + b, fr, err := tr.File("/usr/bin/ksh90", FollowBasenameLinks) + require.NoError(t, err) + assert.True(t, b) + assert.Equal(t, ref, fr.Reference) +} + func TestFileTree_AllFiles(t *testing.T) { tr := New() From dbb5dc0081eacd7f447a567ee17573ab577c3b7f Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Wed, 1 Mar 2023 14:17:55 -0500 Subject: [PATCH 11/23] fix: thread safety for progress (#162) --- go.mod | 2 +- go.sum | 4 ++-- pkg/image/docker/pull_status.go | 10 +++++----- pkg/image/image.go | 10 +++++----- pkg/image/layer.go | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index e50a4d1c..03fd095a 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sylabs/sif/v2 v2.8.1 github.com/sylabs/squashfs v0.6.1 github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d - github.com/wagoodman/go-progress v0.0.0-20200621122631-1a2120f0695a + github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd ) diff --git a/go.sum b/go.sum index fbbc8b4a..562cb7ce 100644 --- a/go.sum +++ b/go.sum @@ -800,8 +800,8 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d h1:KOxOL6qpmqwoPloNwi+CEgc1ayjHNOFNrvoOmeDOjDg= github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d/go.mod h1:JPirS5jde/CF5qIjcK4WX+eQmKXdPc6vcZkJ/P0hfPw= -github.com/wagoodman/go-progress v0.0.0-20200621122631-1a2120f0695a h1:lV3ioFpbqvfZ1bXSQfloLWzom1OPU/5UjyU0wmBlkNc= -github.com/wagoodman/go-progress v0.0.0-20200621122631-1a2120f0695a/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= +github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 h1:lwgTsTy18nYqASnH58qyfRW/ldj7Gt2zzBvgYPzdA4s= +github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/pkg/image/docker/pull_status.go b/pkg/image/docker/pull_status.go index 76272ec9..34a889b4 100644 --- a/pkg/image/docker/pull_status.go +++ b/pkg/image/docker/pull_status.go @@ -122,17 +122,17 @@ func (p *PullStatus) onEvent(event *pullEvent) { if currentPhase >= AlreadyExistsPhase { phaseProgress.SetCompleted() } else { - phaseProgress.N = int64(event.ProgressDetail.Current) - phaseProgress.Total = int64(event.ProgressDetail.Total) + phaseProgress.Set(int64(event.ProgressDetail.Current)) + phaseProgress.SetTotal(int64(event.ProgressDetail.Total)) } if currentPhase == DownloadingPhase { dl := p.downloadProgress[layer] - dl.N = int64(event.ProgressDetail.Current) - dl.Total = int64(event.ProgressDetail.Total) + dl.Set(int64(event.ProgressDetail.Current)) + dl.SetTotal(int64(event.ProgressDetail.Total)) } else if currentPhase >= DownloadCompletePhase { dl := p.downloadProgress[layer] - dl.N = dl.Total + dl.Set(dl.Size()) dl.SetCompleted() } } diff --git a/pkg/image/image.go b/pkg/image/image.go index 9798197b..b8a34b5c 100644 --- a/pkg/image/image.go +++ b/pkg/image/image.go @@ -155,10 +155,10 @@ func (i *Image) IDs() []string { } func (i *Image) trackReadProgress(metadata Metadata) *progress.Manual { - prog := &progress.Manual{ + prog := progress.NewManual( // x2 for read and squash of each layer - Total: int64(len(metadata.Config.RootFS.DiffIDs) * 2), - } + int64(len(metadata.Config.RootFS.DiffIDs) * 2), + ) bus.Publish(partybus.Event{ Type: event.ReadImage, @@ -217,7 +217,7 @@ func (i *Image) Read() error { i.Metadata.Size += layer.Metadata.Size layers = append(layers, layer) - readProg.N++ + readProg.Increment() } i.Layers = layers @@ -257,7 +257,7 @@ func (i *Image) squash(prog *progress.Manual) error { layer.SquashedSearchContext = filetree.NewSearchContext(layer.SquashedTree, layer.fileCatalog.Index) lastSquashTree = squashedTree - prog.N++ + prog.Increment() } prog.SetCompleted() diff --git a/pkg/image/layer.go b/pkg/image/layer.go index f0b1267d..df202cd4 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -240,7 +240,7 @@ func layerTarIndexer(ft filetree.Writer, fileCatalog *FileCatalog, size *int64, fileCatalog.addImageReferences(ref.ID(), layerRef, index.Open) if monitor != nil { - monitor.N++ + monitor.Increment() } return nil } @@ -285,7 +285,7 @@ func squashfsVisitor(ft filetree.Writer, fileCatalog *FileCatalog, size *int64, return r }) - monitor.N++ + monitor.Increment() return nil } } From 9425f2afc33b27671fd0d8f3be8f212a951df920 Mon Sep 17 00:00:00 2001 From: Kushal Beniwal Date: Fri, 17 Mar 2023 19:17:07 +0530 Subject: [PATCH 12/23] chore: fix typo (#163) Signed-off-by: Kushal Beniwal --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index 4bd93625..deaacb5a 100644 --- a/client.go +++ b/client.go @@ -146,7 +146,7 @@ func selectImageProvider(imgStr string, source image.Source, cfg config) (image. } provider = sif.NewProviderFromPath(imgStr, tempDirGenerator) default: - return nil, fmt.Errorf("unable determine image source") + return nil, fmt.Errorf("unable to determine image source") } return provider, nil } From 7aad86f30be860b79d09093ed8cffe1dfde6f915 Mon Sep 17 00:00:00 2001 From: Ville Aikas <11279988+vaikas@users.noreply.github.com> Date: Thu, 23 Mar 2023 06:39:31 -0700 Subject: [PATCH 13/23] Add ability to explicitly specify an Authenticator as well as Keychain. (#164) * Add Authenticator as an explicit option. Signed-off-by: Ville Aikas * wire in keychain also. Signed-off-by: Ville Aikas --------- Signed-off-by: Ville Aikas --- pkg/image/oci/registry_provider.go | 10 +++++++--- pkg/image/registry_credentials.go | 9 ++++++++- pkg/image/registry_credentials_test.go | 13 +++++++++++++ pkg/image/registry_options.go | 3 +++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pkg/image/oci/registry_provider.go b/pkg/image/oci/registry_provider.go index 52128a62..df49ff74 100644 --- a/pkg/image/oci/registry_provider.go +++ b/pkg/image/oci/registry_provider.go @@ -111,11 +111,15 @@ func prepareRemoteOptions(ctx context.Context, ref name.Reference, registryOptio } // note: the authn.Authenticator and authn.Keychain options are mutually exclusive, only one may be provided. - // If no explicit authenticator can be found, then fallback to the keychain. + // If no explicit authenticator can be found, check if explicit Keychain has + // been provided, and if not, then fallback to the default keychain. authenticator := registryOptions.Authenticator(ref.Context().RegistryStr()) - if authenticator != nil { + switch { + case authenticator != nil: options = append(options, remote.WithAuth(authenticator)) - } else { + case registryOptions.Keychain != nil: + options = append(options, remote.WithAuthFromKeychain(registryOptions.Keychain)) + default: // use the Keychain specified from a docker config file. log.Debugf("no registry credentials configured, using the default keychain") options = append(options, remote.WithAuthFromKeychain(authn.DefaultKeychain)) diff --git a/pkg/image/registry_credentials.go b/pkg/image/registry_credentials.go index 6c8fa034..890cf9f0 100644 --- a/pkg/image/registry_credentials.go +++ b/pkg/image/registry_credentials.go @@ -6,18 +6,25 @@ import ( ) // RegistryCredentials contains any information necessary to authenticate against an OCI-distribution-compliant -// registry (either with basic auth or bearer token). Note: only valid for the OCI registry provider. +// registry (either with basic auth, or bearer token, or ggcr authenticator +// implementation). Note: only valid for the OCI registry provider. type RegistryCredentials struct { Authority string Username string Password string Token string + // Explicitly pass in the Authenticator, allowing for things like + // k8schain to be passed through explicitly. + Authenticator authn.Authenticator } // authenticator returns an authn.Authenticator for the given credentials. // Authentication methods are attempted in the following order until a viable method is found: (1) basic auth, // (2) bearer token. If no viable authentication method is found, authenticator returns nil. func (c RegistryCredentials) authenticator() authn.Authenticator { + if c.Authenticator != nil { + return c.Authenticator + } if c.Username != "" && c.Password != "" { log.Debugf("using basic auth for registry %q", c.Authority) return &authn.Basic{ diff --git a/pkg/image/registry_credentials_test.go b/pkg/image/registry_credentials_test.go index d5850490..0552c524 100644 --- a/pkg/image/registry_credentials_test.go +++ b/pkg/image/registry_credentials_test.go @@ -29,6 +29,19 @@ func TestRegistryCredentials_Authenticator(t *testing.T) { Password: examplePassword, }), }, + { + name: "basic auth with authn.Authenticator", + credentials: RegistryCredentials{ + Authenticator: &authn.Basic{ + Username: exampleUsername, + Password: examplePassword, + }, + }, + authenticatorAssertion: basicAuth(authn.Basic{ + Username: exampleUsername, + Password: examplePassword, + }), + }, { name: "basic auth without username", credentials: RegistryCredentials{ diff --git a/pkg/image/registry_options.go b/pkg/image/registry_options.go index 41e98113..5d3e7ba0 100644 --- a/pkg/image/registry_options.go +++ b/pkg/image/registry_options.go @@ -6,10 +6,13 @@ import ( ) // RegistryOptions for the OCI registry provider. +// If no specific Credential is found in the RegistryCredentials, will check +// for Keychain, and barring that will use Default Keychain. type RegistryOptions struct { InsecureSkipTLSVerify bool InsecureUseHTTP bool Credentials []RegistryCredentials + Keychain authn.Keychain Platform string } From 19345b8584e2d4608b1ceaa076df733a60c545f4 Mon Sep 17 00:00:00 2001 From: JB <74689600+slimdevl@users.noreply.github.com> Date: Thu, 23 Mar 2023 11:25:29 -0300 Subject: [PATCH 14/23] fix directory leak leading to out of disk (#161) * fix tmpDirGenerator chain of responsibility associated with https://github.com/anchore/stereoscope/issues/132 Signed-off-by: Joseph Barnett Signed-off-by: jb@slim.ai * reduce log message to debug Signed-off-by: Joseph Barnett Signed-off-by: jb@slim.ai * restore global cleanup function Signed-off-by: Alex Goodman --------- Signed-off-by: Joseph Barnett Signed-off-by: jb@slim.ai Signed-off-by: Alex Goodman Co-authored-by: Alex Goodman --- client.go | 4 ++-- pkg/image/docker/tarball_provider.go | 2 +- pkg/image/image.go | 28 +++++++++++++++++++++------- pkg/image/image_test.go | 7 ++++--- pkg/image/oci/directory_provider.go | 2 +- pkg/image/oci/registry_provider.go | 2 +- pkg/image/sif/provider.go | 2 +- 7 files changed, 31 insertions(+), 16 deletions(-) diff --git a/client.go b/client.go index deaacb5a..d67d7a46 100644 --- a/client.go +++ b/client.go @@ -169,8 +169,8 @@ func SetBus(b *partybus.Bus) { bus.SetPublisher(b) } -// Cleanup deletes all directories created by stereoscope calls. Note: please use image.Image.Cleanup() over this -// function when possible. +// Cleanup deletes all directories created by stereoscope calls. +// Deprecated: please use image.Image.Cleanup() over this. func Cleanup() { if err := rootTempDirGenerator.Cleanup(); err != nil { log.Errorf("failed to cleanup tempdir root: %w", err) diff --git a/pkg/image/docker/tarball_provider.go b/pkg/image/docker/tarball_provider.go index 663f809e..4001fdf1 100644 --- a/pkg/image/docker/tarball_provider.go +++ b/pkg/image/docker/tarball_provider.go @@ -82,5 +82,5 @@ func (p *TarballImageProvider) Provide(_ context.Context, userMetadata ...image. return nil, err } - return image.New(img, contentTempDir, metadata...), nil + return image.New(img, p.tmpDirGen, contentTempDir, metadata...), nil } diff --git a/pkg/image/image.go b/pkg/image/image.go index b8a34b5c..17776905 100644 --- a/pkg/image/image.go +++ b/pkg/image/image.go @@ -6,6 +6,7 @@ import ( "io" "os" + "github.com/hashicorp/go-multierror" "github.com/scylladb/go-set/strset" "github.com/anchore/stereoscope/internal/bus" @@ -23,6 +24,9 @@ import ( type Image struct { // image is the raw image metadata and content provider from the GCR lib image v1.Image + // tmpDirGen is a dir generator used by Providers. Multiple directories may + // be created and cleanup must use this to prevent polluting the disk + tmpDirGen *file.TempDirGenerator // contentCacheDir is where all layer tar cache is stored. contentCacheDir string // Metadata contains select image attributes @@ -131,14 +135,15 @@ func WithOS(o string) AdditionalMetadata { // NewImage provides a new (unread) image object. // Deprecated: use New() instead -func NewImage(image v1.Image, contentCacheDir string, additionalMetadata ...AdditionalMetadata) *Image { - return New(image, contentCacheDir, additionalMetadata...) +func NewImage(image v1.Image, tmpDirGen *file.TempDirGenerator, contentCacheDir string, additionalMetadata ...AdditionalMetadata) *Image { + return New(image, tmpDirGen, contentCacheDir, additionalMetadata...) } // New provides a new (unread) image object. -func New(image v1.Image, contentCacheDir string, additionalMetadata ...AdditionalMetadata) *Image { +func New(image v1.Image, tmpDirGen *file.TempDirGenerator, contentCacheDir string, additionalMetadata ...AdditionalMetadata) *Image { imgObj := &Image{ image: image, + tmpDirGen: tmpDirGen, contentCacheDir: contentCacheDir, overrideMetadata: additionalMetadata, } @@ -341,10 +346,19 @@ func (i *Image) Cleanup() error { if i == nil { return nil } - if i.contentCacheDir != "" { - if err := os.RemoveAll(i.contentCacheDir); err != nil { - return err + var errs error + if i.tmpDirGen != nil { + if err := i.tmpDirGen.Cleanup(); err != nil { + errs = multierror.Append(errs, err) + } + + if i.contentCacheDir != "" { + if _, err := os.Stat(i.contentCacheDir); !os.IsNotExist(err) { + if err := os.RemoveAll(i.contentCacheDir); err != nil { + errs = multierror.Append(errs, err) + } + } } } - return nil + return errs } diff --git a/pkg/image/image_test.go b/pkg/image/image_test.go index 3a44a626..4d2b2064 100644 --- a/pkg/image/image_test.go +++ b/pkg/image/image_test.go @@ -3,11 +3,12 @@ package image import ( "crypto/sha256" "fmt" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "os" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/go-containerregistry/pkg/name" ) @@ -98,7 +99,7 @@ func TestImageAdditionalMetadata(t *testing.T) { os.Remove(tempFile.Name()) }) - img := New(nil, tempFile.Name(), test.options...) + img := New(nil, nil, tempFile.Name(), test.options...) err = img.applyOverrideMetadata() if err != nil { diff --git a/pkg/image/oci/directory_provider.go b/pkg/image/oci/directory_provider.go index 08220cb5..f66d3032 100644 --- a/pkg/image/oci/directory_provider.go +++ b/pkg/image/oci/directory_provider.go @@ -69,5 +69,5 @@ func (p *DirectoryImageProvider) Provide(_ context.Context, userMetadata ...imag return nil, err } - return image.New(img, contentTempDir, metadata...), nil + return image.New(img, p.tmpDirGen, contentTempDir, metadata...), nil } diff --git a/pkg/image/oci/registry_provider.go b/pkg/image/oci/registry_provider.go index df49ff74..7578c9f8 100644 --- a/pkg/image/oci/registry_provider.go +++ b/pkg/image/oci/registry_provider.go @@ -80,7 +80,7 @@ func (p *RegistryImageProvider) Provide(ctx context.Context, userMetadata ...ima // apply user-supplied metadata last to override any default behavior metadata = append(metadata, userMetadata...) - return image.New(img, imageTempDir, metadata...), nil + return image.New(img, p.tmpDirGen, imageTempDir, metadata...), nil } func prepareReferenceOptions(registryOptions image.RegistryOptions) []name.Option { diff --git a/pkg/image/sif/provider.go b/pkg/image/sif/provider.go index e30f2033..c10b67e9 100644 --- a/pkg/image/sif/provider.go +++ b/pkg/image/sif/provider.go @@ -51,5 +51,5 @@ func (p *SingularityImageProvider) Provide(ctx context.Context, userMetadata ... } metadata = append(metadata, userMetadata...) - return image.New(ui, contentCacheDir, metadata...), nil + return image.New(ui, p.tmpDirGen, contentCacheDir, metadata...), nil } From b7456e627b6eff2cdacd45b3c3ea48ae45c7a82f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 23 Mar 2023 12:15:19 -0400 Subject: [PATCH 15/23] Set the default platform for select sources based on host arch (#152) * set the default platform for select sources based on host arch Signed-off-by: Alex Goodman * decompose into smaller function and add tests for setting default platform Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- client.go | 44 ++++++++---- client_test.go | 191 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 13 deletions(-) create mode 100644 client_test.go diff --git a/client.go b/client.go index d67d7a46..78592fb5 100644 --- a/client.go +++ b/client.go @@ -3,6 +3,7 @@ package stereoscope import ( "context" "fmt" + "runtime" "github.com/anchore/go-logger" @@ -101,13 +102,13 @@ func GetImageFromSource(ctx context.Context, imgStr string, source image.Source, func selectImageProvider(imgStr string, source image.Source, cfg config) (image.Provider, error) { var provider image.Provider tempDirGenerator := rootTempDirGenerator.NewGenerator() - platformSelectionUnsupported := fmt.Errorf("specified platform=%q however image source=%q does not support selecting platform", cfg.Platform.String(), source.String()) + + if err := setPlatform(source, &cfg, runtime.GOARCH); err != nil { + return nil, err + } switch source { case image.DockerTarballSource: - if cfg.Platform != nil { - return nil, platformSelectionUnsupported - } // note: the imgStr is the path on disk to the tar file provider = docker.NewProviderFromTarball(imgStr, tempDirGenerator) case image.DockerDaemonSource: @@ -129,21 +130,12 @@ func selectImageProvider(imgStr string, source image.Source, cfg config) (image. return nil, err } case image.OciDirectorySource: - if cfg.Platform != nil { - return nil, platformSelectionUnsupported - } provider = oci.NewProviderFromPath(imgStr, tempDirGenerator) case image.OciTarballSource: - if cfg.Platform != nil { - return nil, platformSelectionUnsupported - } provider = oci.NewProviderFromTarball(imgStr, tempDirGenerator) case image.OciRegistrySource: provider = oci.NewProviderFromRegistry(imgStr, tempDirGenerator, cfg.Registry, cfg.Platform) case image.SingularitySource: - if cfg.Platform != nil { - return nil, platformSelectionUnsupported - } provider = sif.NewProviderFromPath(imgStr, tempDirGenerator) default: return nil, fmt.Errorf("unable to determine image source") @@ -151,6 +143,32 @@ func selectImageProvider(imgStr string, source image.Source, cfg config) (image. return provider, nil } +func setPlatform(source image.Source, cfg *config, defaultArch string) error { + // we should override the platform based on the host architecture if the user did not specify a platform + // see https://github.com/anchore/stereoscope/issues/149 for more details + defaultPlatform, err := image.NewPlatform(defaultArch) + if err != nil { + log.WithFields("error", err).Warnf("unable to set default platform to %q", runtime.GOARCH) + defaultPlatform = nil + } + + switch source { + case image.DockerTarballSource, image.OciDirectorySource, image.OciTarballSource, image.SingularitySource: + if cfg.Platform != nil { + return fmt.Errorf("specified platform=%q however image source=%q does not support selecting platform", cfg.Platform.String(), source.String()) + } + + case image.DockerDaemonSource, image.PodmanDaemonSource, image.OciRegistrySource: + if cfg.Platform == nil { + cfg.Platform = defaultPlatform + } + + default: + return fmt.Errorf("unable to determine image source to select platform") + } + return nil +} + // GetImage parses the user provided image string and provides an image object; // note: the source where the image should be referenced from is automatically inferred. func GetImage(ctx context.Context, userStr string, options ...Option) (*image.Image, error) { diff --git a/client_test.go b/client_test.go new file mode 100644 index 00000000..a85708e6 --- /dev/null +++ b/client_test.go @@ -0,0 +1,191 @@ +package stereoscope + +import ( + "github.com/anchore/stereoscope/pkg/image" + "github.com/scylladb/go-set/i8set" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func Test_setPlatform(t *testing.T) { + + expectedSources := i8set.New() + for _, s := range image.AllSources { + expectedSources.Add(int8(s)) + } + actualSources := i8set.New() + + tests := []struct { + name string + source image.Source + defaultArch string + initialPlatform *image.Platform + wantPlatform *image.Platform + wantErr require.ErrorAssertionFunc + }{ + // allow defaults --------------------------------------------------------- + { + name: "docker daemon", + source: image.DockerDaemonSource, + defaultArch: "amd64", + initialPlatform: nil, + wantPlatform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + { + name: "docker daemon (do not override)", + source: image.DockerDaemonSource, + defaultArch: "amd64", + initialPlatform: &image.Platform{ + Architecture: "arm64", // not different than default arch + OS: "linux", + }, + wantPlatform: &image.Platform{ + Architecture: "arm64", // note: did not change + OS: "linux", + }, + }, + { + name: "podman daemon", + source: image.PodmanDaemonSource, + defaultArch: "amd64", + initialPlatform: nil, + wantPlatform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + { + name: "podman daemon (do not override)", + source: image.PodmanDaemonSource, + defaultArch: "amd64", + initialPlatform: &image.Platform{ + Architecture: "arm64", // not different than default arch + OS: "linux", + }, + wantPlatform: &image.Platform{ + Architecture: "arm64", // note: did not change + OS: "linux", + }, + }, + { + name: "OCI registry", + source: image.OciRegistrySource, + defaultArch: "amd64", + initialPlatform: nil, + wantPlatform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + { + name: "OCI registry (do not override)", + source: image.OciRegistrySource, + defaultArch: "amd64", + initialPlatform: &image.Platform{ + Architecture: "arm64", // not different than default arch + OS: "linux", + }, + wantPlatform: &image.Platform{ + Architecture: "arm64", // note: did not change + OS: "linux", + }, + }, + // disallow defaults --------------------------------------------------------- + { + name: "docker tarball", + source: image.DockerTarballSource, + defaultArch: "amd64", + initialPlatform: nil, + wantPlatform: nil, + }, + { + name: "docker tarball (override fails)", + source: image.DockerTarballSource, + defaultArch: "amd64", + initialPlatform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + wantErr: require.Error, + }, + { + name: "OCI dir", + source: image.OciDirectorySource, + defaultArch: "amd64", + initialPlatform: nil, + wantPlatform: nil, + }, + { + name: "OCI dir (override fails)", + source: image.OciDirectorySource, + defaultArch: "amd64", + initialPlatform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + wantErr: require.Error, + }, + { + name: "OCI tarball", + source: image.OciTarballSource, + defaultArch: "amd64", + initialPlatform: nil, + wantPlatform: nil, + }, + { + name: "OCI tarball (override fails)", + source: image.OciTarballSource, + defaultArch: "amd64", + initialPlatform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + wantErr: require.Error, + }, + { + name: "singularity", + source: image.SingularitySource, + defaultArch: "amd64", + initialPlatform: nil, + wantPlatform: nil, + }, + { + name: "singularity (override fails)", + source: image.SingularitySource, + defaultArch: "amd64", + initialPlatform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + + actualSources.Add(int8(tt.source)) + cfg := config{ + Platform: tt.initialPlatform, + } + err := setPlatform(tt.source, &cfg, tt.defaultArch) + tt.wantErr(t, err) + if err != nil { + return + } + + assert.Equal(t, tt.wantPlatform, cfg.Platform) + }) + } + + diff := i8set.Difference(expectedSources, actualSources) + if !diff.IsEmpty() { + t.Errorf("missing test cases for sources: %v", diff.List()) + } +} From d9a9a5160e9c4d9e21c71dac41486b2f80778e44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 10:32:06 -0400 Subject: [PATCH 16/23] Bump github.com/docker/docker (#167) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.12+incompatible to 20.10.24+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v20.10.12...v20.10.24) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 03fd095a..a7efe0fb 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.0.2 github.com/containerd/containerd v1.6.18 github.com/docker/cli v20.10.12+incompatible - github.com/docker/docker v20.10.12+incompatible + github.com/docker/docker v20.10.24+incompatible github.com/gabriel-vasile/mimetype v1.4.0 github.com/go-test/deep v1.0.8 github.com/google/go-cmp v0.5.8 diff --git a/go.sum b/go.sum index 562cb7ce..cabf030e 100644 --- a/go.sum +++ b/go.sum @@ -303,8 +303,8 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY= github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= -github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= +github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= @@ -470,7 +470,6 @@ github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1K github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= From 6c1941dbaeb1d1d65e77bbda146f65dd5a1e469e Mon Sep 17 00:00:00 2001 From: Adrian Wobito Date: Tue, 11 Apr 2023 15:53:24 -0400 Subject: [PATCH 17/23] Preserve time and expose link strategy (#166) * bind file times to metadata Signed-off-by: Adrian Wobito * expose link strategy to walkConditions Signed-off-by: Adrian Wobito * update: test times Signed-off-by: Adrian Wobito * update: link options api Signed-off-by: Adrian Wobito * stub mod time for dynamic tar test fixtures Signed-off-by: Alex Goodman * set mtime on tar fixture explicitly Signed-off-by: Alex Goodman * always interpret tar header timestamps as UTC Signed-off-by: Alex Goodman * interpret all file metadata timestamps as UTC Signed-off-by: Alex Goodman --------- Signed-off-by: Adrian Wobito Signed-off-by: Alex Goodman Co-authored-by: Alex Goodman --- pkg/file/metadata.go | 23 +++++++++++------ pkg/file/metadata_test.go | 25 ++++++++++++------- pkg/file/tarutil_test.go | 7 ++++++ .../test-fixtures/generators/fixture-1.sh | 2 +- pkg/filetree/depth_first_path_walker.go | 20 ++++++++++----- pkg/filetree/depth_first_path_walker_test.go | 5 ++-- pkg/filetree/filetree_test.go | 3 ++- pkg/filetree/search_test.go | 3 ++- pkg/image/file_catalog_test.go | 19 +++++++------- 9 files changed, 71 insertions(+), 36 deletions(-) diff --git a/pkg/file/metadata.go b/pkg/file/metadata.go index 990e7a4f..90c6d764 100644 --- a/pkg/file/metadata.go +++ b/pkg/file/metadata.go @@ -6,6 +6,7 @@ import ( "os" "path" "path/filepath" + "time" "github.com/anchore/stereoscope/internal/log" @@ -19,13 +20,16 @@ type Metadata struct { // LinkDestination is populated only for hardlinks / symlinks, can be an absolute or relative LinkDestination string // Size of the file in bytes - Size int64 - UserID int - GroupID int - Type Type - IsDir bool - Mode os.FileMode - MIMEType string + Size int64 + UserID int + GroupID int + Type Type + IsDir bool + Mode os.FileMode + MIMEType string + ModTime time.Time + AccessTime time.Time + ChangeTime time.Time } func NewMetadata(header tar.Header, content io.Reader) Metadata { @@ -38,6 +42,9 @@ func NewMetadata(header tar.Header, content io.Reader) Metadata { UserID: header.Uid, GroupID: header.Gid, IsDir: header.FileInfo().IsDir(), + ModTime: header.ModTime.UTC(), + AccessTime: header.AccessTime.UTC(), + ChangeTime: header.ChangeTime.UTC(), MIMEType: MIMEType(content), } } @@ -79,6 +86,7 @@ func NewMetadataFromSquashFSFile(path string, f *squashfs.File) (Metadata, error Size: fi.Size(), IsDir: f.IsDir(), Mode: fi.Mode(), + ModTime: fi.ModTime().UTC(), Type: ty, } @@ -121,5 +129,6 @@ func NewMetadataFromPath(path string, info os.FileInfo) Metadata { Size: info.Size(), MIMEType: mimeType, IsDir: info.IsDir(), + ModTime: info.ModTime().UTC(), } } diff --git a/pkg/file/metadata_test.go b/pkg/file/metadata_test.go index aa60bc95..7d043f35 100644 --- a/pkg/file/metadata_test.go +++ b/pkg/file/metadata_test.go @@ -4,12 +4,14 @@ package file import ( - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "io" "os" "strings" "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/go-test/deep" ) @@ -18,13 +20,13 @@ func TestFileMetadataFromTar(t *testing.T) { tarReader := getTarFixture(t, "fixture-1") expected := []Metadata{ - {Path: "/path", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch/one", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o700, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch/one/file-1.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o700, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, - {Path: "/path/branch/two", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: ""}, - {Path: "/path/branch/two/file-2.txt", Type: TypeRegular, LinkDestination: "", Size: 12, Mode: 0o755, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, - {Path: "/path/file-3.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o664, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain"}, + {Path: "/path", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, + {Path: "/path/branch", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, + {Path: "/path/branch/one", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o700, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, + {Path: "/path/branch/one/file-1.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o700, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, + {Path: "/path/branch/two", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, + {Path: "/path/branch/two/file-2.txt", Type: TypeRegular, LinkDestination: "", Size: 12, Mode: 0o755, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, + {Path: "/path/file-3.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o664, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, } var actual []Metadata @@ -33,6 +35,11 @@ func TestFileMetadataFromTar(t *testing.T) { if strings.HasSuffix(entry.Header.Name, ".txt") { contents = strings.NewReader("#!/usr/bin/env bash\necho 'awesome script'") } + + entry.Header.ModTime = time.Time{} + entry.Header.ChangeTime = time.Time{} + entry.Header.AccessTime = time.Time{} + actual = append(actual, NewMetadata(entry.Header, contents)) return nil } diff --git a/pkg/file/tarutil_test.go b/pkg/file/tarutil_test.go index 2103f242..e9488c04 100644 --- a/pkg/file/tarutil_test.go +++ b/pkg/file/tarutil_test.go @@ -12,6 +12,7 @@ import ( "path" "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -71,6 +72,9 @@ func TestMetadataFromTar(t *testing.T) { IsDir: false, Mode: 0x1ed, MIMEType: "application/octet-stream", + ModTime: time.Date(2019, time.September, 16, 0, 0, 0, 0, time.UTC), + AccessTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + ChangeTime: time.Time{}, }, }, { @@ -86,6 +90,9 @@ func TestMetadataFromTar(t *testing.T) { IsDir: true, Mode: 0x800001ed, MIMEType: "", + ModTime: time.Date(2019, time.September, 16, 0, 0, 0, 0, time.UTC), + AccessTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + ChangeTime: time.Time{}, }, }, } diff --git a/pkg/file/test-fixtures/generators/fixture-1.sh b/pkg/file/test-fixtures/generators/fixture-1.sh index ac79a48c..09349ed7 100755 --- a/pkg/file/test-fixtures/generators/fixture-1.sh +++ b/pkg/file/test-fixtures/generators/fixture-1.sh @@ -34,7 +34,7 @@ pushd /tmp/stereoscope # tar + owner # note: sort by name is important for test file header entry ordering - tar --sort=name --owner=1337 --group=5432 -cvf "/scratch/${FIXTURE_NAME}" path/ + tar --sort=name --owner=1337 --group=5432 --mtime='UTC 2019-09-16' -cvf "/scratch/${FIXTURE_NAME}" path/ popd EOF diff --git a/pkg/filetree/depth_first_path_walker.go b/pkg/filetree/depth_first_path_walker.go index 71d3d7f4..9b542185 100644 --- a/pkg/filetree/depth_first_path_walker.go +++ b/pkg/filetree/depth_first_path_walker.go @@ -31,6 +31,8 @@ type WalkConditions struct { // Whether we should consider children of this Node to be included in the traversal path. // Return true to traverse children of this Node. ShouldContinueBranch func(file.Path, filenode.FileNode) bool + + LinkOptions []LinkResolutionOption } // DepthFirstPathWalker implements stateful depth-first Tree traversal. @@ -64,17 +66,23 @@ func (w *DepthFirstPathWalker) Walk(from file.Path) (file.Path, *filenode.FileNo err error ) + linkOpts := []LinkResolutionOption{followAncestorLinks} + // Setup link options defaults + if w.conditions.LinkOptions == nil { + linkOpts = []LinkResolutionOption{followAncestorLinks, DoNotFollowDeadBasenameLinks, FollowBasenameLinks} + } + + linkOpts = append(linkOpts, w.conditions.LinkOptions...) + linkStrat := newLinkResolutionStrategy(linkOpts...) + for w.pathStack.Size() > 0 { currentPath = w.pathStack.Pop() - // TODO: should we make these link resolutions configurable so you can observe the links on walk as well? (take link resolution options as a parameter) - currentNode, err = w.tree.node(currentPath, linkResolutionStrategy{ - FollowAncestorLinks: true, - FollowBasenameLinks: true, - DoNotFollowDeadBasenameLinks: true, - }) + + currentNode, err = w.tree.node(currentPath, linkStrat) if err != nil { return "", nil, err } + if !currentNode.HasFileNode() { return "", nil, fmt.Errorf("nil Node at path=%q", currentPath) } diff --git a/pkg/filetree/depth_first_path_walker_test.go b/pkg/filetree/depth_first_path_walker_test.go index 74678709..9db3fd3c 100644 --- a/pkg/filetree/depth_first_path_walker_test.go +++ b/pkg/filetree/depth_first_path_walker_test.go @@ -2,11 +2,12 @@ package filetree import ( "errors" + "strings" + "testing" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/go-test/deep" - "strings" - "testing" ) func dfsTestTree(t *testing.T) (*FileTree, map[string]*file.Reference) { diff --git a/pkg/filetree/filetree_test.go b/pkg/filetree/filetree_test.go index a6aac4a7..178ca174 100644 --- a/pkg/filetree/filetree_test.go +++ b/pkg/filetree/filetree_test.go @@ -3,9 +3,10 @@ package filetree import ( "errors" "fmt" - "github.com/scylladb/go-set/strset" "testing" + "github.com/scylladb/go-set/strset" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" diff --git a/pkg/filetree/search_test.go b/pkg/filetree/search_test.go index 6033646c..73fcc334 100644 --- a/pkg/filetree/search_test.go +++ b/pkg/filetree/search_test.go @@ -2,13 +2,14 @@ package filetree import ( "fmt" + "testing" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) func Test_searchContext_SearchByPath(t *testing.T) { diff --git a/pkg/image/file_catalog_test.go b/pkg/image/file_catalog_test.go index a6100156..bcf6cab4 100644 --- a/pkg/image/file_catalog_test.go +++ b/pkg/image/file_catalog_test.go @@ -6,11 +6,6 @@ package image import ( "crypto/sha256" "fmt" - "github.com/anchore/stereoscope/pkg/filetree" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "io" "os" "os/exec" @@ -19,6 +14,12 @@ import ( "strings" "testing" + "github.com/anchore/stereoscope/pkg/filetree" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/go-test/deep" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" @@ -359,7 +360,7 @@ func TestFileCatalog_GetByExtension(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), ); d != "" { t.Errorf("diff: %s", d) } @@ -461,7 +462,7 @@ func TestFileCatalog_GetByBasename(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), ); d != "" { t.Errorf("diff: %s", d) } @@ -571,7 +572,7 @@ func TestFileCatalog_GetByBasenameGlob(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), ); d != "" { t.Errorf("diff: %s", d) } @@ -672,7 +673,7 @@ func TestFileCatalog_GetByMimeType(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), ); d != "" { t.Errorf("diff: %s", d) } From 162a14e4c6435218739d89be5fbec542ea6beffd Mon Sep 17 00:00:00 2001 From: Amar Babu Date: Wed, 12 Apr 2023 09:20:24 -0700 Subject: [PATCH 18/23] Bump github.com/containerd/containerd from 1.6.18 to 1.7.0 (#168) Signed-off-by: Amar Babu --- go.mod | 58 +-- go.sum | 1233 +++----------------------------------------------------- 2 files changed, 95 insertions(+), 1196 deletions(-) diff --git a/go.mod b/go.mod index a7efe0fb..f4ca26e4 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 github.com/becheran/wildmatch-go v1.0.0 github.com/bmatcuk/doublestar/v4 v4.0.2 - github.com/containerd/containerd v1.6.18 - github.com/docker/cli v20.10.12+incompatible + github.com/containerd/containerd v1.7.0 + github.com/docker/cli v20.10.20+incompatible github.com/docker/docker v20.10.24+incompatible github.com/gabriel-vasile/mimetype v1.4.0 github.com/go-test/deep v1.0.8 - github.com/google/go-cmp v0.5.8 - github.com/google/go-containerregistry v0.7.0 + github.com/google/go-cmp v0.5.9 + github.com/google/go-containerregistry v0.13.0 github.com/hashicorp/go-multierror v1.1.1 github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 github.com/mitchellh/go-homedir v1.1.0 @@ -25,17 +25,16 @@ require ( github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e github.com/sergi/go-diff v1.2.0 github.com/spf13/afero v1.6.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/sylabs/sif/v2 v2.8.1 github.com/sylabs/squashfs v0.6.1 github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 - golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd + golang.org/x/crypto v0.1.0 ) require ( - cloud.google.com/go v0.97.0 // indirect - github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect github.com/aws/aws-sdk-go-v2 v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.5.0 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.3.1 // indirect @@ -47,40 +46,49 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.3.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.6.0 // indirect github.com/aws/smithy-go v1.6.0 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.10.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect // docker/distribution for https://github.com/advisories/GHSA-qq97-vm5h-rrhg - github.com/docker/distribution v2.8.0+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.4 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/klauspost/compress v1.15.9 // indirect + github.com/klauspost/compress v1.16.4 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/vbatts/tar-split v0.11.2 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/term v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect - google.golang.org/grpc v1.47.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +require ( + cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/kr/pretty v0.2.1 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/tools v0.8.0 // indirect +) diff --git a/go.sum b/go.sum index cabf030e..1c0c4a4e 100644 --- a/go.sum +++ b/go.sum @@ -1,111 +1,18 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible h1:juIaKLLVhqzP55d8x4cSVgwyQv76Z55/fRv/UBr2KkQ= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 h1:imgMA0gN0TZx7PSa/pdWqXadBvrz8WsN6zySzCe4XX0= github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8/go.mod h1:+gPap4jha079qzRTUaehv+UZ6sSdaNwkH0D3b6zhTuk= github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8= github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go-v2 v1.7.1 h1:TswSc7KNqZ/K1Ijt3IkpXk/2+62vi3Q82Yrr5wSbRBQ= github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= github.com/aws/aws-sdk-go-v2/config v1.5.0 h1:tRQcWXVmO7wC+ApwYc2LiYKfIBoIrdzcJ+7HIh6AlR0= @@ -132,1246 +39,230 @@ github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237- github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04/go.mod h1:Z+bXnIbhKJYSvxNwsNnwde7pDKxuqlEZCbUBoTwAqf0= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar/v4 v4.0.2 h1:X0krlUVAVmtr2cRoTqR8aDMrDqnB36ht8wpWTiQ3jsA= github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= -github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter/estargz v0.10.0 h1:glqzafvxBBAMo+x2w2sdDjUDZeTqqLJmqZPY05qehCU= -github.com/containerd/stargz-snapshotter/estargz v0.10.0/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg= +github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc= +github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= +github.com/containerd/stargz-snapshotter/estargz v0.12.1/go.mod h1:12VUuCq3qPq4y8yUW+l5w3+oXV3cx2Po3KSe/SmPGqw= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= -github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v20.10.10+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA= -github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY= -github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4= +github.com/docker/cli v20.10.20+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= -github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -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/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.7.0 h1:u0onUUOcyoCDHEiJoyR1R1gx5er1+r06V5DBhUU5ndk= -github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k= +github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= +github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2-0.20210730191737-8e42a01fb1b7/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/sylabs/sif/v2 v2.8.1 h1:whr4Vz12RXfLnYyVGHoD/rD/hbF2g9OW7BJHa+WIqW8= github.com/sylabs/sif/v2 v2.8.1/go.mod h1:LQOdYXC9a8i7BleTKRw9lohi0rTbXkJOeS9u0ebvgyM= github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ= github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d h1:KOxOL6qpmqwoPloNwi+CEgc1ayjHNOFNrvoOmeDOjDg= github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d/go.mod h1:JPirS5jde/CF5qIjcK4WX+eQmKXdPc6vcZkJ/P0hfPw= github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 h1:lwgTsTy18nYqASnH58qyfRW/ldj7Gt2zzBvgYPzdA4s= github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211111162719-482062a4217b/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -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= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -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/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From abe688130fe473319fe1e9ce5ffd7f308da60a0b Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 12 Apr 2023 14:37:29 -0400 Subject: [PATCH 19/23] Add format make target (#170) * fix linting Signed-off-by: Alex Goodman * add format make target Signed-off-by: Alex Goodman * add developing reference to format target Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- DEVELOPING.md | 2 +- Makefile | 27 ++++++++++++++----- client.go | 4 +-- client_test.go | 6 +++-- examples/basic.go | 4 +-- internal/podman/client.go | 3 ++- internal/podman/ssh.go | 3 ++- internal/string_set_test.go | 3 ++- pkg/event/parsers/parsers.go | 6 ++--- pkg/file/id_set_test.go | 3 ++- pkg/file/metadata.go | 4 +-- pkg/file/metadata_test.go | 3 +-- pkg/file/mime_type_test.go | 5 ++-- pkg/file/path_set_test.go | 3 ++- pkg/file/resolution_test.go | 3 ++- pkg/file/tarutil.go | 3 ++- pkg/filetree/depth_first_path_walker_test.go | 3 ++- pkg/filetree/filetree.go | 5 ++-- pkg/filetree/filetree_test.go | 5 ++-- pkg/filetree/glob.go | 3 +-- pkg/filetree/glob_parser_test.go | 3 ++- pkg/filetree/glob_test.go | 3 ++- pkg/filetree/index.go | 6 ++--- pkg/filetree/index_test.go | 3 ++- pkg/filetree/search.go | 6 ++--- pkg/filetree/search_test.go | 5 ++-- pkg/image/docker/daemon_provider.go | 11 ++++---- pkg/image/docker/daemon_provider_test.go | 2 +- pkg/image/docker/manifest.go | 5 ++-- pkg/image/docker/manifest_test.go | 2 +- pkg/image/docker/tarball_provider.go | 5 ++-- pkg/image/file_catalog_test.go | 9 +++---- pkg/image/image.go | 8 +++--- pkg/image/image_test.go | 1 - pkg/image/layer.go | 11 ++++---- pkg/image/oci/credhelpers/ecr_helper.go | 3 ++- pkg/image/oci/credhelpers/gcr_helper.go | 1 + pkg/image/oci/directory_provider.go | 3 ++- pkg/image/oci/directory_provider_test.go | 3 ++- pkg/image/oci/registry_provider.go | 7 ++--- pkg/image/oci/registry_provider_test.go | 5 ++-- pkg/image/oci/tarball_provider_test.go | 3 ++- pkg/image/platform_test.go | 3 ++- pkg/image/registry_credentials.go | 3 ++- pkg/image/registry_credentials_test.go | 3 +-- pkg/image/registry_options.go | 3 ++- pkg/image/sif/image.go | 3 ++- pkg/image/sif/provider.go | 5 ++-- pkg/image/sif/provider_test.go | 3 ++- pkg/image/source.go | 7 ++--- pkg/imagetest/image_fixtures.go | 5 ++-- pkg/tree/node/id_test.go | 3 ++- pkg/tree/tree_test.go | 3 ++- .../fixture_image_opaque_directory_test.go | 3 ++- test/integration/fixture_image_simple_test.go | 7 ++--- .../fixture_image_symlinks_test.go | 5 ++-- test/integration/mime_type_detection_test.go | 8 +++--- test/integration/oci_registry_source_test.go | 5 ++-- test/integration/platform_test.go | 10 ++++--- test/integration/utils_test.go | 4 +-- 60 files changed, 168 insertions(+), 118 deletions(-) diff --git a/DEVELOPING.md b/DEVELOPING.md index 43577bb2..18e673cd 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -12,7 +12,7 @@ After cloning the following step can help you get setup: 1. run `make bootstrap` to download go mod dependencies, create the `/.tmp` dir, and download helper utilities. 2. run `make help` to view the selection of developer commands in the Makefile -The main make tasks for common static analysis and testing are `lint`, `lint-fix`, `unit`, and `integration`. +The main make tasks for common static analysis and testing are `lint`, `format`, `lint-fix`, `unit`, and `integration`. See `make help` for all the current make tasks. diff --git a/Makefile b/Makefile index b5f3d3d6..6366859a 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,13 @@ TEMP_DIR = ./.tmp # Command templates ################################# LINT_CMD = $(TEMP_DIR)/golangci-lint run --tests=false --config .golangci.yaml +GOIMPORTS_CMD := $(TEMP_DIR)/gosimports -local github.com/anchore # Tool versions ################################# -GOLANGCILINT_VERSION := v1.51.0 -GOSIMPORTS_VERSION := v0.3.5 +GOLANGCILINT_VERSION := v1.52.2 +GOSIMPORTS_VERSION := v0.3.8 BOUNCER_VERSION := v0.4.0 -CHRONICLE_VERSION := v0.5.1 +CHRONICLE_VERSION := v0.6.0 # Formatting variables ################################# BOLD := $(shell tput -T linux bold) @@ -91,16 +92,30 @@ static-analysis: check-licenses lint .PHONY: lint lint: ## Run gofmt + golangci lint checks $(call title,Running linters) + # ensure there are no go fmt differences @printf "files with gofmt issues: [$(shell gofmt -l -s .)]\n" @test -z "$(shell gofmt -l -s .)" + + # run all golangci-lint rules $(LINT_CMD) + @[ -z "$(shell $(GOIMPORTS_CMD) -d .)" ] || (echo "goimports needs to be fixed" && false) + + # go tooling does not play well with certain filename characters, ensure the common cases don't result in future "go get" failures + $(eval MALFORMED_FILENAMES := $(shell find . | grep -v tar-cache | grep -e ':')) + @bash -c "[[ '$(MALFORMED_FILENAMES)' == '' ]] || (printf '\nfound unsupported filename characters:\n$(MALFORMED_FILENAMES)\n\n' && false)" + + +.PHONY: format +format: ## Auto-format all source code + $(call title,Running formatters) + gofmt -w -s . + $(GOIMPORTS_CMD) -w . + go mod tidy .PHONY: lint-fix -lint-fix: ## Auto-format all source code + run golangci lint fixers +lint-fix: format ## Auto-format all source code + run golangci lint fixers $(call title,Running lint fixers) - gofmt -w -s . $(LINT_CMD) --fix - go mod tidy .PHONY: check-licenses check-licenses: diff --git a/client.go b/client.go index 78592fb5..ce0f4d16 100644 --- a/client.go +++ b/client.go @@ -5,8 +5,9 @@ import ( "fmt" "runtime" - "github.com/anchore/go-logger" + "github.com/wagoodman/go-partybus" + "github.com/anchore/go-logger" "github.com/anchore/stereoscope/internal/bus" dockerClient "github.com/anchore/stereoscope/internal/docker" "github.com/anchore/stereoscope/internal/log" @@ -16,7 +17,6 @@ import ( "github.com/anchore/stereoscope/pkg/image/docker" "github.com/anchore/stereoscope/pkg/image/oci" "github.com/anchore/stereoscope/pkg/image/sif" - "github.com/wagoodman/go-partybus" ) var rootTempDirGenerator = file.NewTempDirGenerator("stereoscope") diff --git a/client_test.go b/client_test.go index a85708e6..82a420e5 100644 --- a/client_test.go +++ b/client_test.go @@ -1,11 +1,13 @@ package stereoscope import ( - "github.com/anchore/stereoscope/pkg/image" + "testing" + "github.com/scylladb/go-set/i8set" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" + + "github.com/anchore/stereoscope/pkg/image" ) func Test_setPlatform(t *testing.T) { diff --git a/examples/basic.go b/examples/basic.go index 687245b9..1609304f 100644 --- a/examples/basic.go +++ b/examples/basic.go @@ -4,12 +4,10 @@ import ( "context" "fmt" "io" + "os" "github.com/anchore/go-logger" "github.com/anchore/go-logger/adapter/logrus" - - "os" - "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" diff --git a/internal/podman/client.go b/internal/podman/client.go index 026f2b95..eda86547 100644 --- a/internal/podman/client.go +++ b/internal/podman/client.go @@ -6,9 +6,10 @@ import ( "os" "time" - "github.com/anchore/stereoscope/internal/log" "github.com/docker/docker/client" "github.com/pkg/errors" + + "github.com/anchore/stereoscope/internal/log" ) var ( diff --git a/internal/podman/ssh.go b/internal/podman/ssh.go index ffa5711e..6ecd6e3e 100644 --- a/internal/podman/ssh.go +++ b/internal/podman/ssh.go @@ -12,11 +12,12 @@ import ( "strconv" "time" - "github.com/anchore/stereoscope/internal/log" "github.com/docker/docker/pkg/homedir" "github.com/pkg/errors" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" + + "github.com/anchore/stereoscope/internal/log" ) type sshClientConfig struct { diff --git a/internal/string_set_test.go b/internal/string_set_test.go index e04727ae..b0c103ad 100644 --- a/internal/string_set_test.go +++ b/internal/string_set_test.go @@ -2,8 +2,9 @@ package internal import ( "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestStringSet_Size(t *testing.T) { diff --git a/pkg/event/parsers/parsers.go b/pkg/event/parsers/parsers.go index becd8ca6..867fee67 100644 --- a/pkg/event/parsers/parsers.go +++ b/pkg/event/parsers/parsers.go @@ -3,12 +3,12 @@ package parsers import ( "fmt" - "github.com/anchore/stereoscope/pkg/image/docker" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" "github.com/anchore/stereoscope/pkg/event" "github.com/anchore/stereoscope/pkg/image" - "github.com/wagoodman/go-partybus" - "github.com/wagoodman/go-progress" + "github.com/anchore/stereoscope/pkg/image/docker" ) type ErrBadPayload struct { diff --git a/pkg/file/id_set_test.go b/pkg/file/id_set_test.go index b0d146db..5de315c0 100644 --- a/pkg/file/id_set_test.go +++ b/pkg/file/id_set_test.go @@ -2,8 +2,9 @@ package file import ( "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestIDSet_Size(t *testing.T) { diff --git a/pkg/file/metadata.go b/pkg/file/metadata.go index 90c6d764..6ea3882f 100644 --- a/pkg/file/metadata.go +++ b/pkg/file/metadata.go @@ -8,9 +8,9 @@ import ( "path/filepath" "time" - "github.com/anchore/stereoscope/internal/log" - "github.com/sylabs/squashfs" + + "github.com/anchore/stereoscope/internal/log" ) // Metadata represents all file metadata of interest (used today for in-tar file resolution). diff --git a/pkg/file/metadata_test.go b/pkg/file/metadata_test.go index 7d043f35..5bd26c0e 100644 --- a/pkg/file/metadata_test.go +++ b/pkg/file/metadata_test.go @@ -10,10 +10,9 @@ import ( "testing" "time" + "github.com/go-test/deep" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/go-test/deep" ) func TestFileMetadataFromTar(t *testing.T) { diff --git a/pkg/file/mime_type_test.go b/pkg/file/mime_type_test.go index d8035a8c..10b871b1 100644 --- a/pkg/file/mime_type_test.go +++ b/pkg/file/mime_type_test.go @@ -1,12 +1,13 @@ package file import ( - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "io" "os" "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_MIMEType(t *testing.T) { diff --git a/pkg/file/path_set_test.go b/pkg/file/path_set_test.go index 1e578911..8580ba5b 100644 --- a/pkg/file/path_set_test.go +++ b/pkg/file/path_set_test.go @@ -2,8 +2,9 @@ package file import ( "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestPathSet_Size(t *testing.T) { diff --git a/pkg/file/resolution_test.go b/pkg/file/resolution_test.go index 3a444950..c3ba39b7 100644 --- a/pkg/file/resolution_test.go +++ b/pkg/file/resolution_test.go @@ -1,9 +1,10 @@ package file import ( - "github.com/stretchr/testify/assert" "sort" "testing" + + "github.com/stretchr/testify/assert" ) func TestResolution_Less(t *testing.T) { diff --git a/pkg/file/tarutil.go b/pkg/file/tarutil.go index 4a01c09a..374c60f6 100644 --- a/pkg/file/tarutil.go +++ b/pkg/file/tarutil.go @@ -7,8 +7,9 @@ import ( "os" "path/filepath" - "github.com/anchore/stereoscope/internal/log" "github.com/pkg/errors" + + "github.com/anchore/stereoscope/internal/log" ) const perFileReadLimit = 2 * GB diff --git a/pkg/filetree/depth_first_path_walker_test.go b/pkg/filetree/depth_first_path_walker_test.go index 9db3fd3c..ec79b390 100644 --- a/pkg/filetree/depth_first_path_walker_test.go +++ b/pkg/filetree/depth_first_path_walker_test.go @@ -5,9 +5,10 @@ import ( "strings" "testing" + "github.com/go-test/deep" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" - "github.com/go-test/deep" ) func dfsTestTree(t *testing.T) (*FileTree, map[string]*file.Reference) { diff --git a/pkg/filetree/filetree.go b/pkg/filetree/filetree.go index e2ea64a5..ecce35d5 100644 --- a/pkg/filetree/filetree.go +++ b/pkg/filetree/filetree.go @@ -7,15 +7,14 @@ import ( "sort" "strings" - "github.com/scylladb/go-set/strset" - + "github.com/bmatcuk/doublestar/v4" "github.com/scylladb/go-set/iset" + "github.com/scylladb/go-set/strset" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/anchore/stereoscope/pkg/tree" "github.com/anchore/stereoscope/pkg/tree/node" - "github.com/bmatcuk/doublestar/v4" ) var ErrRemovingRoot = errors.New("cannot remove the root path (`/`) from the FileTree") diff --git a/pkg/filetree/filetree_test.go b/pkg/filetree/filetree_test.go index 178ca174..41d5842b 100644 --- a/pkg/filetree/filetree_test.go +++ b/pkg/filetree/filetree_test.go @@ -5,15 +5,14 @@ import ( "fmt" "testing" - "github.com/scylladb/go-set/strset" - "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/scylladb/go-set/strset" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" - "github.com/stretchr/testify/assert" ) func TestFileTree_AddPath(t *testing.T) { diff --git a/pkg/filetree/glob.go b/pkg/filetree/glob.go index a99ad154..1ba234c0 100644 --- a/pkg/filetree/glob.go +++ b/pkg/filetree/glob.go @@ -7,9 +7,8 @@ import ( "path/filepath" "time" - "github.com/anchore/stereoscope/pkg/filetree/filenode" - "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree/filenode" ) // basic interface assertion diff --git a/pkg/filetree/glob_parser_test.go b/pkg/filetree/glob_parser_test.go index 64ea8380..c92bf1bf 100644 --- a/pkg/filetree/glob_parser_test.go +++ b/pkg/filetree/glob_parser_test.go @@ -1,8 +1,9 @@ package filetree import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func Test_parseGlob(t *testing.T) { diff --git a/pkg/filetree/glob_test.go b/pkg/filetree/glob_test.go index e5671561..4e7fb527 100644 --- a/pkg/filetree/glob_test.go +++ b/pkg/filetree/glob_test.go @@ -4,9 +4,10 @@ import ( "sort" "testing" + "github.com/go-test/deep" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" - "github.com/go-test/deep" ) func TestFileInfoAdapter(t *testing.T) { diff --git a/pkg/filetree/index.go b/pkg/filetree/index.go index a7433d48..4cf1fd1b 100644 --- a/pkg/filetree/index.go +++ b/pkg/filetree/index.go @@ -8,11 +8,11 @@ import ( "strings" "sync" - "github.com/anchore/stereoscope/internal/log" - - "github.com/anchore/stereoscope/pkg/file" "github.com/becheran/wildmatch-go" "github.com/scylladb/go-set/strset" + + "github.com/anchore/stereoscope/internal/log" + "github.com/anchore/stereoscope/pkg/file" ) type Index interface { diff --git a/pkg/filetree/index_test.go b/pkg/filetree/index_test.go index d8cd6b91..0cd2150c 100644 --- a/pkg/filetree/index_test.go +++ b/pkg/filetree/index_test.go @@ -4,11 +4,12 @@ package filetree import ( + "testing" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" "github.com/anchore/stereoscope/pkg/file" ) diff --git a/pkg/filetree/search.go b/pkg/filetree/search.go index e5152ab7..d40b48be 100644 --- a/pkg/filetree/search.go +++ b/pkg/filetree/search.go @@ -5,12 +5,12 @@ import ( "path" "sort" + "github.com/bmatcuk/doublestar/v4" + "github.com/anchore/stereoscope/internal/log" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/anchore/stereoscope/pkg/tree/node" - - "github.com/anchore/stereoscope/pkg/file" - "github.com/bmatcuk/doublestar/v4" ) // Searcher is a facade for searching a file tree with optional indexing support. diff --git a/pkg/filetree/search_test.go b/pkg/filetree/search_test.go index 73fcc334..ed7bacf6 100644 --- a/pkg/filetree/search_test.go +++ b/pkg/filetree/search_test.go @@ -4,12 +4,13 @@ import ( "fmt" "testing" - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree/filenode" ) func Test_searchContext_SearchByPath(t *testing.T) { diff --git a/pkg/image/docker/daemon_provider.go b/pkg/image/docker/daemon_provider.go index 5a2a22d9..bb389ce7 100644 --- a/pkg/image/docker/daemon_provider.go +++ b/pkg/image/docker/daemon_provider.go @@ -14,11 +14,6 @@ import ( "strings" "time" - "github.com/anchore/stereoscope/internal/bus" - "github.com/anchore/stereoscope/internal/log" - "github.com/anchore/stereoscope/pkg/event" - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/image" "github.com/docker/cli/cli/config" configTypes "github.com/docker/cli/cli/config/types" "github.com/docker/docker/api/types" @@ -26,6 +21,12 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/wagoodman/go-partybus" "github.com/wagoodman/go-progress" + + "github.com/anchore/stereoscope/internal/bus" + "github.com/anchore/stereoscope/internal/log" + "github.com/anchore/stereoscope/pkg/event" + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/image" ) // DaemonImageProvider is a image.Provider capable of fetching and representing a docker image from the docker daemon API. diff --git a/pkg/image/docker/daemon_provider_test.go b/pkg/image/docker/daemon_provider_test.go index ba8741f5..f2760604 100644 --- a/pkg/image/docker/daemon_provider_test.go +++ b/pkg/image/docker/daemon_provider_test.go @@ -3,11 +3,11 @@ package docker import ( "encoding/base64" "encoding/json" - "github.com/stretchr/testify/require" "testing" configTypes "github.com/docker/cli/cli/config/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestEncodeCredentials(t *testing.T) { diff --git a/pkg/image/docker/manifest.go b/pkg/image/docker/manifest.go index 4b2b98db..0621e6e9 100644 --- a/pkg/image/docker/manifest.go +++ b/pkg/image/docker/manifest.go @@ -7,11 +7,12 @@ import ( "io" "os" - "github.com/anchore/stereoscope/internal/log" - "github.com/anchore/stereoscope/pkg/file" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/types" + + "github.com/anchore/stereoscope/internal/log" + "github.com/anchore/stereoscope/pkg/file" ) type dockerManifest struct { diff --git a/pkg/image/docker/manifest_test.go b/pkg/image/docker/manifest_test.go index 8bb01a11..b4a7c45b 100644 --- a/pkg/image/docker/manifest_test.go +++ b/pkg/image/docker/manifest_test.go @@ -8,10 +8,10 @@ import ( "os" "testing" + "github.com/go-test/deep" "github.com/sergi/go-diff/diffmatchpatch" "github.com/anchore/go-testutils" - "github.com/go-test/deep" ) var update = flag.Bool("update", false, "update the *.golden files for the oci manifest assembly test") diff --git a/pkg/image/docker/tarball_provider.go b/pkg/image/docker/tarball_provider.go index 4001fdf1..0e5b6474 100644 --- a/pkg/image/docker/tarball_provider.go +++ b/pkg/image/docker/tarball_provider.go @@ -5,11 +5,12 @@ import ( "encoding/json" "fmt" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/anchore/stereoscope/internal/log" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/image" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/tarball" ) var ErrMultipleManifests = fmt.Errorf("cannot process multiple docker manifests") diff --git a/pkg/image/file_catalog_test.go b/pkg/image/file_catalog_test.go index bcf6cab4..ca0fefc9 100644 --- a/pkg/image/file_catalog_test.go +++ b/pkg/image/file_catalog_test.go @@ -14,17 +14,16 @@ import ( "strings" "testing" - "github.com/anchore/stereoscope/pkg/filetree" + "github.com/go-test/deep" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/go-test/deep" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree" ) const ( diff --git a/pkg/image/image.go b/pkg/image/image.go index 17776905..188d546a 100644 --- a/pkg/image/image.go +++ b/pkg/image/image.go @@ -6,18 +6,18 @@ import ( "io" "os" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/hashicorp/go-multierror" "github.com/scylladb/go-set/strset" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" "github.com/anchore/stereoscope/internal/bus" "github.com/anchore/stereoscope/internal/log" "github.com/anchore/stereoscope/pkg/event" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" - "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/wagoodman/go-partybus" - "github.com/wagoodman/go-progress" ) // Image represents a container image. diff --git a/pkg/image/image_test.go b/pkg/image/image_test.go index 4d2b2064..55907fc1 100644 --- a/pkg/image/image_test.go +++ b/pkg/image/image_test.go @@ -8,7 +8,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/google/go-containerregistry/pkg/name" ) diff --git a/pkg/image/layer.go b/pkg/image/layer.go index df202cd4..ec2b7e50 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -9,16 +9,17 @@ import ( "os" "path" - "github.com/anchore/stereoscope/internal/bus" - "github.com/anchore/stereoscope/internal/log" - "github.com/anchore/stereoscope/pkg/event" - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/filetree" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sylabs/squashfs" "github.com/wagoodman/go-partybus" "github.com/wagoodman/go-progress" + + "github.com/anchore/stereoscope/internal/bus" + "github.com/anchore/stereoscope/internal/log" + "github.com/anchore/stereoscope/pkg/event" + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree" ) const SingularitySquashFSLayer = "application/vnd.sylabs.sif.layer.v1.squashfs" diff --git a/pkg/image/oci/credhelpers/ecr_helper.go b/pkg/image/oci/credhelpers/ecr_helper.go index ca95744e..1afeb14f 100644 --- a/pkg/image/oci/credhelpers/ecr_helper.go +++ b/pkg/image/oci/credhelpers/ecr_helper.go @@ -1,8 +1,9 @@ package credhelpers import ( - "github.com/anchore/stereoscope/pkg/image" ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login" + + "github.com/anchore/stereoscope/pkg/image" ) type ECRHelper struct { diff --git a/pkg/image/oci/credhelpers/gcr_helper.go b/pkg/image/oci/credhelpers/gcr_helper.go index d78d7220..7a45da6b 100644 --- a/pkg/image/oci/credhelpers/gcr_helper.go +++ b/pkg/image/oci/credhelpers/gcr_helper.go @@ -6,6 +6,7 @@ import ( "github.com/GoogleCloudPlatform/docker-credential-gcr/config" "github.com/GoogleCloudPlatform/docker-credential-gcr/credhelper" "github.com/GoogleCloudPlatform/docker-credential-gcr/store" + "github.com/anchore/stereoscope/pkg/image" ) diff --git a/pkg/image/oci/directory_provider.go b/pkg/image/oci/directory_provider.go index f66d3032..529ea8da 100644 --- a/pkg/image/oci/directory_provider.go +++ b/pkg/image/oci/directory_provider.go @@ -4,9 +4,10 @@ import ( "context" "fmt" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/image" - "github.com/google/go-containerregistry/pkg/v1/layout" ) // DirectoryImageProvider is an image.Provider for an OCI image (V1) for an existing tar on disk (from a buildah push oci: command). diff --git a/pkg/image/oci/directory_provider_test.go b/pkg/image/oci/directory_provider_test.go index af866d76..b9e279ab 100644 --- a/pkg/image/oci/directory_provider_test.go +++ b/pkg/image/oci/directory_provider_test.go @@ -3,8 +3,9 @@ package oci import ( "testing" - "github.com/anchore/stereoscope/pkg/file" "github.com/stretchr/testify/assert" + + "github.com/anchore/stereoscope/pkg/file" ) func Test_NewProviderFromPath(t *testing.T) { diff --git a/pkg/image/oci/registry_provider.go b/pkg/image/oci/registry_provider.go index 7578c9f8..3ae3a875 100644 --- a/pkg/image/oci/registry_provider.go +++ b/pkg/image/oci/registry_provider.go @@ -6,13 +6,14 @@ import ( "fmt" "net/http" - "github.com/anchore/stereoscope/internal/log" - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/image" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" containerregistryV1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" + + "github.com/anchore/stereoscope/internal/log" + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/image" ) // RegistryImageProvider is an image.Provider capable of fetching and representing a container image fetched from a remote registry (described by the OCI distribution spec). diff --git a/pkg/image/oci/registry_provider_test.go b/pkg/image/oci/registry_provider_test.go index bf0f8055..431331dc 100644 --- a/pkg/image/oci/registry_provider_test.go +++ b/pkg/image/oci/registry_provider_test.go @@ -5,10 +5,11 @@ import ( "reflect" "testing" - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/image" "github.com/google/go-containerregistry/pkg/name" "github.com/stretchr/testify/assert" + + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/image" ) func Test_NewProviderFromRegistry(t *testing.T) { diff --git a/pkg/image/oci/tarball_provider_test.go b/pkg/image/oci/tarball_provider_test.go index 7aea6400..ae2707ca 100644 --- a/pkg/image/oci/tarball_provider_test.go +++ b/pkg/image/oci/tarball_provider_test.go @@ -3,8 +3,9 @@ package oci import ( "testing" - "github.com/anchore/stereoscope/pkg/file" "github.com/stretchr/testify/assert" + + "github.com/anchore/stereoscope/pkg/file" ) func Test_NewProviderFromTarball(t *testing.T) { diff --git a/pkg/image/platform_test.go b/pkg/image/platform_test.go index 375cc979..328dc347 100644 --- a/pkg/image/platform_test.go +++ b/pkg/image/platform_test.go @@ -2,8 +2,9 @@ package image import ( "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestNewPlatform(t *testing.T) { diff --git a/pkg/image/registry_credentials.go b/pkg/image/registry_credentials.go index 890cf9f0..05be01cd 100644 --- a/pkg/image/registry_credentials.go +++ b/pkg/image/registry_credentials.go @@ -1,8 +1,9 @@ package image import ( - "github.com/anchore/stereoscope/internal/log" "github.com/google/go-containerregistry/pkg/authn" + + "github.com/anchore/stereoscope/internal/log" ) // RegistryCredentials contains any information necessary to authenticate against an OCI-distribution-compliant diff --git a/pkg/image/registry_credentials_test.go b/pkg/image/registry_credentials_test.go index 0552c524..f14a3c50 100644 --- a/pkg/image/registry_credentials_test.go +++ b/pkg/image/registry_credentials_test.go @@ -3,9 +3,8 @@ package image import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/google/go-containerregistry/pkg/authn" + "github.com/stretchr/testify/assert" ) func TestRegistryCredentials_Authenticator(t *testing.T) { diff --git a/pkg/image/registry_options.go b/pkg/image/registry_options.go index 5d3e7ba0..36ee7e5a 100644 --- a/pkg/image/registry_options.go +++ b/pkg/image/registry_options.go @@ -1,8 +1,9 @@ package image import ( - "github.com/anchore/stereoscope/internal/log" "github.com/google/go-containerregistry/pkg/authn" + + "github.com/anchore/stereoscope/internal/log" ) // RegistryOptions for the OCI registry provider. diff --git a/pkg/image/sif/image.go b/pkg/image/sif/image.go index bcbf9d9a..da32d819 100644 --- a/pkg/image/sif/image.go +++ b/pkg/image/sif/image.go @@ -7,11 +7,12 @@ import ( "io" "os" - "github.com/anchore/stereoscope/pkg/image" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sylabs/sif/v2/pkg/sif" + + "github.com/anchore/stereoscope/pkg/image" ) const SingularityMediaType = "application/vnd.sylabs.sif.layer.v1.sif" diff --git a/pkg/image/sif/provider.go b/pkg/image/sif/provider.go index c10b67e9..2d81b838 100644 --- a/pkg/image/sif/provider.go +++ b/pkg/image/sif/provider.go @@ -3,9 +3,10 @@ package sif import ( "context" + "github.com/google/go-containerregistry/pkg/v1/partial" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/image" - "github.com/google/go-containerregistry/pkg/v1/partial" ) // SingularityImageProvider is an image.Provider for a Singularity Image Format (SIF) image. @@ -24,7 +25,7 @@ func NewProviderFromPath(path string, tmpDirGen *file.TempDirGenerator) *Singula } // Provide returns an Image that represents a Singularity Image Format (SIF) image. -func (p *SingularityImageProvider) Provide(ctx context.Context, userMetadata ...image.AdditionalMetadata) (*image.Image, error) { +func (p *SingularityImageProvider) Provide(_ context.Context, userMetadata ...image.AdditionalMetadata) (*image.Image, error) { // We need to map the SIF to a GGCR v1.Image. Start with an implementation of the GGCR // partial.UncompressedImageCore interface. si, err := newSIFImage(p.path) diff --git a/pkg/image/sif/provider_test.go b/pkg/image/sif/provider_test.go index 15cacee8..0c3901e1 100644 --- a/pkg/image/sif/provider_test.go +++ b/pkg/image/sif/provider_test.go @@ -6,9 +6,10 @@ import ( "path/filepath" "testing" + "github.com/sylabs/sif/v2/pkg/sif" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/image" - "github.com/sylabs/sif/v2/pkg/sif" ) func TestSingularityImageProvider_Provide(t *testing.T) { diff --git a/pkg/image/source.go b/pkg/image/source.go index de697afc..d96bde14 100644 --- a/pkg/image/source.go +++ b/pkg/image/source.go @@ -10,13 +10,14 @@ import ( "strings" "time" - "github.com/anchore/stereoscope/internal/docker" - "github.com/anchore/stereoscope/internal/podman" - "github.com/anchore/stereoscope/pkg/file" "github.com/google/go-containerregistry/pkg/name" "github.com/mitchellh/go-homedir" "github.com/spf13/afero" "github.com/sylabs/sif/v2/pkg/sif" + + "github.com/anchore/stereoscope/internal/docker" + "github.com/anchore/stereoscope/internal/podman" + "github.com/anchore/stereoscope/pkg/file" ) const ( diff --git a/pkg/imagetest/image_fixtures.go b/pkg/imagetest/image_fixtures.go index 7429f542..51f31862 100644 --- a/pkg/imagetest/image_fixtures.go +++ b/pkg/imagetest/image_fixtures.go @@ -9,11 +9,12 @@ import ( "path/filepath" "testing" + "github.com/logrusorgru/aurora" + "github.com/stretchr/testify/require" + "github.com/anchore/go-testutils" "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/image" - "github.com/logrusorgru/aurora" - "github.com/stretchr/testify/require" ) const ( diff --git a/pkg/tree/node/id_test.go b/pkg/tree/node/id_test.go index 222981bc..9ab451d6 100644 --- a/pkg/tree/node/id_test.go +++ b/pkg/tree/node/id_test.go @@ -2,8 +2,9 @@ package node import ( "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestIDSet_Size(t *testing.T) { diff --git a/pkg/tree/tree_test.go b/pkg/tree/tree_test.go index 69b2be87..c5ac4467 100644 --- a/pkg/tree/tree_test.go +++ b/pkg/tree/tree_test.go @@ -4,8 +4,9 @@ import ( "fmt" "testing" - "github.com/anchore/stereoscope/pkg/tree/node" "github.com/stretchr/testify/assert" + + "github.com/anchore/stereoscope/pkg/tree/node" ) type testNode struct { diff --git a/test/integration/fixture_image_opaque_directory_test.go b/test/integration/fixture_image_opaque_directory_test.go index 2e58fe2b..8c3c314e 100644 --- a/test/integration/fixture_image_opaque_directory_test.go +++ b/test/integration/fixture_image_opaque_directory_test.go @@ -1,10 +1,11 @@ package integration import ( + "testing" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/imagetest" - "testing" ) func TestImage_SquashedTree_OpaqueDirectoryExistsInFileCatalog(t *testing.T) { diff --git a/test/integration/fixture_image_simple_test.go b/test/integration/fixture_image_simple_test.go index 2a378015..c4323a3a 100644 --- a/test/integration/fixture_image_simple_test.go +++ b/test/integration/fixture_image_simple_test.go @@ -10,15 +10,16 @@ import ( "strings" "testing" + v1Types "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/scylladb/go-set" + "github.com/stretchr/testify/require" + "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/stereoscope/pkg/image/sif" "github.com/anchore/stereoscope/pkg/imagetest" - v1Types "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/scylladb/go-set" - "github.com/stretchr/testify/require" ) // Common layer metadata for OCI / Docker / Podman. MediaType will be filled in during test. diff --git a/test/integration/fixture_image_symlinks_test.go b/test/integration/fixture_image_symlinks_test.go index 684af08f..d3cfcd73 100644 --- a/test/integration/fixture_image_symlinks_test.go +++ b/test/integration/fixture_image_symlinks_test.go @@ -5,15 +5,16 @@ package integration import ( "fmt" - "github.com/stretchr/testify/require" "io" "testing" + "github.com/scylladb/go-set" + "github.com/stretchr/testify/require" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/stereoscope/pkg/imagetest" - "github.com/scylladb/go-set" ) type linkFetchConfig struct { diff --git a/test/integration/mime_type_detection_test.go b/test/integration/mime_type_detection_test.go index eb43edd7..eb088685 100644 --- a/test/integration/mime_type_detection_test.go +++ b/test/integration/mime_type_detection_test.go @@ -2,11 +2,13 @@ package integration import ( "context" - "github.com/anchore/stereoscope" - "github.com/anchore/stereoscope/pkg/imagetest" + "testing" + "github.com/scylladb/go-set/strset" "github.com/stretchr/testify/assert" - "testing" + + "github.com/anchore/stereoscope" + "github.com/anchore/stereoscope/pkg/imagetest" ) func TestContentMIMETypeDetection(t *testing.T) { diff --git a/test/integration/oci_registry_source_test.go b/test/integration/oci_registry_source_test.go index 61671d9a..feab94d9 100644 --- a/test/integration/oci_registry_source_test.go +++ b/test/integration/oci_registry_source_test.go @@ -3,11 +3,12 @@ package integration import ( "context" "fmt" - "github.com/stretchr/testify/require" "testing" - "github.com/anchore/stereoscope" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/stereoscope" ) func TestOciRegistrySourceMetadata(t *testing.T) { diff --git a/test/integration/platform_test.go b/test/integration/platform_test.go index 818c5ef3..91e2d503 100644 --- a/test/integration/platform_test.go +++ b/test/integration/platform_test.go @@ -3,12 +3,14 @@ package integration import ( "context" "fmt" - "github.com/anchore/stereoscope" - "github.com/anchore/stereoscope/pkg/image" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/stereoscope" + "github.com/anchore/stereoscope/pkg/image" ) func TestPlatformSelection(t *testing.T) { diff --git a/test/integration/utils_test.go b/test/integration/utils_test.go index 4f9fa268..12b980e6 100644 --- a/test/integration/utils_test.go +++ b/test/integration/utils_test.go @@ -1,11 +1,11 @@ package integration import ( - "github.com/anchore/stereoscope/pkg/filetree" - "github.com/anchore/stereoscope/pkg/filetree/filenode" "testing" "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree" + "github.com/anchore/stereoscope/pkg/filetree/filenode" "github.com/anchore/stereoscope/pkg/image" ) From 3ab9510958751db129e3a72c676d794a71b031aa Mon Sep 17 00:00:00 2001 From: Adam Hughes <9903835+tri-adam@users.noreply.github.com> Date: Mon, 8 May 2023 09:30:58 -0400 Subject: [PATCH 20/23] refactor: embed fs.FileInfo within file.Metadata (#172) * refactor: embed fs.FileInfo within file.Metadata Embed the original `fs.FileInfo` directly within the `file.Metadata struct`, replacing the previous `Size`, `IsDir`, `Mode`, and `ModTime` fields. Signed-off-by: Adam Hughes <9903835+tri-adam@users.noreply.github.com> * add ManualInfo helper struct Signed-off-by: Alex Goodman --------- Signed-off-by: Adam Hughes <9903835+tri-adam@users.noreply.github.com> Signed-off-by: Alex Goodman Co-authored-by: Alex Goodman --- pkg/file/metadata.go | 91 +++++++++++++++------- pkg/file/metadata_test.go | 135 +++++++++++++++++++++++++++++---- pkg/file/tarutil_test.go | 26 +++---- pkg/filetree/index_test.go | 87 +++++++++++++-------- pkg/image/docker/manifest.go | 2 +- pkg/image/file_catalog_test.go | 48 ++++++++---- pkg/image/layer.go | 4 +- 7 files changed, 289 insertions(+), 104 deletions(-) diff --git a/pkg/file/metadata.go b/pkg/file/metadata.go index 6ea3882f..8442d92a 100644 --- a/pkg/file/metadata.go +++ b/pkg/file/metadata.go @@ -3,6 +3,7 @@ package file import ( "archive/tar" "io" + "io/fs" "os" "path" "path/filepath" @@ -13,38 +14,62 @@ import ( "github.com/anchore/stereoscope/internal/log" ) -// Metadata represents all file metadata of interest (used today for in-tar file resolution). +var _ fs.FileInfo = (*ManualInfo)(nil) + +// Metadata represents all file metadata of interest. type Metadata struct { + fs.FileInfo + // Path is the absolute path representation to the file Path string // LinkDestination is populated only for hardlinks / symlinks, can be an absolute or relative LinkDestination string - // Size of the file in bytes - Size int64 - UserID int - GroupID int - Type Type - IsDir bool - Mode os.FileMode - MIMEType string - ModTime time.Time - AccessTime time.Time - ChangeTime time.Time + UserID int + GroupID int + Type Type + MIMEType string +} + +type ManualInfo struct { + NameValue string + SizeValue int64 + ModeValue fs.FileMode + ModTimeValue time.Time + SysValue any +} + +func (m ManualInfo) Name() string { + return m.NameValue +} + +func (m ManualInfo) Size() int64 { + return m.SizeValue +} + +func (m ManualInfo) Mode() fs.FileMode { + return m.ModeValue +} + +func (m ManualInfo) ModTime() time.Time { + return m.ModTimeValue +} + +func (m ManualInfo) IsDir() bool { + return m.ModeValue.IsDir() +} + +func (m ManualInfo) Sys() any { + return m.SysValue } func NewMetadata(header tar.Header, content io.Reader) Metadata { return Metadata{ + FileInfo: header.FileInfo(), Path: path.Clean(DirSeparator + header.Name), Type: TypeFromTarType(header.Typeflag), LinkDestination: header.Linkname, - Size: header.FileInfo().Size(), - Mode: header.FileInfo().Mode(), UserID: header.Uid, GroupID: header.Gid, - IsDir: header.FileInfo().IsDir(), - ModTime: header.ModTime.UTC(), - AccessTime: header.AccessTime.UTC(), - ChangeTime: header.ChangeTime.UTC(), MIMEType: MIMEType(content), } } @@ -81,12 +106,11 @@ func NewMetadataFromSquashFSFile(path string, f *squashfs.File) (Metadata, error } md := Metadata{ + FileInfo: fi, Path: filepath.Clean(filepath.Join("/", path)), LinkDestination: f.SymlinkPath(), - Size: fi.Size(), - IsDir: f.IsDir(), - Mode: fi.Mode(), - ModTime: fi.ModTime().UTC(), + UserID: -1, + GroupID: -1, Type: ty, } @@ -120,15 +144,26 @@ func NewMetadataFromPath(path string, info os.FileInfo) Metadata { } return Metadata{ - Path: path, - Mode: info.Mode(), - Type: ty, + FileInfo: info, + Path: path, + Type: ty, // unsupported across platforms UserID: uid, GroupID: gid, - Size: info.Size(), MIMEType: mimeType, - IsDir: info.IsDir(), - ModTime: info.ModTime().UTC(), } } + +func (m Metadata) Equal(other Metadata) bool { + return m.Path == other.Path && + m.LinkDestination == other.LinkDestination && + m.UserID == other.UserID && + m.GroupID == other.GroupID && + m.Type == other.Type && + m.MIMEType == other.MIMEType && + m.FileInfo.Name() == other.FileInfo.Name() && + m.FileInfo.IsDir() == other.FileInfo.IsDir() && + m.FileInfo.Mode() == other.FileInfo.Mode() && + m.FileInfo.Size() == other.FileInfo.Size() && + m.FileInfo.ModTime().UTC().Equal(other.FileInfo.ModTime().UTC()) +} diff --git a/pkg/file/metadata_test.go b/pkg/file/metadata_test.go index 5bd26c0e..e7f60075 100644 --- a/pkg/file/metadata_test.go +++ b/pkg/file/metadata_test.go @@ -10,22 +10,132 @@ import ( "testing" "time" - "github.com/go-test/deep" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func assertMetadataEqual(t *testing.T, expected, actual Metadata) { + if !assert.True(t, expected.Equal(actual)) { + assert.Equal(t, expected.Path, actual.Path, "mismatched path") + assert.Equal(t, expected.Type, actual.Type, "mismatched type") + assert.Equal(t, expected.LinkDestination, actual.LinkDestination, "mismatched link destination") + assert.Equal(t, expected.Name(), actual.Name(), "mismatched name") + assert.Equal(t, expected.Size(), actual.Size(), "mismatched size") + assert.Equal(t, expected.Mode(), actual.Mode(), "mismatched mode") + assert.Equal(t, expected.UserID, actual.UserID, "mismatched user id") + assert.Equal(t, expected.GroupID, actual.GroupID, "mismatched group id") + assert.Equal(t, expected.IsDir(), actual.IsDir(), "mismatched is dir") + assert.Equal(t, expected.MIMEType, actual.MIMEType, "mismatched mime type") + exMod := expected.FileInfo.ModTime() + acMod := actual.FileInfo.ModTime() + if !assert.True(t, exMod.UTC().Equal(acMod.UTC()), "mismatched mod time (UTC)") { + assert.Equal(t, exMod, acMod, "mod time details") + } + } +} + func TestFileMetadataFromTar(t *testing.T) { tarReader := getTarFixture(t, "fixture-1") - expected := []Metadata{ - {Path: "/path", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, - {Path: "/path/branch", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, - {Path: "/path/branch/one", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o700, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, - {Path: "/path/branch/one/file-1.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o700, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, - {Path: "/path/branch/two", Type: TypeDirectory, LinkDestination: "", Size: 0, Mode: os.ModeDir | 0o755, UserID: 1337, GroupID: 5432, IsDir: true, MIMEType: "", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, - {Path: "/path/branch/two/file-2.txt", Type: TypeRegular, LinkDestination: "", Size: 12, Mode: 0o755, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, - {Path: "/path/file-3.txt", Type: TypeRegular, LinkDestination: "", Size: 11, Mode: 0o664, UserID: 1337, GroupID: 5432, IsDir: false, MIMEType: "text/plain", ModTime: time.Time{}, AccessTime: time.Time{}, ChangeTime: time.Time{}}, + ex := []Metadata{ + { + Path: "/path", + Type: TypeDirectory, + LinkDestination: "", + UserID: 1337, + GroupID: 5432, + MIMEType: "", + FileInfo: ManualInfo{ + NameValue: "path", + SizeValue: 0, + ModeValue: os.ModeDir | 0o755, + ModTimeValue: time.Time{}, + }, + }, + { + Path: "/path/branch", + Type: TypeDirectory, + LinkDestination: "", + UserID: 1337, + GroupID: 5432, + MIMEType: "", + FileInfo: ManualInfo{ + NameValue: "branch", + SizeValue: 0, + ModeValue: os.ModeDir | 0o755, + ModTimeValue: time.Time{}, + }, + }, + { + Path: "/path/branch/one", + Type: TypeDirectory, + LinkDestination: "", + UserID: 1337, + GroupID: 5432, + MIMEType: "", + FileInfo: ManualInfo{ + NameValue: "one", + SizeValue: 0, + ModeValue: os.ModeDir | 0o700, + ModTimeValue: time.Time{}, + }, + }, + { + Path: "/path/branch/one/file-1.txt", + Type: TypeRegular, + LinkDestination: "", + UserID: 1337, + GroupID: 5432, + MIMEType: "text/plain", + FileInfo: ManualInfo{ + NameValue: "file-1.txt", + SizeValue: 11, + ModeValue: 0o700, + ModTimeValue: time.Time{}, + }, + }, + { + Path: "/path/branch/two", + Type: TypeDirectory, + LinkDestination: "", + UserID: 1337, + GroupID: 5432, + MIMEType: "", + FileInfo: ManualInfo{ + NameValue: "two", + SizeValue: 0, + ModeValue: os.ModeDir | 0o755, + ModTimeValue: time.Time{}, + }, + }, + { + Path: "/path/branch/two/file-2.txt", + Type: TypeRegular, + LinkDestination: "", + UserID: 1337, + GroupID: 5432, + MIMEType: "text/plain", + FileInfo: ManualInfo{ + NameValue: "file-2.txt", + SizeValue: 12, + ModeValue: 0o755, + ModTimeValue: time.Time{}, + }, + }, + { + Path: "/path/file-3.txt", + Type: TypeRegular, + LinkDestination: "", + UserID: 1337, + GroupID: 5432, + MIMEType: "text/plain", + FileInfo: ManualInfo{ + NameValue: "file-3.txt", + SizeValue: 11, + ModeValue: 0o664, + ModTimeValue: time.Time{}, + }, + }, } var actual []Metadata @@ -36,8 +146,6 @@ func TestFileMetadataFromTar(t *testing.T) { } entry.Header.ModTime = time.Time{} - entry.Header.ChangeTime = time.Time{} - entry.Header.AccessTime = time.Time{} actual = append(actual, NewMetadata(entry.Header, contents)) return nil @@ -47,8 +155,9 @@ func TestFileMetadataFromTar(t *testing.T) { t.Fatalf("unable to iterate through tar: %+v", err) } - for _, d := range deep.Equal(expected, actual) { - t.Errorf("diff: %s", d) + assert.Equal(t, len(ex), len(actual)) + for i, e := range ex { + assertMetadataEqual(t, e, actual[i]) } } diff --git a/pkg/file/tarutil_test.go b/pkg/file/tarutil_test.go index e9488c04..036c0c5c 100644 --- a/pkg/file/tarutil_test.go +++ b/pkg/file/tarutil_test.go @@ -65,16 +65,16 @@ func TestMetadataFromTar(t *testing.T) { expected: Metadata{ Path: "/path/branch/two/file-2.txt", LinkDestination: "", - Size: 12, UserID: 1337, GroupID: 5432, Type: TypeRegular, - IsDir: false, - Mode: 0x1ed, MIMEType: "application/octet-stream", - ModTime: time.Date(2019, time.September, 16, 0, 0, 0, 0, time.UTC), - AccessTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), - ChangeTime: time.Time{}, + FileInfo: ManualInfo{ + NameValue: "file-2.txt", + SizeValue: 12, + ModeValue: 0x1ed, + ModTimeValue: time.Date(2019, time.September, 16, 0, 0, 0, 0, time.UTC), + }, }, }, { @@ -83,16 +83,16 @@ func TestMetadataFromTar(t *testing.T) { expected: Metadata{ Path: "/path/branch/two", LinkDestination: "", - Size: 0, UserID: 1337, GroupID: 5432, Type: TypeDirectory, - IsDir: true, - Mode: 0x800001ed, MIMEType: "", - ModTime: time.Date(2019, time.September, 16, 0, 0, 0, 0, time.UTC), - AccessTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), - ChangeTime: time.Time{}, + FileInfo: ManualInfo{ + NameValue: "two", + SizeValue: 0, + ModeValue: 0x800001ed, + ModTimeValue: time.Date(2019, time.September, 16, 0, 0, 0, 0, time.UTC), + }, }, }, } @@ -101,7 +101,7 @@ func TestMetadataFromTar(t *testing.T) { f := getTarFixture(t, test.fixture) metadata, err := MetadataFromTar(f, test.name) assert.NoError(t, err) - assert.Equal(t, test.expected, metadata) + assertMetadataEqual(t, test.expected, metadata) }) } } diff --git a/pkg/filetree/index_test.go b/pkg/filetree/index_test.go index 0cd2150c..a3531794 100644 --- a/pkg/filetree/index_test.go +++ b/pkg/filetree/index_test.go @@ -4,6 +4,7 @@ package filetree import ( + "io/fs" "testing" "github.com/google/go-cmp/cmp" @@ -14,6 +15,14 @@ import ( "github.com/anchore/stereoscope/pkg/file" ) +func basicMetadataComparer(x, y file.Metadata) bool { + // override Metadata.Equal to ignore fields + return x.Path == y.Path && + x.Type == y.Type && + x.MIMEType == y.MIMEType && + x.LinkDestination == y.LinkDestination +} + func commonIndexFixture(t *testing.T) Index { t.Helper() @@ -24,7 +33,7 @@ func commonIndexFixture(t *testing.T) Index { ref, err := tree.AddDir(path) require.NoError(t, err, "failed to add DIR reference to index") require.NotNil(t, ref, "failed to add DIR reference to index (nil ref") - idx.Add(*ref, file.Metadata{Path: string(path), Type: file.TypeDirectory, IsDir: true}) + idx.Add(*ref, file.Metadata{FileInfo: file.ManualInfo{ModeValue: fs.ModeDir}, Path: string(path), Type: file.TypeDirectory}) } addFile := func(path file.Path) { @@ -38,7 +47,7 @@ func commonIndexFixture(t *testing.T) Index { ref, err := tree.AddSymLink(from, to) require.NoError(t, err, "failed to add LINK reference to index") require.NotNil(t, ref, "failed to add LINK reference to index (nil ref") - idx.Add(*ref, file.Metadata{Path: string(from), LinkDestination: string(to), Type: file.TypeSymLink}) + idx.Add(*ref, file.Metadata{FileInfo: file.ManualInfo{ModeValue: fs.ModeSymlink}, Path: string(from), LinkDestination: string(to), Type: file.TypeSymLink}) } // mkdir -p path/branch.d/one @@ -202,42 +211,52 @@ func TestFileCatalog_GetByFileType(t *testing.T) { { Reference: file.Reference{RealPath: "/path"}, Metadata: file.Metadata{ - Path: "/path", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path", + Type: file.TypeDirectory, }, }, { Reference: file.Reference{RealPath: "/path/branch.d"}, Metadata: file.Metadata{ - Path: "/path/branch.d", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d", + Type: file.TypeDirectory, }, }, { Reference: file.Reference{RealPath: "/path/branch.d/one"}, Metadata: file.Metadata{ - Path: "/path/branch.d/one", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d/one", + Type: file.TypeDirectory, }, }, { Reference: file.Reference{RealPath: "/path/branch.d/two"}, Metadata: file.Metadata{ - Path: "/path/branch.d/two", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d/two", + Type: file.TypeDirectory, }, }, { Reference: file.Reference{RealPath: "/path/common"}, Metadata: file.Metadata{ - Path: "/path/common", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/common", + Type: file.TypeDirectory, }, }, }, @@ -299,7 +318,7 @@ func TestFileCatalog_GetByFileType(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } @@ -355,9 +374,11 @@ func TestFileCatalog_GetByExtension(t *testing.T) { Reference: file.Reference{RealPath: "/path/branch.d"}, Metadata: file.Metadata{ - Path: "/path/branch.d", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d", + Type: file.TypeDirectory, }, }, { @@ -453,7 +474,7 @@ func TestFileCatalog_GetByExtension(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } @@ -496,9 +517,11 @@ func TestFileCatalog_GetByBasename(t *testing.T) { { Reference: file.Reference{RealPath: "/path/branch.d"}, Metadata: file.Metadata{ - Path: "/path/branch.d", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d", + Type: file.TypeDirectory, }, }, { @@ -544,7 +567,7 @@ func TestFileCatalog_GetByBasename(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } @@ -595,9 +618,11 @@ func TestFileCatalog_GetByBasenameGlob(t *testing.T) { { Reference: file.Reference{RealPath: "/path/branch.d"}, Metadata: file.Metadata{ - Path: "/path/branch.d", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d", + Type: file.TypeDirectory, }, }, { @@ -643,7 +668,7 @@ func TestFileCatalog_GetByBasenameGlob(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } @@ -733,7 +758,7 @@ func TestFileCatalog_GetByMimeType(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } diff --git a/pkg/image/docker/manifest.go b/pkg/image/docker/manifest.go index 0621e6e9..beb3a478 100644 --- a/pkg/image/docker/manifest.go +++ b/pkg/image/docker/manifest.go @@ -107,7 +107,7 @@ func generateOCIManifest(tarPath string, manifest *dockerManifest) (*v1.Manifest if err != nil { return nil, nil, fmt.Errorf("unable to find layer tar: %w", err) } - layerSizes[idx] = layerMetadata.Size + layerSizes[idx] = layerMetadata.Size() } theManifest, err := assembleOCIManifest(configContents, layerSizes) diff --git a/pkg/image/file_catalog_test.go b/pkg/image/file_catalog_test.go index ca0fefc9..7685707f 100644 --- a/pkg/image/file_catalog_test.go +++ b/pkg/image/file_catalog_test.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "fmt" "io" + "io/fs" "os" "os/exec" "path" @@ -35,18 +36,27 @@ var ( tarCachePath = path.Join(fixturesPath, "tar-cache") ) +func basicMetadataComparer(x, y file.Metadata) bool { + // override Metadata.Equal to ignore fields + return x.Path == y.Path && + x.Type == y.Type && + x.MIMEType == y.MIMEType && + x.LinkDestination == y.LinkDestination +} + func TestFileCatalog_Add(t *testing.T) { ref := file.NewFileReference("/somepath") metadata := file.Metadata{ + FileInfo: file.ManualInfo{ + SizeValue: 1, + ModeValue: fs.ModeDir | 5, + }, Path: "a", LinkDestination: "c", - Size: 1, UserID: 2, GroupID: 3, Type: 4, - IsDir: true, - Mode: 5, } layer := &Layer{ @@ -261,9 +271,11 @@ func TestFileCatalog_GetByExtension(t *testing.T) { Reference: file.Reference{RealPath: "/path/branch.d"}, Metadata: file.Metadata{ - Path: "/path/branch.d", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d", + Type: file.TypeDirectory, }, }, { @@ -359,7 +371,7 @@ func TestFileCatalog_GetByExtension(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } @@ -413,9 +425,11 @@ func TestFileCatalog_GetByBasename(t *testing.T) { { Reference: file.Reference{RealPath: "/path/branch.d"}, Metadata: file.Metadata{ - Path: "/path/branch.d", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d", + Type: file.TypeDirectory, }, }, { @@ -461,7 +475,7 @@ func TestFileCatalog_GetByBasename(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } @@ -523,9 +537,11 @@ func TestFileCatalog_GetByBasenameGlob(t *testing.T) { { Reference: file.Reference{RealPath: "/path/branch.d"}, Metadata: file.Metadata{ - Path: "/path/branch.d", - Type: file.TypeDirectory, - IsDir: true, + FileInfo: file.ManualInfo{ + ModeValue: fs.ModeDir, + }, + Path: "/path/branch.d", + Type: file.TypeDirectory, }, }, { @@ -571,7 +587,7 @@ func TestFileCatalog_GetByBasenameGlob(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } @@ -672,7 +688,7 @@ func TestFileCatalog_GetByMimeType(t *testing.T) { if d := cmp.Diff(tt.want, actual, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(file.Reference{}), - cmpopts.IgnoreFields(file.Metadata{}, "Mode", "GroupID", "UserID", "Size", "ModTime", "AccessTime", "ChangeTime"), + cmp.Comparer(basicMetadataComparer), ); d != "" { t.Errorf("diff: %s", d) } diff --git a/pkg/image/layer.go b/pkg/image/layer.go index ec2b7e50..f803bca6 100644 --- a/pkg/image/layer.go +++ b/pkg/image/layer.go @@ -236,7 +236,7 @@ func layerTarIndexer(ft filetree.Writer, fileCatalog *FileCatalog, size *int64, } if size != nil { - *(size) += metadata.Size + *(size) += metadata.Size() } fileCatalog.addImageReferences(ref.ID(), layerRef, index.Open) @@ -273,7 +273,7 @@ func squashfsVisitor(ft filetree.Writer, fileCatalog *FileCatalog, size *int64, } if size != nil { - *(size) += metadata.Size + *(size) += metadata.Size() } fileCatalog.addImageReferences(fileReference.ID(), layerRef, func() io.ReadCloser { r, err := fsys.Open(path) From 73cb649bcadb0194518bffe99a38a63e51af9704 Mon Sep 17 00:00:00 2001 From: William Murphy Date: Mon, 22 May 2023 11:11:14 -0400 Subject: [PATCH 21/23] Specify platform in integ test images (#181) Otherwise, "make integration" will fail on M1 Macs. Note that this is a workaround which assumes that, if "make integration" is run on an arm64 host, that host is able to run containers from amd64 images. This change enables development on stereoscope on M1 Macs, but may not enable it on other arm64 machines. Signed-off-by: Will Murphy --- .../test-fixtures/image-opaque-directory/Dockerfile | 2 +- test/integration/tools/singularity/Dockerfile | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/test-fixtures/image-opaque-directory/Dockerfile b/test/integration/test-fixtures/image-opaque-directory/Dockerfile index 36905a24..388d536f 100644 --- a/test/integration/test-fixtures/image-opaque-directory/Dockerfile +++ b/test/integration/test-fixtures/image-opaque-directory/Dockerfile @@ -1,4 +1,4 @@ -FROM centos:7 +FROM --platform=linux/amd64 centos:7 RUN curl -sLO https://corretto.aws/downloads/latest/amazon-corretto-11-x64-linux-jdk.rpm RUN rpm -i amazon-corretto-11-x64-linux-jdk.rpm diff --git a/test/integration/tools/singularity/Dockerfile b/test/integration/tools/singularity/Dockerfile index 052479c8..87804450 100644 --- a/test/integration/tools/singularity/Dockerfile +++ b/test/integration/tools/singularity/Dockerfile @@ -1,4 +1,6 @@ -FROM ubuntu:20.04 +FROM --platform=linux/amd64 ubuntu:20.04 +# force platform since https://github.com/sylabs/singularity/releases +# has no arm64 releases. RUN apt-get update && apt-get install curl -y && \ curl -sLO https://github.com/sylabs/singularity/releases/download/v3.10.0/singularity-ce_3.10.0-focal_amd64.deb && \ apt-get install -f ./singularity-ce_3.10.0-focal_amd64.deb -y From ad7eb2176d77dac35f0abb7154d4d5db4c9dc750 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 13:06:32 -0400 Subject: [PATCH 22/23] Bump github.com/docker/distribution (#178) Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.8.1+incompatible to 2.8.2+incompatible. - [Release notes](https://github.com/docker/distribution/releases) - [Commits](https://github.com/docker/distribution/compare/v2.8.1...v2.8.2) --- updated-dependencies: - dependency-name: github.com/docker/distribution dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f4ca26e4..cbb9352c 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect // docker/distribution for https://github.com/advisories/GHSA-qq97-vm5h-rrhg - github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/go.sum b/go.sum index 1c0c4a4e..02ca1498 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4= github.com/docker/cli v20.10.20+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= From 7f87e8b1510e1dbfc12a466cb1999f6c04ee0a67 Mon Sep 17 00:00:00 2001 From: William Murphy Date: Fri, 9 Jun 2023 15:05:19 -0400 Subject: [PATCH 23/23] Change platform selection logic (#189) This reverts commit d7551b7f46f53179922d6229709d3d1602881080. Defaulting to platform for all providers resulted in syft having unnecessary when pulling an image that had a particular digest, if that digest didn't match the architecture of the host running the pull. Revert commit d7551b7f46f53179922d6229709d3d1602881080, which introduced that error, but then add platform defaulting logic back for the OCI Registry Provider, since defaulting there has been specifically requested. Add integ tests to cover the new behavior. Also, update integ tests to use the manifest ID for assertsion, since the RepoDigests array can be empty. Signed-off-by: Will Murphy --- client.go | 51 ++++---- client_test.go | 193 ------------------------------ test/integration/platform_test.go | 94 +++++++++++---- 3 files changed, 97 insertions(+), 241 deletions(-) delete mode 100644 client_test.go diff --git a/client.go b/client.go index ce0f4d16..ea8ee736 100644 --- a/client.go +++ b/client.go @@ -102,13 +102,13 @@ func GetImageFromSource(ctx context.Context, imgStr string, source image.Source, func selectImageProvider(imgStr string, source image.Source, cfg config) (image.Provider, error) { var provider image.Provider tempDirGenerator := rootTempDirGenerator.NewGenerator() - - if err := setPlatform(source, &cfg, runtime.GOARCH); err != nil { - return nil, err - } + platformSelectionUnsupported := fmt.Errorf("specified platform=%q however image source=%q does not support selecting platform", cfg.Platform.String(), source.String()) switch source { case image.DockerTarballSource: + if cfg.Platform != nil { + return nil, platformSelectionUnsupported + } // note: the imgStr is the path on disk to the tar file provider = docker.NewProviderFromTarball(imgStr, tempDirGenerator) case image.DockerDaemonSource: @@ -130,12 +130,22 @@ func selectImageProvider(imgStr string, source image.Source, cfg config) (image. return nil, err } case image.OciDirectorySource: + if cfg.Platform != nil { + return nil, platformSelectionUnsupported + } provider = oci.NewProviderFromPath(imgStr, tempDirGenerator) case image.OciTarballSource: + if cfg.Platform != nil { + return nil, platformSelectionUnsupported + } provider = oci.NewProviderFromTarball(imgStr, tempDirGenerator) case image.OciRegistrySource: + defaultPlatformIfNil(&cfg) provider = oci.NewProviderFromRegistry(imgStr, tempDirGenerator, cfg.Registry, cfg.Platform) case image.SingularitySource: + if cfg.Platform != nil { + return nil, platformSelectionUnsupported + } provider = sif.NewProviderFromPath(imgStr, tempDirGenerator) default: return nil, fmt.Errorf("unable to determine image source") @@ -143,30 +153,19 @@ func selectImageProvider(imgStr string, source image.Source, cfg config) (image. return provider, nil } -func setPlatform(source image.Source, cfg *config, defaultArch string) error { - // we should override the platform based on the host architecture if the user did not specify a platform - // see https://github.com/anchore/stereoscope/issues/149 for more details - defaultPlatform, err := image.NewPlatform(defaultArch) - if err != nil { - log.WithFields("error", err).Warnf("unable to set default platform to %q", runtime.GOARCH) - defaultPlatform = nil - } - - switch source { - case image.DockerTarballSource, image.OciDirectorySource, image.OciTarballSource, image.SingularitySource: - if cfg.Platform != nil { - return fmt.Errorf("specified platform=%q however image source=%q does not support selecting platform", cfg.Platform.String(), source.String()) - } - - case image.DockerDaemonSource, image.PodmanDaemonSource, image.OciRegistrySource: - if cfg.Platform == nil { - cfg.Platform = defaultPlatform +// defaultPlatformIfNil sets the platform to use the host's architecture +// if no platform was specified. The OCI registry provider uses "linux/amd64" +// as a hard-coded default platform, which has surprised customers +// running stereoscope on non-amd64 hosts. If platform is already +// set on the config, or the code can't generate a matching platform, +// do nothing. +func defaultPlatformIfNil(cfg *config) { + if cfg.Platform == nil { + p, err := image.NewPlatform(fmt.Sprintf("linux/%s", runtime.GOARCH)) + if err == nil { + cfg.Platform = p } - - default: - return fmt.Errorf("unable to determine image source to select platform") } - return nil } // GetImage parses the user provided image string and provides an image object; diff --git a/client_test.go b/client_test.go deleted file mode 100644 index 82a420e5..00000000 --- a/client_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package stereoscope - -import ( - "testing" - - "github.com/scylladb/go-set/i8set" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/anchore/stereoscope/pkg/image" -) - -func Test_setPlatform(t *testing.T) { - - expectedSources := i8set.New() - for _, s := range image.AllSources { - expectedSources.Add(int8(s)) - } - actualSources := i8set.New() - - tests := []struct { - name string - source image.Source - defaultArch string - initialPlatform *image.Platform - wantPlatform *image.Platform - wantErr require.ErrorAssertionFunc - }{ - // allow defaults --------------------------------------------------------- - { - name: "docker daemon", - source: image.DockerDaemonSource, - defaultArch: "amd64", - initialPlatform: nil, - wantPlatform: &image.Platform{ - Architecture: "amd64", - OS: "linux", - }, - }, - { - name: "docker daemon (do not override)", - source: image.DockerDaemonSource, - defaultArch: "amd64", - initialPlatform: &image.Platform{ - Architecture: "arm64", // not different than default arch - OS: "linux", - }, - wantPlatform: &image.Platform{ - Architecture: "arm64", // note: did not change - OS: "linux", - }, - }, - { - name: "podman daemon", - source: image.PodmanDaemonSource, - defaultArch: "amd64", - initialPlatform: nil, - wantPlatform: &image.Platform{ - Architecture: "amd64", - OS: "linux", - }, - }, - { - name: "podman daemon (do not override)", - source: image.PodmanDaemonSource, - defaultArch: "amd64", - initialPlatform: &image.Platform{ - Architecture: "arm64", // not different than default arch - OS: "linux", - }, - wantPlatform: &image.Platform{ - Architecture: "arm64", // note: did not change - OS: "linux", - }, - }, - { - name: "OCI registry", - source: image.OciRegistrySource, - defaultArch: "amd64", - initialPlatform: nil, - wantPlatform: &image.Platform{ - Architecture: "amd64", - OS: "linux", - }, - }, - { - name: "OCI registry (do not override)", - source: image.OciRegistrySource, - defaultArch: "amd64", - initialPlatform: &image.Platform{ - Architecture: "arm64", // not different than default arch - OS: "linux", - }, - wantPlatform: &image.Platform{ - Architecture: "arm64", // note: did not change - OS: "linux", - }, - }, - // disallow defaults --------------------------------------------------------- - { - name: "docker tarball", - source: image.DockerTarballSource, - defaultArch: "amd64", - initialPlatform: nil, - wantPlatform: nil, - }, - { - name: "docker tarball (override fails)", - source: image.DockerTarballSource, - defaultArch: "amd64", - initialPlatform: &image.Platform{ - Architecture: "amd64", - OS: "linux", - }, - wantErr: require.Error, - }, - { - name: "OCI dir", - source: image.OciDirectorySource, - defaultArch: "amd64", - initialPlatform: nil, - wantPlatform: nil, - }, - { - name: "OCI dir (override fails)", - source: image.OciDirectorySource, - defaultArch: "amd64", - initialPlatform: &image.Platform{ - Architecture: "amd64", - OS: "linux", - }, - wantErr: require.Error, - }, - { - name: "OCI tarball", - source: image.OciTarballSource, - defaultArch: "amd64", - initialPlatform: nil, - wantPlatform: nil, - }, - { - name: "OCI tarball (override fails)", - source: image.OciTarballSource, - defaultArch: "amd64", - initialPlatform: &image.Platform{ - Architecture: "amd64", - OS: "linux", - }, - wantErr: require.Error, - }, - { - name: "singularity", - source: image.SingularitySource, - defaultArch: "amd64", - initialPlatform: nil, - wantPlatform: nil, - }, - { - name: "singularity (override fails)", - source: image.SingularitySource, - defaultArch: "amd64", - initialPlatform: &image.Platform{ - Architecture: "amd64", - OS: "linux", - }, - wantErr: require.Error, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.wantErr == nil { - tt.wantErr = require.NoError - } - - actualSources.Add(int8(tt.source)) - cfg := config{ - Platform: tt.initialPlatform, - } - err := setPlatform(tt.source, &cfg, tt.defaultArch) - tt.wantErr(t, err) - if err != nil { - return - } - - assert.Equal(t, tt.wantPlatform, cfg.Platform) - }) - } - - diff := i8set.Difference(expectedSources, actualSources) - if !diff.IsEmpty() { - t.Errorf("missing test cases for sources: %v", diff.List()) - } -} diff --git a/test/integration/platform_test.go b/test/integration/platform_test.go index 91e2d503..1206e62e 100644 --- a/test/integration/platform_test.go +++ b/test/integration/platform_test.go @@ -2,8 +2,9 @@ package integration import ( "context" + "encoding/json" "fmt" - "strings" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -14,6 +15,11 @@ import ( ) func TestPlatformSelection(t *testing.T) { + /* + All digests were obtained by: + $ docker image pull --platform $PLATFORM busybox:1.31 + $ docker image inspect busybox:1.31 | jq -r '.[0].Id' + */ imageName := "busybox:1.31" tests := []struct { source image.Source @@ -26,37 +32,49 @@ func TestPlatformSelection(t *testing.T) { source: image.OciRegistrySource, architecture: "arm64", os: "linux", - expectedDigest: "sha256:1ee006886991ad4689838d3a288e0dd3fd29b70e276622f16b67a8922831a853", // direct from repo manifest + expectedDigest: "sha256:19d689bc58fd64da6a46d46512ea965a12b6bfb5b030400e21bc0a04c4ff155e", + }, + { + source: image.OciRegistrySource, + architecture: "s390x", + os: "linux", + expectedDigest: "sha256:5bf065699d2e6ddb8b5f7e30f02edc3cfe15d7400e7101b5b505d61fde01f84c", }, { source: image.OciRegistrySource, architecture: "amd64", os: "linux", - expectedDigest: "sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209", // direct from repo manifest + expectedDigest: "sha256:1c35c441208254cb7c3844ba95a96485388cef9ccc0646d562c7fc026e04c807", }, { source: image.DockerDaemonSource, architecture: "arm64", os: "linux", - expectedDigest: "sha256:dcd4bbdd7ea2360002c684968429a2105997c3ce5821e84bfc2703c5ec984971", // from generated manifest + expectedDigest: "sha256:19d689bc58fd64da6a46d46512ea965a12b6bfb5b030400e21bc0a04c4ff155e", }, { source: image.DockerDaemonSource, architecture: "amd64", os: "linux", - expectedDigest: "sha256:79d3cb76a5a8ba402af164ace87bd0f3e0759979f94caf7247745126359711da", // from generated manifest + expectedDigest: "sha256:1c35c441208254cb7c3844ba95a96485388cef9ccc0646d562c7fc026e04c807", + }, + { + source: image.DockerDaemonSource, + architecture: "s390x", + os: "linux", + expectedDigest: "sha256:5bf065699d2e6ddb8b5f7e30f02edc3cfe15d7400e7101b5b505d61fde01f84c", }, { source: image.PodmanDaemonSource, architecture: "arm64", os: "linux", - expectedDigest: "sha256:dcd4bbdd7ea2360002c684968429a2105997c3ce5821e84bfc2703c5ec984971", // from generated manifest + expectedDigest: "sha256:19d689bc58fd64da6a46d46512ea965a12b6bfb5b030400e21bc0a04c4ff155e", }, { source: image.PodmanDaemonSource, architecture: "amd64", os: "linux", - expectedDigest: "sha256:79d3cb76a5a8ba402af164ace87bd0f3e0759979f94caf7247745126359711da", // from generated manifest + expectedDigest: "sha256:1c35c441208254cb7c3844ba95a96485388cef9ccc0646d562c7fc026e04c807", }, } @@ -71,21 +89,53 @@ func TestPlatformSelection(t *testing.T) { tt.expectedErr(t, err) require.NotNil(t, img) - assert.Equal(t, tt.os, img.Metadata.OS) - assert.Equal(t, tt.architecture, img.Metadata.Architecture) - found := false - if img.Metadata.ManifestDigest == tt.expectedDigest { - found = true - } - for _, d := range img.Metadata.RepoDigests { - if found { - break - } - if strings.Contains(d, tt.expectedDigest) { - found = true - } - } - assert.True(t, found, "could not find repo digest that matches the expected digest:\nfound manifest digest: %+v\nfound repo digests: %+v\nexpected digest: %+v", img.Metadata.ManifestDigest, img.Metadata.RepoDigests, tt.expectedDigest) + assertArchAndOs(t, img, tt.os, tt.architecture) + assert.Equal(t, tt.expectedDigest, img.Metadata.ID) }) } } + +func TestDigestThatNarrowsToOnePlatform(t *testing.T) { + // This digest is busybox:1.31 on linux/s390x + // Test assumes that the host running these tests _isn't_ linux/s390x, but the behavior + // should be the same regardless. + imageStrWithDigest := "busybox:1.31@sha256:91c15b1ba6f408a648be60f8c047ef79058f26fa640025f374281f31c8704387" + tests := []struct { + name string + source image.Source + }{ + { + name: "docker", + source: image.DockerDaemonSource, + }, + { + name: "registry", + source: image.OciRegistrySource, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + img, err := stereoscope.GetImageFromSource(context.TODO(), imageStrWithDigest, tt.source) + assert.NoError(t, err) + assertArchAndOs(t, img, "linux", "s390x") + }) + } +} + +func TestDefaultPlatformWithOciRegistry(t *testing.T) { + img, err := stereoscope.GetImageFromSource(context.TODO(), "busybox:1.31", image.OciRegistrySource) + require.NoError(t, err) + assertArchAndOs(t, img, "linux", runtime.GOARCH) +} + +func assertArchAndOs(t *testing.T, img *image.Image, os string, architecture string) { + type platform struct { + Architecture string `json:"architecture"` + Os string `json:"os"` + } + var got platform + err := json.Unmarshal(img.Metadata.RawConfig, &got) + require.NoError(t, err) + assert.Equal(t, os, got.Os) + assert.Equal(t, architecture, got.Architecture) +}