Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buildah build: use the same overlay for the context directory for the whole build #5975

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 12 additions & 3 deletions imagebuildah/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
options.AdditionalBuildContexts = make(map[string]*define.AdditionalBuildContext)
}

contextDirectory, processLabel, mountLabel, usingContextOverlay, cleanupOverlay, err := platformSetupContextDirectoryOverlay(store, &options)
if err != nil {
return "", nil, fmt.Errorf("mounting an overlay over build context directory: %w", err)
}
defer cleanupOverlay()
if contextDirectory != "" {
options.ContextDirectory = contextDirectory
}

if len(options.Platforms) == 0 {
options.Platforms = append(options.Platforms, struct{ OS, Arch, Variant string }{
OS: options.SystemContext.OSChoice,
Expand Down Expand Up @@ -283,7 +292,7 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
platformOptions.ReportWriter = reporter
platformOptions.Err = stderr
}
thisID, thisRef, err := buildDockerfilesOnce(ctx, store, loggerPerPlatform, logPrefix, platformOptions, paths, files)
thisID, thisRef, err := buildDockerfilesOnce(ctx, store, loggerPerPlatform, logPrefix, platformOptions, paths, files, processLabel, mountLabel, usingContextOverlay)
if err != nil {
if errorContext := strings.TrimSpace(logPrefix); errorContext != "" {
return fmt.Errorf("%s: %w", errorContext, err)
Expand Down Expand Up @@ -394,7 +403,7 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
return id, ref, nil
}

func buildDockerfilesOnce(ctx context.Context, store storage.Store, logger *logrus.Logger, logPrefix string, options define.BuildOptions, containerFiles []string, dockerfilecontents [][]byte) (string, reference.Canonical, error) {
func buildDockerfilesOnce(ctx context.Context, store storage.Store, logger *logrus.Logger, logPrefix string, options define.BuildOptions, containerFiles []string, dockerfilecontents [][]byte, processLabel, mountLabel string, usingContextOverlay bool) (string, reference.Canonical, error) {
mainNode, err := imagebuilder.ParseDockerfile(bytes.NewReader(dockerfilecontents[0]))
if err != nil {
return "", nil, fmt.Errorf("parsing main Dockerfile: %s: %w", containerFiles[0], err)
Expand Down Expand Up @@ -445,7 +454,7 @@ func buildDockerfilesOnce(ctx context.Context, store storage.Store, logger *logr
mainNode.Children = append(mainNode.Children, additionalNode.Children...)
}

exec, err := newExecutor(logger, logPrefix, store, options, mainNode, containerFiles)
exec, err := newExecutor(logger, logPrefix, store, options, mainNode, containerFiles, processLabel, mountLabel, usingContextOverlay)
if err != nil {
return "", nil, fmt.Errorf("creating build executor: %w", err)
}
Expand Down
86 changes: 86 additions & 0 deletions imagebuildah/build_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package imagebuildah

import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"

"github.com/containers/buildah/define"
"github.com/containers/buildah/internal/tmpdir"
"github.com/containers/buildah/pkg/overlay"
"github.com/containers/storage"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)

// platformSetupContextDirectoryOverlay() may set up an overlay _over_ the
// build context directory, and sorts out labeling. Returns either the new
// location which should be used as the base build context or the old location;
// the process label and mount label for the build; a boolean value that
// indicates whether we did, in fact, mount an overlay; a cleanup function
// which should be called when the location is no longer needed (on success);
// and a non-nil fatal error if any of that failed.
func platformSetupContextDirectoryOverlay(store storage.Store, options *define.BuildOptions) (string, string, string, bool, func(), error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return values are a bit unwieldy, maybe a struct instead? Although I guess the single caller unpacks them anyways, so doesn't matter. Yeah, one caller, so makes sense as is.

var succeeded bool
var tmpDir, contentDir string
cleanup := func() {
if contentDir != "" {
if err := overlay.CleanupContent(tmpDir); err != nil {
logrus.Debugf("cleaning up overlay scaffolding for build context directory: %v", err)
}
}
if tmpDir != "" {
if err := os.Remove(tmpDir); err != nil && !errors.Is(err, fs.ErrNotExist) {
logrus.Debugf("removing should-be-empty temporary directory %q: %v", tmpDir, err)
}
}
}
defer func() {
if !succeeded {
cleanup()
}
}()
// double-check that the context directory location is an absolute path
contextDirectoryAbsolute, err := filepath.Abs(options.ContextDirectory)
if err != nil {
return "", "", "", false, nil, fmt.Errorf("determining absolute path of %q: %w", options.ContextDirectory, err)
}
var st unix.Stat_t
if err := unix.Stat(contextDirectoryAbsolute, &st); err != nil {
return "", "", "", false, nil, fmt.Errorf("checking ownership of %q: %w", options.ContextDirectory, err)
}
// figure out the labeling situation
processLabel, mountLabel, err := label.InitLabels(options.CommonBuildOpts.LabelOpts)
if err != nil {
return "", "", "", false, nil, err
}
// create a temporary directory
tmpDir, err = os.MkdirTemp(tmpdir.GetTempDir(), "buildah-context-")
if err != nil {
return "", "", "", false, nil, fmt.Errorf("creating temporary directory: %w", err)
}
// create the scaffolding for an overlay mount under it
contentDir, err = overlay.TempDir(tmpDir, 0, 0)
if err != nil {
return "", "", "", false, nil, fmt.Errorf("creating overlay scaffolding for build context directory: %w", err)
}
// mount an overlay that uses it as a lower
overlayOptions := overlay.Options{
GraphOpts: slices.Clone(store.GraphOptions()),
ForceMount: true,
MountLabel: mountLabel,
}
targetDir := filepath.Join(contentDir, "target")
contextDirMountSpec, err := overlay.MountWithOptions(contentDir, contextDirectoryAbsolute, targetDir, &overlayOptions)
if err != nil {
return "", "", "", false, nil, fmt.Errorf("creating overlay scaffolding for build context directory: %w", err)
}
// going forward, pretend that the merged directory is the actual context directory
logrus.Debugf("mounted an overlay at %q over %q", contextDirMountSpec.Source, contextDirectoryAbsolute)
succeeded = true
return contextDirMountSpec.Source, processLabel, mountLabel, true, cleanup, nil
}
19 changes: 19 additions & 0 deletions imagebuildah/build_notlinux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//go:build !linux

package imagebuildah

import (
"github.com/containers/buildah/define"
"github.com/containers/storage"
)

// platformSetupContextDirectoryOverlay() may set up an overlay _over_ the
// build context directory, and sorts out labeling. Returns either the new
// location which should be used as the base build context or the old location;
// the process label and mount label for the build; a boolean value that
// indicates whether we did, in fact, mount an overlay; a cleanup function
// which should be called when the location is no longer needed (on success);
// and a non-nil fatal error if any of that failed.
func platformSetupContextDirectoryOverlay(store storage.Store, options *define.BuildOptions) (string, string, string, bool, func(), error) {
return options.ContextDirectory, "", "", false, func() {}, nil
}
6 changes: 5 additions & 1 deletion imagebuildah/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Executor struct {
stages map[string]*StageExecutor
store storage.Store
contextDir string
contextDirWritesAreDiscarded bool
pullPolicy define.PullPolicy
registry string
ignoreUnrecognizedInstructions bool
Expand Down Expand Up @@ -173,7 +174,7 @@ type imageTypeAndHistoryAndDiffIDs struct {
}

// newExecutor creates a new instance of the imagebuilder.Executor interface.
func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, options define.BuildOptions, mainNode *parser.Node, containerFiles []string) (*Executor, error) {
func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, options define.BuildOptions, mainNode *parser.Node, containerFiles []string, processLabel, mountLabel string, contextWritesDiscarded bool) (*Executor, error) {
defaultContainerConfig, err := config.Default()
if err != nil {
return nil, fmt.Errorf("failed to get container config: %w", err)
Expand Down Expand Up @@ -238,6 +239,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
stages: make(map[string]*StageExecutor),
store: store,
contextDir: options.ContextDirectory,
contextDirWritesAreDiscarded: contextWritesDiscarded,
excludes: excludes,
groupAdd: options.GroupAdd,
ignoreFile: options.IgnoreFile,
Expand Down Expand Up @@ -273,6 +275,8 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
squash: options.Squash,
labels: slices.Clone(options.Labels),
layerLabels: slices.Clone(options.LayerLabels),
processLabel: processLabel,
mountLabel: mountLabel,
annotations: slices.Clone(options.Annotations),
layers: options.Layers,
noHostname: options.CommonBuildOpts.NoHostname,
Expand Down
29 changes: 29 additions & 0 deletions imagebuildah/imagebuildah_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package imagebuildah

import (
"flag"
"os"
"testing"

"github.com/sirupsen/logrus"
)

func TestMain(m *testing.M) {
var logLevel string
debug := false
if InitReexec() {
return
}
flag.BoolVar(&debug, "debug", false, "turn on debug logging")
flag.StringVar(&logLevel, "log-level", "error", "log level")
flag.Parse()
level, err := logrus.ParseLevel(logLevel)
if err != nil {
logrus.Fatalf("error parsing log level %q: %v", logLevel, err)
}
if debug && level < logrus.DebugLevel {
level = logrus.DebugLevel
}
logrus.SetLevel(level)
os.Exit(m.Run())
}
Loading