Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 36 additions & 51 deletions cmd/container-structure-test/app/cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"runtime"

"github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app/cmd/test"
"github.com/GoogleContainerTools/container-structure-test/internal/pkgutil"
v1 "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/GoogleContainerTools/container-structure-test/pkg/color"
Expand All @@ -33,7 +34,6 @@ import (
docker "github.com/fsouza/go-dockerclient"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -106,66 +106,51 @@ func run(out io.Writer) error {
var err error

if opts.ImageFromLayout != "" {
if opts.Driver != drivers.Docker {
logrus.Fatal("--image-from-oci-layout is not supported when not using Docker driver")
}
l, err := layout.ImageIndexFromPath(opts.ImageFromLayout)
if err != nil {
logrus.Fatalf("loading %s as OCI layout: %v", opts.ImageFromLayout, err)
}
m, err := l.IndexManifest()
if err != nil {
logrus.Fatalf("could not read OCI index manifest %s: %v", opts.ImageFromLayout, err)
}

if len(m.Manifests) != 1 {
logrus.Fatalf("OCI layout contains %d entries. expected only one", len(m.Manifests))
if opts.Driver != drivers.Docker && opts.Driver != drivers.Tar {
logrus.Fatal("--image-from-oci-layout is only supported with Docker or Tar drivers")
}

desc := m.Manifests[0]

if desc.MediaType.IsIndex() {
logrus.Fatal("multi-arch images are not supported yet.")
}

img, err := l.Image(desc.Digest)

if err != nil {
logrus.Fatalf("could not get image from %s: %v", opts.ImageFromLayout, err)
}
if opts.Driver == drivers.Tar {
args.OCILayout = opts.ImageFromLayout
} else {
img, desc, err := pkgutil.ImageFromOCILayout(opts.ImageFromLayout)
if err != nil {
logrus.Fatalf("loading OCI layout %s: %v", opts.ImageFromLayout, err)
}

var tag name.Tag
var tag name.Tag

ref := desc.Annotations[v1.AnnotationRefName]
if ref != "" && !opts.IgnoreRefAnnotation {
tag, err = name.NewTag(ref)
if err != nil {
logrus.Fatalf("could not parse ref annotation %s: %v", v1.AnnotationRefName, err)
ref := desc.Annotations[v1.AnnotationRefName]
if ref != "" && !opts.IgnoreRefAnnotation {
tag, err = name.NewTag(ref)
if err != nil {
logrus.Fatalf("could not parse ref annotation %s: %v", v1.AnnotationRefName, err)
}
} else {
if opts.DefaultImageTag == "" {
logrus.Fatalf("index does not contain a reference annotation. --default-image-tag must be provided.")
}
tag, err = name.NewTag(opts.DefaultImageTag, name.StrictValidation)
if err != nil {
logrus.Fatalf("could parse the default image tag %s: %v", opts.DefaultImageTag, err)
}
}
} else {
if opts.DefaultImageTag == "" {
logrus.Fatalf("index does not contain a reference annotation. --default-image-tag must be provided.")
var r string
if r, err = daemon.Write(tag, img); err != nil {
logrus.Fatalf("error loading oci layout into daemon:, %v", err)
}
Comment thread
malt3 marked this conversation as resolved.
tag, err = name.NewTag(opts.DefaultImageTag, name.StrictValidation)
// For some reason, daemon.Write doesn't return errors for some edge cases.
// We should always print what the daemon sent back so that errors are transparent.
fmt.Println("Loaded ", tag.String(), r)

_, err = daemon.Image(tag)
if err != nil {
logrus.Fatalf("could parse the default image tag %s: %v", opts.DefaultImageTag, err)
logrus.Fatalf("error loading oci layout into daemon: %v", err)
}
}
var r string
if r, err = daemon.Write(tag, img); err != nil {
logrus.Fatalf("error loading oci layout into daemon: %v, %s", err)
}
// For some reason, daemon.Write doesn't return errors for some edge cases.
// We should always print what the daemon sent back so that errors are transparent.
fmt.Println("Loaded ", tag.String(), r)

_, err = daemon.Image(tag)
if err != nil {
logrus.Fatalf("error loading oci layout into daemon: %v", err)
opts.ImagePath = tag.String()
args.Image = tag.String()
}

opts.ImagePath = tag.String()
args.Image = tag.String()
}

if opts.Pull {
Expand Down
53 changes: 53 additions & 0 deletions internal/pkgutil/image_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
Expand Down Expand Up @@ -180,6 +181,58 @@ func GetImage(imageName string, includeLayers bool, cacheDir string) (Image, err
}, nil
}

// ImageFromV1 takes a pre-loaded v1.Image and extracts its filesystem,
// returning an Image suitable for use with the tar driver.
func ImageFromV1(img v1.Image, source string) (Image, error) {
imageDigest, err := getImageDigest(img)
if err != nil {
return Image{}, err
}
path, err := getExtractPathForName("", "")
if err != nil {
return Image{}, err
}
if err := GetFileSystemForImage(img, path, nil); err != nil {
os.RemoveAll(path)
return Image{}, errors.Wrap(err, "getting filesystem for image")
}
Comment thread
malt3 marked this conversation as resolved.
return Image{
Image: img,
Source: source,
FSPath: path,
Digest: imageDigest,
}, nil
}

// ImageFromOCILayout loads a single image from an OCI image layout directory.
// It returns the image and its descriptor (for access to annotations).
// The layout must contain exactly one non-index manifest entry.
func ImageFromOCILayout(path string) (v1.Image, v1.Descriptor, error) {
l, err := layout.ImageIndexFromPath(path)
if err != nil {
return nil, v1.Descriptor{}, errors.Wrapf(err, "loading %s as OCI layout", path)
}
m, err := l.IndexManifest()
if err != nil {
return nil, v1.Descriptor{}, errors.Wrapf(err, "reading OCI index manifest %s", path)
}
if len(m.Manifests) != 1 {
return nil, v1.Descriptor{}, errors.Errorf("OCI layout contains %d entries, expected only one", len(m.Manifests))
}

desc := m.Manifests[0]
if desc.MediaType.IsIndex() {
return nil, v1.Descriptor{}, errors.New("multi-arch images are not supported yet")
}

img, err := l.Image(desc.Digest)
if err != nil {
return nil, v1.Descriptor{}, errors.Wrapf(err, "getting image from %s", path)
}

return img, desc, nil
}

func getExtractPathForName(name string, cacheDir string) (string, error) {
path := cacheDir
var err error
Expand Down
13 changes: 7 additions & 6 deletions pkg/drivers/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ const (
)

type DriverConfig struct {
Image string // used by Docker/Tar drivers
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
Image string // used by Docker/Tar drivers
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
OCILayout string // used by Tar driver for OCI layout directories
}

type Driver interface {
Expand Down
18 changes: 18 additions & 0 deletions pkg/drivers/tar_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ type TarDriver struct {
}

func NewTarDriver(args DriverConfig) (Driver, error) {
if args.OCILayout != "" {
image, err := imageFromOCILayout(args.OCILayout)
if err != nil {
return nil, errors.Wrap(err, "processing OCI layout")
}
return &TarDriver{
Image: image,
Save: args.Save,
}, nil
}
if pkgutil.IsTar(args.Image) {
// tar provided, so don't provide any prefix. container-diff can figure this out.
image, err := pkgutil.GetImageForName(args.Image)
Expand Down Expand Up @@ -68,6 +78,14 @@ func NewTarDriver(args DriverConfig) (Driver, error) {
}, nil
}

func imageFromOCILayout(path string) (pkgutil.Image, error) {
img, _, err := pkgutil.ImageFromOCILayout(path)
if err != nil {
return pkgutil.Image{}, err
}
return pkgutil.ImageFromV1(img, path)
}
Comment thread
malt3 marked this conversation as resolved.

func (d *TarDriver) Destroy() {
if !d.Save {
pkgutil.CleanupImage(d.Image)
Expand Down
24 changes: 24 additions & 0 deletions tests/amd64/ubuntu_22_04_tar_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: '2.0.0'
fileContentTests:
- name: 'Debian Sources'
excludedContents: ['.*gce_debian_mirror.*']
expectedContents: ['.*archive\.ubuntu\.com.*']
path: '/etc/apt/sources.list'
- name: 'Passwd file'
expectedContents: ['root:x:0:0:root:/root:/bin/bash']
path: '/etc/passwd'
fileExistenceTests:
- name: 'Date'
path: '/bin/date'
isExecutableBy: 'owner'
- name: 'Hosts File'
path: '/etc/hosts'
shouldExist: true
- name: 'Dummy File'
path: '/etc/dummy'
shouldExist: false
licenseTests:
- debian: false
files:
- "/usr/share/doc/ubuntu-keyring/copyright"
- "/usr/share/doc/dash/copyright"
24 changes: 24 additions & 0 deletions tests/arm64/ubuntu_22_04_tar_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: '2.0.0'
fileContentTests:
- name: 'Debian Sources'
excludedContents: ['.*gce_debian_mirror.*']
expectedContents: ['.*ports\.ubuntu\.com.*']
path: '/etc/apt/sources.list'
- name: 'Passwd file'
expectedContents: ['root:x:0:0:root:/root:/bin/bash']
path: '/etc/passwd'
fileExistenceTests:
- name: 'Date'
path: '/bin/date'
isExecutableBy: 'owner'
- name: 'Hosts File'
path: '/etc/hosts'
shouldExist: true
- name: 'Dummy File'
path: '/etc/dummy'
shouldExist: false
licenseTests:
- debian: false
files:
- "/usr/share/doc/ubuntu-keyring/copyright"
- "/usr/share/doc/dash/copyright"
24 changes: 24 additions & 0 deletions tests/ppc64le/ubuntu_22_04_tar_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: '2.0.0'
fileContentTests:
- name: 'Debian Sources'
excludedContents: ['.*gce_debian_mirror.*']
expectedContents: ['.*ports\.ubuntu\.com.*']
path: '/etc/apt/sources.list'
- name: 'Passwd file'
expectedContents: ['root:x:0:0:root:/root:/bin/bash']
path: '/etc/passwd'
fileExistenceTests:
- name: 'Date'
path: '/bin/date'
isExecutableBy: 'owner'
- name: 'Hosts File'
path: '/etc/hosts'
shouldExist: true
- name: 'Dummy File'
path: '/etc/dummy'
shouldExist: false
licenseTests:
- debian: false
files:
- "/usr/share/doc/ubuntu-keyring/copyright"
- "/usr/share/doc/dash/copyright"
24 changes: 24 additions & 0 deletions tests/s390x/ubuntu_22_04_tar_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: '2.0.0'
fileContentTests:
- name: 'Debian Sources'
excludedContents: ['.*gce_debian_mirror.*']
expectedContents: ['.*ports\.ubuntu\.com.*']
path: '/etc/apt/sources.list'
- name: 'Passwd file'
expectedContents: ['root:x:0:0:root:/root:/bin/bash']
path: '/etc/passwd'
fileExistenceTests:
- name: 'Date'
path: '/bin/date'
isExecutableBy: 'owner'
- name: 'Hosts File'
path: '/etc/hosts'
shouldExist: true
- name: 'Dummy File'
path: '/etc/dummy'
shouldExist: false
licenseTests:
- debian: false
files:
- "/usr/share/doc/ubuntu-keyring/copyright"
- "/usr/share/doc/dash/copyright"
16 changes: 16 additions & 0 deletions tests/structure_test_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,22 @@ else
echo "PASS: oci success test case"
fi

HEADER "OCI layout with tar driver test case"

res=$(./out/container-structure-test test --driver tar --image-from-oci-layout="$tmp" --config "${test_config_dir}/ubuntu_22_04_tar_test.yaml" 2>&1)
code=$?
if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]];
then
echo "FAIL: oci layout with tar driver test case"
echo "$res"
echo "$code"
failures=$((failures +1))
else
echo "PASS: oci layout with tar driver test case"
fi

rm -rf "$tmp"

HEADER "Platform test cases"

docker run --rm --privileged tonistiigi/binfmt --install all > /dev/null
Expand Down