Skip to content

Commit

Permalink
build: support OCI hooks for ephemeral build containers
Browse files Browse the repository at this point in the history
Following PR adds support for OCI hooks attached to each of ephemeral
build contains which are created by `RUN` step invoked during `buildah
build` command.

Following PR also adds `--hooks-dir` flag to `buildah build` option
which allows end-users to specify one or more configuration directories
for OCI-hooks.

Note: Following PR migrates `pkg/hooks` from `podman` to `buildah` so
buildah could implement this feature and once merged `podman` can start
using `buildah/pkg/hooks`.

For usage see man-page and example test-case.

See: https://github.com/opencontainers/runtime-spec/blob/main/config.md#posix-platform-hooks

Closes: containers#4068

Signed-off-by: Aditya R <[email protected]>
  • Loading branch information
flouthoc committed Jun 23, 2022
1 parent b00abcf commit 21679bd
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 0 deletions.
2 changes: 2 additions & 0 deletions buildah.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ type BuilderOptions struct {
ProcessLabel string
// MountLabel is the SELinux mount label associated with the container
MountLabel string
// OCIHooksDir is the location of OCI hooks for the build containers
OCIHooksDir []string
}

// ImportOptions are used to initialize a Builder from an existing container
Expand Down
2 changes: 2 additions & 0 deletions define/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ type CommonBuildOptions struct {
Secrets []string
// SSHSources is the available ssh agent connections to forward in the build
SSHSources []string
// OCIHooksDir is the location of OCI hooks for the build containers
OCIHooksDir []string
}

// BuildOptions can be used to alter how an image is built.
Expand Down
12 changes: 12 additions & 0 deletions docs/buildah-build.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,18 @@ FROM instructions in a Containerfile, only the first is changed.

Print usage statement

**--hooks-dir** *path*

Each `*.json` file in the path configures a hook for Podman containers. For more details on the syntax of the JSON files and the semantics of hook injection. Buildah currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated.

This option may be set multiple times; paths from later options have higher precedence.

For the annotation conditions, buildah uses any annotations set in the generated OCI configuration.

For the bind-mount conditions, only mounts explicitly requested by the caller via --volume are considered. Bind mounts that buildah inserts by default (e.g. /dev/shm) are not considered.

If --hooks-dir is unset for root callers, Buildah will currently default to /usr/share/containers/oci/hooks.d and /etc/containers/oci/hooks.d in order of increasing precedence. Using these defaults is deprecated, and callers should migrate to explicitly setting --hooks-dir.

**--http-proxy**=true

