2017-03-28 15:06:13 +08:00
|
|
|
package imagebuildah
|
|
|
|
|
|
|
|
import (
|
2018-08-01 18:31:02 +08:00
|
|
|
"bytes"
|
2018-04-12 22:20:36 +08:00
|
|
|
"context"
|
2020-05-19 20:20:14 +08:00
|
|
|
"fmt"
|
2017-03-28 15:06:13 +08:00
|
|
|
"io"
|
2018-08-01 18:31:02 +08:00
|
|
|
"io/ioutil"
|
2017-03-28 15:06:13 +08:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2018-08-01 18:31:02 +08:00
|
|
|
"os/exec"
|
2017-03-28 15:06:13 +08:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2021-02-07 06:49:40 +08:00
|
|
|
"github.com/containers/buildah/define"
|
2021-05-12 03:05:48 +08:00
|
|
|
"github.com/containers/buildah/util"
|
2020-02-08 01:54:18 +08:00
|
|
|
"github.com/containers/common/pkg/config"
|
2019-10-26 05:19:30 +08:00
|
|
|
"github.com/containers/image/v5/docker/reference"
|
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"
|
2019-10-02 04:03:57 +08:00
|
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
2017-03-28 15:06:13 +08:00
|
|
|
"github.com/openshift/imagebuilder"
|
2020-05-19 20:20:14 +08:00
|
|
|
"github.com/openshift/imagebuilder/dockerfile/parser"
|
2017-06-02 03:23:02 +08:00
|
|
|
"github.com/pkg/errors"
|
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 (
|
2021-02-07 06:49:40 +08:00
|
|
|
PullIfMissing = define.PullIfMissing
|
|
|
|
PullAlways = define.PullAlways
|
|
|
|
PullIfNewer = define.PullIfNewer
|
|
|
|
PullNever = define.PullNever
|
2017-04-11 22:27:05 +08:00
|
|
|
|
|
|
|
Gzip = archive.Gzip
|
|
|
|
Bzip2 = archive.Bzip2
|
|
|
|
Xz = archive.Xz
|
2019-07-09 05:50:33 +08:00
|
|
|
Zstd = archive.Zstd
|
2017-04-11 22:27:05 +08:00
|
|
|
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
|
|
|
|
|
2021-03-02 02:07:58 +08:00
|
|
|
type BuildOptions = define.BuildOptions
|
2017-03-28 15:06:13 +08:00
|
|
|
|
|
|
|
// 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.
|
2021-03-02 02:07:58 +08:00
|
|
|
func BuildDockerfiles(ctx context.Context, store storage.Store, options define.BuildOptions, paths ...string) (string, reference.Canonical, error) {
|
2018-04-11 01:35:03 +08:00
|
|
|
if len(paths) == 0 {
|
2018-10-12 04:58:04 +08:00
|
|
|
return "", nil, errors.Errorf("error building: no dockerfiles specified")
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-02-25 06:40:44 +08:00
|
|
|
var dockerfiles []io.ReadCloser
|
|
|
|
defer func(dockerfiles ...io.ReadCloser) {
|
|
|
|
for _, d := range dockerfiles {
|
|
|
|
d.Close()
|
|
|
|
}
|
|
|
|
}(dockerfiles...)
|
2018-09-22 07:37:02 +08:00
|
|
|
|
2021-05-12 03:05:48 +08:00
|
|
|
for _, tag := range append([]string{options.Output}, options.AdditionalTags...) {
|
|
|
|
if tag == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if _, err := util.VerifyTagName(tag); err != nil {
|
|
|
|
return "", nil, errors.Wrapf(err, "tag %s", tag)
|
|
|
|
}
|
|
|
|
}
|
2018-04-11 01:35:03 +08:00
|
|
|
for _, dfile := range paths {
|
2018-08-01 18:31:02 +08:00
|
|
|
var data io.ReadCloser
|
|
|
|
|
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 {
|
2020-10-15 17:16:50 +08:00
|
|
|
return "", nil, err
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
|
|
|
if resp.ContentLength == 0 {
|
|
|
|
resp.Body.Close()
|
2018-10-12 04:58:04 +08:00
|
|
|
return "", nil, errors.Errorf("no contents in %q", dfile)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-08-01 18:31:02 +08:00
|
|
|
data = resp.Body
|
2017-03-28 15:06:13 +08:00
|
|
|
} else {
|
2018-10-28 03:47:03 +08:00
|
|
|
dinfo, err := os.Stat(dfile)
|
2020-11-09 11:14:56 +08:00
|
|
|
if err != nil {
|
|
|
|
// If the Dockerfile isn't available, try again with
|
|
|
|
// context directory prepended (if not prepended yet).
|
2020-10-13 09:30:01 +08:00
|
|
|
if !strings.HasPrefix(dfile, options.ContextDirectory) {
|
|
|
|
dfile = filepath.Join(options.ContextDirectory, dfile)
|
2020-11-09 11:14:56 +08:00
|
|
|
dinfo, err = os.Stat(dfile)
|
2020-10-13 09:30:01 +08:00
|
|
|
}
|
2018-10-28 03:47:03 +08:00
|
|
|
}
|
2020-11-09 11:14:56 +08:00
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
2021-03-15 19:52:22 +08:00
|
|
|
var contents *os.File
|
2018-10-28 03:47:03 +08:00
|
|
|
// If given a directory, add '/Dockerfile' to it.
|
|
|
|
if dinfo.Mode().IsDir() {
|
2021-03-15 19:52:22 +08:00
|
|
|
for _, file := range []string{"Containerfile", "Dockerfile"} {
|
|
|
|
f := filepath.Join(dfile, file)
|
|
|
|
logrus.Debugf("reading local %q", f)
|
|
|
|
contents, err = os.Open(f)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
contents, err = os.Open(dfile)
|
2018-10-28 03:47:03 +08:00
|
|
|
}
|
2021-03-15 19:52:22 +08:00
|
|
|
|
2018-02-24 18:09:25 +08:00
|
|
|
if err != nil {
|
2020-10-15 17:16:50 +08:00
|
|
|
return "", nil, err
|
2018-02-24 18:09:25 +08:00
|
|
|
}
|
2018-10-28 03:47:03 +08:00
|
|
|
dinfo, err = contents.Stat()
|
2018-02-24 18:09:25 +08:00
|
|
|
if err != nil {
|
|
|
|
contents.Close()
|
2018-10-12 04:58:04 +08:00
|
|
|
return "", nil, errors.Wrapf(err, "error reading info about %q", dfile)
|
2018-02-24 18:09:25 +08:00
|
|
|
}
|
2018-07-27 22:48:16 +08:00
|
|
|
if dinfo.Mode().IsRegular() && dinfo.Size() == 0 {
|
2018-02-24 18:09:25 +08:00
|
|
|
contents.Close()
|
2020-10-15 17:16:50 +08:00
|
|
|
return "", nil, errors.Errorf("no contents in %q", dfile)
|
2018-02-24 18:09:25 +08:00
|
|
|
}
|
2018-08-01 18:31:02 +08:00
|
|
|
data = contents
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-08-01 18:31:02 +08:00
|
|
|
|
|
|
|
// pre-process Dockerfiles with ".in" suffix
|
|
|
|
if strings.HasSuffix(dfile, ".in") {
|
|
|
|
pData, err := preprocessDockerfileContents(data, options.ContextDirectory)
|
|
|
|
if err != nil {
|
2018-10-12 04:58:04 +08:00
|
|
|
return "", nil, err
|
2018-08-01 18:31:02 +08:00
|
|
|
}
|
|
|
|
data = *pData
|
|
|
|
}
|
|
|
|
|
|
|
|
dockerfiles = append(dockerfiles, data)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-11-17 06:43:48 +08:00
|
|
|
|
2018-02-25 06:40:44 +08:00
|
|
|
mainNode, err := imagebuilder.ParseDockerfile(dockerfiles[0])
|
|
|
|
if err != nil {
|
2020-10-15 17:16:50 +08:00
|
|
|
return "", nil, errors.Wrapf(err, "error parsing main Dockerfile: %s", dockerfiles[0])
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2020-05-19 20:20:14 +08:00
|
|
|
|
|
|
|
warnOnUnsetBuildArgs(mainNode, options.Args)
|
|
|
|
|
2018-02-25 06:40:44 +08:00
|
|
|
for _, d := range dockerfiles[1:] {
|
|
|
|
additionalNode, err := imagebuilder.ParseDockerfile(d)
|
|
|
|
if err != nil {
|
2020-10-15 17:16:50 +08:00
|
|
|
return "", nil, errors.Wrapf(err, "error parsing additional Dockerfile %s", d)
|
2018-02-25 06:40:44 +08:00
|
|
|
}
|
|
|
|
mainNode.Children = append(mainNode.Children, additionalNode.Children...)
|
|
|
|
}
|
2019-04-16 23:13:31 +08:00
|
|
|
exec, err := NewExecutor(store, options, mainNode)
|
2018-02-25 06:40:44 +08:00
|
|
|
if err != nil {
|
2018-10-12 04:58:04 +08:00
|
|
|
return "", nil, errors.Wrapf(err, "error creating build executor")
|
2018-02-25 06:40:44 +08:00
|
|
|
}
|
|
|
|
b := imagebuilder.NewBuilder(options.Args)
|
2020-02-08 01:54:18 +08:00
|
|
|
defaultContainerConfig, err := config.Default()
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, errors.Wrapf(err, "failed to get container config")
|
|
|
|
}
|
|
|
|
b.Env = append(defaultContainerConfig.GetDefaultEnv(), b.Env...)
|
2018-11-08 18:31:14 +08:00
|
|
|
stages, err := imagebuilder.NewStages(mainNode, b)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, errors.Wrap(err, "error reading multiple stages")
|
|
|
|
}
|
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
|
|
|
|
}
|
2018-04-12 22:20:36 +08:00
|
|
|
return exec.Build(ctx, stages)
|
2017-03-28 15:06:13 +08:00
|
|
|
}
|
2018-08-01 00:02:06 +08:00
|
|
|
|
2020-05-19 20:20:14 +08:00
|
|
|
func warnOnUnsetBuildArgs(node *parser.Node, args map[string]string) {
|
2021-04-18 08:27:35 +08:00
|
|
|
argFound := make(map[string]bool)
|
2020-05-19 20:20:14 +08:00
|
|
|
for _, child := range node.Children {
|
|
|
|
switch strings.ToUpper(child.Value) {
|
|
|
|
case "ARG":
|
|
|
|
argName := child.Next.Value
|
2021-04-18 08:27:35 +08:00
|
|
|
if strings.Contains(argName, "=") {
|
|
|
|
res := strings.Split(argName, "=")
|
|
|
|
if res[1] != "" {
|
|
|
|
argFound[res[0]] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
argHasValue := true
|
|
|
|
if !strings.Contains(argName, "=") {
|
|
|
|
argHasValue = argFound[argName]
|
|
|
|
}
|
|
|
|
if _, ok := args[argName]; !argHasValue && !ok {
|
2020-05-19 20:20:14 +08:00
|
|
|
logrus.Warnf("missing %q build argument. Try adding %q to the command line", argName, fmt.Sprintf("--build-arg %s=<VALUE>", argName))
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-01 18:31:02 +08:00
|
|
|
// 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.
|
2019-09-03 23:40:37 +08:00
|
|
|
func preprocessDockerfileContents(r io.Reader, ctxDir string) (rdrCloser *io.ReadCloser, err error) {
|
2018-08-01 18:31:02 +08:00
|
|
|
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{}
|
|
|
|
|
2019-12-24 19:31:09 +08:00
|
|
|
cmd := exec.Command(cppPath, "-E", "-iquote", ctxDir, "-traditional", "-undef", "-")
|
2018-08-01 18:31:02 +08:00
|
|
|
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 {
|
2020-04-04 04:34:43 +08:00
|
|
|
err = errors.Wrapf(err, "%v", strings.TrimSpace(stderr.String()))
|
2018-08-01 18:31:02 +08:00
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(err, "error pre-processing Dockerfile")
|
|
|
|
}
|
|
|
|
|
|
|
|
rc := ioutil.NopCloser(bytes.NewReader(stdout.Bytes()))
|
|
|
|
return &rc, nil
|
|
|
|
}
|