From 3c2601aff6dcf63a53a3f68f11c58817491673f0 Mon Sep 17 00:00:00 2001 From: Thomas Cooper <57812123+coopernetes@users.noreply.github.com> Date: Fri, 6 Oct 2023 09:00:03 -0400 Subject: [PATCH 1/5] feat: --platform flag - for images under test, pass the new --platform flag to pull the corresponding image if it is multi-platform capable - test execution via CreateContainerOptions passes in platform. If unset, will default to linux/amd64 --- cmd/container-structure-test/app/cmd/test.go | 4 +++- go.mod | 6 ++++-- go.sum | 8 ++++---- pkg/config/options.go | 1 + pkg/drivers/docker_driver.go | 6 ++++++ pkg/drivers/driver.go | 1 + 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/cmd/container-structure-test/app/cmd/test.go b/cmd/container-structure-test/app/cmd/test.go index b22edcc9..ae574224 100644 --- a/cmd/container-structure-test/app/cmd/test.go +++ b/cmd/container-structure-test/app/cmd/test.go @@ -99,6 +99,7 @@ func run(out io.Writer) error { Save: opts.Save, Metadata: opts.Metadata, Runtime: opts.Runtime, + Platform: opts.Platform, } var err error @@ -170,6 +171,7 @@ func run(out io.Writer) error { logrus.Fatalf("error connecting to daemon: %v", err) } if err = client.PullImage(docker.PullImageOptions{ + Platform: opts.Platform, Repository: ref.Context().RepositoryStr(), Tag: ref.Identifier(), Registry: ref.Context().RegistryStr(), @@ -223,7 +225,7 @@ func AddTestFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&opts.Driver, "driver", "d", "docker", "driver to use when running tests") cmd.Flags().StringVar(&opts.Metadata, "metadata", "", "path to image metadata file") cmd.Flags().StringVar(&opts.Runtime, "runtime", "", "runtime to use with docker driver") - + cmd.Flags().StringVar(&opts.Platform, "platform", "linux/amd64", "Set platform if host is multi-platform capable") cmd.Flags().BoolVar(&opts.Pull, "pull", false, "force a pull of the image before running tests") cmd.MarkFlagsMutuallyExclusive("image-from-oci-layout", "pull") cmd.Flags().BoolVar(&opts.Save, "save", false, "preserve created containers after test run") diff --git a/go.mod b/go.mod index e6cf1520..0e5096a6 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/GoogleContainerTools/container-diff v0.17.1-0.20230727210151-35d9770aeea3 - github.com/fsouza/go-dockerclient v1.9.7 + github.com/fsouza/go-dockerclient v1.10.0 github.com/google/go-cmp v0.5.9 github.com/google/go-containerregistry v0.15.2 github.com/joho/godotenv v1.5.1 @@ -16,6 +16,8 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) +exclude github.com/docker/docker v24.0.6+incompatible // indirect + require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect @@ -32,7 +34,7 @@ require ( github.com/klauspost/compress v1.16.5 // indirect github.com/kr/text v0.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/moby/patternmatcher v0.5.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/morikuni/aec v1.0.0 // indirect diff --git a/go.sum b/go.sum index 30bf738c..8276fb3b 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 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/fsouza/go-dockerclient v1.3.6/go.mod h1:ptN6nXBwrXuiHAz2TYGOFCBB1aKGr371sGjMFdJEr1A= -github.com/fsouza/go-dockerclient v1.9.7 h1:FlIrT71E62zwKgRvCvWGdxRD+a/pIy+miY/n3MXgfuw= -github.com/fsouza/go-dockerclient v1.9.7/go.mod h1:vx9C32kE2D15yDSOMCDaAEIARZpDQDFBHeqL3MgQy/U= +github.com/fsouza/go-dockerclient v1.10.0 h1:ppSBsbR60I1DFbV4Ag7LlHlHakHFRNLk9XakATW1yVQ= +github.com/fsouza/go-dockerclient v1.10.0/go.mod h1:+iNzAW78AzClIBTZ6WFjkaMvOgz68GyCJ236b1opLTs= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= 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= @@ -148,8 +148,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= -github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= diff --git a/pkg/config/options.go b/pkg/config/options.go index 86df8c05..576af6a2 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -22,6 +22,7 @@ type StructureTestOptions struct { DefaultImageTag string Driver string Runtime string + Platform string Metadata string TestReport string ConfigFiles []string diff --git a/pkg/drivers/docker_driver.go b/pkg/drivers/docker_driver.go index c71bcb46..2dea126e 100644 --- a/pkg/drivers/docker_driver.go +++ b/pkg/drivers/docker_driver.go @@ -42,6 +42,7 @@ type DockerDriver struct { env map[string]string save bool runtime string + platform string runOpts unversioned.ContainerRunOptions } @@ -57,6 +58,7 @@ func NewDockerDriver(args DriverConfig) (Driver, error) { env: nil, save: args.Save, runtime: args.Runtime, + platform: args.Platform, runOpts: args.RunOpts, }, nil } @@ -98,6 +100,7 @@ func (d *DockerDriver) Destroy() { func (d *DockerDriver) SetEnv(envVars []unversioned.EnvVar) error { env := d.processEnvVars(envVars) container, err := d.cli.CreateContainer(docker.CreateContainerOptions{ + Platform: d.platform, Config: &docker.Config{ Image: d.currentImage, Env: env, @@ -205,6 +208,7 @@ func (d *DockerDriver) retrieveTar(path string) (*tar.Reader, error) { // this contains a placeholder command which does not get run, since // the client doesn't allow creating a container without a command. container, err := d.cli.CreateContainer(docker.CreateContainerOptions{ + Platform: d.platform, Config: &docker.Config{ Image: d.currentImage, Cmd: []string{utils.NoopCommand}, @@ -316,6 +320,7 @@ func (d *DockerDriver) ReadDir(target string) ([]os.FileInfo, error) { // and sets that image as the new "current image" func (d *DockerDriver) runAndCommit(env []string, command []string) (string, error) { createOpts := docker.CreateContainerOptions{ + Platform: d.platform, Config: &docker.Config{ Image: d.currentImage, Env: env, @@ -365,6 +370,7 @@ func (d *DockerDriver) runAndCommit(env []string, command []string) (string, err func (d *DockerDriver) exec(env []string, command []string) (string, string, int, error) { createOpts := docker.CreateContainerOptions{ + Platform: d.platform, Config: &docker.Config{ Image: d.currentImage, Env: env, diff --git a/pkg/drivers/driver.go b/pkg/drivers/driver.go index d6726c43..d743a85f 100644 --- a/pkg/drivers/driver.go +++ b/pkg/drivers/driver.go @@ -33,6 +33,7 @@ type DriverConfig struct { Save bool // used by Docker/Tar drivers Metadata string // used by Host driver Runtime string // used by Docker driver + Platform string // used by Docker driver RunOpts unversioned.ContainerRunOptions // used by Docker driver } From bcf1a902a5240df36980c97189a0407d802b1f34 Mon Sep 17 00:00:00 2001 From: Thomas Cooper <57812123+coopernetes@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:18:06 -0400 Subject: [PATCH 2/5] add e2e tests for --platform usage gofmt tidy specify correct folder for platform tests --- cmd/container-structure-test/app/cmd/test.go | 2 +- pkg/config/options.go | 2 +- pkg/drivers/docker_driver.go | 2 +- pkg/drivers/driver.go | 2 +- tests/structure_test_tests.sh | 49 +++++++++++++++++++- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/cmd/container-structure-test/app/cmd/test.go b/cmd/container-structure-test/app/cmd/test.go index ae574224..2f31a4f5 100644 --- a/cmd/container-structure-test/app/cmd/test.go +++ b/cmd/container-structure-test/app/cmd/test.go @@ -171,7 +171,7 @@ func run(out io.Writer) error { logrus.Fatalf("error connecting to daemon: %v", err) } if err = client.PullImage(docker.PullImageOptions{ - Platform: opts.Platform, + Platform: opts.Platform, Repository: ref.Context().RepositoryStr(), Tag: ref.Identifier(), Registry: ref.Context().RegistryStr(), diff --git a/pkg/config/options.go b/pkg/config/options.go index 576af6a2..e819f1e4 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -22,7 +22,7 @@ type StructureTestOptions struct { DefaultImageTag string Driver string Runtime string - Platform string + Platform string Metadata string TestReport string ConfigFiles []string diff --git a/pkg/drivers/docker_driver.go b/pkg/drivers/docker_driver.go index 2dea126e..40960463 100644 --- a/pkg/drivers/docker_driver.go +++ b/pkg/drivers/docker_driver.go @@ -58,7 +58,7 @@ func NewDockerDriver(args DriverConfig) (Driver, error) { env: nil, save: args.Save, runtime: args.Runtime, - platform: args.Platform, + platform: args.Platform, runOpts: args.RunOpts, }, nil } diff --git a/pkg/drivers/driver.go b/pkg/drivers/driver.go index d743a85f..a2145696 100644 --- a/pkg/drivers/driver.go +++ b/pkg/drivers/driver.go @@ -33,7 +33,7 @@ type DriverConfig struct { Save bool // used by Docker/Tar drivers Metadata string // used by Host driver Runtime string // used by Docker driver - Platform string // used by Docker driver + Platform string // used by Docker driver RunOpts unversioned.ContainerRunOptions // used by Docker driver } diff --git a/tests/structure_test_tests.sh b/tests/structure_test_tests.sh index 632ca9d5..bd8182cc 100755 --- a/tests/structure_test_tests.sh +++ b/tests/structure_test_tests.sh @@ -19,6 +19,10 @@ #End to end tests to make sure the structure tests do what we expect them #to do on a known quantity, the latest debian docker image. +if [[ -n "$DEBUG" ]]; +then + set -x +fi failures=0 # Get the architecture to load the right configurations @@ -230,7 +234,7 @@ then echo "$res" echo "$code" failures=$((failures +1)) -else +else echo "PASS: oci failing test case" fi @@ -242,10 +246,51 @@ then echo "$res" echo "$code" failures=$((failures +1)) -else +else echo "PASS: oci success test case" fi +HEADER "Platform test cases" + +docker run --rm --privileged tonistiigi/binfmt --install all > /dev/null +res=$(./out/container-structure-test test --image "$test_image" --platform="linux/$go_architecture" --config "${test_config_dir}/ubuntu_20_04_test.yaml" 2>&1) +code=$? +if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; +then + echo "FAIL: current host platform test case" + echo "$res" + echo "$code" + failures=$((failures +1)) +else + echo "PASS: current host platform test case" +fi + +res=$(./out/container-structure-test test --image "$test_image" --platform="linux/riscv64" --config "${test_config_dir}/ubuntu_20_04_test.yaml" 2>&1) +code=$? +if ! [[ ("$res" =~ image\ with\ reference.+was\ found\ but\ does\ not\ match\ the\ specified\ platform:\ wanted\ linux\/\riscv64,\ actual:\ linux\/$go_architecture && "$code" == "1") ]]; +then + echo "FAIL: platform failing test case" + echo "$res" + echo "$code" + failures=$((failures +1)) +else + echo "PASS: platform failing test case" +fi + +test_config_dir="${test_dir}/s390x" +res=$(./out/container-structure-test test --image "$test_image" --platform="linux/s390x" --pull --config "${test_config_dir}/ubuntu_20_04_test.yaml" 2>&1) +code=$? +if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; +then + echo "FAIL: platform w/ --pull test case" + echo "$res" + echo "$code" + failures=$((failures +1)) +else + echo "PASS: platform w/ --pull test case" +fi + + if [ $failures -gt 0 ]; then echo "Some tests did not pass. $failures" exit 1 From debfcde6387ab26d6e0faa9a403bd8ba32f26912 Mon Sep 17 00:00:00 2001 From: Thomas Cooper <57812123+coopernetes@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:32:43 -0400 Subject: [PATCH 3/5] set default value from host runtime --- cmd/container-structure-test/app/cmd/test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/container-structure-test/app/cmd/test.go b/cmd/container-structure-test/app/cmd/test.go index 2f31a4f5..48857959 100644 --- a/cmd/container-structure-test/app/cmd/test.go +++ b/cmd/container-structure-test/app/cmd/test.go @@ -18,7 +18,7 @@ import ( "fmt" "io" "os" - + "runtime" "github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app/cmd/test" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -225,7 +225,7 @@ func AddTestFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&opts.Driver, "driver", "d", "docker", "driver to use when running tests") cmd.Flags().StringVar(&opts.Metadata, "metadata", "", "path to image metadata file") cmd.Flags().StringVar(&opts.Runtime, "runtime", "", "runtime to use with docker driver") - cmd.Flags().StringVar(&opts.Platform, "platform", "linux/amd64", "Set platform if host is multi-platform capable") + cmd.Flags().StringVar(&opts.Platform, "platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), "Set platform if host is multi-platform capable") cmd.Flags().BoolVar(&opts.Pull, "pull", false, "force a pull of the image before running tests") cmd.MarkFlagsMutuallyExclusive("image-from-oci-layout", "pull") cmd.Flags().BoolVar(&opts.Save, "save", false, "preserve created containers after test run") From 54e7f7ac60a36b4d8b969bbda1f78ebe98f150d5 Mon Sep 17 00:00:00 2001 From: Thomas Cooper <57812123+coopernetes@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:34:09 -0400 Subject: [PATCH 4/5] update flags section in docs --- README.md | 29 ++++++++++++++++------------- tests/structure_test_tests.sh | 4 ---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index ae852d71..595aa11b 100644 --- a/README.md +++ b/README.md @@ -378,19 +378,22 @@ container_structure_test( ### Flags: `container-structure-test test -h` ``` - -c, --config stringArray test config files - -d, --driver string driver to use when running tests (default "docker") - -f, --force force run of host driver (without user prompt) - -h, --help help for test - -i, --image string path to test image - --metadata string path to image metadata file - --no-color no color in the output - -o, --output string output format for the test report (available format: text, json, junit) (default "text") - --pull force a pull of the image before running tests - -q, --quiet flag to suppress output - --runtime string runtime to use with docker driver - --save preserve created containers after test run - --test-report string generate test report and write it to specified file (supported format: json, junit; default: json) + -c, --config stringArray test config files + --default-image-tag string default image tag to used when loading images to the daemon. required when --image-from-oci-layout refers to a oci layout lacking the reference annotation. + -d, --driver string driver to use when running tests (default "docker") + -f, --force force run of host driver (without user prompt) + -h, --help help for test + -i, --image string path to test image + --image-from-oci-layout string path to the oci layout to test against + --metadata string path to image metadata file + --no-color no color in the output + -o, --output string output format for the test report (available format: text, json, junit) (default "text") + --platform string Set platform if host is multi-platform capable (default "linux/amd64") + --pull force a pull of the image before running tests + -q, --quiet flag to suppress output + --runtime string runtime to use with docker driver + --save preserve created containers after test run + --test-report string generate test report and write it to specified file (supported format: json, junit; default: json) ``` See this [example repo](https://github.com/nkubala/structure-test-examples) for a full working example. diff --git a/tests/structure_test_tests.sh b/tests/structure_test_tests.sh index bd8182cc..39bba2bc 100755 --- a/tests/structure_test_tests.sh +++ b/tests/structure_test_tests.sh @@ -19,10 +19,6 @@ #End to end tests to make sure the structure tests do what we expect them #to do on a known quantity, the latest debian docker image. -if [[ -n "$DEBUG" ]]; -then - set -x -fi failures=0 # Get the architecture to load the right configurations From 6d65644c41da4ad362ed7fb7f8e0c78d5d069b9c Mon Sep 17 00:00:00 2001 From: Thomas Cooper <57812123+coopernetes@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:05:30 -0400 Subject: [PATCH 5/5] only need to support linux for running containers MacOS cannot run containers natively. M1/M2 defaults to linux/arm64 so the "os" field as part of --platform only needs to use linux/ as a default with GOARCH read in for the particular CPU architecture. Same deal with Windows, Docker on windows is virtualized Linux (AFAIU) --- cmd/container-structure-test/app/cmd/test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/container-structure-test/app/cmd/test.go b/cmd/container-structure-test/app/cmd/test.go index 48857959..163e6909 100644 --- a/cmd/container-structure-test/app/cmd/test.go +++ b/cmd/container-structure-test/app/cmd/test.go @@ -225,7 +225,7 @@ func AddTestFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&opts.Driver, "driver", "d", "docker", "driver to use when running tests") cmd.Flags().StringVar(&opts.Metadata, "metadata", "", "path to image metadata file") cmd.Flags().StringVar(&opts.Runtime, "runtime", "", "runtime to use with docker driver") - cmd.Flags().StringVar(&opts.Platform, "platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), "Set platform if host is multi-platform capable") + cmd.Flags().StringVar(&opts.Platform, "platform", fmt.Sprintf("linux/%s", runtime.GOARCH), "Set platform if host is multi-platform capable") cmd.Flags().BoolVar(&opts.Pull, "pull", false, "force a pull of the image before running tests") cmd.MarkFlagsMutuallyExclusive("image-from-oci-layout", "pull") cmd.Flags().BoolVar(&opts.Save, "save", false, "preserve created containers after test run")