Skip to content

Commit 3e6fe0f

Browse files
authored
feat: support --image-from-oci-layout (#306)
1 parent 59c5221 commit 3e6fe0f

File tree

6 files changed

+122
-48
lines changed

6 files changed

+122
-48
lines changed

cmd/container-structure-test/app/cmd/test.go

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ import (
2020
"io/ioutil"
2121

2222
"os"
23-
"regexp"
24-
"strings"
2523

2624
"github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app/cmd/test"
25+
v1 "github.com/opencontainers/image-spec/specs-go/v1"
2726

2827
"github.com/GoogleContainerTools/container-structure-test/pkg/color"
2928
"github.com/GoogleContainerTools/container-structure-test/pkg/config"
@@ -33,6 +32,9 @@ import (
3332
"github.com/GoogleContainerTools/container-structure-test/pkg/utils"
3433

3534
docker "github.com/fsouza/go-dockerclient"
35+
"github.com/google/go-containerregistry/pkg/name"
36+
"github.com/google/go-containerregistry/pkg/v1/daemon"
37+
"github.com/google/go-containerregistry/pkg/v1/layout"
3638
"github.com/sirupsen/logrus"
3739
"github.com/spf13/cobra"
3840
)
@@ -43,9 +45,6 @@ Be sure you know what you're doing before continuing!
4345
4446
Continue? (y/n)`
4547

46-
var totalTests int
47-
var testReportFile *os.File
48-
4948
var (
5049
opts = &config.StructureTestOptions{}
5150

@@ -106,21 +105,76 @@ func run(out io.Writer) error {
106105

107106
var err error
108107

108+
if opts.ImageFromLayout != "" {
109+
if opts.Driver != drivers.Docker {
110+
logrus.Fatal("--image-from-oci-layout is not supported when not using Docker driver")
111+
}
112+
l, err := layout.ImageIndexFromPath(opts.ImageFromLayout)
113+
if err != nil {
114+
logrus.Fatalf("loading %s as OCI layout: %v", opts.ImageFromLayout, err)
115+
}
116+
m, err := l.IndexManifest()
117+
if err != nil {
118+
logrus.Fatalf("could not read OCI index manifest %s: %v", opts.ImageFromLayout, err)
119+
}
120+
121+
if len(m.Manifests) != 1 {
122+
logrus.Fatalf("OCI layout contains %d entries. expected only one", len(m.Manifests))
123+
}
124+
125+
desc := m.Manifests[0]
126+
127+
if desc.MediaType.IsIndex() {
128+
logrus.Fatal("multi-arch images are not supported yet.")
129+
}
130+
131+
img, err := l.Image(desc.Digest)
132+
133+
if err != nil {
134+
logrus.Fatalf("could not get image from %s: %v", opts.ImageFromLayout, err)
135+
}
136+
137+
var tag name.Tag
138+
139+
ref := desc.Annotations[v1.AnnotationRefName]
140+
if ref != "" {
141+
tag, err = name.NewTag(ref)
142+
if err != nil {
143+
logrus.Fatalf("could not parse ref annotation %s: %v", v1.AnnotationRefName, err)
144+
}
145+
} else {
146+
if opts.DefaultImageTag == "" {
147+
logrus.Fatalf("index does not contain a reference annotation. --default-image-tag must be provided.")
148+
}
149+
tag, err = name.NewTag(opts.DefaultImageTag, name.StrictValidation)
150+
if err != nil {
151+
logrus.Fatalf("could parse the default image tag %s: %v", opts.DefaultImageTag, err)
152+
}
153+
}
154+
if _, err = daemon.Write(tag, img); err != nil {
155+
logrus.Fatalf("error loading oci layout into daemon: %v, %s", err)
156+
}
157+
158+
opts.ImagePath = tag.String()
159+
args.Image = tag.String()
160+
}
161+
109162
if opts.Pull {
110163
if opts.Driver != drivers.Docker {
111164
logrus.Fatal("image pull not supported when not using Docker driver")
112165
}
113-
var repository, tag string
114-
parts := splitImagePath(opts.ImagePath)
115-
if len(parts) < 2 {
116-
logrus.Fatal("no tag specified for provided image")
166+
ref, err := name.ParseReference(opts.ImagePath)
167+
if err != nil {
168+
logrus.Fatal(err)
117169
}
118-
repository = parts[0]
119-
tag = parts[1]
120170
client, err := docker.NewClientFromEnv()
171+
if err != nil {
172+
logrus.Fatalf("error connecting to daemon: %v", err)
173+
}
121174
if err = client.PullImage(docker.PullImageOptions{
122-
Repository: repository,
123-
Tag: tag,
175+
Repository: ref.Context().RepositoryStr(),
176+
Tag: ref.Identifier(),
177+
Registry: ref.Context().RegistryStr(),
124178
OutputStream: out,
125179
}, docker.AuthConfiguration{}); err != nil {
126180
logrus.Fatalf("error pulling remote image %s: %s", opts.ImagePath, err.Error())
@@ -163,38 +217,17 @@ func runTests(out io.Writer, channel chan interface{}, args *drivers.DriverConfi
163217
close(channel)
164218
}
165219

166-
func splitImagePath(imagePath string) []string {
167-
var parts []string
168-
if strings.Contains(imagePath, "@") {
169-
parts = strings.Split(imagePath, "@")
170-
} else {
171-
switch countColons := strings.Count(imagePath, ":"); countColons {
172-
case 0:
173-
parts = []string{imagePath}
174-
case 1:
175-
match, _ := regexp.MatchString(`:\d{1,5}\/`, imagePath)
176-
if match {
177-
//colon is part of a registry port and no tag available
178-
parts = []string{imagePath}
179-
} else {
180-
//colon separates image name and tag
181-
parts = strings.Split(imagePath, ":")
182-
}
183-
default:
184-
imageTagRegex, _ := regexp.Compile("(.*):(.*)")
185-
parts = imageTagRegex.FindStringSubmatch(imagePath)[1:]
186-
}
187-
}
188-
return parts
189-
}
190-
191220
func AddTestFlags(cmd *cobra.Command) {
192221
cmd.Flags().StringVarP(&opts.ImagePath, "image", "i", "", "path to test image")
222+
cmd.Flags().StringVar(&opts.ImageFromLayout, "image-from-oci-layout", "", "path to the oci layout to test against")
223+
cmd.Flags().StringVar(&opts.DefaultImageTag, "default-image-tag", "", "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.")
224+
cmd.MarkFlagsMutuallyExclusive("image", "image-from-oci-layout")
193225
cmd.Flags().StringVarP(&opts.Driver, "driver", "d", "docker", "driver to use when running tests")
194226
cmd.Flags().StringVar(&opts.Metadata, "metadata", "", "path to image metadata file")
195227
cmd.Flags().StringVar(&opts.Runtime, "runtime", "", "runtime to use with docker driver")
196228

197229
cmd.Flags().BoolVar(&opts.Pull, "pull", false, "force a pull of the image before running tests")
230+
cmd.MarkFlagsMutuallyExclusive("image-from-oci-layout", "pull")
198231
cmd.Flags().BoolVar(&opts.Save, "save", false, "preserve created containers after test run")
199232
cmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "flag to suppress output")
200233
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "force run of host driver (without user prompt)")

cmd/container-structure-test/app/cmd/test/util.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ func ValidateArgs(opts *config.StructureTestOptions) error {
4141
return fmt.Errorf("Cannot provide both image path and metadata file")
4242
}
4343
} else {
44-
if opts.ImagePath == "" {
45-
return fmt.Errorf("Please supply path to image or tarball to test against")
44+
if opts.ImagePath == "" && opts.ImageFromLayout == "" {
45+
return fmt.Errorf("Please supply path to image or oci image layout to test against")
4646
}
4747
if opts.Metadata != "" {
4848
return fmt.Errorf("Cannot provide both image path and metadata file")

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/google/go-containerregistry v0.5.1
1010
github.com/pkg/errors v0.9.1
1111
github.com/sirupsen/logrus v1.8.1
12-
github.com/spf13/cobra v1.0.0
12+
github.com/spf13/cobra v1.5.0
1313
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
1414
gopkg.in/yaml.v2 v2.4.0
1515
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
216216
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
217217
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
218218
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
219+
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
219220
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
220221
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
221222
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -576,6 +577,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
576577
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
577578
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
578579
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
580+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
579581
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
580582
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
581583
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
@@ -602,6 +604,8 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv
602604
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
603605
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
604606
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
607+
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
608+
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
605609
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
606610
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
607611
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=

pkg/config/options.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ package config
1717
import "github.com/GoogleContainerTools/container-structure-test/pkg/types/unversioned"
1818

1919
type StructureTestOptions struct {
20-
ImagePath string
21-
Driver string
22-
Runtime string
23-
Metadata string
24-
TestReport string
25-
ConfigFiles []string
20+
ImagePath string
21+
ImageFromLayout string
22+
DefaultImageTag string
23+
Driver string
24+
Runtime string
25+
Metadata string
26+
TestReport string
27+
ConfigFiles []string
2628

2729
JSON bool
2830
Output unversioned.OutputValue

tests/structure_test_tests.sh

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
# Copyright 2017 Google Inc. All rights reserved.
44

@@ -125,3 +125,38 @@ then
125125
else
126126
echo "PASS: Failure test failed"
127127
fi
128+
129+
130+
echo "###"
131+
echo "# OCI layout test case"
132+
echo "###"
133+
134+
go install github.com/google/go-containerregistry/cmd/crane/cmd
135+
tmp="$(mktemp -d)"
136+
137+
crane pull "$test_image" --format=oci "$tmp" --platform=linux/arm64
138+
139+
140+
res=$(./out/container-structure-test test --image-from-oci-layout="$tmp" --config "${test_config_dir}/ubuntu_20_04_test.yaml" 2>&1)
141+
code=$?
142+
if ! [[ ("$res" =~ "index does not contain a reference annotation. --default-image-tag must be provided." && "$code" == "1") ]];
143+
then
144+
echo "FAIL: oci failing test case"
145+
echo "$res"
146+
echo "$code"
147+
failures=$((failures +1))
148+
else
149+
echo "PASS: oci failing test case"
150+
fi
151+
152+
res=$(./out/container-structure-test test --image-from-oci-layout="$tmp" --default-image-tag="test.local/library/$test_image" --config "${test_config_dir}/ubuntu_20_04_test.yaml" 2>&1)
153+
code=$?
154+
if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]];
155+
then
156+
echo "FAIL: oci success test case"
157+
echo "$res"
158+
echo "$code"
159+
failures=$((failures +1))
160+
else
161+
echo "PASS: oci success test case"
162+
fi

0 commit comments

Comments
 (0)