use layer cache when building images

to more closely mimic docker default behavior, the --layers
cli option is set to true by default for podman.  the buildah
environment variable of BUILDAH_LAYERS is still honored and will
override the command line input.

this should be considered in place of PR #1383.

Many thanks for Scott McCarty for inspiring this welcome change.

Signed-off-by: baude <bbaude@redhat.com>

Closes: #1422
Approved by: rhatdan
This commit is contained in:
baude 2018-09-06 16:10:06 -05:00 committed by Atomic Bot
parent 782caea801
commit d92650a922
12 changed files with 173 additions and 42 deletions

View file

@ -1,11 +1,6 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
@ -15,16 +10,26 @@ import (
"github.com/projectatomic/buildah/pkg/parse"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
var (
layerFlags = []cli.Flag{
cli.BoolTFlag{
Name: "layers",
Usage: "cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. ",
},
}
buildDescription = "Builds an OCI or Docker image using instructions from one\n" +
"or more Dockerfiles and a specified build context directory."
buildCommand = cli.Command{
Name: "build",
Usage: "Build an image using instructions from Dockerfiles",
Description: buildDescription,
Flags: append(buildahcli.BudFlags, buildahcli.FromAndBudFlags...),
Flags: append(append(buildahcli.BudFlags, layerFlags...), buildahcli.FromAndBudFlags...),
Action: buildCmd,
ArgsUsage: "CONTEXT-DIRECTORY | URL",
SkipArgReorder: true,
@ -84,6 +89,13 @@ func buildCmd(c *cli.Context) error {
}
contextDir := ""
cliArgs := c.Args()
layers := c.BoolT("layers") // layers for podman defaults to true
// Check to see if the BUILDAH_LAYERS environment variable is set and override command-line
if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok {
layers = buildahcli.UseLayers()
}
if len(cliArgs) > 0 {
// The context directory could be a URL. Try to handle that.
tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", cliArgs[0])
@ -215,7 +227,7 @@ func buildCmd(c *cli.Context) error {
Squash: c.Bool("squash"),
Labels: c.StringSlice("label"),
Annotations: c.StringSlice("annotation"),
Layers: c.Bool("layers"),
Layers: layers,
NoCache: c.Bool("no-cache"),
RemoveIntermediateCtrs: c.BoolT("rm"),
ForceRmIntermediateCtrs: c.Bool("force-rm"),

View file

@ -90,7 +90,7 @@ k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/
k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils
github.com/mrunalp/fileutils master
github.com/varlink/go master
github.com/projectatomic/buildah 2423a90e23ced88c72e30664631af18c9af75148
github.com/projectatomic/buildah 9c8c58c33b0b6e15f2fa780042ef46552a8a26d4
github.com/Nvveen/Gotty master
github.com/fsouza/go-dockerclient master
github.com/openshift/imagebuilder master

View file

@ -133,6 +133,7 @@ func SetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmou
if err = unix.Mount(rootPath, rootfs, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil {
return unmountAll, errors.Wrapf(err, "error bind mounting root filesystem from %q to %q", rootPath, rootfs)
}
logrus.Debugf("bind mounted %q to %q", rootPath, rootfs)
unmount = append([]string{rootfs}, unmount...)
spec.Root.Path = rootfs

View file

@ -98,6 +98,7 @@ func RunUsingChroot(spec *specs.Spec, bundlePath string, stdin io.Reader, stdout
if err = ioutils.AtomicWriteFile(filepath.Join(bundlePath, "config.json"), specbytes, 0600); err != nil {
return errors.Wrapf(err, "error storing runtime configuration")
}
logrus.Debugf("config = %v", string(specbytes))
// Run the grandparent subprocess in a user namespace that reuses the mappings that we have.
uidmap, gidmap, err := util.GetHostIDMappings("")
@ -381,11 +382,15 @@ func runUsingChrootMain() {
logrus.Error(what)
return false
}
for readFd := range relays {
for readFd, writeFd := range relays {
if err := unix.SetNonblock(readFd, true); err != nil {
logrus.Errorf("error setting descriptor %d (%s) non-blocking: %v", readFd, fdDesc[readFd], err)
return
}
if err := unix.SetNonblock(writeFd, false); err != nil {
logrus.Errorf("error setting descriptor %d (%s) blocking: %v", relays[writeFd], fdDesc[writeFd], err)
return
}
}
go func() {
buffers := make(map[int]*bytes.Buffer)
@ -429,6 +434,26 @@ func runUsingChrootMain() {
continue
}
}
// If this is the last of the data we'll be able to read
// from this descriptor, read as much as there is to read.
for rfd.Revents&unix.POLLHUP == unix.POLLHUP {
nr, err := unix.Read(int(rfd.Fd), b)
logIfNotRetryable(err, fmt.Sprintf("read %s: %v", fdDesc[int(rfd.Fd)], err))
if nr <= 0 {
break
}
if wfd, ok := relays[int(rfd.Fd)]; ok {
nwritten, err := buffers[wfd].Write(b[:nr])
if err != nil {
logrus.Debugf("buffer: %v", err)
break
}
if nwritten != nr {
logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", nr, nwritten)
break
}
}
}
}
if nread == 0 {
removeFds[int(rfd.Fd)] = struct{}{}
@ -592,8 +617,7 @@ func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io
// main() for parent subprocess. Its main job is to try to make our
// environment look like the one described by the runtime configuration blob,
// and then launch the intended command as a child, since we can't exec()
// directly.
// and then launch the intended command as a child.
func runUsingChrootExecMain() {
args := os.Args[1:]
var options runUsingChrootExecSubprocOptions
@ -630,6 +654,31 @@ func runUsingChrootExecMain() {
}
}
// Try to chroot into the root. Do this before we potentially block the syscall via the
// seccomp profile.
var oldst, newst unix.Stat_t
if err := unix.Stat(options.Spec.Root.Path, &oldst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chdir(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing to intended root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Chroot(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
if err := unix.Stat("/", &newst); err != nil {
fmt.Fprintf(os.Stderr, "error stat()ing current root directory: %v\n", err)
os.Exit(1)
}
if oldst.Dev != newst.Dev || oldst.Ino != newst.Ino {
fmt.Fprintf(os.Stderr, "unknown error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("chrooted into %q", options.Spec.Root.Path)
// not doing because it's still shared: creating devices
// not doing because it's not applicable: setting annotations
// not doing because it's still shared: setting sysctl settings
@ -663,20 +712,21 @@ func runUsingChrootExecMain() {
os.Exit(1)
}
// Try to chroot into the root.
if err := unix.Chroot(options.Spec.Root.Path); err != nil {
fmt.Fprintf(os.Stderr, "error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
// Try to change to the directory.
cwd := options.Spec.Process.Cwd
if !filepath.IsAbs(cwd) {
cwd = "/" + cwd
}
if err := unix.Chdir(cwd); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into directory %q: %v\n", cwd, err)
cwd = filepath.Clean(cwd)
if err := unix.Chdir("/"); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into new root directory %q: %v\n", options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("chrooted into %q, changed working directory to %q", options.Spec.Root.Path, cwd)
if err := unix.Chdir(cwd); err != nil {
fmt.Fprintf(os.Stderr, "error chdir()ing into directory %q under root %q: %v\n", cwd, options.Spec.Root.Path, err)
os.Exit(1)
}
logrus.Debugf("changed working directory to %q", cwd)
// Drop privileges.
user := options.Spec.Process.User

View file

@ -105,7 +105,7 @@ func setSeccomp(spec *specs.Spec) error {
for _, name := range rule.Names {
scnum, err := libseccomp.GetSyscallFromName(name)
if err != nil {
logrus.Debugf("error mapping syscall %q to a syscall, ignoring %q rule for %q", name, rule.Action)
logrus.Debugf("error mapping syscall %q to a syscall, ignoring %q rule for %q", name, rule.Action, name)
continue
}
scnames[scnum] = name

View file

@ -1,11 +0,0 @@
// +build !seccomp
package buildah
import "github.com/opencontainers/runtime-spec/specs-go"
func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error {
// If no seccomp is being used, the Seccomp profile in the Linux spec
// is not set
return nil
}

View file

@ -10,6 +10,7 @@ import (
"strings"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/projectatomic/buildah"
"github.com/projectatomic/buildah/util"
"github.com/urfave/cli"
@ -68,6 +69,13 @@ var (
},
}
LayerFlags = []cli.Flag{
cli.BoolFlag{
Name: "layers",
Usage: fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. (default %t)", UseLayers()),
},
}
BudFlags = []cli.Flag{
cli.StringSliceFlag{
Name: "annotation",
@ -129,10 +137,6 @@ var (
Name: "label",
Usage: "Set metadata for an image (default [])",
},
cli.BoolFlag{
Name: "layers",
Usage: fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. (default %t)", UseLayers()),
},
cli.BoolFlag{
Name: "no-cache",
Usage: "Do not use existing cached images for the container build. Build from the start with a new set of cached layers.",
@ -280,3 +284,12 @@ func DefaultIsolation() string {
}
return buildah.OCI
}
func VerifyFlagsArgsOrder(args []string) error {
for _, arg := range args {
if strings.HasPrefix(arg, "-") {
return errors.Errorf("No options (%s) can be specified after the image or container name", arg)
}
}
return nil
}

View file

@ -973,8 +973,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
} else if b.WorkDir() != "" {
g.SetProcessCwd(b.WorkDir())
}
g.SetProcessSelinuxLabel(b.ProcessLabel)
g.SetLinuxMountLabel(b.MountLabel)
setupSelinux(g, b.ProcessLabel, b.MountLabel)
mountPoint, err := b.Mount(b.MountLabel)
if err != nil {
return err
@ -1017,6 +1016,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
if spec.Process.Cwd == "" {
spec.Process.Cwd = DefaultWorkingDir
}
logrus.Debugf("ensuring working directory %q exists", filepath.Join(mountPoint, spec.Process.Cwd))
if err = os.MkdirAll(filepath.Join(mountPoint, spec.Process.Cwd), 0755); err != nil {
return errors.Wrapf(err, "error ensuring working directory %q exists", spec.Process.Cwd)
}
@ -1760,11 +1760,14 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy
writeDesc[unix.Stderr] = "stderr"
}
// Set our reading descriptors to non-blocking.
for fd := range relayMap {
if err := unix.SetNonblock(fd, true); err != nil {
logrus.Errorf("error setting %s to nonblocking: %v", readDesc[fd], err)
for rfd, wfd := range relayMap {
if err := unix.SetNonblock(rfd, true); err != nil {
logrus.Errorf("error setting %s to nonblocking: %v", readDesc[rfd], err)
return
}
if err := unix.SetNonblock(wfd, false); err != nil {
logrus.Errorf("error setting descriptor %d (%s) blocking: %v", wfd, writeDesc[wfd], err)
}
}
// A helper that returns false if err is an error that would cause us
// to give up.
@ -1837,7 +1840,33 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy
}
if n > 0 {
// Buffer the data in case we get blocked on where they need to go.
relayBuffer[writeFD].Write(buf[:n])
nwritten, err := relayBuffer[writeFD].Write(buf[:n])
if err != nil {
logrus.Debugf("buffer: %v", err)
continue
}
if nwritten != n {
logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", n, nwritten)
continue
}
// If this is the last of the data we'll be able to read from this
// descriptor, read all that there is to read.
for pollFd.Revents&unix.POLLHUP == unix.POLLHUP {
nr, err := unix.Read(readFD, buf)
logIfNotRetryable(err, fmt.Sprintf("read %s: %v", readDesc[readFD], err))
if nr <= 0 {
break
}
nwritten, err := relayBuffer[writeFD].Write(buf[:nr])
if err != nil {
logrus.Debugf("buffer: %v", err)
break
}
if nwritten != nr {
logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", nr, nwritten)
break
}
}
}
}
}

View file

@ -1,4 +1,4 @@
// +build seccomp
// +build seccomp,linux
package buildah

View file

@ -0,0 +1,15 @@
// +build !seccomp !linux
package buildah
import (
"github.com/opencontainers/runtime-spec/specs-go"
)
func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error {
if spec.Linux != nil {
// runtime-tools may have supplied us with a default filter
spec.Linux.Seccomp = nil
}
return nil
}

12
vendor/github.com/projectatomic/buildah/selinux.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
// +build selinux,linux
package buildah
import (
"github.com/opencontainers/runtime-tools/generate"
)
func setupSelinux(g *generate.Generator, processLabel, mountLabel string) {
g.SetProcessSelinuxLabel(processLabel)
g.SetLinuxMountLabel(mountLabel)
}

View file

@ -0,0 +1,10 @@
// +build !selinux !linux
package buildah
import (
"github.com/opencontainers/runtime-tools/generate"
)
func setupSelinux(g *generate.Generator, processLabel, mountLabel string) {
}