buildah/imagebuildah/build.go

322 lines
11 KiB
Go
Raw Normal View History

package imagebuildah
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/containers/buildah"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/opencontainers/runc/libcontainer/configs"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/openshift/imagebuilder"
"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 []Mount
// 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
// Capabilities is a list of capabilities to use when
// handling RUN instructions.
Capabilities []string
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.
Add --target to bud command Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com> Add the --target option to the bud command. This allows the user to specify the last stage to build in a multi stage Dockerfile. Addresses #632 Tests: ``` cat ./bud/target/Dockerfile FROM ubuntu:latest RUN touch /1 FROM alpine:latest AS mytarget RUN touch /2 FROM busybox:latest AS mytarget2 RUN touch /3 buildah bud --debug=false -t tom --target mytarget ./bud/target . STEP 1: FROM ubuntu:latest STEP 2: RUN touch /1 STEP 3: FROM alpine:latest AS mytarget STEP 4: RUN touch /2 STEP 5: COMMIT containers-storage:[overlay@/var/lib/containers/storage+/var/run/containers/storage]localhost/tom:latest Getting image source signatures Skipping blob 503e53e365f3 (already present): 5.52 MiB / 5.52 MiB [=========] 0s Copying blob 66e5f29a7649: 3.50 KiB / 3.50 KiB [============================] 0s Copying config e72f1fa3d72d: 704 B / 704 B [================================] 0s Writing manifest to image destination Storing signatures --> e72f1fa3d72ddd3f23acb22a059ecce33dad571433223389e3ce92a5fd9ebae5 STEP 6: COMMIT containers-storage:[overlay@/var/lib/containers/storage+/var/run/containers/storage]localhost/tom:latest Getting image source signatures Skipping blob 503e53e365f3 (already present): 5.52 MiB / 5.52 MiB [=========] 0s Skipping blob 66e5f29a7649 (already present): 3.50 KiB / 3.50 KiB [=========] 0s Copying config e72620d8efe7: 704 B / 704 B [================================] 0s Writing manifest to image destination Storing signatures --> e72620d8efe764178d1352dfb3a9a773794309ee9e879e17d3803b18553f5025 buildah images IMAGE NAME IMAGE TAG IMAGE ID CREATED AT SIZE docker.io/library/ubuntu latest 20bb25d32758 Jan 22, 2019 17:41 90 MB docker.io/library/alpine latest caf27325b298 Jan 30, 2019 17:19 5.8 MB localhost/tom latest 993ee7ded616 Feb 5, 2019 13:49 5.8 MB ``` Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com> Closes: #1321 Approved by: vrothberg
2019-02-03 07:31:44 +08:00
Target string
// Devices are the additional devices to add to the containers.
Devices []configs.Device
// DefaultEnv for containers.
DefaultEnv []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
// OS is the specifies the operating system of the image to be built.
OS 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, errors.Wrapf(err, "error getting %q", dfile)
}
if resp.ContentLength == 0 {
resp.Body.Close()
return "", nil, errors.Errorf("no contents in %q", dfile)
}
data = resp.Body
} else {
// If the Dockerfile isn't found try prepending the
// context directory to it.
dinfo, err := os.Stat(dfile)
if os.IsNotExist(err) {
dfile = filepath.Join(options.ContextDirectory, dfile)
dinfo, err = os.Stat(dfile)
if err != nil {
return "", nil, errors.Wrapf(err, "error reading info about %q", dfile)
}
}
// 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, errors.Wrapf(err, "error reading %q", dfile)
}
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.Wrapf(err, "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")
}
for _, d := range dockerfiles[1:] {
additionalNode, err := imagebuilder.ParseDockerfile(d)
if err != nil {
return "", nil, errors.Wrapf(err, "error parsing additional Dockerfile")
}
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)
b.Env = append(options.DefaultEnv, b.Env...)
stages, err := imagebuilder.NewStages(mainNode, b)
if err != nil {
return "", nil, errors.Wrap(err, "error reading multiple stages")
}
Add --target to bud command Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com> Add the --target option to the bud command. This allows the user to specify the last stage to build in a multi stage Dockerfile. Addresses #632 Tests: ``` cat ./bud/target/Dockerfile FROM ubuntu:latest RUN touch /1 FROM alpine:latest AS mytarget RUN touch /2 FROM busybox:latest AS mytarget2 RUN touch /3 buildah bud --debug=false -t tom --target mytarget ./bud/target . STEP 1: FROM ubuntu:latest STEP 2: RUN touch /1 STEP 3: FROM alpine:latest AS mytarget STEP 4: RUN touch /2 STEP 5: COMMIT containers-storage:[overlay@/var/lib/containers/storage+/var/run/containers/storage]localhost/tom:latest Getting image source signatures Skipping blob 503e53e365f3 (already present): 5.52 MiB / 5.52 MiB [=========] 0s Copying blob 66e5f29a7649: 3.50 KiB / 3.50 KiB [============================] 0s Copying config e72f1fa3d72d: 704 B / 704 B [================================] 0s Writing manifest to image destination Storing signatures --> e72f1fa3d72ddd3f23acb22a059ecce33dad571433223389e3ce92a5fd9ebae5 STEP 6: COMMIT containers-storage:[overlay@/var/lib/containers/storage+/var/run/containers/storage]localhost/tom:latest Getting image source signatures Skipping blob 503e53e365f3 (already present): 5.52 MiB / 5.52 MiB [=========] 0s Skipping blob 66e5f29a7649 (already present): 3.50 KiB / 3.50 KiB [=========] 0s Copying config e72620d8efe7: 704 B / 704 B [================================] 0s Writing manifest to image destination Storing signatures --> e72620d8efe764178d1352dfb3a9a773794309ee9e879e17d3803b18553f5025 buildah images IMAGE NAME IMAGE TAG IMAGE ID CREATED AT SIZE docker.io/library/ubuntu latest 20bb25d32758 Jan 22, 2019 17:41 90 MB docker.io/library/alpine latest caf27325b298 Jan 30, 2019 17:19 5.8 MB localhost/tom latest 993ee7ded616 Feb 5, 2019 13:49 5.8 MB ``` Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com> Closes: #1321 Approved by: vrothberg
2019-02-03 07:31:44 +08:00
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)
}
// 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 = fmt.Errorf("%v: %s", err, strings.TrimSpace(stderr.String()))
}
return nil, errors.Wrapf(err, "error pre-processing Dockerfile")
}
rc := ioutil.NopCloser(bytes.NewReader(stdout.Bytes()))
return &rc, nil
}