Skip to content

Commit

Permalink
build: Add support for buildkit like --mount=type=bind
Browse files Browse the repository at this point in the history
Following commit adds support for using buildkit like
`--mount=type=bind` with `RUN` statements. Mounts created by `--mount`
are transient in nature and only scoped to current RUN statements.

Signed-off-by: Aditya Rajan <arajan@redhat.com>
  • Loading branch information
flouthoc committed Sep 29, 2021
1 parent 455f2f1 commit 7943032
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 15 deletions.
4 changes: 4 additions & 0 deletions buildah.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ type Builder struct {

// Args define variables that users can pass at build-time to the builder
Args map[string]string
// Context Dir for current build
ContextDir string
// Type is used to help identify a build container's metadata. It
// should not be modified.
Type string `json:"type"`
Expand Down Expand Up @@ -259,6 +261,8 @@ type BuilderOptions struct {
FromImage string
// Container is a desired name for the build container.
Container string
// ContextDirectory of current build
ContextDir string
// PullPolicy decides whether or not we should pull the image that
// we're using as a base image. It should be PullIfMissing,
// PullAlways, or PullNever.
Expand Down
5 changes: 4 additions & 1 deletion cmd/buildah/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type runInputOptions struct {
addHistory bool
capAdd []string
capDrop []string
contextDir string
env []string
hostname string
isolation string
Expand Down Expand Up @@ -58,6 +59,7 @@ func init() {
flags.BoolVar(&opts.addHistory, "add-history", false, "add an entry for this operation to the image's history. Use BUILDAH_HISTORY environment variable to override. (default false)")
flags.StringSliceVar(&opts.capAdd, "cap-add", []string{}, "add the specified capability (default [])")
flags.StringSliceVar(&opts.capDrop, "cap-drop", []string{}, "drop the specified capability (default [])")
flags.StringVar(&opts.contextDir, "contextdir", "", "context directory path")
flags.StringArrayVarP(&opts.env, "env", "e", []string{}, "add environment variable to be set temporarily when running command (default [])")
flags.StringVar(&opts.hostname, "hostname", "", "set the hostname inside of the container")
flags.StringVar(&opts.isolation, "isolation", "", "`type` of process isolation to use. Use BUILDAH_ISOLATION environment variable to override.")
Expand Down Expand Up @@ -130,6 +132,7 @@ func runCmd(c *cobra.Command, args []string, iopts runInputOptions) error {
Isolation: isolation,
NamespaceOptions: namespaceOptions,
ConfigureNetwork: networkPolicy,
ContextDir: iopts.contextDir,
CNIPluginPath: iopts.CNIPlugInPath,
CNIConfigDir: iopts.CNIConfigDir,
AddCapabilities: iopts.capAdd,
Expand All @@ -146,7 +149,7 @@ func runCmd(c *cobra.Command, args []string, iopts runInputOptions) error {
}
}

mounts, err := parse.GetVolumes(iopts.volumes, iopts.mounts)
mounts, err := parse.GetVolumes(iopts.volumes, iopts.mounts, iopts.contextDir)
if err != nil {
return err
}
Expand Down
6 changes: 6 additions & 0 deletions docs/buildah-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ not disabled.
List of directories in which the CNI plugins which will be used for configuring
network namespaces can be found.

**--contextdir** *directory*

Build context directory. Specifying a context directory causes Buildah to
chroot into that context directory. This means copying files pointed at
by symbolic links outside of the chroot will fail.

**--env**, **-e** *env=value*

Temporarily add a value (e.g. env=*value*) to the environment for the running
Expand Down
1 change: 1 addition & 0 deletions imagebuildah/stage_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ func (s *StageExecutor) prepare(ctx context.Context, from string, initializeIBCo
ConfigureNetwork: s.executor.configureNetwork,
CNIPluginPath: s.executor.cniPluginPath,
CNIConfigDir: s.executor.cniConfigDir,
ContextDir: s.executor.contextDir,
IDMappingOptions: s.executor.idmappingOptions,
CommonBuildOpts: s.executor.commonBuildOptions,
DefaultMountsFilePath: s.executor.defaultMountsFilePath,
Expand Down
1 change: 1 addition & 0 deletions new.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions
FromImageDigest: imageDigest,
Container: name,
ContainerID: container.ID,
ContextDir: options.ContextDir,
ImageAnnotations: imageAnnotations,
ImageCreatedBy: "",
ProcessLabel: container.ProcessLabel(),
Expand Down
32 changes: 22 additions & 10 deletions pkg/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ func getVolumeMounts(volumes []string) (map[string]specs.Mount, error) {
}

// GetVolumes gets the volumes from --volume and --mount
func GetVolumes(volumes []string, mounts []string) ([]specs.Mount, error) {
unifiedMounts, err := getMounts(mounts)
func GetVolumes(volumes []string, mounts []string, contextDir string) ([]specs.Mount, error) {
unifiedMounts, err := getMounts(mounts, contextDir)
if err != nil {
return nil, err
}
Expand All @@ -284,7 +284,7 @@ func GetVolumes(volumes []string, mounts []string) ([]specs.Mount, error) {
// spec mounts.
// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
// buildah run --mount type=tmpfs,target=/dev/shm ...
func getMounts(mounts []string) (map[string]specs.Mount, error) {
func getMounts(mounts []string, contextDir string) (map[string]specs.Mount, error) {
finalMounts := make(map[string]specs.Mount)

errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>[,options]")
Expand All @@ -307,7 +307,7 @@ func getMounts(mounts []string) (map[string]specs.Mount, error) {
tokens := strings.Split(arr[1], ",")
switch kv[1] {
case TypeBind:
mount, err := GetBindMount(tokens)
mount, err := GetBindMount(tokens, contextDir)
if err != nil {
return nil, err
}
Expand All @@ -333,20 +333,21 @@ func getMounts(mounts []string) (map[string]specs.Mount, error) {
}

// GetBindMount parses a single bind mount entry from the --mount flag.
func GetBindMount(args []string) (specs.Mount, error) {
func GetBindMount(args []string, contextDir string) (specs.Mount, error) {
newMount := specs.Mount{
Type: TypeBind,
}

setSource := false
setDest := false
bindNonRecursive := false

for _, val := range args {
kv := strings.SplitN(val, "=", 2)
switch kv[0] {
case "bind-nonrecursive":
newMount.Options = append(newMount.Options, "bind")
case "ro", "nosuid", "nodev", "noexec":
bindNonRecursive = true
case "ro", "nosuid", "nodev", "noexec", "rw", "readwrite":
// TODO: detect duplication of these options.
// (Is this necessary?)
newMount.Options = append(newMount.Options, kv[0])
Expand All @@ -368,7 +369,6 @@ func GetBindMount(args []string) (specs.Mount, error) {
return newMount, err
}
newMount.Source = kv[1]
setSource = true
case "target", "dst", "destination":
if len(kv) == 1 {
return newMount, errors.Wrapf(optionArgError, kv[0])
Expand All @@ -387,12 +387,24 @@ func GetBindMount(args []string) (specs.Mount, error) {
}
}

// buildkit parity: default bind option must be `rbind`
// unless specified
if !bindNonRecursive {
newMount.Options = append(newMount.Options, "rbind")
}

if !setDest {
return newMount, noDestError
}

if !setSource {
newMount.Source = newMount.Destination
// builkit parity: support absolute path for sources from current build context
if newMount.Source == "" || newMount.Source == "." || newMount.Source == "./" {
newMount.Source = contextDir
}
// if path begins with `./` replace `./` with context dir
if strings.HasPrefix(newMount.Source, "./") {
// path should be /contextDir/specified path
newMount.Source = strings.Replace(newMount.Source, ".", contextDir, 1)
}

opts, err := parse.ValidateVolumeOpts(newMount.Options)
Expand Down
2 changes: 2 additions & 0 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ type RunOptions struct {
User string
// WorkingDir is an override for the working directory.
WorkingDir string
// ContextDirectory for the current RUN invocation.
ContextDir string
// Shell is default shell to run in a container.
Shell string
// Cmd is an override for the configured default command.
Expand Down
22 changes: 18 additions & 4 deletions run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/containers/buildah/copier"
"github.com/containers/buildah/define"
"github.com/containers/buildah/pkg/overlay"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/pkg/sshagent"
"github.com/containers/buildah/util"
"github.com/containers/common/pkg/capabilities"
Expand Down Expand Up @@ -66,6 +67,12 @@ func setChildProcess() error {

// Run runs the specified command in the container's root filesystem.
func (b *Builder) Run(command []string, options RunOptions) error {
// override contextdir from RunOptions if specified explicitly via --contextDir
// otherwise use ContextDir configured from build
if options.ContextDir != "" {
b.ContextDir = options.ContextDir
}

p, err := ioutil.TempDir("", define.Package)
if err != nil {
return err
Expand Down Expand Up @@ -523,7 +530,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st

// Get the list of mounts that are just for this Run() call.
// TODO: acui: de-spaghettify run mounts
runMounts, mountArtifacts, err := runSetupRunMounts(runFileMounts, secrets, sshSources, b.MountLabel, cdir, spec.Linux.UIDMappings, spec.Linux.GIDMappings, b.ProcessLabel)
runMounts, mountArtifacts, err := b.runSetupRunMounts(runFileMounts, secrets, sshSources, cdir, spec.Linux.UIDMappings, spec.Linux.GIDMappings)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2347,7 +2354,7 @@ func init() {
}

// runSetupRunMounts sets up mounts that exist only in this RUN, not in subsequent runs
func runSetupRunMounts(mounts []string, secrets map[string]string, sshSources map[string]*sshagent.Source, mountlabel string, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping, processLabel string) ([]spec.Mount, *runMountArtifacts, error) {
func (b *Builder) runSetupRunMounts(mounts []string, secrets map[string]string, sshSources map[string]*sshagent.Source, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping) ([]spec.Mount, *runMountArtifacts, error) {
mountTargets := make([]string, 0, 10)
finalMounts := make([]specs.Mount, 0, len(mounts))
agents := make([]*sshagent.AgentServer, 0, len(mounts))
Expand All @@ -2367,7 +2374,7 @@ func runSetupRunMounts(mounts []string, secrets map[string]string, sshSources ma
// For now, we only support type secret.
switch kv[1] {
case "secret":
mount, err := getSecretMount(tokens, secrets, mountlabel, containerWorkingDir, uidmap, gidmap)
mount, err := getSecretMount(tokens, secrets, b.MountLabel, containerWorkingDir, uidmap, gidmap)
if err != nil {
return nil, nil, err
}
Expand All @@ -2377,7 +2384,7 @@ func runSetupRunMounts(mounts []string, secrets map[string]string, sshSources ma

}
case "ssh":
mount, agent, err := getSSHMount(tokens, sshCount, sshSources, mountlabel, uidmap, gidmap, processLabel)
mount, agent, err := getSSHMount(tokens, sshCount, sshSources, b.MountLabel, uidmap, gidmap, b.ProcessLabel)
if err != nil {
return nil, nil, err
}
Expand All @@ -2391,6 +2398,13 @@ func runSetupRunMounts(mounts []string, secrets map[string]string, sshSources ma
// Count is needed as the default destination of the ssh sock inside the container is /run/buildkit/ssh_agent.{i}
sshCount++
}
case "bind":
mount, err := parse.GetBindMount(tokens, b.ContextDir)
if err != nil {
return nil, nil, err
}
finalMounts = append(finalMounts, mount)
mountTargets = append(mountTargets, mount.Destination)
default:
return nil, nil, errors.Errorf("invalid mount type %q", kv[1])
}
Expand Down
10 changes: 10 additions & 0 deletions tests/bud.bats
Original file line number Diff line number Diff line change
Expand Up @@ -3498,3 +3498,13 @@ _EOF
## exported /run should not be empty
assert "$count" == "1"
}

@test "bud-with-mount-like-buildkit" {
# Create file in current build context and mount inside RUN step
touch input_file
echo hello > input_file
run_buildah build -t testbud --signature-policy ${TESTSDIR}/policy.json -f ${TESTSDIR}/bud/buildkit-mount/Dockerfile
expect_output --substring "hello"
rm input_file
run_buildah rmi -f testbud
}
4 changes: 4 additions & 0 deletions tests/bud/buildkit-mount/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM alpine
RUN mkdir /test
# use option z if selinux is enabled
RUN --mount=type=bind,source=.,target=/test,z cat /test/input_file

0 comments on commit 7943032

Please sign in to comment.