pkg/build/builder/docker.go
8da7af8f
 package builder
 
 import (
 	"fmt"
 	"io/ioutil"
 	"os"
70672304
 	"os/exec"
8da7af8f
 	"path/filepath"
 	"strings"
 	"time"
 
09b57bf7
 	dockercmd "github.com/docker/docker/builder/command"
 	"github.com/docker/docker/builder/parser"
8933ecc1
 	docker "github.com/fsouza/go-dockerclient"
83c702b4
 	kapi "k8s.io/kubernetes/pkg/api"
09b57bf7
 
05696c1e
 	s2iapi "github.com/openshift/source-to-image/pkg/api"
aa60c834
 	"github.com/openshift/source-to-image/pkg/tar"
b427b4f2
 	"github.com/openshift/source-to-image/pkg/util"
97eb0bfd
 
 	"github.com/openshift/origin/pkg/build/api"
 	"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
70672304
 	"github.com/openshift/origin/pkg/build/controller/strategy"
e66f133f
 	"github.com/openshift/origin/pkg/client"
21e0dd92
 	"github.com/openshift/origin/pkg/generate/git"
b10d7000
 	imageapi "github.com/openshift/origin/pkg/image/api"
97eb0bfd
 	"github.com/openshift/origin/pkg/util/docker/dockerfile"
8da7af8f
 )
 
21f6fc01
 // defaultDockerfilePath is the default path of the Dockerfile
 const defaultDockerfilePath = "Dockerfile"
 
8da7af8f
 // DockerBuilder builds Docker images given a git repository URL
 type DockerBuilder struct {
 	dockerClient DockerClient
21e0dd92
 	gitClient    GitClient
8da7af8f
 	tar          tar.Tar
 	build        *api.Build
 	urlTimeout   time.Duration
e66f133f
 	client       client.BuildInterface
05696c1e
 	cgLimits     *s2iapi.CGroupLimits
8da7af8f
 }
 
 // NewDockerBuilder creates a new instance of DockerBuilder
05696c1e
 func NewDockerBuilder(dockerClient DockerClient, buildsClient client.BuildInterface, build *api.Build, gitClient GitClient, cgLimits *s2iapi.CGroupLimits) *DockerBuilder {
8da7af8f
 	return &DockerBuilder{
 		dockerClient: dockerClient,
 		build:        build,
21e0dd92
 		gitClient:    gitClient,
aa60c834
 		tar:          tar.New(),
8da7af8f
 		urlTimeout:   urlCheckTimeout,
e66f133f
 		client:       buildsClient,
05696c1e
 		cgLimits:     cgLimits,
8da7af8f
 	}
 }
 
 // Build executes a Docker build
 func (d *DockerBuilder) Build() error {
9d089a21
 	if d.build.Spec.Source.Git == nil && d.build.Spec.Source.Binary == nil && d.build.Spec.Source.Dockerfile == nil && d.build.Spec.Source.Images == nil {
 		return fmt.Errorf("must provide a value for at least one of source, binary, images, or dockerfile")
 	}
51c78d25
 	var push bool
3e5a8591
 	pushTag := d.build.Status.OutputDockerImageReference
51c78d25
 
8da7af8f
 	buildDir, err := ioutil.TempDir("", "docker-build")
 	if err != nil {
 		return err
 	}
bb7ce114
 	sourceInfo, err := fetchSource(d.dockerClient, buildDir, d.build, d.urlTimeout, os.Stdin, d.gitClient)
e66f133f
 	if err != nil {
8da7af8f
 		return err
 	}
e66f133f
 	if sourceInfo != nil {
 		updateBuildRevision(d.client, d.build, sourceInfo)
 	}
b10d7000
 	if err := d.addBuildParameters(buildDir); err != nil {
8da7af8f
 		return err
 	}
70672304
 
b10d7000
 	glog.V(4).Infof("Starting Docker build from build config %s ...", d.build.Name)
8933ecc1
 	// if there is no output target, set one up so the docker build logic
51c78d25
 	// (which requires a tag) will still work, but we won't push it at the end.
8933ecc1
 	if d.build.Spec.Output.To == nil || len(d.build.Spec.Output.To.Name) == 0 {
51c78d25
 		d.build.Status.OutputDockerImageReference = d.build.Name
8933ecc1
 	} else {
 		push = true
 	}
0cc72926
 
3e5a8591
 	buildTag := randomBuildTag(d.build.Namespace, d.build.Name)
 
 	if err := d.dockerBuild(buildDir, buildTag, d.build.Spec.Source.Secrets); err != nil {
 		return err
 	}
 
 	cname := containerName("docker", d.build.Name, d.build.Namespace, "post-commit")
 	if err := execPostCommitHook(d.dockerClient, d.build.Spec.PostCommit, buildTag, cname); err != nil {
0cc72926
 		return err
 	}
 
16a3adc9
 	if push {
 		if err := tagImage(d.dockerClient, buildTag, pushTag); err != nil {
 			return err
 		}
3e5a8591
 	}
16a3adc9
 
3e5a8591
 	if err := removeImage(d.dockerClient, buildTag); err != nil {
1b10493b
 		glog.V(0).Infof("warning: Failed to remove temporary build tag %v: %v", buildTag, err)
3e5a8591
 	}
 
8933ecc1
 	if push {
13acec54
 		// Get the Docker push authentication
 		pushAuthConfig, authPresent := dockercfg.NewHelper().GetDockerAuth(
3e5a8591
 			pushTag,
13acec54
 			dockercfg.PushAuthType,
 		)
 		if authPresent {
b10d7000
 			glog.V(4).Infof("Authenticating Docker push with user %q", pushAuthConfig.Username)
13acec54
 		}
1b10493b
 		glog.V(1).Infof("Pushing image %s ...", pushTag)
3e5a8591
 		if err := pushImage(d.dockerClient, pushTag, pushAuthConfig); err != nil {
47e16125
 			return fmt.Errorf("Failed to push image: %v", err)
f24be2f3
 		}
1b10493b
 		glog.V(1).Infof("Push successful")
8da7af8f
 	}
1b9f255f
 	return nil
8da7af8f
 }
 
