2017-03-28 15:06:13 +08:00
|
|
|
package imagebuildah
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
is "github.com/containers/image/storage"
|
|
|
|
"github.com/containers/image/transports"
|
|
|
|
"github.com/containers/image/transports/alltransports"
|
|
|
|
"github.com/containers/image/types"
|
2017-05-17 23:53:28 +08:00
|
|
|
"github.com/containers/storage"
|
2017-03-28 15:06:13 +08:00
|
|
|
"github.com/containers/storage/pkg/archive"
|
|
|
|
"github.com/containers/storage/pkg/stringid"
|
|
|
|
"github.com/docker/docker/builder/dockerfile/parser"
|
|
|
|
docker "github.com/fsouza/go-dockerclient"
|
|
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
|
|
"github.com/openshift/imagebuilder"
|
2017-06-02 03:23:02 +08:00
|
|
|
"github.com/pkg/errors"
|
2017-03-28 15:06:13 +08:00
|
|
|
"github.com/projectatomic/buildah"
|
2017-10-10 03:05:56 +08:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-03-28 15:06:13 +08:00
|
|
|
)
|
|
|
|
|
2017-04-11 02:15:30 +08:00
|
|
|
const (
|
2017-05-18 05:02:40 +08:00
|
|
|
PullIfMissing = buildah.PullIfMissing
|
|
|
|
PullAlways = buildah.PullAlways
|
|
|
|
PullNever = buildah.PullNever
|
|
|
|
DefaultRuntime = buildah.DefaultRuntime
|
|
|
|
OCIv1ImageFormat = buildah.OCIv1ImageManifest
|
|
|
|
Dockerv2ImageFormat = buildah.Dockerv2ImageManifest
|
2017-04-11 22:27:05 +08:00
|
|
|
|
|
|
|
Gzip = archive.Gzip
|
|
|
|
Bzip2 = archive.Bzip2
|
|
|
|
Xz = archive.Xz
|
|
|
|
Uncompressed = archive.Uncompressed
|
2017-04-11 02:15:30 +08:00
|
|
|
)
|
|
|
|
|
2017-04-11 22:27:05 +08:00
|
|
|
// Mount is a mountpoint for the build container.
|
|
|
|
type Mount specs.Mount
|
|
|
|
|
2017-03-28 15:06:13 +08:00
|
|
|
// 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
|
2017-04-11 02:15:30 +08:00
|
|
|
// PullPolicy controls whether or not we pull images. It should be one
|
|
|
|
// of PullIfMissing, PullAlways, or PullNever.
|
|
|
|
PullPolicy int
|
2017-03-28 15:06:13 +08:00
|
|
|
// 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
|
2017-07-31 22:44:21 +08:00
|
|
|
// reference to a source image. No separator is implicitly added.
|
2017-03-28 15:06:13 +08:00
|
|
|
Registry string
|
2017-07-31 22:44:21 +08:00
|
|
|
// Transport is a value which is prepended to the image's name, if it
|
|
|
|
// needs to be pulled and the image name alone, or the image name and
|
|
|
|
// the registry together, can not be resolved to a reference to a
|
|
|
|
// source image. No separator is implicitly added.
|
|
|
|
Transport string
|
2017-03-28 15:06:13 +08:00
|
|
|
// 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
|
|
|
|
// Runtime is the name of the command to run for RUN instructions. 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.
|
2017-04-11 22:27:05 +08:00
|
|
|
TransientMounts []Mount
|
2017-03-28 15:06:13 +08:00
|
|
|
// 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
|
2017-04-11 02:25:07 +08:00
|
|
|
// Additional tags to add to the image that we write, if we know of a
|
|
|
|
// way to add them.
|
|
|
|
AdditionalTags []string
|
2017-04-11 02:17:15 +08:00
|
|
|
// 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{})
|
|
|
|
// 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
|
2017-03-28 15:06:13 +08:00
|
|
|
// 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
|
2017-05-09 23:56:44 +08:00
|
|
|
// 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
|
2017-05-18 05:02:40 +08:00
|
|
|
// OutputFormat is the format of the output image's manifest and
|
|
|
|
// configuration data.
|
|
|
|
// Accepted values are OCIv1ImageFormat and Dockerv2ImageFormat.
|
|
|
|
OutputFormat string
|
2018-01-28 07:17:05 +08:00
|
|
|
// SystemContext holds parameters used for authentication.
|
2018-02-14 03:58:56 +08:00
|
|
|
SystemContext *types.SystemContext
|
|
|
|
CommonBuildOpts *buildah.CommonBuildOptions
|
2018-02-24 01:38:39 +08:00
|
|
|
// DefaultMountsFilePath is the file path holding the mounts to be mounted in "host-path:container-path" format
|
|
|
|
DefaultMountsFilePath string
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Executor is a buildah-based implementation of the imagebuilder.Executor
|
|
|
|
// interface.
|
|
|
|
type Executor struct {
|
|
|
|
store storage.Store
|
|
|
|
contextDir string
|
|
|
|
builder *buildah.Builder
|
2017-04-11 02:15:30 +08:00
|
|
|
pullPolicy int
|
2017-03-28 15:06:13 +08:00
|
|
|
registry string
|
2017-07-31 22:44:21 +08:00
|
|
|
transport string
|
2017-03-28 15:06:13 +08:00
|
|
|
ignoreUnrecognizedInstructions bool
|
|
|
|
quiet bool
|
|
|
|
runtime string
|
|
|
|
runtimeArgs []string
|
2017-04-11 22:27:05 +08:00
|
|
|
transientMounts []Mount
|
2017-03-28 15:06:13 +08:00
|
|
|
compression archive.Compression
|
|
|
|
output string
|
2017-05-30 23:51:40 +08:00
|
|
|
outputFormat string
|
2017-04-11 02:25:07 +08:00
|
|
|
additionalTags []string
|
2017-04-11 02:17:15 +08:00
|
|
|
log func(format string, args ...interface{})
|
|
|
|
out io.Writer
|
|
|
|
err io.Writer
|
2017-03-28 15:06:13 +08:00
|
|
|
signaturePolicyPath string
|
|
|
|
systemContext *types.SystemContext
|
|
|
|
mountPoint string
|
|
|
|
preserved int
|
|
|
|
volumes imagebuilder.VolumeSet
|
|
|
|
volumeCache map[string]string
|
|
|
|
volumeCacheInfo map[string]os.FileInfo
|
2017-05-09 23:56:44 +08:00
|
|
|
reportWriter io.Writer
|
2018-02-14 03:58:56 +08:00
|
|
|
commonBuildOptions *buildah.CommonBuildOptions
|
2018-02-24 01:38:39 +08:00
|
|
|
defaultMountsFilePath string
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Preserve informs the executor that from this point on, it needs to ensure
|
|
|
|
// that only COPY and ADD instructions can modify the contents of this
|
|
|
|
// directory or anything below it.
|
|
|
|
// The Executor handles this by caching the contents of directories which have
|
|
|
|
// been marked this way before executing a RUN instruction, invalidating that
|
|
|
|
// cache when an ADD or COPY instruction sets any location under the directory
|
|
|
|
// as the destination, and using the cache to reset the contents of the
|
|
|
|
// directory tree after processing each RUN instruction.
|
|
|
|
// It would be simpler if we could just mark the directory as a read-only bind
|
|
|
|
// mount of itself during Run(), but the directory is expected to be remain
|
|
|
|
// writeable, even if any changes within it are ultimately discarded.
|
|
|
|
func (b *Executor) Preserve(path string) error {
|
|
|
|
logrus.Debugf("PRESERVE %q", path)
|
|
|
|
if b.volumes.Covers(path) {
|
|
|
|
// This path is already a subdirectory of a volume path that
|
2017-06-21 05:37:50 +08:00
|
|
|
// we're already preserving, so there's nothing new to be done
|
|
|
|
// except ensure that it exists.
|
|
|
|
archivedPath := filepath.Join(b.mountPoint, path)
|
|
|
|
if err := os.MkdirAll(archivedPath, 0755); err != nil {
|
|
|
|
return errors.Wrapf(err, "error ensuring volume path %q exists", archivedPath)
|
|
|
|
}
|
|
|
|
if err := b.volumeCacheInvalidate(path); err != nil {
|
|
|
|
return errors.Wrapf(err, "error ensuring volume path %q is preserved", archivedPath)
|
|
|
|
}
|
2017-03-28 15:06:13 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// Figure out where the cache for this volume would be stored.
|
|
|
|
b.preserved++
|
2017-05-17 23:53:28 +08:00
|
|
|
cacheDir, err := b.store.ContainerDirectory(b.builder.ContainerID)
|
2017-03-28 15:06:13 +08:00
|
|
|
if err != nil {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("unable to locate temporary directory for container")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
cacheFile := filepath.Join(cacheDir, fmt.Sprintf("volume%d.tar", b.preserved))
|
|
|
|
// Save info about the top level of the location that we'll be archiving.
|
|
|
|
archivedPath := filepath.Join(b.mountPoint, path)
|
2018-03-28 02:06:00 +08:00
|
|
|
|
|
|
|
// Try and resolve the symlink (if one exists)
|
|
|
|
// Set archivedPath and path based on whether a symlink is found or not
|
|
|
|
if symLink, err := resolveSymLink(b.mountPoint, path); err == nil {
|
|
|
|
archivedPath = filepath.Join(b.mountPoint, symLink)
|
|
|
|
path = symLink
|
|
|
|
} else {
|
|
|
|
return errors.Wrapf(err, "error reading symbolic link to %q", path)
|
|
|
|
}
|
|
|
|
|
2017-03-28 15:06:13 +08:00
|
|
|
st, err := os.Stat(archivedPath)
|
2017-06-21 05:37:50 +08:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
if err = os.MkdirAll(archivedPath, 0755); err != nil {
|
|
|
|
return errors.Wrapf(err, "error ensuring volume path %q exists", archivedPath)
|
|
|
|
}
|
|
|
|
st, err = os.Stat(archivedPath)
|
|
|
|
}
|
2017-03-28 15:06:13 +08:00
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("error reading info about %q: %v", archivedPath, err)
|
2017-06-21 05:37:50 +08:00
|
|
|
return errors.Wrapf(err, "error reading info about volume path %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
b.volumeCacheInfo[path] = st
|
|
|
|
if !b.volumes.Add(path) {
|
|
|
|
// This path is not a subdirectory of a volume path that we're
|
|
|
|
// already preserving, so adding it to the list should work.
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("error adding %q to the volume cache", path)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
b.volumeCache[path] = cacheFile
|
|
|
|
// Now prune cache files for volumes that are now supplanted by this one.
|
|
|
|
removed := []string{}
|
|
|
|
for cachedPath := range b.volumeCache {
|
|
|
|
// Walk our list of cached volumes, and check that they're
|
|
|
|
// still in the list of locations that we need to cache.
|
|
|
|
found := false
|
|
|
|
for _, volume := range b.volumes {
|
|
|
|
if volume == cachedPath {
|
|
|
|
// We need to keep this volume's cache.
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
// We don't need to keep this volume's cache. Make a
|
|
|
|
// note to remove it.
|
|
|
|
removed = append(removed, cachedPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Actually remove the caches that we decided to remove.
|
|
|
|
for _, cachedPath := range removed {
|
|
|
|
archivedPath := filepath.Join(b.mountPoint, cachedPath)
|
|
|
|
logrus.Debugf("no longer need cache of %q in %q", archivedPath, b.volumeCache[cachedPath])
|
|
|
|
if err := os.Remove(b.volumeCache[cachedPath]); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error removing %q", b.volumeCache[cachedPath])
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
delete(b.volumeCache, cachedPath)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove any volume cache item which will need to be re-saved because we're
|
|
|
|
// writing to part of it.
|
|
|
|
func (b *Executor) volumeCacheInvalidate(path string) error {
|
|
|
|
invalidated := []string{}
|
|
|
|
for cachedPath := range b.volumeCache {
|
|
|
|
if strings.HasPrefix(path, cachedPath+string(os.PathSeparator)) {
|
|
|
|
invalidated = append(invalidated, cachedPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, cachedPath := range invalidated {
|
|
|
|
if err := os.Remove(b.volumeCache[cachedPath]); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error removing volume cache %q", b.volumeCache[cachedPath])
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
archivedPath := filepath.Join(b.mountPoint, cachedPath)
|
|
|
|
logrus.Debugf("invalidated volume cache for %q from %q", archivedPath, b.volumeCache[cachedPath])
|
|
|
|
delete(b.volumeCache, cachedPath)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the contents of each of the executor's list of volumes for which we
|
|
|
|
// don't already have a cache file.
|
|
|
|
func (b *Executor) volumeCacheSave() error {
|
|
|
|
for cachedPath, cacheFile := range b.volumeCache {
|
|
|
|
archivedPath := filepath.Join(b.mountPoint, cachedPath)
|
|
|
|
_, err := os.Stat(cacheFile)
|
|
|
|
if err == nil {
|
|
|
|
logrus.Debugf("contents of volume %q are already cached in %q", archivedPath, cacheFile)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !os.IsNotExist(err) {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error checking for cache of %q in %q", archivedPath, cacheFile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2017-06-21 05:37:50 +08:00
|
|
|
if err := os.MkdirAll(archivedPath, 0755); err != nil {
|
|
|
|
return errors.Wrapf(err, "error ensuring volume path %q exists", archivedPath)
|
|
|
|
}
|
2017-03-28 15:06:13 +08:00
|
|
|
logrus.Debugf("caching contents of volume %q in %q", archivedPath, cacheFile)
|
|
|
|
cache, err := os.Create(cacheFile)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error creating archive at %q", cacheFile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
defer cache.Close()
|
|
|
|
rc, err := archive.Tar(archivedPath, archive.Uncompressed)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error archiving %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
defer rc.Close()
|
|
|
|
_, err = io.Copy(cache, rc)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error archiving %q to %q", archivedPath, cacheFile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restore the contents of each of the executor's list of volumes.
|
|
|
|
func (b *Executor) volumeCacheRestore() error {
|
|
|
|
for cachedPath, cacheFile := range b.volumeCache {
|
|
|
|
archivedPath := filepath.Join(b.mountPoint, cachedPath)
|
|
|
|
logrus.Debugf("restoring contents of volume %q from %q", archivedPath, cacheFile)
|
|
|
|
cache, err := os.Open(cacheFile)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error opening archive at %q", cacheFile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
defer cache.Close()
|
|
|
|
if err := os.RemoveAll(archivedPath); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error clearing volume path %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2017-06-21 05:37:50 +08:00
|
|
|
if err := os.MkdirAll(archivedPath, 0755); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error recreating volume path %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
err = archive.Untar(cache, archivedPath, nil)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error extracting archive at %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
if st, ok := b.volumeCacheInfo[cachedPath]; ok {
|
|
|
|
if err := os.Chmod(archivedPath, st.Mode()); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error restoring permissions on %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
if err := os.Chown(archivedPath, 0, 0); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error setting ownership on %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
if err := os.Chtimes(archivedPath, st.ModTime(), st.ModTime()); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error restoring datestamps on %q", archivedPath)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy copies data into the working tree. The "Download" field is how
|
|
|
|
// imagebuilder tells us the instruction was "ADD" and not "COPY".
|
|
|
|
func (b *Executor) Copy(excludes []string, copies ...imagebuilder.Copy) error {
|
|
|
|
for _, copy := range copies {
|
|
|
|
logrus.Debugf("COPY %#v, %#v", excludes, copy)
|
|
|
|
if err := b.volumeCacheInvalidate(copy.Dest); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sources := []string{}
|
|
|
|
for _, src := range copy.Src {
|
|
|
|
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
|
|
|
|
sources = append(sources, src)
|
|
|
|
} else {
|
|
|
|
sources = append(sources, filepath.Join(b.contextDir, src))
|
|
|
|
}
|
|
|
|
}
|
2017-11-30 22:34:02 +08:00
|
|
|
if err := b.builder.Add(copy.Dest, copy.Download, buildah.AddAndCopyOptions{}, sources...); err != nil {
|
2017-03-28 15:06:13 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-04-11 22:27:05 +08:00
|
|
|
func convertMounts(mounts []Mount) []specs.Mount {
|
|
|
|
specmounts := []specs.Mount{}
|
|
|
|
for _, m := range mounts {
|
|
|
|
s := specs.Mount{
|
|
|
|
Destination: m.Destination,
|
|
|
|
Type: m.Type,
|
|
|
|
Source: m.Source,
|
|
|
|
Options: m.Options,
|
|
|
|
}
|
|
|
|
specmounts = append(specmounts, s)
|
|
|
|
}
|
|
|
|
return specmounts
|
|
|
|
}
|
|
|
|
|
2017-03-28 15:06:13 +08:00
|
|
|
// Run executes a RUN instruction using the working container as a root
|
|
|
|
// directory.
|
|
|
|
func (b *Executor) Run(run imagebuilder.Run, config docker.Config) error {
|
|
|
|
logrus.Debugf("RUN %#v, %#v", run, config)
|
|
|
|
if b.builder == nil {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("no build container available")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
options := buildah.RunOptions{
|
|
|
|
Hostname: config.Hostname,
|
|
|
|
Runtime: b.runtime,
|
|
|
|
Args: b.runtimeArgs,
|
2017-04-11 22:27:05 +08:00
|
|
|
Mounts: convertMounts(b.transientMounts),
|
2017-03-28 15:06:13 +08:00
|
|
|
Env: config.Env,
|
|
|
|
User: config.User,
|
|
|
|
WorkingDir: config.WorkingDir,
|
|
|
|
Entrypoint: config.Entrypoint,
|
|
|
|
Cmd: config.Cmd,
|
|
|
|
NetworkDisabled: config.NetworkDisabled,
|
2018-01-24 00:17:31 +08:00
|
|
|
Quiet: b.quiet,
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
args := run.Args
|
|
|
|
if run.Shell {
|
|
|
|
args = append([]string{"/bin/sh", "-c"}, args...)
|
|
|
|
}
|
|
|
|
if err := b.volumeCacheSave(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err := b.builder.Run(args, options)
|
|
|
|
if err2 := b.volumeCacheRestore(); err2 != nil {
|
|
|
|
if err == nil {
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnrecognizedInstruction is called when we encounter an instruction that the
|
|
|
|
// imagebuilder parser didn't understand.
|
|
|
|
func (b *Executor) UnrecognizedInstruction(step *imagebuilder.Step) error {
|
2018-02-09 05:10:18 +08:00
|
|
|
err_str := fmt.Sprintf("Build error: Unknown instruction: %q ", step.Command)
|
|
|
|
err := fmt.Sprintf(err_str+"%#v", step)
|
2018-02-07 01:02:21 +08:00
|
|
|
if b.ignoreUnrecognizedInstructions {
|
2018-02-09 05:10:18 +08:00
|
|
|
logrus.Debugf(err)
|
2017-03-28 15:06:13 +08:00
|
|
|
return nil
|
|
|
|
}
|
2018-02-09 05:10:18 +08:00
|
|
|
|
|
|
|
switch logrus.GetLevel() {
|
|
|
|
case logrus.ErrorLevel:
|
|
|
|
logrus.Errorf(err_str)
|
|
|
|
case logrus.DebugLevel:
|
|
|
|
logrus.Debugf(err)
|
|
|
|
default:
|
|
|
|
logrus.Errorf("+(UNHANDLED LOGLEVEL) %#v", step)
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Errorf(err)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewExecutor creates a new instance of the imagebuilder.Executor interface.
|
|
|
|
func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) {
|
|
|
|
exec := Executor{
|
|
|
|
store: store,
|
|
|
|
contextDir: options.ContextDirectory,
|
2017-04-11 02:15:30 +08:00
|
|
|
pullPolicy: options.PullPolicy,
|
2017-03-28 15:06:13 +08:00
|
|
|
registry: options.Registry,
|
2017-07-31 22:44:21 +08:00
|
|
|
transport: options.Transport,
|
2017-03-28 15:06:13 +08:00
|
|
|
ignoreUnrecognizedInstructions: options.IgnoreUnrecognizedInstructions,
|
2018-02-24 01:38:39 +08:00
|
|
|
quiet: options.Quiet,
|
|
|
|
runtime: options.Runtime,
|
|
|
|
runtimeArgs: options.RuntimeArgs,
|
|
|
|
transientMounts: options.TransientMounts,
|
|
|
|
compression: options.Compression,
|
|
|
|
output: options.Output,
|
|
|
|
outputFormat: options.OutputFormat,
|
|
|
|
additionalTags: options.AdditionalTags,
|
|
|
|
signaturePolicyPath: options.SignaturePolicyPath,
|
|
|
|
systemContext: options.SystemContext,
|
|
|
|
volumeCache: make(map[string]string),
|
|
|
|
volumeCacheInfo: make(map[string]os.FileInfo),
|
|
|
|
log: options.Log,
|
|
|
|
out: options.Out,
|
|
|
|
err: options.Err,
|
|
|
|
reportWriter: options.ReportWriter,
|
|
|
|
commonBuildOptions: options.CommonBuildOpts,
|
|
|
|
defaultMountsFilePath: options.DefaultMountsFilePath,
|
2017-04-11 02:17:15 +08:00
|
|
|
}
|
|
|
|
if exec.err == nil {
|
|
|
|
exec.err = os.Stderr
|
|
|
|
}
|
|
|
|
if exec.out == nil {
|
|
|
|
exec.out = os.Stdout
|
|
|
|
}
|
|
|
|
if exec.log == nil {
|
|
|
|
stepCounter := 0
|
|
|
|
exec.log = func(format string, args ...interface{}) {
|
|
|
|
stepCounter++
|
|
|
|
prefix := fmt.Sprintf("STEP %d: ", stepCounter)
|
|
|
|
suffix := "\n"
|
|
|
|
fmt.Fprintf(exec.err, prefix+format+suffix, args...)
|
|
|
|
}
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
return &exec, nil
|
|
|
|
}
|
|
|
|
|
2017-04-12 06:18:35 +08:00
|
|
|
// Prepare creates a working container based on specified image, or if one
|
|
|
|
// isn't specified, the first FROM instruction we can find in the parsed tree.
|
|
|
|
func (b *Executor) Prepare(ib *imagebuilder.Builder, node *parser.Node, from string) error {
|
|
|
|
if from == "" {
|
|
|
|
base, err := ib.From(node)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("Prepare(node.Children=%#v)", node.Children)
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error determining starting point for build")
|
2017-04-12 06:18:35 +08:00
|
|
|
}
|
|
|
|
from = base
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
logrus.Debugf("FROM %#v", from)
|
|
|
|
if !b.quiet {
|
2017-04-11 02:17:15 +08:00
|
|
|
b.log("FROM %s", from)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
builderOptions := buildah.BuilderOptions{
|
2018-02-24 01:38:39 +08:00
|
|
|
FromImage: from,
|
|
|
|
PullPolicy: b.pullPolicy,
|
|
|
|
Registry: b.registry,
|
|
|
|
Transport: b.transport,
|
|
|
|
SignaturePolicyPath: b.signaturePolicyPath,
|
|
|
|
ReportWriter: b.reportWriter,
|
|
|
|
SystemContext: b.systemContext,
|
|
|
|
CommonBuildOpts: b.commonBuildOptions,
|
|
|
|
DefaultMountsFilePath: b.defaultMountsFilePath,
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
builder, err := buildah.NewBuilder(b.store, builderOptions)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error creating build container")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2017-05-18 23:38:38 +08:00
|
|
|
volumes := map[string]struct{}{}
|
|
|
|
for _, v := range builder.Volumes() {
|
|
|
|
volumes[v] = struct{}{}
|
|
|
|
}
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
dConfig := docker.Config{
|
2017-05-18 23:38:38 +08:00
|
|
|
Hostname: builder.Hostname(),
|
|
|
|
Domainname: builder.Domainname(),
|
|
|
|
User: builder.User(),
|
|
|
|
Env: builder.Env(),
|
|
|
|
Cmd: builder.Cmd(),
|
|
|
|
Image: from,
|
|
|
|
Volumes: volumes,
|
|
|
|
WorkingDir: builder.WorkDir(),
|
|
|
|
Entrypoint: builder.Entrypoint(),
|
|
|
|
Labels: builder.Labels(),
|
2018-03-16 19:57:36 +08:00
|
|
|
Shell: builder.Shell(),
|
2017-05-18 23:38:38 +08:00
|
|
|
}
|
|
|
|
var rootfs *docker.RootFS
|
|
|
|
if builder.Docker.RootFS != nil {
|
|
|
|
rootfs = &docker.RootFS{
|
|
|
|
Type: builder.Docker.RootFS.Type,
|
|
|
|
}
|
|
|
|
for _, id := range builder.Docker.RootFS.DiffIDs {
|
|
|
|
rootfs.Layers = append(rootfs.Layers, id.String())
|
|
|
|
}
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
}
|
2017-03-28 15:06:13 +08:00
|
|
|
dImage := docker.Image{
|
2017-05-18 23:38:38 +08:00
|
|
|
Parent: builder.FromImage,
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
ContainerConfig: dConfig,
|
2017-05-18 23:38:38 +08:00
|
|
|
Container: builder.Container,
|
|
|
|
Author: builder.Maintainer(),
|
|
|
|
Architecture: builder.Architecture(),
|
|
|
|
RootFS: rootfs,
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2017-05-18 23:38:38 +08:00
|
|
|
dImage.Config = &dImage.ContainerConfig
|
2017-03-28 15:06:13 +08:00
|
|
|
err = ib.FromImage(&dImage, node)
|
|
|
|
if err != nil {
|
|
|
|
if err2 := builder.Delete(); err2 != nil {
|
|
|
|
logrus.Debugf("error deleting container which we failed to update: %v", err2)
|
|
|
|
}
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error updating build context")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2017-11-22 21:57:31 +08:00
|
|
|
mountPoint, err := builder.Mount(builder.MountLabel)
|
2017-03-28 15:06:13 +08:00
|
|
|
if err != nil {
|
|
|
|
if err2 := builder.Delete(); err2 != nil {
|
|
|
|
logrus.Debugf("error deleting container which we failed to mount: %v", err2)
|
|
|
|
}
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error mounting new container")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
b.mountPoint = mountPoint
|
|
|
|
b.builder = builder
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete deletes the working container, if we have one. The Executor object
|
|
|
|
// should not be used to build another image, as the name of the output image
|
|
|
|
// isn't resettable.
|
|
|
|
func (b *Executor) Delete() (err error) {
|
|
|
|
if b.builder != nil {
|
|
|
|
err = b.builder.Delete()
|
|
|
|
b.builder = nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute runs each of the steps in the parsed tree, in turn.
|
|
|
|
func (b *Executor) Execute(ib *imagebuilder.Builder, node *parser.Node) error {
|
|
|
|
for i, node := range node.Children {
|
|
|
|
step := ib.Step()
|
|
|
|
if err := step.Resolve(node); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error resolving step %+v", *node)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
logrus.Debugf("Parsed Step: %+v", *step)
|
|
|
|
if !b.quiet {
|
2017-04-11 02:17:15 +08:00
|
|
|
b.log("%s", step.Original)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
requiresStart := false
|
|
|
|
if i < len(node.Children)-1 {
|
|
|
|
requiresStart = ib.RequiresStart(&parser.Node{Children: node.Children[i+1:]})
|
|
|
|
}
|
|
|
|
err := ib.Run(step, b, requiresStart)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error building at step %+v", *step)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commit writes the container's contents to an image, using a passed-in tag as
|
|
|
|
// the name if there is one, generating a unique ID-based one otherwise.
|
2017-04-12 02:39:16 +08:00
|
|
|
func (b *Executor) Commit(ib *imagebuilder.Builder) (err error) {
|
2017-03-28 15:06:13 +08:00
|
|
|
var imageRef types.ImageReference
|
|
|
|
if b.output != "" {
|
|
|
|
imageRef, err = alltransports.ParseImageName(b.output)
|
|
|
|
if err != nil {
|
|
|
|
imageRef2, err2 := is.Transport.ParseStoreReference(b.store, b.output)
|
|
|
|
if err2 == nil {
|
|
|
|
imageRef = imageRef2
|
|
|
|
err = nil
|
2018-02-11 09:38:24 +08:00
|
|
|
} else {
|
|
|
|
err = err2
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
imageRef, err = is.Transport.ParseStoreReference(b.store, "@"+stringid.GenerateRandomID())
|
|
|
|
}
|
2017-05-18 23:36:39 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error parsing reference for image to be written")
|
2017-05-18 23:36:39 +08:00
|
|
|
}
|
2018-02-06 05:26:21 +08:00
|
|
|
if ib.Author != "" {
|
|
|
|
b.builder.SetMaintainer(ib.Author)
|
|
|
|
}
|
2017-05-18 23:38:38 +08:00
|
|
|
config := ib.Config()
|
|
|
|
b.builder.SetHostname(config.Hostname)
|
|
|
|
b.builder.SetDomainname(config.Domainname)
|
|
|
|
b.builder.SetUser(config.User)
|
|
|
|
b.builder.ClearPorts()
|
|
|
|
for p := range config.ExposedPorts {
|
|
|
|
b.builder.SetPort(string(p))
|
|
|
|
}
|
|
|
|
b.builder.ClearEnv()
|
|
|
|
for _, envSpec := range config.Env {
|
|
|
|
spec := strings.SplitN(envSpec, "=", 2)
|
|
|
|
b.builder.SetEnv(spec[0], spec[1])
|
|
|
|
}
|
|
|
|
b.builder.SetCmd(config.Cmd)
|
|
|
|
b.builder.ClearVolumes()
|
|
|
|
for v := range config.Volumes {
|
|
|
|
b.builder.AddVolume(v)
|
|
|
|
}
|
|
|
|
b.builder.SetWorkDir(config.WorkingDir)
|
|
|
|
b.builder.SetEntrypoint(config.Entrypoint)
|
2018-03-16 19:57:36 +08:00
|
|
|
b.builder.SetShell(config.Shell)
|
2017-05-18 23:38:38 +08:00
|
|
|
b.builder.ClearLabels()
|
|
|
|
for k, v := range config.Labels {
|
|
|
|
b.builder.SetLabel(k, v)
|
|
|
|
}
|
2017-03-28 15:06:13 +08:00
|
|
|
if imageRef != nil {
|
|
|
|
logName := transports.ImageName(imageRef)
|
|
|
|
logrus.Debugf("COMMIT %q", logName)
|
|
|
|
if !b.quiet {
|
2017-04-11 02:17:15 +08:00
|
|
|
b.log("COMMIT %s", logName)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logrus.Debugf("COMMIT")
|
|
|
|
if !b.quiet {
|
2017-04-11 02:17:15 +08:00
|
|
|
b.log("COMMIT")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
options := buildah.CommitOptions{
|
2017-05-30 23:51:40 +08:00
|
|
|
Compression: b.compression,
|
|
|
|
SignaturePolicyPath: b.signaturePolicyPath,
|
|
|
|
AdditionalTags: b.additionalTags,
|
|
|
|
ReportWriter: b.reportWriter,
|
|
|
|
PreferredManifestType: b.outputFormat,
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
return b.builder.Commit(imageRef, options)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build takes care of the details of running Prepare/Execute/Commit/Delete
|
|
|
|
// over each of the one or more parsed Dockerfiles.
|
|
|
|
func (b *Executor) Build(ib *imagebuilder.Builder, node []*parser.Node) (err error) {
|
|
|
|
if len(node) == 0 {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error building: no build instructions")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
first := node[0]
|
2017-04-12 06:18:35 +08:00
|
|
|
from, err := ib.From(first)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("Build(first.Children=%#v)", first.Children)
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error determining starting point for build")
|
2017-04-12 06:18:35 +08:00
|
|
|
}
|
|
|
|
if err = b.Prepare(ib, first, from); err != nil {
|
2017-03-28 15:06:13 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer b.Delete()
|
|
|
|
for _, this := range node {
|
|
|
|
if err = b.Execute(ib, this); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-04-12 02:39:16 +08:00
|
|
|
if err = b.Commit(ib); err != nil {
|
2017-03-28 15:06:13 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(store storage.Store, options BuildOptions, dockerfile ...string) error {
|
|
|
|
if len(dockerfile) == 0 {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("error building: no dockerfiles specified")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-03-16 19:57:36 +08:00
|
|
|
nodes := []*parser.Node{}
|
2017-03-28 15:06:13 +08:00
|
|
|
for _, dfile := range dockerfile {
|
2018-03-16 19:57:36 +08:00
|
|
|
var (
|
|
|
|
parsed *parser.Node
|
|
|
|
err error
|
|
|
|
)
|
2017-03-28 15:06:13 +08:00
|
|
|
if strings.HasPrefix(dfile, "http://") || strings.HasPrefix(dfile, "https://") {
|
|
|
|
logrus.Debugf("reading remote Dockerfile %q", dfile)
|
|
|
|
resp, err := http.Get(dfile)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error getting %q", dfile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
if resp.ContentLength == 0 {
|
|
|
|
resp.Body.Close()
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("no contents in %q", dfile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-03-16 19:57:36 +08:00
|
|
|
parsed, err = imagebuilder.ParseDockerfile(resp.Body)
|
|
|
|
resp.Body.Close()
|
2017-03-28 15:06:13 +08:00
|
|
|
} else {
|
|
|
|
if !filepath.IsAbs(dfile) {
|
|
|
|
logrus.Debugf("resolving local Dockerfile %q", dfile)
|
|
|
|
dfile = filepath.Join(options.ContextDirectory, dfile)
|
|
|
|
}
|
|
|
|
logrus.Debugf("reading local Dockerfile %q", dfile)
|
2018-03-16 19:57:36 +08:00
|
|
|
parsed, err = imagebuilder.ParseFile(dfile)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "unable to parse %s", dfile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-03-16 19:57:36 +08:00
|
|
|
nodes = append(nodes, parsed)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-03-16 19:57:36 +08:00
|
|
|
builder := imagebuilder.NewBuilder(options.Args)
|
|
|
|
exec, err := NewExecutor(store, options)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating build executor")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-03-16 19:57:36 +08:00
|
|
|
return exec.Build(builder, nodes)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|