mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

Systemd is now complaining or mentioning /var/run as a legacy directory. It has been many years where /var/run is a symlink to /run on all most distributions, make the change to the default. Partial fix for https://github.com/containers/podman/issues/8369 Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
372 lines
13 KiB
Go
372 lines
13 KiB
Go
package imagebuildah
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/buildah"
|
|
"github.com/containers/common/pkg/config"
|
|
"github.com/containers/image/v5/docker/reference"
|
|
"github.com/containers/image/v5/types"
|
|
encconfig "github.com/containers/ocicrypt/config"
|
|
"github.com/containers/storage"
|
|
"github.com/containers/storage/pkg/archive"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/openshift/imagebuilder"
|
|
"github.com/openshift/imagebuilder/dockerfile/parser"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
PullIfMissing = buildah.PullIfMissing
|
|
PullAlways = buildah.PullAlways
|
|
PullIfNewer = buildah.PullIfNewer
|
|
PullNever = buildah.PullNever
|
|
|
|
Gzip = archive.Gzip
|
|
Bzip2 = archive.Bzip2
|
|
Xz = archive.Xz
|
|
Zstd = archive.Zstd
|
|
Uncompressed = archive.Uncompressed
|
|
)
|
|
|
|
// Mount is a mountpoint for the build container.
|
|
type Mount specs.Mount
|
|
|
|
// BuildOptions can be used to alter how an image is built.
|
|
type BuildOptions struct {
|
|
// ContextDirectory is the default source location for COPY and ADD
|
|
// commands.
|
|
ContextDirectory string
|
|
// PullPolicy controls whether or not we pull images. It should be one
|
|
// of PullIfMissing, PullAlways, PullIfNewer, or PullNever.
|
|
PullPolicy buildah.PullPolicy
|
|
// Registry is a value which is prepended to the image's name, if it
|
|
// needs to be pulled and the image name alone can not be resolved to a
|
|
// reference to a source image. No separator is implicitly added.
|
|
Registry string
|
|
// IgnoreUnrecognizedInstructions tells us to just log instructions we
|
|
// don't recognize, and try to keep going.
|
|
IgnoreUnrecognizedInstructions bool
|
|
// Quiet tells us whether or not to announce steps as we go through them.
|
|
Quiet bool
|
|
// Isolation controls how Run() runs things.
|
|
Isolation buildah.Isolation
|
|
// Runtime is the name of the command to run for RUN instructions when
|
|
// Isolation is either IsolationDefault or IsolationOCI. It should
|
|
// accept the same arguments and flags that runc does.
|
|
Runtime string
|
|
// RuntimeArgs adds global arguments for the runtime.
|
|
RuntimeArgs []string
|
|
// TransientMounts is a list of mounts that won't be kept in the image.
|
|
TransientMounts []string
|
|
// Compression specifies the type of compression which is applied to
|
|
// layer blobs. The default is to not use compression, but
|
|
// archive.Gzip is recommended.
|
|
Compression archive.Compression
|
|
// Arguments which can be interpolated into Dockerfiles
|
|
Args map[string]string
|
|
// Name of the image to write to.
|
|
Output string
|
|
// Additional tags to add to the image that we write, if we know of a
|
|
// way to add them.
|
|
AdditionalTags []string
|
|
// Log is a callback that will print a progress message. If no value
|
|
// is supplied, the message will be sent to Err (or os.Stderr, if Err
|
|
// is nil) by default.
|
|
Log func(format string, args ...interface{})
|
|
// In is connected to stdin for RUN instructions.
|
|
In io.Reader
|
|
// Out is a place where non-error log messages are sent.
|
|
Out io.Writer
|
|
// Err is a place where error log messages should be sent.
|
|
Err io.Writer
|
|
// SignaturePolicyPath specifies an override location for the signature
|
|
// policy which should be used for verifying the new image as it is
|
|
// being written. Except in specific circumstances, no value should be
|
|
// specified, indicating that the shared, system-wide default policy
|
|
// should be used.
|
|
SignaturePolicyPath string
|
|
// ReportWriter is an io.Writer which will be used to report the
|
|
// progress of the (possible) pulling of the source image and the
|
|
// writing of the new image.
|
|
ReportWriter io.Writer
|
|
// OutputFormat is the format of the output image's manifest and
|
|
// configuration data.
|
|
// Accepted values are buildah.OCIv1ImageManifest and buildah.Dockerv2ImageManifest.
|
|
OutputFormat string
|
|
// SystemContext holds parameters used for authentication.
|
|
SystemContext *types.SystemContext
|
|
// NamespaceOptions controls how we set up namespaces processes that we
|
|
// might need when handling RUN instructions.
|
|
NamespaceOptions []buildah.NamespaceOption
|
|
// ConfigureNetwork controls whether or not network interfaces and
|
|
// routing are configured for a new network namespace (i.e., when not
|
|
// joining another's namespace and not just using the host's
|
|
// namespace), effectively deciding whether or not the process has a
|
|
// usable network.
|
|
ConfigureNetwork buildah.NetworkConfigurationPolicy
|
|
// CNIPluginPath is the location of CNI plugin helpers, if they should be
|
|
// run from a location other than the default location.
|
|
CNIPluginPath string
|
|
// CNIConfigDir is the location of CNI configuration files, if the files in
|
|
// the default configuration directory shouldn't be used.
|
|
CNIConfigDir string
|
|
// ID mapping options to use if we're setting up our own user namespace
|
|
// when handling RUN instructions.
|
|
IDMappingOptions *buildah.IDMappingOptions
|
|
// AddCapabilities is a list of capabilities to add to the default set when
|
|
// handling RUN instructions.
|
|
AddCapabilities []string
|
|
// DropCapabilities is a list of capabilities to remove from the default set
|
|
// when handling RUN instructions. If a capability appears in both lists, it
|
|
// will be dropped.
|
|
DropCapabilities []string
|
|
// CommonBuildOpts is *required*.
|
|
CommonBuildOpts *buildah.CommonBuildOptions
|
|
// DefaultMountsFilePath is the file path holding the mounts to be mounted in "host-path:container-path" format
|
|
DefaultMountsFilePath string
|
|
// IIDFile tells the builder to write the image ID to the specified file
|
|
IIDFile string
|
|
// Squash tells the builder to produce an image with a single layer
|
|
// instead of with possibly more than one layer.
|
|
Squash bool
|
|
// Labels metadata for an image
|
|
Labels []string
|
|
// Annotation metadata for an image
|
|
Annotations []string
|
|
// OnBuild commands to be run by images based on this image
|
|
OnBuild []string
|
|
// Layers tells the builder to create a cache of images for each step in the Dockerfile
|
|
Layers bool
|
|
// NoCache tells the builder to build the image from scratch without checking for a cache.
|
|
// It creates a new set of cached images for the build.
|
|
NoCache bool
|
|
// RemoveIntermediateCtrs tells the builder whether to remove intermediate containers used
|
|
// during the build process. Default is true.
|
|
RemoveIntermediateCtrs bool
|
|
// ForceRmIntermediateCtrs tells the builder to remove all intermediate containers even if
|
|
// the build was unsuccessful.
|
|
ForceRmIntermediateCtrs bool
|
|
// BlobDirectory is a directory which we'll use for caching layer blobs.
|
|
BlobDirectory string
|
|
// Target the targeted FROM in the Dockerfile to build.
|
|
Target string
|
|
// Devices are the additional devices to add to the containers.
|
|
Devices []string
|
|
// SignBy is the fingerprint of a GPG key to use for signing images.
|
|
SignBy string
|
|
// Architecture specifies the target architecture of the image to be built.
|
|
Architecture string
|
|
// Timestamp sets the created timestamp to the specified time, allowing
|
|
// for deterministic, content-addressable builds.
|
|
Timestamp *time.Time
|
|
// OS is the specifies the operating system of the image to be built.
|
|
OS string
|
|
// MaxPullPushRetries is the maximum number of attempts we'll make to pull or push any one
|
|
// image from or to an external registry if the first attempt fails.
|
|
MaxPullPushRetries int
|
|
// PullPushRetryDelay is how long to wait before retrying a pull or push attempt.
|
|
PullPushRetryDelay time.Duration
|
|
// OciDecryptConfig contains the config that can be used to decrypt an image if it is
|
|
// encrypted if non-nil. If nil, it does not attempt to decrypt an image.
|
|
OciDecryptConfig *encconfig.DecryptConfig
|
|
// Jobs is the number of stages to run in parallel. If not specified it defaults to 1.
|
|
Jobs *int
|
|
// LogRusage logs resource usage for each step.
|
|
LogRusage bool
|
|
// Excludes is a list of excludes to be used instead of the .dockerignore file.
|
|
Excludes []string
|
|
// From is the image name to use to replace the value specified in the first
|
|
// FROM instruction in the Containerfile
|
|
From string
|
|
}
|
|
|
|
// BuildDockerfiles parses a set of one or more Dockerfiles (which may be
|
|
// URLs), creates a new Executor, and then runs Prepare/Execute/Commit/Delete
|
|
// over the entire set of instructions.
|
|
func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOptions, paths ...string) (string, reference.Canonical, error) {
|
|
if len(paths) == 0 {
|
|
return "", nil, errors.Errorf("error building: no dockerfiles specified")
|
|
}
|
|
var dockerfiles []io.ReadCloser
|
|
defer func(dockerfiles ...io.ReadCloser) {
|
|
for _, d := range dockerfiles {
|
|
d.Close()
|
|
}
|
|
}(dockerfiles...)
|
|
|
|
for _, dfile := range paths {
|
|
var data io.ReadCloser
|
|
|
|
if strings.HasPrefix(dfile, "http://") || strings.HasPrefix(dfile, "https://") {
|
|
logrus.Debugf("reading remote Dockerfile %q", dfile)
|
|
resp, err := http.Get(dfile)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
if resp.ContentLength == 0 {
|
|
resp.Body.Close()
|
|
return "", nil, errors.Errorf("no contents in %q", dfile)
|
|
}
|
|
data = resp.Body
|
|
} else {
|
|
dinfo, err := os.Stat(dfile)
|
|
if err != nil {
|
|
// If the Dockerfile isn't available, try again with
|
|
// context directory prepended (if not prepended yet).
|
|
if !strings.HasPrefix(dfile, options.ContextDirectory) {
|
|
dfile = filepath.Join(options.ContextDirectory, dfile)
|
|
dinfo, err = os.Stat(dfile)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
// If given a directory, add '/Dockerfile' to it.
|
|
if dinfo.Mode().IsDir() {
|
|
dfile = filepath.Join(dfile, "Dockerfile")
|
|
}
|
|
logrus.Debugf("reading local Dockerfile %q", dfile)
|
|
contents, err := os.Open(dfile)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
dinfo, err = contents.Stat()
|
|
if err != nil {
|
|
contents.Close()
|
|
return "", nil, errors.Wrapf(err, "error reading info about %q", dfile)
|
|
}
|
|
if dinfo.Mode().IsRegular() && dinfo.Size() == 0 {
|
|
contents.Close()
|
|
return "", nil, errors.Errorf("no contents in %q", dfile)
|
|
}
|
|
data = contents
|
|
}
|
|
|
|
// pre-process Dockerfiles with ".in" suffix
|
|
if strings.HasSuffix(dfile, ".in") {
|
|
pData, err := preprocessDockerfileContents(data, options.ContextDirectory)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
data = *pData
|
|
}
|
|
|
|
dockerfiles = append(dockerfiles, data)
|
|
}
|
|
|
|
mainNode, err := imagebuilder.ParseDockerfile(dockerfiles[0])
|
|
if err != nil {
|
|
return "", nil, errors.Wrapf(err, "error parsing main Dockerfile: %s", dockerfiles[0])
|
|
}
|
|
|
|
warnOnUnsetBuildArgs(mainNode, options.Args)
|
|
|
|
for _, d := range dockerfiles[1:] {
|
|
additionalNode, err := imagebuilder.ParseDockerfile(d)
|
|
if err != nil {
|
|
return "", nil, errors.Wrapf(err, "error parsing additional Dockerfile %s", d)
|
|
}
|
|
mainNode.Children = append(mainNode.Children, additionalNode.Children...)
|
|
}
|
|
exec, err := NewExecutor(store, options, mainNode)
|
|
if err != nil {
|
|
return "", nil, errors.Wrapf(err, "error creating build executor")
|
|
}
|
|
b := imagebuilder.NewBuilder(options.Args)
|
|
defaultContainerConfig, err := config.Default()
|
|
if err != nil {
|
|
return "", nil, errors.Wrapf(err, "failed to get container config")
|
|
}
|
|
b.Env = append(defaultContainerConfig.GetDefaultEnv(), b.Env...)
|
|
stages, err := imagebuilder.NewStages(mainNode, b)
|
|
if err != nil {
|
|
return "", nil, errors.Wrap(err, "error reading multiple stages")
|
|
}
|
|
if options.Target != "" {
|
|
stagesTargeted, ok := stages.ThroughTarget(options.Target)
|
|
if !ok {
|
|
return "", nil, errors.Errorf("The target %q was not found in the provided Dockerfile", options.Target)
|
|
}
|
|
stages = stagesTargeted
|
|
}
|
|
return exec.Build(ctx, stages)
|
|
}
|
|
|
|
func warnOnUnsetBuildArgs(node *parser.Node, args map[string]string) {
|
|
for _, child := range node.Children {
|
|
switch strings.ToUpper(child.Value) {
|
|
case "ARG":
|
|
argName := child.Next.Value
|
|
if _, ok := args[argName]; !strings.Contains(argName, "=") && !ok {
|
|
logrus.Warnf("missing %q build argument. Try adding %q to the command line", argName, fmt.Sprintf("--build-arg %s=<VALUE>", argName))
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// preprocessDockerfileContents runs CPP(1) in preprocess-only mode on the input
|
|
// dockerfile content and will use ctxDir as the base include path.
|
|
//
|
|
// Note: we cannot use cmd.StdoutPipe() as cmd.Wait() closes it.
|
|
func preprocessDockerfileContents(r io.Reader, ctxDir string) (rdrCloser *io.ReadCloser, err error) {
|
|
cppPath := "/usr/bin/cpp"
|
|
if _, err = os.Stat(cppPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = errors.Errorf("error: Dockerfile.in support requires %s to be installed", cppPath)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
stdout := bytes.Buffer{}
|
|
stderr := bytes.Buffer{}
|
|
|
|
cmd := exec.Command(cppPath, "-E", "-iquote", ctxDir, "-traditional", "-undef", "-")
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
pipe, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
pipe.Close()
|
|
}
|
|
}()
|
|
|
|
if err = cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err = io.Copy(pipe, r); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pipe.Close()
|
|
if err = cmd.Wait(); err != nil {
|
|
if stderr.Len() > 0 {
|
|
err = errors.Wrapf(err, "%v", strings.TrimSpace(stderr.String()))
|
|
}
|
|
return nil, errors.Wrapf(err, "error pre-processing Dockerfile")
|
|
}
|
|
|
|
rc := ioutil.NopCloser(bytes.NewReader(stdout.Bytes()))
|
|
return &rc, nil
|
|
}
|