70672304
 // copySecrets copies all files from the directory where the secret is
 // mounted in the builder pod to a directory where the is the Dockerfile, so
 // users can ADD or COPY the files inside their Dockerfile.
 func (d *DockerBuilder) copySecrets(secrets []api.SecretBuildSource, buildDir string) error {
 	for _, s := range secrets {
 		dstDir := filepath.Join(buildDir, s.DestinationDir)
 		if err := os.MkdirAll(dstDir, 0777); err != nil {
 			return err
 		}
 		srcDir := filepath.Join(strategy.SecretBuildSourceBaseMountPath, s.Secret.Name)
1b10493b
 		glog.V(3).Infof("Copying files from the build secret %q to %q", s.Secret.Name, filepath.Clean(s.DestinationDir))
70672304
 		out, err := exec.Command("cp", "-vrf", srcDir+"/.", dstDir+"/").Output()
 		if err != nil {
1b10493b
 			glog.V(4).Infof("Secret %q failed to copy: %q", s.Secret.Name, string(out))
70672304
 			return err
 		}
 		// See what is copied where when debugging.
 		glog.V(5).Infof(string(out))
 	}
 	return nil
 }
 
62c9fcbb
 // addBuildParameters checks if a Image is set to replace the default base image.
17d00230
 // If that's the case then change the Dockerfile to make the build with the given image.
b427b4f2
 // Also append the environment variables and labels in the Dockerfile.
17d00230
 func (d *DockerBuilder) addBuildParameters(dir string) error {
21f6fc01
 	var contextDirPath string
8933ecc1
 	if d.build.Spec.Strategy.DockerStrategy != nil && len(d.build.Spec.Source.ContextDir) > 0 {
21f6fc01
 		contextDirPath = filepath.Join(dir, d.build.Spec.Source.ContextDir)
 	} else {
 		contextDirPath = dir
 	}
 
 	var dockerfilePath string
 	if d.build.Spec.Strategy.DockerStrategy != nil && len(d.build.Spec.Strategy.DockerStrategy.DockerfilePath) > 0 {
 		dockerfilePath = filepath.Join(contextDirPath, d.build.Spec.Strategy.DockerStrategy.DockerfilePath)
 	} else {
 		dockerfilePath = filepath.Join(contextDirPath, defaultDockerfilePath)
bcba00e9
 	}
8da7af8f
 
97eb0bfd
 	f, err := os.Open(dockerfilePath)
84d46285
 	if err != nil {
 		return err
 	}
 
97eb0bfd
 	// Parse the Dockerfile.
 	node, err := parser.Parse(f)
8da7af8f
 	if err != nil {
 		return err
 	}
17d00230
 
97eb0bfd
 	// Update base image if build strategy specifies the From field.
8933ecc1
 	if d.build.Spec.Strategy.DockerStrategy.From != nil && d.build.Spec.Strategy.DockerStrategy.From.Kind == "DockerImage" {
b10d7000
 		// Reduce the name to a minimal canonical form for the daemon
 		name := d.build.Spec.Strategy.DockerStrategy.From.Name
 		if ref, err := imageapi.ParseDockerImageReference(name); err == nil {
a54b74fb
 			name = ref.DaemonMinimal().Exact()
b10d7000
 		}
 		err := replaceLastFrom(node, name)
09b57bf7
 		if err != nil {
 			return err
 		}
8da7af8f
 	}
17d00230
 
97eb0bfd
 	// Append build info as environment variables.
 	err = appendEnv(node, d.buildInfo())
 	if err != nil {
 		return err
b427b4f2
 	}
d67b0734
 
97eb0bfd
 	// Append build labels.
 	err = appendLabel(node, d.buildLabels(dir))
d67b0734
 	if err != nil {
d8b46d02
 		return err
 	}
 
97eb0bfd
 	// Insert environment variables defined in the build strategy.
d67b0734
 	err = insertEnvAfterFrom(node, d.build.Spec.Strategy.DockerStrategy.Env)
 	if err != nil {
 		return err
 	}
 
 	instructions := dockerfile.ParseTreeToDockerfile(node)
 
97eb0bfd
 	// Overwrite the Dockerfile.
 	fi, err := f.Stat()
 	if err != nil {
 		return err
 	}
 	return ioutil.WriteFile(dockerfilePath, instructions, fi.Mode())
d8b46d02
 }
 
