diff --git a/.gitignore b/.gitignore index 20bb82a1..d8ae2bad 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ pause/pause.o ocid.conf *.orig *.rej +test/bin2img/bin2img diff --git a/Makefile b/Makefile index 30615c38..616723ac 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,9 @@ conmon: pause: make -C $@ +bin2img: + make -C test/$@ + ocid: ifndef GOPATH $(error GOPATH is not set) @@ -73,6 +76,7 @@ clean: find . -name \#\* -delete make -C conmon clean make -C pause clean + make -C test/bin2img clean ocidimage: docker build -t ${OCID_IMAGE} . @@ -86,7 +90,7 @@ integration: ocidimage localintegration: binaries ./test/test_runner.sh ${TESTFLAGS} -binaries: ocid ocic kpod conmon pause +binaries: ocid ocic kpod conmon pause bin2img MANPAGES_MD := $(wildcard docs/*.md) MANPAGES := $(MANPAGES_MD:%.md=%) @@ -180,6 +184,7 @@ install.tools: .install.gitvalidation .install.gometalinter .install.md2man go get -u github.com/cpuguy83/go-md2man .PHONY: \ + bin2img \ binaries \ clean \ conmon \ diff --git a/test/bin2img/Makefile b/test/bin2img/Makefile new file mode 100644 index 00000000..69ad14b1 --- /dev/null +++ b/test/bin2img/Makefile @@ -0,0 +1,6 @@ +bin2img: $(wildcard *.go) + go build -o $@ + +.PHONY: clean +clean: + rm -f bin2img diff --git a/test/bin2img/bin2img.go b/test/bin2img/bin2img.go new file mode 100644 index 00000000..45eba0de --- /dev/null +++ b/test/bin2img/bin2img.go @@ -0,0 +1,225 @@ +package main + +import ( + "archive/tar" + "bytes" + "encoding/json" + "io" + "os" + "runtime" + + "github.com/Sirupsen/logrus" + "github.com/containers/image/storage" + "github.com/containers/image/types" + "github.com/containers/storage/pkg/reexec" + sstorage "github.com/containers/storage/storage" + digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go" + "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/urfave/cli" +) + +func main() { + if reexec.Init() { + return + } + + app := cli.NewApp() + app.Name = "bin2img" + app.Usage = "barebones image builder" + app.Version = "0.0.1" + + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + Usage: "turn on debug logging", + }, + cli.StringFlag{ + Name: "root", + Usage: "graph root directory", + }, + cli.StringFlag{ + Name: "runroot", + Usage: "run root directory", + }, + cli.StringFlag{ + Name: "storage-driver", + Usage: "storage driver", + }, + cli.StringSliceFlag{ + Name: "storage-option", + Usage: "storage option", + }, + cli.StringFlag{ + Name: "image-name", + Usage: "set image name", + Value: "kubernetes/pause", + }, + cli.StringFlag{ + Name: "source-binary", + Usage: "source binary", + Value: "../../pause/pause", + }, + cli.StringFlag{ + Name: "image-binary", + Usage: "image binary", + Value: "/pause", + }, + } + + app.Action = func(c *cli.Context) error { + debug := c.GlobalBool("debug") + rootDir := c.GlobalString("root") + runrootDir := c.GlobalString("runroot") + storageDriver := c.GlobalString("storage-driver") + storageOptions := c.GlobalStringSlice("storage-option") + imageName := c.GlobalString("image-name") + sourceBinary := c.GlobalString("source-binary") + imageBinary := c.GlobalString("image-binary") + + if debug { + logrus.SetLevel(logrus.DebugLevel) + } else { + logrus.SetLevel(logrus.ErrorLevel) + } + if rootDir == "" && runrootDir != "" { + logrus.Errorf("must set --root and --runroot, or neither") + os.Exit(1) + } + if rootDir != "" && runrootDir == "" { + logrus.Errorf("must set --root and --runroot, or neither") + os.Exit(1) + } + storeOptions := sstorage.DefaultStoreOptions + if rootDir != "" && runrootDir != "" { + storeOptions.GraphDriverName = storageDriver + storeOptions.GraphDriverOptions = storageOptions + storeOptions.GraphRoot = rootDir + storeOptions.RunRoot = runrootDir + } + store, err := sstorage.GetStore(storeOptions) + if err != nil { + logrus.Errorf("error opening storage: %v", err) + os.Exit(1) + } + defer store.Shutdown(false) + + layerBuffer := &bytes.Buffer{} + binary, err := os.Open(sourceBinary) + if err != nil { + logrus.Errorf("error opening image binary: %v", err) + os.Exit(1) + } + binInfo, err := binary.Stat() + if err != nil { + logrus.Errorf("error statting image binary: %v", err) + os.Exit(1) + } + archive := tar.NewWriter(layerBuffer) + err = archive.WriteHeader(&tar.Header{ + Name: imageBinary, + Size: binInfo.Size(), + Mode: 0555, + ModTime: binInfo.ModTime(), + Typeflag: tar.TypeReg, + Uname: "root", + Gname: "root", + }) + if err != nil { + logrus.Errorf("error writing archive header: %v", err) + os.Exit(1) + } + _, err = io.Copy(archive, binary) + if err != nil { + logrus.Errorf("error archiving image binary: %v", err) + os.Exit(1) + } + archive.Close() + binary.Close() + layerInfo := types.BlobInfo{ + Digest: digest.Canonical.FromBytes(layerBuffer.Bytes()), + Size: int64(layerBuffer.Len()), + } + + ref, err := storage.Transport.ParseStoreReference(store, imageName) + if err != nil { + logrus.Errorf("error parsing image name: %v", err) + os.Exit(1) + } + img, err := ref.NewImageDestination(nil) + if err != nil { + logrus.Errorf("error preparing to write image: %v", err) + os.Exit(1) + } + defer img.Close() + layer, err := img.PutBlob(layerBuffer, layerInfo) + if err != nil { + logrus.Errorf("error preparing to write image: %v", err) + os.Exit(1) + } + config := &v1.Image{ + Architecture: runtime.GOARCH, + OS: runtime.GOOS, + Config: v1.ImageConfig{ + User: "root", + Entrypoint: []string{imageBinary}, + }, + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []string{ + layer.Digest.String(), + }, + }, + } + cbytes, err := json.Marshal(config) + if err != nil { + logrus.Errorf("error encoding configuration: %v", err) + os.Exit(1) + } + configInfo := types.BlobInfo{ + Digest: digest.Canonical.FromBytes(cbytes), + Size: int64(len(cbytes)), + } + configInfo, err = img.PutBlob(bytes.NewBuffer(cbytes), configInfo) + if err != nil { + logrus.Errorf("error saving configuration: %v", err) + os.Exit(1) + } + manifest := &v1.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, + MediaType: v1.MediaTypeImageManifest, + }, + Config: v1.Descriptor{ + MediaType: v1.MediaTypeImageConfig, + Digest: configInfo.Digest.String(), + Size: int64(len(cbytes)), + }, + Layers: []v1.Descriptor{{ + MediaType: v1.MediaTypeImageLayer, + Digest: layer.Digest.String(), + Size: layer.Size, + }}, + } + mbytes, err := json.Marshal(manifest) + if err != nil { + logrus.Errorf("error encoding manifest: %v", err) + os.Exit(1) + } + err = img.PutManifest(mbytes) + if err != nil { + logrus.Errorf("error saving manifest: %v", err) + os.Exit(1) + } + err = img.Commit() + if err != nil { + logrus.Errorf("error committing image: %v", err) + os.Exit(1) + } + return nil + } + + if err := app.Run(os.Args); err != nil { + logrus.Fatal(err) + } +} diff --git a/test/helpers.bash b/test/helpers.bash index f705b85c..226fef8e 100644 --- a/test/helpers.bash +++ b/test/helpers.bash @@ -36,6 +36,8 @@ APPARMOR_TEST_PROFILE_NAME=${APPARMOR_TEST_PROFILE_NAME:-apparmor-test-deny-writ BOOT_CONFIG_FILE_PATH=${BOOT_CONFIG_FILE_PATH:-/boot/config-`uname -r`} # Path of apparmor parameters file. APPARMOR_PARAMETERS_FILE_PATH=${APPARMOR_PARAMETERS_FILE_PATH:-/sys/module/apparmor/parameters/enabled} +# Path of the bin2img binary. +BIN2IMG_BINARY=${BIN2IMG_BINARY:-${OCID_ROOT}/cri-o/test/bin2img/bin2img} TESTDIR=$(mktemp -d) if [ -e /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled; then @@ -112,6 +114,10 @@ function start_ocid() { apparmor="$APPARMOR_PROFILE" fi + # Don't forget: bin2img and ocid have their own default drivers, so if you override either, you probably need to override both + if ! [ "$3" = "--no-pause-image" ] ; then + "$BIN2IMG_BINARY" --root "$TESTDIR/ocid" --runroot "$TESTDIR/ocid-run" --source-binary "$PAUSE_BINARY" + fi "$OCID_BINARY" --conmon "$CONMON_BINARY" --listen "$OCID_SOCKET" --runtime "$RUNC_BINARY" --root "$TESTDIR/ocid" --runroot "$TESTDIR/ocid-run" --seccomp-profile "$seccomp" --apparmor-profile "$apparmor" --cni-config-dir "$OCID_CNI_CONFIG" --signature-policy "$INTEGRATION_ROOT"/policy.json config >$OCID_CONFIG "$OCID_BINARY" --debug --config "$OCID_CONFIG" & OCID_PID=$! wait_until_reachable diff --git a/test/image.bats b/test/image.bats new file mode 100644 index 00000000..88b2ac73 --- /dev/null +++ b/test/image.bats @@ -0,0 +1,93 @@ +#!/usr/bin/env bats + +load helpers + +IMAGE=kubernetes/pause + +function teardown() { + cleanup_test +} + +@test "image pull" { + start_ocid "" "" --no-pause-image + run ocic image pull "$IMAGE" + echo "$output" + [ "$status" -eq 0 ] + cleanup_images + stop_ocid +} + +@test "image list with filter" { + start_ocid "" "" --no-pause-image + run ocic image pull "$IMAGE" + echo "$output" + [ "$status" -eq 0 ] + run ocic image list --quiet "$IMAGE" + echo "$output" + [ "$status" -eq 0 ] + printf '%s\n' "$output" | while IFS= read -r id; do + run ocic image remove --id "$id" + echo "$output" + [ "$status" -eq 0 ] + done + run ocic image list --quiet + echo "$output" + [ "$status" -eq 0 ] + printf '%s\n' "$output" | while IFS= read -r id; do + echo "$id" + status=1 + done + cleanup_images + stop_ocid +} + +@test "image list/remove" { + start_ocid "" "" --no-pause-image + run ocic image pull "$IMAGE" + echo "$output" + [ "$status" -eq 0 ] + run ocic image list --quiet + echo "$output" + [ "$status" -eq 0 ] + printf '%s\n' "$output" | while IFS= read -r id; do + run ocic image remove --id "$id" + echo "$output" + [ "$status" -eq 0 ] + done + run ocic image list --quiet + echo "$output" + [ "$status" -eq 0 ] + printf '%s\n' "$output" | while IFS= read -r id; do + echo "$id" + status=1 + done + cleanup_images + stop_ocid +} + +@test "image status/remove" { + start_ocid "" "" --no-pause-image + run ocic image pull "$IMAGE" + echo "$output" + [ "$status" -eq 0 ] + run ocic image list --quiet + echo "$output" + [ "$status" -eq 0 ] + printf '%s\n' "$output" | while IFS= read -r id; do + run ocic image status --id "$id" + echo "$output" + [ "$status" -eq 0 ] + run ocic image remove --id "$id" + echo "$output" + [ "$status" -eq 0 ] + done + run ocic image list --quiet + echo "$output" + [ "$status" -eq 0 ] + printf '%s\n' "$output" | while IFS= read -r id; do + echo "$id" + status=1 + done + cleanup_images + stop_ocid +}