By default proxy environment variables are passed into the container if set
Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type BudResults struct {
NoCache bool
Timestamp int64
OmitHistory bool
OCIHooksDir []string
Pull string
PullAlways bool
PullNever bool
Expand Down Expand Up @@ -194,6 +195,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet {
fs.String("arch", runtime.GOARCH, "set the ARCH of the image to the provided value instead of the architecture of the host")
fs.StringArrayVar(&flags.Annotation, "annotation", []string{}, "set metadata for an image (default [])")
fs.StringVar(&flags.Authfile, "authfile", "", "path of the authentication file.")
fs.StringArrayVar(&flags.OCIHooksDir, "hooks-dir", []string{}, "set the OCI hooks directory path (may be set multiple times)")
fs.StringArrayVar(&flags.BuildArg, "build-arg", []string{}, "`argument=value` to supply to the builder")
fs.StringArrayVar(&flags.BuildContext, "build-context", []string{}, "`argument=value` to supply additional build context to the builder")
fs.StringVar(&flags.CacheFrom, "cache-from", "", "images to utilise as potential cache sources. The build process does not currently support caching so this is a NOOP.")
Expand Down
2 changes: 2 additions & 0 deletions pkg/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name

secrets, _ := flags.GetStringArray("secret")
sshsources, _ := flags.GetStringArray("ssh")
ociHooks, _ := flags.GetStringArray("hooks-dir")

commonOpts := &define.CommonBuildOptions{
AddHost: addHost,
Expand All @@ -170,6 +171,7 @@ func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name
Volumes: volumes,
Secrets: secrets,
SSHSources: sshsources,
OCIHooksDir: ociHooks,
}
securityOpts, _ := flags.GetStringArray("security-opt")
if err := parseSecurityOpts(securityOpts, commonOpts); err != nil {
Expand Down
57 changes: 57 additions & 0 deletions run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package buildah

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -29,6 +30,8 @@ import (
"github.com/containers/buildah/internal"
internalParse "github.com/containers/buildah/internal/parse"
internalUtil "github.com/containers/buildah/internal/util"
"github.com/containers/common/pkg/hooks"
hooksExec "github.com/containers/common/pkg/hooks/exec"
"github.com/containers/buildah/pkg/overlay"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/pkg/sshagent"
Expand Down Expand Up @@ -317,6 +320,12 @@ rootless=%d
bindFiles["/run/.containerenv"] = containerenvPath
}

// Setup OCI hooks
_, err = b.setupOCIHooks(spec, (len(options.Mounts) > 0 || len(volumes) > 0))
if err != nil {
return errors.Wrap(err, "unable to setup OCI hooks")
}

runMountInfo := runMountInfo{
ContextDir: options.ContextDir,
Secrets: options.Secrets,
Expand Down Expand Up @@ -372,6 +381,54 @@ rootless=%d
return err
}

func (b *Builder) setupOCIHooks(config *spec.Spec, hasVolumes bool) (map[string][]spec.Hook, error) {
allHooks := make(map[string][]spec.Hook)
if len(b.CommonBuildOpts.OCIHooksDir) == 0 {
if unshare.IsRootless() {
return nil, nil
}
for _, hDir := range []string{hooks.DefaultDir, hooks.OverrideDir} {
manager, err := hooks.New(context.Background(), []string{hDir}, []string{})
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}
ociHooks, err := manager.Hooks(config, b.ImageAnnotations, hasVolumes)
if err != nil {
return nil, err
}
if len(ociHooks) > 0 || config.Hooks != nil {
logrus.Warnf("Implicit hook directories are deprecated; set --hooks-dir=%q explicitly to continue to load ociHooks from this directory", hDir)
}
for i, hook := range ociHooks {
allHooks[i] = hook
}
}
} else {
manager, err := hooks.New(context.Background(), b.CommonBuildOpts.OCIHooksDir, []string{})
if err != nil {
return nil, err
}

allHooks, err = manager.Hooks(config, b.ImageAnnotations, hasVolumes)
if err != nil {
return nil, err
}
}

hookErr, err := hooksExec.RuntimeConfigFilter(context.Background(), allHooks["precreate"], config, hooksExec.DefaultPostKillTimeout)
if err != nil {
logrus.Warnf("Container: precreate hook: %v", err)
if hookErr != nil && hookErr != err {
logrus.Debugf("container: precreate hook (hook error): %v", hookErr)
}
return nil, err
}
return allHooks, nil
}

func addCommonOptsToSpec(commonOpts *define.CommonBuildOptions, g *generate.Generator) error {
// Resources - CPU
if commonOpts.CPUPeriod != 0 {
Expand Down
35 changes: 35 additions & 0 deletions tests/bud.bats
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,41 @@ _EOF
expect_output --substring " 0 0 1"
}

# Test bud with prestart hook
@test "build-test with OCI prestart hook" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform/hooks

cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile << _EOF
FROM alpine
RUN echo hello
_EOF

cat > ${TEST_SCRATCH_DIR}/bud/platform/hooks/test.json << _EOF
{
"version": "1.0.0",
"hook": {
"path": "${TEST_SCRATCH_DIR}/bud/platform/hooks/test"
},
"when": {
"always": true
},
"stages": ["prestart"]
}
_EOF

cat > ${TEST_SCRATCH_DIR}/bud/platform/hooks/test << _EOF
#!/bin/sh
echo from-hook > ${TEST_SCRATCH_DIR}/bud/platform/hooks/hook-output
_EOF

# make actual hook executable
chmod +x ${TEST_SCRATCH_DIR}/bud/platform/hooks/test
run_buildah build $WITH_POLICY_JSON -t source --hooks-dir=${TEST_SCRATCH_DIR}/bud/platform/hooks -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile
run cat ${TEST_SCRATCH_DIR}/bud/platform/hooks/hook-output
expect_output --substring "from-hook"
}

# Test skipping images with FROM
@test "build-test skipping unwanted stages with FROM" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
Expand Down

0 comments on commit 21679bd

Please sign in to comment.