Skip to content

Commit

Permalink
initial testing infrastructure
Browse files Browse the repository at this point in the history
* tests run as root, directly on the host
  * can't really find a good way around this
  * at one point i had the tests build + run the task with the docker
    CLI, with some ugly volume mount shenanigans, but assertions need to
    be made against the outputs, and they're owned by root in this case
    anyway
  * changed setup-cgroups to just no-op if /sys/fs/cgroup already exists
    so it doesn't mess around with the host
* main entrypoint is scripts/test
  * builds tests using go test -c and runs them with sudo
  * builds latest buildkit from master via the submodule
    * this submodule can be removed in favor of using a released archive
      once a new version is out which supports named oci outputs
* fixed a few bugs that the tests found along the way!
  * manifest.json -> metadata.json (d'oh)
  * XDG_RUNTIME_PATH -> XDG_RUNTIME_DIR (d'oh)
  * build args flag was completely wrong (d'oh)
  * build args file choked on trailing linebreak

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
  • Loading branch information
vito committed Aug 22, 2019
1 parent 4fd09a9 commit 54afb69
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
# created by scripts/test
scripts/.tests
bin/buildctl
bin/buildkitd
bin/buildkit-runc
3 changes: 3 additions & 0 deletions .gitmodules
@@ -0,0 +1,3 @@
[submodule "buildkit"]
path = buildkit
url = https://github.com/moby/buildkit
10 changes: 6 additions & 4 deletions bin/setup-cgroups
Expand Up @@ -2,11 +2,13 @@

set -e -u

mkdir -p /sys/fs/cgroup
mountpoint -q /sys/fs/cgroup || \
mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
if mountpoint -q /sys/fs/cgroup; then
# already mounted; skip
exit 0
fi

mount -o remount,rw none /sys/fs/cgroup
mkdir -p /sys/fs/cgroup
mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup

sed -e 1d /proc/cgroups | while read sys hierarchy num enabled; do
if [ "$enabled" != "1" ]; then
Expand Down
1 change: 1 addition & 0 deletions buildkit
Submodule buildkit added at 763379
2 changes: 1 addition & 1 deletion cmd/builder-task/main.go
Expand Up @@ -30,7 +30,7 @@ func main() {
}

res, err := task.Build(wd, req)
failIf("failed to build", err)
failIf("build", err)

responseFile, err := os.Create(req.ResponsePath)
failIf("open response path", err)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -16,6 +16,7 @@ require (
github.com/opencontainers/image-spec v1.0.1
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0
github.com/u-root/u-root v6.0.0+incompatible
github.com/vbauerster/mpb v3.4.0+incompatible
github.com/vrischmann/envconfig v1.2.0
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Expand Up @@ -2,6 +2,7 @@ github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdc
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/concourse/go-archive v1.0.1 h1:6jQk0VDiE4G6lNJQ0mLZ7XmxbqI3spO4x0wgVwk4pfo=
github.com/concourse/go-archive v1.0.1/go.mod h1:Xfo080IPQBmVz3I5ehjCddW3phA2mwv0NFwlpjf5CO8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
Expand Down Expand Up @@ -38,9 +39,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/u-root/u-root v6.0.0+incompatible h1:YqPGmRoRyYmeg17KIWFRSyVq6LX5T6GSzawyA6wG6EE=
github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/vbauerster/mpb v3.4.0+incompatible h1:mfiiYw87ARaeRW6x5gWwYRUawxaW1tLAD8IceomUCNw=
Expand Down Expand Up @@ -72,3 +77,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
138 changes: 138 additions & 0 deletions integration_test.go
@@ -0,0 +1,138 @@
package task_test

import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"testing"

task "github.com/concourse/builder-task"
"github.com/stretchr/testify/suite"
)

type TaskSuite struct {
suite.Suite

rootDir string
req task.Request
}

func (s *TaskSuite) SetupSuite() {
binDir, err := filepath.Abs("bin")
s.NoError(err)

// inject setup-cgroups and other utilities into the $PATH
//
// this is also a good place to install buildkitd/buildctl
err = os.Setenv("PATH", binDir+":"+os.Getenv("PATH"))
s.NoError(err)
}

func (s *TaskSuite) SetupTest() {
var err error
s.rootDir, err = ioutil.TempDir("", "builder-task-test")
s.NoError(err)

s.req = task.Request{
ResponsePath: filepath.Join(s.rootDir, "response.json"),
Config: task.Config{
Repository: "builder-task-test",
},
}
}

func (s *TaskSuite) TearDownTest() {
err := os.RemoveAll(s.rootDir)
s.NoError(err)
}

func (s *TaskSuite) TestMissingRepositoryValidation() {
s.req.Config.Repository = ""

_, err := task.Build(s.rootDir, s.req)
s.EqualError(err, "config: repository must be specified")
}

func (s *TaskSuite) TestBasicBuild() {
s.req.Config.ContextPath = "testdata/basic"

_, err := task.Build(s.rootDir, s.req)
s.NoError(err)
}

func (s *TaskSuite) TestBuildArgs() {
s.req.Config.ContextPath = "testdata/build-args"
s.req.Config.BuildArgs = []string{
"some_arg=some_value",
"some_other_arg=some_other_value",
}

// the Dockerfile itself asserts that the arg has been received
_, err := task.Build(s.rootDir, s.req)
s.NoError(err)
}

func (s *TaskSuite) TestBuildArgsFile() {
s.req.Config.ContextPath = "testdata/build-args"
s.req.Config.BuildArgsFile = "testdata/build-args/build_args_file"

// the Dockerfile itself asserts that the arg has been received
_, err := task.Build(s.rootDir, s.req)
s.NoError(err)
}

func (s *TaskSuite) TestBuildArgsStaticAndFile() {
s.req.Config.ContextPath = "testdata/build-args"
s.req.Config.BuildArgs = []string{"some_arg=some_value"}
s.req.Config.BuildArgsFile = "testdata/build-args/build_arg_file"

// the Dockerfile itself asserts that the arg has been received
_, err := task.Build(s.rootDir, s.req)
s.NoError(err)
}

func (s *TaskSuite) TestUnpackRootfs() {
s.req.Config.ContextPath = "testdata/unpack-rootfs"
s.req.Config.UnpackRootfs = true

_, err := task.Build(s.rootDir, s.req)
s.NoError(err)

meta, err := s.imageMetadata()
s.NoError(err)

rootfsContent, err := ioutil.ReadFile(s.imagePath("rootfs", "Dockerfile"))
s.NoError(err)

expectedContent, err := ioutil.ReadFile("testdata/unpack-rootfs/Dockerfile")
s.NoError(err)

s.Equal(rootfsContent, expectedContent)

s.Equal(meta.User, "banana")
s.Equal(meta.Env, []string{"PATH=/darkness", "BA=nana"})
}

func (s *TaskSuite) imagePath(path ...string) string {
return filepath.Join(append([]string{s.rootDir, "image"}, path...)...)
}

func (s *TaskSuite) imageMetadata() (task.ImageMetadata, error) {
metadataPayload, err := ioutil.ReadFile(s.imagePath("metadata.json"))
if err != nil {
return task.ImageMetadata{}, err
}

var meta task.ImageMetadata
err = json.Unmarshal(metadataPayload, &meta)
if err != nil {
return task.ImageMetadata{}, err
}

return meta, nil
}

func TestSuite(t *testing.T) {
suite.Run(t, &TaskSuite{})
}
24 changes: 24 additions & 0 deletions scripts/test
@@ -0,0 +1,24 @@
#!/bin/bash

set -e -u -x

cd $(dirname $0)/..

if ! [ -e bin/buildctl ] || ! [ -e bin/buildkitd ]; then
dest=$(pwd)
pushd buildkit
make
make DESTDIR=$dest install
popd

# once a new version of buildkit is out, remove the submodule and do the
# following instead
#
# BUILDKIT_VERSION=0.6.1
# BUILDKIT_URL=https://github.com/moby/buildkit/releases/download/v$BUILDKIT_VERSION/buildkit-v$BUILDKIT_VERSION.linux-amd64.tar.gz

# curl -fsSL "$BUILDKIT_URL" | tar zxf -
fi

go test -c -o scripts/.tests
sudo scripts/.tests "$@"
55 changes: 35 additions & 20 deletions task.go
Expand Up @@ -35,23 +35,23 @@ func Build(rootPath string, req Request) (Response, error) {
Outputs: []string{"image", "cache"},
}

err := os.MkdirAll(cacheDir, 0755)
err := os.MkdirAll(imageDir, 0755)
if err != nil {
return Response{}, errors.Wrap(err, "create image output folder")
}

cfg := req.Config
err = sanitize(&cfg)
err = os.MkdirAll(cacheDir, 0755)
if err != nil {
return Response{}, errors.Wrap(err, "config")
return Response{}, errors.Wrap(err, "create cache output folder")
}

err = run(os.Stdout, "setup-cgroups")
cfg := req.Config
err = sanitize(&cfg)
if err != nil {
return Response{}, errors.Wrap(err, "setup cgroups")
return Response{}, errors.Wrap(err, "config")
}

addr, err := spawnBuildkitd("/var/log/buildkitd.log")
addr, err := spawnBuildkitd()
if err != nil {
return Response{}, errors.Wrap(err, "spawn buildkitd")
}
Expand All @@ -64,8 +64,8 @@ func Build(rootPath string, req Request) (Response, error) {
"--frontend", "dockerfile.v0",
"--local", "context=" + cfg.ContextPath,
"--local", "dockerfile=" + dockerfileDir,
"--frontend-opt", "filename=" + dockerfileName,
"--export-cache", "type=local,mode=min,dest=cache",
"--opt", "filename=" + dockerfileName,
"--export-cache", "type=local,mode=min,dest=" + cacheDir,
}

if _, err := os.Stat(filepath.Join(cacheDir, "index.json")); err == nil {
Expand All @@ -90,7 +90,7 @@ func Build(rootPath string, req Request) (Response, error) {

for _, arg := range cfg.BuildArgs {
buildctlArgs = append(buildctlArgs,
"--build-arg", arg,
"--opt", "build-arg:"+arg,
)
}

Expand All @@ -116,7 +116,7 @@ func Build(rootPath string, req Request) (Response, error) {
func unpackRootfs(dest string, ociImagePath string, cfg Config) error {
layoutDir := filepath.Join(dest, "layout")
rootfsDir := filepath.Join(dest, "rootfs")
manifestPath := filepath.Join(dest, "manifest.json")
metadataPath := filepath.Join(dest, "metadata.json")

logrus.Debug("unpacking oci layout")

Expand Down Expand Up @@ -175,12 +175,12 @@ func unpackRootfs(dest string, ociImagePath string, cfg Config) error {
return errors.Wrap(err, "get image from oci layout")
}

err = unpackImage(rootfsDir, image, false)
err = unpackImage(rootfsDir, image, cfg.Debug)
if err != nil {
return errors.Wrap(err, "unpack image")
}

err = writeImageManifest(manifestPath, image)
err = writeImageManifest(metadataPath, image)
if err != nil {
return errors.Wrap(err, "write image manifest")
}
Expand All @@ -195,13 +195,13 @@ func unpackRootfs(dest string, ociImagePath string, cfg Config) error {
return nil
}

func writeImageManifest(manifestPath string, image v1.Image) error {
func writeImageManifest(metadataPath string, image v1.Image) error {
cfg, err := image.ConfigFile()
if err != nil {
return errors.Wrap(err, "load image config")
}

meta, err := os.Create(manifestPath)
meta, err := os.Create(metadataPath)
if err != nil {
return errors.Wrap(err, "create metadata file")
}
Expand Down Expand Up @@ -272,6 +272,11 @@ func sanitize(cfg *Config) error {
}

for _, arg := range strings.Split(string(buildArgs), "\n") {
if len(arg) == 0 {
// skip blank lines
continue
}

cfg.BuildArgs = append(cfg.BuildArgs, arg)
}
}
Expand All @@ -291,15 +296,25 @@ func run(out io.Writer, path string, args ...string) error {
return cmd.Run()
}

func spawnBuildkitd(logPath string) (string, error) {
runPath := os.Getenv("XDG_RUNTIME_PATH")
if runPath == "" {
runPath = "/run"
func spawnBuildkitd() (string, error) {
err := run(os.Stdout, "setup-cgroups")
if err != nil {
return "", errors.Wrap(err, "setup cgroups")
}

var logPath string

runDir := os.Getenv("XDG_RUNTIME_DIR")
if runDir == "" {
runDir = "/run"
logPath = "/var/log/buildkitd.log"
} else {
logPath = filepath.Join(runDir, "buildkitd.log")
}

addr := (&url.URL{
Scheme: "unix",
Path: path.Join(runPath, "buildkitd", "buildkitd.sock"),
Path: path.Join(runDir, "buildkitd", "buildkitd.sock"),
}).String()

buildkitdFlags := []string{"--addr=" + addr}
Expand Down
1 change: 1 addition & 0 deletions testdata/basic/Dockerfile
@@ -0,0 +1 @@
FROM scratch
5 changes: 5 additions & 0 deletions testdata/build-args/Dockerfile
@@ -0,0 +1,5 @@
FROM busybox
ARG some_arg
ARG some_other_arg
RUN test "$some_arg" = "some_value"
RUN test "$some_other_arg" = "some_other_value"
1 change: 1 addition & 0 deletions testdata/build-args/build_arg_file
@@ -0,0 +1 @@
some_other_arg=some_other_value
2 changes: 2 additions & 0 deletions testdata/build-args/build_args_file
@@ -0,0 +1,2 @@
some_arg=some_value
some_other_arg=some_other_value
5 changes: 5 additions & 0 deletions testdata/unpack-rootfs/Dockerfile
@@ -0,0 +1,5 @@
FROM scratch
USER banana
ADD Dockerfile /Dockerfile
ENV PATH=/darkness
ENV BA=nana

0 comments on commit 54afb69

Please sign in to comment.