diff --git a/cmd/attest.go b/cmd/attest.go index 1a7b430aa..db2df8dae 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -131,7 +131,7 @@ func selectPassFunc(keypath string) (cosign.PassFunc, error) { func attestExec(ctx context.Context, _ *cobra.Command, args []string) error { // can only be an image for attestation or OCI DIR userInput := args[0] - si, err := source.ParseInput(userInput, false) + si, err := source.ParseInput(userInput, appConfig.Platform, false) if err != nil { return fmt.Errorf("could not generate source input for attest command: %w", err) } @@ -304,6 +304,11 @@ func setAttestFlags(flags *pflag.FlagSet) { "output", "o", formatAliases(syftjson.ID)[0], fmt.Sprintf("the SBOM format encapsulated within the attestation, available options=%v", formatAliases(attestFormats...)), ) + + flags.StringP( + "platform", "", "", + "an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')", + ) } func bindAttestConfigOptions(flags *pflag.FlagSet) error { diff --git a/cmd/cmd.go b/cmd/cmd.go index 7f178934f..f60b017d6 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -72,8 +72,8 @@ func initCmdAliasBindings() { panic(err) } case attestCmd: - // the --output option is an independently defined flag, but a shared config option - if err = bindSharedOutputConfigOption(attestCmd.Flags()); err != nil { + // the --output and --platform options are independently defined flags, but a shared config option + if err = bindSharedConfigOption(attestCmd.Flags()); err != nil { panic(err) } // even though the root command or packages command is NOT being run, we still need default bindings @@ -90,11 +90,15 @@ func initCmdAliasBindings() { } } -func bindSharedOutputConfigOption(flags *pflag.FlagSet) error { +func bindSharedConfigOption(flags *pflag.FlagSet) error { if err := viper.BindPFlag("output", flags.Lookup("output")); err != nil { return err } + if err := viper.BindPFlag("platform", flags.Lookup("platform")); err != nil { + return err + } + return nil } diff --git a/cmd/packages.go b/cmd/packages.go index b14331833..059173a8b 100644 --- a/cmd/packages.go +++ b/cmd/packages.go @@ -6,17 +6,15 @@ import ( "io/ioutil" "os" - "github.com/anchore/syft/internal/formats/table" - - "github.com/anchore/syft/syft" - "github.com/anchore/stereoscope" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/anchore" "github.com/anchore/syft/internal/bus" + "github.com/anchore/syft/internal/formats/table" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/ui" "github.com/anchore/syft/internal/version" + "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/pkg/cataloger" @@ -112,6 +110,11 @@ func setPackageFlags(flags *pflag.FlagSet) { "file to write the default report output to (default is STDOUT)", ) + flags.StringP( + "platform", "", "", + "an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')", + ) + // Upload options ////////////////////////////////////////////////////////// flags.StringP( "host", "H", "", @@ -153,7 +156,7 @@ func bindPackagesConfigOptions(flags *pflag.FlagSet) error { if err := bindExclusivePackagesConfigOptions(flags); err != nil { return err } - if err := bindSharedOutputConfigOption(flags); err != nil { + if err := bindSharedConfigOption(flags); err != nil { return err } return nil @@ -232,7 +235,7 @@ func packagesExec(_ *cobra.Command, args []string) error { // could be an image or a directory, with or without a scheme userInput := args[0] - si, err := source.ParseInput(userInput, true) + si, err := source.ParseInput(userInput, appConfig.Platform, true) if err != nil { return fmt.Errorf("could not generate source input for packages command: %w", err) } diff --git a/cmd/power_user.go b/cmd/power_user.go index 59c52b262..8b9df4c71 100644 --- a/cmd/power_user.go +++ b/cmd/power_user.go @@ -114,7 +114,7 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error { return } - si, err := source.ParseInput(userInput, true) + si, err := source.ParseInput(userInput, appConfig.Platform, true) if err != nil { errs <- err return diff --git a/go.mod b/go.mod index 31e02eb79..75c536c11 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29 - github.com/anchore/stereoscope v0.0.0-20220301220648-8aa8a4a0bf50 + github.com/anchore/stereoscope v0.0.0-20220304014943-22a4c2bb498e github.com/antihax/optional v1.0.0 github.com/bmatcuk/doublestar/v4 v4.0.2 github.com/docker/docker v20.10.12+incompatible diff --git a/go.sum b/go.sum index f10a8a515..1863d69de 100644 --- a/go.sum +++ b/go.sum @@ -284,8 +284,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29 h1:K9LfnxwhqvihqU0+MF325FNy7fsKV9EGaUxdfR4gnWk= github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29/go.mod h1:Oc1UkGaJwY6ND6vtAqPSlYrptKRJngHwkwB6W7l1uP0= -github.com/anchore/stereoscope v0.0.0-20220301220648-8aa8a4a0bf50 h1:+Fe67xv6NRLdZ5V9X2kw959XsKQCUx5/6RL/wQZfs44= -github.com/anchore/stereoscope v0.0.0-20220301220648-8aa8a4a0bf50/go.mod h1:QpDHHV2h1NNfu7klzU75XC8RvSlaPK6HHgi0dy8A6sk= +github.com/anchore/stereoscope v0.0.0-20220304014943-22a4c2bb498e h1:+nlnKqlR8UwG/PhTD8qSN7RphXOI9fK77q5p3PQLx0k= +github.com/anchore/stereoscope v0.0.0-20220304014943-22a4c2bb498e/go.mod h1:Juw0DqHmBSAMFVcT/kRM0GhLdIEAaiCQbq9mdCp47dY= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= diff --git a/internal/config/application.go b/internal/config/application.go index 35be6cb54..d24d78bb0 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -46,6 +46,7 @@ type Application struct { Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"` Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"` Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"` + Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` } // PowerUserCatalogerEnabledDefault switches all catalogers to be enabled when running power-user command diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 80bc5162e..f9441d41c 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -163,7 +163,9 @@ ], "manifest": "ZXlKelkyaGxiV0ZXWlhKemFXOXVJam95TENKdFpXUnBZVlI1Y0dVaU9pSmguLi4=", "config": "ZXlKaGNtTm9hWFJsWTNSMWNtVWlPaUpoYldRMk5DSXNJbU52Ym1acC4uLg==", - "repoDigests": [] + "repoDigests": [], + "architecture": "", + "os": "" } }, "distro": { diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden index d117183f5..775d2fb05 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden @@ -1,7 +1,7 @@ { "artifacts": [ { - "id": "3a6c7061f86ac3f7", + "id": "d9527e708c11f8b9", "name": "package-1", "version": "1.0.1", "type": "python", @@ -9,7 +9,7 @@ "locations": [ { "path": "/somefile-1.txt", - "layerID": "sha256:135c16aca35f76c25a18cb6650c621a46b8b79864ad6f2834167de2679bb587d" + "layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59" } ], "licenses": [ @@ -32,7 +32,7 @@ } }, { - "id": "4e916af2d387ce9", + "id": "73f796c846875b9e", "name": "package-2", "version": "2.0.1", "type": "deb", @@ -40,7 +40,7 @@ "locations": [ { "path": "/somefile-2.txt", - "layerID": "sha256:c751a2f31455b3049bcab3e3af5861c9431116c9f4a46213e44dbeff8ab36985" + "layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec" } ], "licenses": [], @@ -67,7 +67,7 @@ "type": "image", "target": { "userInput": "user-image-input", - "imageID": "sha256:9998e833a66442934ad948e5cbe898630773c369ef16517623254fa46edd171b", + "imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca", "manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368", "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "tags": [ @@ -77,18 +77,20 @@ "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:135c16aca35f76c25a18cb6650c621a46b8b79864ad6f2834167de2679bb587d", + "digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59", "size": 22 }, { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:c751a2f31455b3049bcab3e3af5861c9431116c9f4a46213e44dbeff8ab36985", + "digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec", "size": 16 } ], - "manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1Njo5OTk4ZTgzM2E2NjQ0MjkzNGFkOTQ4ZTVjYmU4OTg2MzA3NzNjMzY5ZWYxNjUxNzYyMzI1NGZhNDZlZGQxNzFiIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjoxMzVjMTZhY2EzNWY3NmMyNWExOGNiNjY1MGM2MjFhNDZiOGI3OTg2NGFkNmYyODM0MTY3ZGUyNjc5YmI1ODdkIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmM3NTFhMmYzMTQ1NWIzMDQ5YmNhYjNlM2FmNTg2MWM5NDMxMTE2YzlmNGE0NjIxM2U0NGRiZWZmOGFiMzY5ODUifV19", - "config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjItMDEtMThUMjA6MzA6MTMuMDQ0Njg1NTg5WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIyLTAxLTE4VDIwOjMwOjEyLjE5OTA3MjQxOVoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjItMDEtMThUMjA6MzA6MTMuMDQ0Njg1NTg5WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6MTM1YzE2YWNhMzVmNzZjMjVhMThjYjY2NTBjNjIxYTQ2YjhiNzk4NjRhZDZmMjgzNDE2N2RlMjY3OWJiNTg3ZCIsInNoYTI1NjpjNzUxYTJmMzE0NTViMzA0OWJjYWIzZTNhZjU4NjFjOTQzMTExNmM5ZjRhNDYyMTNlNDRkYmVmZjhhYjM2OTg1Il19fQ==", - "repoDigests": [] + "manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19", + "config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==", + "repoDigests": [], + "architecture": "", + "os": "" } }, "distro": { diff --git a/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index 61b0a337e..c1b1d2b79 100644 Binary files a/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden and b/internal/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden differ diff --git a/syft/source/image_metadata.go b/syft/source/image_metadata.go index 9067a330b..0d70ed775 100644 --- a/syft/source/image_metadata.go +++ b/syft/source/image_metadata.go @@ -15,6 +15,9 @@ type ImageMetadata struct { RawManifest []byte `json:"manifest"` RawConfig []byte `json:"config"` RepoDigests []string `json:"repoDigests"` + Architecture string `json:"architecture"` + Variant string `json:"architectureVariant,omitempty"` + OS string `json:"os"` } // LayerMetadata represents all static metadata that defines what a container image layer is. @@ -42,6 +45,9 @@ func NewImageMetadata(img *image.Image, userInput string) ImageMetadata { RawConfig: img.Metadata.RawConfig, RawManifest: img.Metadata.RawManifest, RepoDigests: img.Metadata.RepoDigests, + Architecture: img.Metadata.Architecture, + Variant: img.Metadata.Variant, + OS: img.Metadata.OS, } // populate image metadata diff --git a/syft/source/source.go b/syft/source/source.go index 751dc1c0b..6e36a1779 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -40,12 +40,13 @@ type Input struct { Scheme Scheme ImageSource image.Source Location string + Platform string autoDetectAvailableImageSources bool } // ParseInput generates a source Input that can be used as an argument to generate a new source // from specific providers including a registry. -func ParseInput(userInput string, detectAvailableImageSources bool) (*Input, error) { +func ParseInput(userInput string, platform string, detectAvailableImageSources bool) (*Input, error) { fs := afero.NewOsFs() scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput) if err != nil { @@ -71,12 +72,17 @@ func ParseInput(userInput string, detectAvailableImageSources bool) (*Input, err } } + if scheme != ImageScheme && platform != "" { + return nil, fmt.Errorf("cannot specify a platform for a non-image source") + } + // collect user input for downstream consumption return &Input{ UserInput: userInput, Scheme: scheme, ImageSource: source, Location: location, + Platform: platform, autoDetectAvailableImageSources: detectAvailableImageSources, }, nil } @@ -147,10 +153,19 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions) opts = append(opts, stereoscope.WithRegistryOptions(*registryOptions)) } + if in.Platform != "" { + opts = append(opts, stereoscope.WithPlatform(in.Platform)) + } + img, err := stereoscope.GetImageFromSource(ctx, in.Location, in.ImageSource, opts...) + cleanup := func() { + if err := img.Cleanup(); err != nil { + log.Warnf("unable to cleanup image=%q: %w", in.UserInput, err) + } + } if err == nil { // Success on the first try! - return img, stereoscope.Cleanup, nil + return img, cleanup, nil } scheme := parseScheme(in.UserInput) @@ -180,11 +195,12 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions) in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput) } img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...) - if err != nil { - return nil, nil, err + cleanup = func() { + if err := img.Cleanup(); err != nil { + log.Warnf("unable to cleanup image=%q: %w", in.UserInput, err) + } } - - return img, stereoscope.Cleanup, nil + return img, cleanup, err } func generateDirectorySource(fs afero.Fs, location string) (*Source, func(), error) { diff --git a/syft/source/source_test.go b/syft/source/source_test.go index 433f41094..8945e36cd 100644 --- a/syft/source/source_test.go +++ b/syft/source/source_test.go @@ -26,22 +26,34 @@ func TestParseInput(t *testing.T) { tests := []struct { name string input string + platform string expected Scheme + errFn require.ErrorAssertionFunc }{ { name: "ParseInput parses a file input", input: "test-fixtures/image-simple/file-1.txt", expected: FileScheme, }, + { + name: "errors out when using platform for non-image scheme", + input: "test-fixtures/image-simple/file-1.txt", + platform: "arm64", + errFn: require.Error, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - sourceInput, err := ParseInput(test.input, true) - if err != nil { - t.Errorf("failed to ParseInput") + if test.errFn == nil { + test.errFn = require.NoError + } + sourceInput, err := ParseInput(test.input, test.platform, true) + test.errFn(t, err) + if test.expected != "" { + require.NotNil(t, sourceInput) + assert.Equal(t, sourceInput.Scheme, test.expected) } - assert.Equal(t, sourceInput.Scheme, test.expected) }) } } @@ -452,7 +464,7 @@ func TestDirectoryExclusions(t *testing.T) { registryOpts := &image.RegistryOptions{} for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - sourceInput, err := ParseInput("dir:"+test.input, false) + sourceInput, err := ParseInput("dir:"+test.input, "", false) require.NoError(t, err) src, fn, err := New(*sourceInput, registryOpts, test.exclusions) defer fn() @@ -546,7 +558,7 @@ func TestImageExclusions(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input) - sourceInput, err := ParseInput(archiveLocation, false) + sourceInput, err := ParseInput(archiveLocation, "", false) require.NoError(t, err) src, fn, err := New(*sourceInput, registryOpts, test.exclusions) defer fn() diff --git a/test/cli/packages_cmd_test.go b/test/cli/packages_cmd_test.go index 0a8f4ac55..44bd7a92a 100644 --- a/test/cli/packages_cmd_test.go +++ b/test/cli/packages_cmd_test.go @@ -184,6 +184,14 @@ func TestPackagesCmdFlags(t *testing.T) { assertInOutput("search-indexed-archives: false"), }, }, + { + name: "platform-option-wired-up", + args: []string{"packages", "--platform", "arm64", "-o", "json", "registry:busybox:1.31"}, + assertions: []traitAssertion{ + assertInOutput("sha256:1ee006886991ad4689838d3a288e0dd3fd29b70e276622f16b67a8922831a853"), // linux/arm64 image digest + assertSuccessfulReturnCode, + }, + }, } for _, test := range tests { diff --git a/test/integration/catalog_packages_test.go b/test/integration/catalog_packages_test.go index 7747a5536..b069a5cab 100644 --- a/test/integration/catalog_packages_test.go +++ b/test/integration/catalog_packages_test.go @@ -24,7 +24,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) { for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) { // in case of future alteration where state is persisted, assume no dependency is safe to reuse userInput := "docker-archive:" + tarPath - sourceInput, err := source.ParseInput(userInput, false) + sourceInput, err := source.ParseInput(userInput, "", false) require.NoError(b, err) theSource, cleanupSource, err := source.New(*sourceInput, nil, nil) b.Cleanup(cleanupSource) diff --git a/test/integration/utils_test.go b/test/integration/utils_test.go index 3b8866786..fd33eba82 100644 --- a/test/integration/utils_test.go +++ b/test/integration/utils_test.go @@ -17,7 +17,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (sbom.SBOM, *sou imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName) tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName) userInput := "docker-archive:" + tarPath - sourceInput, err := source.ParseInput(userInput, false) + sourceInput, err := source.ParseInput(userInput, "", false) require.NoError(t, err) theSource, cleanupSource, err := source.New(*sourceInput, nil, nil) t.Cleanup(cleanupSource) @@ -52,7 +52,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (sbom.SBOM, *sou func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) { userInput := "dir:" + dir - sourceInput, err := source.ParseInput(userInput, false) + sourceInput, err := source.ParseInput(userInput, "", false) require.NoError(t, err) theSource, cleanupSource, err := source.New(*sourceInput, nil, nil) t.Cleanup(cleanupSource)