Skip to content
Merged
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,31 @@ globalEnvVars:
value: "/env/bin:$PATH"
```

### Additional Options
The following fields are used to control various options and flags that may be
desirable to set for the running container used to perform a structure test
against an image. This allows an image author to validate certain runtime
behavior that cannot be modified in the image-under-test such as running a
container with an alternative user/UID or mounting a volume.

Note that these options are currently only supported with the `docker` driver.

The following list of options are currently supported:
```yaml
containerRunOptions:
user: "root" # set the --user/-u flag
privileged: true # set the --privileged flag (default: false)
allocateTty: true # set the --tty flag (default: false)
envFile: path/to/.env # load environment variables from file and pass to container (equivalent to --env-file)
envVars: # if not empty, read each envVar from the environment and pass to test (equivalent to --env/e)
- SECRET_KEY_FOO
- OTHER_SECRET_BAR
capabilities: # Add list of Linux capabilities (--cap-add)
- NET_BIND_SERVICE
bindMounts: # Bind mount a volume (--volume, -v)
- /etc/example/dir:/etc/dir
```

## Running Tests On [Google Cloud Build](https://cloud.google.com/cloud-build/docs/)

This tool is released as a builder image, tagged as
Expand Down
16 changes: 6 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/fsouza/go-dockerclient v1.9.0
github.com/google/go-cmp v0.5.9
github.com/google/go-containerregistry v0.12.1
github.com/joho/godotenv v1.5.1
github.com/opencontainers/image-spec v1.1.0-rc2
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.0
Expand All @@ -18,9 +19,9 @@ require (
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/hcsshim v0.9.5 // indirect
github.com/Microsoft/hcsshim v0.9.6 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/containerd/containerd v1.6.10 // indirect
github.com/containerd/containerd v1.6.18 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.13.0 // indirect
github.com/docker/cli v20.10.21+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
Expand All @@ -30,8 +31,6 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
Expand All @@ -45,12 +44,9 @@ require (
github.com/vbatts/tar-split v0.11.2 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/tools v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029 // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)
87 changes: 16 additions & 71 deletions go.sum

Large diffs are not rendered by default.

65 changes: 60 additions & 5 deletions pkg/drivers/docker_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"bufio"
"bytes"
"fmt"
"github.com/joho/godotenv"
"io"
"os"
"path"
Expand All @@ -41,6 +42,7 @@ type DockerDriver struct {
env map[string]string
save bool
runtime string
runOpts unversioned.ContainerRunOptions
}

func NewDockerDriver(args DriverConfig) (Driver, error) {
Expand All @@ -55,10 +57,26 @@ func NewDockerDriver(args DriverConfig) (Driver, error) {
env: nil,
save: args.Save,
runtime: args.Runtime,
runOpts: args.RunOpts,
}, nil
}

func (d *DockerDriver) hostConfig() *docker.HostConfig {
if d.runOpts.IsSet() && d.runtime != "" {
return &docker.HostConfig{
Capabilities: d.runOpts.Capabilities,
Binds: d.runOpts.BindMounts,
Privileged: d.runOpts.Privileged,
Runtime: d.runtime,
}
}
if d.runOpts.IsSet() {
return &docker.HostConfig{
Capabilities: d.runOpts.Capabilities,
Binds: d.runOpts.BindMounts,
Privileged: d.runOpts.Privileged,
}
}
if d.runtime != "" {
return &docker.HostConfig{
Runtime: d.runtime,
Expand Down Expand Up @@ -297,7 +315,7 @@ func (d *DockerDriver) ReadDir(target string) ([]os.FileInfo, error) {
// 3) commits the container with its changes to a new image,
// and sets that image as the new "current image"
func (d *DockerDriver) runAndCommit(env []string, command []string) (string, error) {
container, err := d.cli.CreateContainer(docker.CreateContainerOptions{
createOpts := docker.CreateContainerOptions{
Config: &docker.Config{
Image: d.currentImage,
Env: env,
Expand All @@ -308,7 +326,11 @@ func (d *DockerDriver) runAndCommit(env []string, command []string) (string, err
},
HostConfig: d.hostConfig(),
NetworkingConfig: nil,
})
}
if d.runOpts.IsSet() && len(d.runOpts.User) > 0 {
createOpts.Config.User = d.runOpts.User
}
container, err := d.cli.CreateContainer(createOpts)
if err != nil {
return "", errors.Wrap(err, "Error creating container")
}
Expand Down Expand Up @@ -342,8 +364,7 @@ func (d *DockerDriver) runAndCommit(env []string, command []string) (string, err
}

func (d *DockerDriver) exec(env []string, command []string) (string, string, int, error) {
// first, start container from the current image
container, err := d.cli.CreateContainer(docker.CreateContainerOptions{
createOpts := docker.CreateContainerOptions{
Config: &docker.Config{
Image: d.currentImage,
Env: env,
Expand All @@ -354,7 +375,41 @@ func (d *DockerDriver) exec(env []string, command []string) (string, string, int
},
HostConfig: d.hostConfig(),
NetworkingConfig: nil,
})
}
if d.runOpts.IsSet() {
createOpts.Config.Tty = d.runOpts.TTY
if len(d.runOpts.User) > 0 {
createOpts.Config.User = d.runOpts.User
}
var envVars []string
if d.runOpts.EnvFile != "" {
varMap, err := godotenv.Read(d.runOpts.EnvFile)
if err != nil {
logrus.Warnf("Unable to load envFile %s: %s", d.runOpts.EnvFile, err.Error())
} else {
var varsFromFile []string
for k, v := range varMap {
if k != "" && v != "" {
varsFromFile = append(varsFromFile, fmt.Sprintf("%s=%s", k, v))
}
}
envVars = append(envVars, varsFromFile...)
}
}
if d.runOpts.EnvVars != nil && len(d.runOpts.EnvVars) > 0 {
varsFromEnv := make([]string, len(d.runOpts.EnvVars))
for i, e := range d.runOpts.EnvVars {
v := os.Getenv(e)
if v != "" {
varsFromEnv[i] = fmt.Sprintf("%s=%s", e, v)
}
}
envVars = append(envVars, varsFromEnv...)
}
createOpts.Config.Env = envVars
}
// first, start container from the current image
container, err := d.cli.CreateContainer(createOpts)
if err != nil {
return "", "", -1, errors.Wrap(err, "Error creating container")
}
Expand Down
9 changes: 5 additions & 4 deletions pkg/drivers/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ 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
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
RunOpts unversioned.ContainerRunOptions // used by Docker driver
}

type Driver interface {
Expand Down
18 changes: 9 additions & 9 deletions pkg/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ func FinalResults(out io.Writer, format types.OutputValue, result types.SummaryO
Total int `xml:"tests,attr"`
Duration float64 `xml:"time,attr"`
TestSuite types.JUnitTestSuite `xml:"testsuite"`
} {
XMLName: result.XMLName,
Pass: result.Pass,
Fail: result.Fail,
Total: result.Total,
Duration: time.Duration.Seconds(result.Duration), // JUnit expects durations as float of seconds
TestSuite: types.JUnitTestSuite{
Name: "container-structure-test.test",
Results: junit_cases,
}{
XMLName: result.XMLName,
Pass: result.Pass,
Fail: result.Fail,
Total: result.Total,
Duration: time.Duration.Seconds(result.Duration), // JUnit expects durations as float of seconds
TestSuite: types.JUnitTestSuite{
Name: "container-structure-test.test",
Results: junit_cases,
},
}
res := []byte(strings.ReplaceAll(xml.Header, "\n", ""))
Expand Down
20 changes: 20 additions & 0 deletions pkg/types/unversioned/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ type Config struct {
User string
}

type ContainerRunOptions struct {
User string
Privileged bool
TTY bool `yaml:"allocateTty"`
EnvVars []string `yaml:"envVars"`
EnvFile string `yaml:"envFile"`
Capabilities []string
BindMounts []string `yaml:"bindMounts"`
}

func (opts *ContainerRunOptions) IsSet() bool {
return len(opts.User) != 0 ||
opts.Privileged ||
opts.TTY ||
len(opts.EnvFile) > 0 ||
(opts.EnvVars != nil && len(opts.EnvVars) > 0) ||
(opts.Capabilities != nil && len(opts.Capabilities) > 0) ||
(opts.BindMounts != nil && len(opts.BindMounts) > 0)
}

type TestResult struct {
Name string `xml:"name,attr"`
Pass bool `xml:"-"`
Expand Down
25 changes: 15 additions & 10 deletions pkg/types/v2/structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,24 @@ import (
)

type StructureTest struct {
DriverImpl func(drivers.DriverConfig) (drivers.Driver, error)
DriverArgs drivers.DriverConfig
SchemaVersion string `yaml:"schemaVersion"`
GlobalEnvVars []types.EnvVar `yaml:"globalEnvVars"`
CommandTests []CommandTest `yaml:"commandTests"`
FileExistenceTests []FileExistenceTest `yaml:"fileExistenceTests"`
FileContentTests []FileContentTest `yaml:"fileContentTests"`
MetadataTest MetadataTest `yaml:"metadataTest"`
LicenseTests []LicenseTest `yaml:"licenseTests"`
DriverImpl func(drivers.DriverConfig) (drivers.Driver, error)
DriverArgs drivers.DriverConfig
SchemaVersion string `yaml:"schemaVersion"`
GlobalEnvVars []types.EnvVar `yaml:"globalEnvVars"`
CommandTests []CommandTest `yaml:"commandTests"`
FileExistenceTests []FileExistenceTest `yaml:"fileExistenceTests"`
FileContentTests []FileContentTest `yaml:"fileContentTests"`
MetadataTest MetadataTest `yaml:"metadataTest"`
LicenseTests []LicenseTest `yaml:"licenseTests"`
ContainerRunOptions types.ContainerRunOptions `yaml:"containerRunOptions"`
}

func (st *StructureTest) NewDriver() (drivers.Driver, error) {
return st.DriverImpl(st.DriverArgs)
args := st.DriverArgs
if st.ContainerRunOptions.IsSet() {
args.RunOpts = st.ContainerRunOptions
}
return st.DriverImpl(args)
}

func (st *StructureTest) SetDriverImpl(f func(drivers.DriverConfig) (drivers.Driver, error), args drivers.DriverConfig) {
Expand Down
2 changes: 2 additions & 0 deletions tests/Dockerfile.cap
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y libcap2-bin
5 changes: 5 additions & 0 deletions tests/Dockerfile.unprivileged
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM ubuntu:20.04

RUN useradd --create-home --uid 1001 nonroot

USER 1001
10 changes: 10 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_env_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envVars containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envVars:
- FOO
- BAR

7 changes: 7 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_envfile_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envFile containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envFile: tests/envfile
19 changes: 19 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test Capabilities containerRunOptions"
command: "capsh"
args: ["--print"]
expectedOutput:
- ".*cap_sys_admin.*"
- name: "Test bindMounts containerRunOptions"
command: "test"
args:
- "-d"
- "/tmp/test"
exitCode: 0
containerRunOptions:
privileged: true
capabilities:
- "sys_admin"
bindMounts:
- "/tmp/test:/tmp/test"
11 changes: 11 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_user_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: 'apt-get'
command: 'bash'
args:
- -c
- |
whoami
apt-get update
containerRunOptions:
user: 'root'
10 changes: 10 additions & 0 deletions tests/arm64/ubuntu_20_04_containeropts_env_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envVars containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envVars:
- FOO
- BAR

7 changes: 7 additions & 0 deletions tests/arm64/ubuntu_20_04_containeropts_envfile_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envFile containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envFile: tests/envfile
19 changes: 19 additions & 0 deletions tests/arm64/ubuntu_20_04_containeropts_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test Capabilities containerRunOptions"
command: "capsh"
args: ["--print"]
expectedOutput:
- ".*cap_sys_admin.*"
- name: "Test bindMounts containerRunOptions"
command: "test"
args:
- "-d"
- "/tmp/test"
exitCode: 0
containerRunOptions:
privileged: true
capabilities:
- "sys_admin"
bindMounts:
- "/tmp/test:/tmp/test"
Loading