97eb0bfd
 // buildInfo converts the buildInfo output to a format that appendEnv can
 // consume.
 func (d *DockerBuilder) buildInfo() []dockerfile.KeyValue {
 	bi := buildInfo(d.build)
 	kv := make([]dockerfile.KeyValue, len(bi))
 	for i, item := range bi {
 		kv[i] = dockerfile.KeyValue{Key: item.Key, Value: item.Value}
8da7af8f
 	}
97eb0bfd
 	return kv
17d00230
 }
 
97eb0bfd
 // buildLabels returns a slice of KeyValue pairs in a format that appendEnv can
 // consume.
 func (d *DockerBuilder) buildLabels(dir string) []dockerfile.KeyValue {
 	labels := map[string]string{}
604ac45d
 	// TODO: allow source info to be overridden by build
21e0dd92
 	sourceInfo := &git.SourceInfo{}
9b43b0e3
 	if d.build.Spec.Source.Git != nil {
21e0dd92
 		var errors []error
 		sourceInfo, errors = d.gitClient.GetInfo(dir)
 		if len(errors) > 0 {
 			for _, e := range errors {
1b10493b
 				glog.V(0).Infof("warning: Unable to retrieve Git info: %v", e.Error())
21e0dd92
 			}
 		}
9b43b0e3
 	}
97eb0bfd
 	if len(d.build.Spec.Source.ContextDir) > 0 {
 		sourceInfo.ContextDir = d.build.Spec.Source.ContextDir
09b57bf7
 	}
21e0dd92
 	labels = util.GenerateLabelsFromSourceInfo(labels, &sourceInfo.SourceInfo, api.DefaultDockerLabelNamespace)
9d1ac783
 	kv := make([]dockerfile.KeyValue, 0, len(labels))
97eb0bfd
 	for k, v := range labels {
9d1ac783
 		kv = append(kv, dockerfile.KeyValue{Key: k, Value: v})
97eb0bfd
 	}
 	return kv
 }
09b57bf7
 
97eb0bfd
 // setupPullSecret provides a Docker authentication configuration when the
 // PullSecret is specified.
 func (d *DockerBuilder) setupPullSecret() (*docker.AuthConfigurations, error) {
 	if len(os.Getenv(dockercfg.PullAuthType)) == 0 {
 		return nil, nil
09b57bf7
 	}
90f3434d
 	glog.V(2).Infof("Checking for Docker config file for %s in path %s", dockercfg.PullAuthType, os.Getenv(dockercfg.PullAuthType))
 	dockercfgPath := dockercfg.GetDockercfgFile(os.Getenv(dockercfg.PullAuthType))
 	if len(dockercfgPath) == 0 {
 		return nil, fmt.Errorf("no docker config file found in '%s'", os.Getenv(dockercfg.PullAuthType))
 	}
 	glog.V(2).Infof("Using Docker config file %s", dockercfgPath)
 	r, err := os.Open(dockercfgPath)
97eb0bfd
 	if err != nil {
90f3434d
 		return nil, fmt.Errorf("'%s': %s", dockercfgPath, err)
97eb0bfd
 	}
 	return docker.NewAuthConfigurations(r)
90f3434d
 
97eb0bfd
 }
09b57bf7
 
97eb0bfd
 // dockerBuild performs a docker build on the source that has been retrieved
3e5a8591
 func (d *DockerBuilder) dockerBuild(dir string, tag string, secrets []api.SecretBuildSource) error {
97eb0bfd
 	var noCache bool
 	var forcePull bool
21f6fc01
 	dockerfilePath := defaultDockerfilePath
97eb0bfd
 	if d.build.Spec.Strategy.DockerStrategy != nil {
 		if d.build.Spec.Source.ContextDir != "" {
 			dir = filepath.Join(dir, d.build.Spec.Source.ContextDir)
09b57bf7
 		}
21f6fc01
 		if d.build.Spec.Strategy.DockerStrategy.DockerfilePath != "" {
 			dockerfilePath = d.build.Spec.Strategy.DockerStrategy.DockerfilePath
 		}
97eb0bfd
 		noCache = d.build.Spec.Strategy.DockerStrategy.NoCache
 		forcePull = d.build.Spec.Strategy.DockerStrategy.ForcePull
 	}
 	auth, err := d.setupPullSecret()
 	if err != nil {
 		return err
 	}
70672304
 	if err := d.copySecrets(secrets, dir); err != nil {
 		return err
 	}
3e5a8591
 	return buildImage(d.dockerClient, dir, dockerfilePath, noCache, tag, d.tar, auth, forcePull, d.cgLimits)
97eb0bfd
 }
09b57bf7
 
97eb0bfd
 // replaceLastFrom changes the last FROM instruction of node to point to the
a54b74fb
 // base image.
97eb0bfd
 func replaceLastFrom(node *parser.Node, image string) error {
 	if node == nil {
 		return nil
 	}
 	for i := len(node.Children) - 1; i >= 0; i-- {
 		child := node.Children[i]
 		if child != nil && child.Value == dockercmd.From {
 			from, err := dockerfile.From(image)
 			if err != nil {
 				return err
09b57bf7
 			}
97eb0bfd
 			fromTree, err := parser.Parse(strings.NewReader(from))
 			if err != nil {
 				return err
09b57bf7
 			}
97eb0bfd
 			node.Children[i] = fromTree.Children[0]
 			return nil
09b57bf7
 		}
 	}
97eb0bfd
 	return nil
 }
09b57bf7
 
97eb0bfd
 // appendEnv appends an ENV Dockerfile instruction as the last child of node
 // with keys and values from m.
 func appendEnv(node *parser.Node, m []dockerfile.KeyValue) error {
 	return appendKeyValueInstruction(dockerfile.Env, node, m)
 }
09b57bf7
 
97eb0bfd
 // appendLabel appends a LABEL Dockerfile instruction as the last child of node
 // with keys and values from m.
 func appendLabel(node *parser.Node, m []dockerfile.KeyValue) error {
9d1ac783
 	if len(m) == 0 {
 		return nil
 	}
97eb0bfd
 	return appendKeyValueInstruction(dockerfile.Label, node, m)
09b57bf7
 }
 
97eb0bfd
 // appendKeyValueInstruction is a primitive used to avoid code duplication.
 // Callers should use a derivative of this such as appendEnv or appendLabel.
 // appendKeyValueInstruction appends a Dockerfile instruction with key-value
 // syntax created by f as the last child of node with keys and values from m.
 func appendKeyValueInstruction(f func([]dockerfile.KeyValue) (string, error), node *parser.Node, m []dockerfile.KeyValue) error {
 	if node == nil {
 		return nil
09b57bf7
 	}
97eb0bfd
 	instruction, err := f(m)
 	if err != nil {
 		return err
 	}
 	return dockerfile.InsertInstructions(node, len(node.Children), instruction)
09b57bf7
 }
 
d67b0734
 // insertEnvAfterFrom inserts an ENV instruction with the environment variables
 // from env after every FROM instruction in node.
 func insertEnvAfterFrom(node *parser.Node, env []kapi.EnvVar) error {
 	if node == nil || len(env) == 0 {
 		return nil
 	}
 
 	// Build ENV instruction.
 	var m []dockerfile.KeyValue
 	for _, e := range env {
 		m = append(m, dockerfile.KeyValue{Key: e.Name, Value: e.Value})
 	}
 	buildEnv, err := dockerfile.Env(m)
 	if err != nil {
 		return err
 	}
 
 	// Insert the buildEnv after every FROM instruction.
 	// We iterate in reverse order, otherwise indices would have to be
 	// recomputed after each step, because we're changing node in-place.
 	indices := dockerfile.FindAll(node, dockercmd.From)
 	for i := len(indices) - 1; i >= 0; i-- {
 		err := dockerfile.InsertInstructions(node, indices[i]+1, buildEnv)
 		if err != nil {
 			return err
 		}
 	}
 
 	return nil
 }