Skip to content

Commit

Permalink
Run user-provided command as part of build flow
Browse files Browse the repository at this point in the history
This is meant to be used for running project unit tests as part of a
build.
  • Loading branch information
rhcarvalho committed Jan 25, 2016
1 parent 43a347c commit 3460048
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 15 deletions.
7 changes: 7 additions & 0 deletions pkg/build/builder/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package builder

import (
"fmt"
"math/rand"
"os"

"github.com/fsouza/go-dockerclient"
Expand Down Expand Up @@ -86,6 +87,12 @@ func updateBuildRevision(c client.BuildInterface, build *api.Build, sourceInfo *
}
}

func runPostCommitHook(dockerClient DockerClient, build *api.Build, strategyName, imageID string) error {
glog.Infof("Running post commit hook with image %s ...", imageID)
containerName := fmt.Sprintf("openshift_%s-build_%s_%s_post-commit_%08x", strategyName, build.Name, build.Namespace, rand.Uint32())
return runScriptInContainer(dockerClient, build.Spec.PostCommit.RunBash, imageID, containerName)
}

// runScriptInContainer runs a Bash script in an ephemeral Docker container
// started off imageID, and returns an error if the script cannot be run or
// returns a non-zero exit code.
Expand Down
47 changes: 41 additions & 6 deletions pkg/build/builder/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/docker/distribution/reference"
dockercmd "github.com/docker/docker/builder/command"
"github.com/docker/docker/builder/parser"
docker "github.com/fsouza/go-dockerclient"
Expand All @@ -25,6 +26,7 @@ import (
"github.com/openshift/origin/pkg/generate/git"
imageapi "github.com/openshift/origin/pkg/image/api"
"github.com/openshift/origin/pkg/util/docker/dockerfile"
"github.com/openshift/origin/pkg/util/namer"
)

// defaultDockerfilePath is the default path of the Dockerfile
Expand Down Expand Up @@ -55,6 +57,7 @@ func NewDockerBuilder(dockerClient DockerClient, buildsClient client.BuildInterf
// Build executes a Docker build
func (d *DockerBuilder) Build() error {
var push bool
pushTag := d.build.Status.OutputDockerImageReference

buildDir, err := ioutil.TempDir("", "docker-build")
if err != nil {
Expand All @@ -80,21 +83,53 @@ func (d *DockerBuilder) Build() error {
push = true
}

if err := d.dockerBuild(buildDir, d.build.Spec.Source.Secrets); err != nil {
// TODO remove the code duplication in docker.go and sti.go.

// TODO replace this with a truly random string
suffix := fmt.Sprintf("%x", time.Now().UnixNano())
// buildTag is a random tag used for building the image in such a way
// that we can refer to the built image knowing that it was built here
// and not concurrently by a different goroutine or process.
buildTag := namer.GetName(pushTag, suffix, reference.NameTotalLengthMax)

if err := d.dockerBuild(buildDir, buildTag, d.build.Spec.Source.Secrets); err != nil {
return err
}

// TODO Shall we remove this tag/image or just leave it there? If we
// don't get to tag with the pushTag below, the image will be gone.
defer removeImage(d.dockerClient, buildTag)

if err := runPostCommitHook(d.dockerClient, d.build, "docker", buildTag); err != nil {
return err
}

var repo, tag string
if i := strings.LastIndex(pushTag, ":"); i != -1 {
repo = pushTag[:i]
tag = pushTag[i+1:]
} else {
// TODO do not panic...
panic(fmt.Sprintf("build.Status.OutputDockerImageReference is malformed: %q", pushTag))
}
if err := d.dockerClient.TagImage(buildTag, docker.TagImageOptions{
Repo: repo,
Tag: tag,
}); err != nil {
return err
}

if push {
// Get the Docker push authentication
pushAuthConfig, authPresent := dockercfg.NewHelper().GetDockerAuth(
d.build.Status.OutputDockerImageReference,
pushTag,
dockercfg.PushAuthType,
)
if authPresent {
glog.V(4).Infof("Authenticating Docker push with user %q", pushAuthConfig.Username)
}
glog.Infof("Pushing image %s ...", d.build.Status.OutputDockerImageReference)
if err := pushImage(d.dockerClient, d.build.Status.OutputDockerImageReference, pushAuthConfig); err != nil {
glog.Infof("Pushing image %s ...", pushTag)
if err := pushImage(d.dockerClient, pushTag, pushAuthConfig); err != nil {
return fmt.Errorf("Failed to push image: %v", err)
}
glog.Infof("Push successful")
Expand Down Expand Up @@ -245,7 +280,7 @@ func (d *DockerBuilder) setupPullSecret() (*docker.AuthConfigurations, error) {
}

// dockerBuild performs a docker build on the source that has been retrieved
func (d *DockerBuilder) dockerBuild(dir string, secrets []api.SecretBuildSource) error {
func (d *DockerBuilder) dockerBuild(dir string, tag string, secrets []api.SecretBuildSource) error {
var noCache bool
var forcePull bool
dockerfilePath := defaultDockerfilePath
Expand All @@ -266,7 +301,7 @@ func (d *DockerBuilder) dockerBuild(dir string, secrets []api.SecretBuildSource)
if err := d.copySecrets(secrets, dir); err != nil {
return err
}
return buildImage(d.dockerClient, dir, dockerfilePath, noCache, d.build.Status.OutputDockerImageReference, d.tar, auth, forcePull)
return buildImage(d.dockerClient, dir, dockerfilePath, noCache, tag, d.tar, auth, forcePull)
}

// replaceLastFrom changes the last FROM instruction of node to point to the
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/builder/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func TestDockerfilePath(t *testing.T) {
}

// check that the docker client is called with the right Dockerfile parameter
if err = dockerBuilder.dockerBuild(buildDir, []api.SecretBuildSource{}); err != nil {
if err = dockerBuilder.dockerBuild(buildDir, "", []api.SecretBuildSource{}); err != nil {
t.Errorf("failed to build: %v", err)
continue
}
Expand Down
1 change: 1 addition & 0 deletions pkg/build/builder/dockerutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type DockerClient interface {
StartContainer(id string, hostConfig *docker.HostConfig) error
WaitContainer(id string) (int, error)
Logs(opts docker.LogsOptions) error
TagImage(name string, opts docker.TagImageOptions) error
}

// pushImage pushes a docker image to the registry specified in its tag.
Expand Down
3 changes: 3 additions & 0 deletions pkg/build/builder/dockerutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ func (d *FakeDocker) WaitContainer(id string) (int, error) {
func (d *FakeDocker) Logs(opts docker.LogsOptions) error {
return nil
}
func (d *FakeDocker) TagImage(name string, opts docker.TagImageOptions) error {
return nil
}

func TestDockerPush(t *testing.T) {
verifyFunc := func(opts docker.PushImageOptions, auth docker.AuthConfiguration) error {
Expand Down
50 changes: 42 additions & 8 deletions pkg/build/builder/sti.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"net/url"
"os"
"path/filepath"
"strings"
"time"

"github.com/docker/distribution/reference"
"github.com/fsouza/go-dockerclient"
"github.com/golang/glog"

s2iapi "github.com/openshift/source-to-image/pkg/api"
Expand All @@ -23,6 +26,7 @@ import (
"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
"github.com/openshift/origin/pkg/build/controller/strategy"
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/util/namer"
)

// builderFactory is the internal interface to decouple S2I-specific code from Origin builder code
Expand Down Expand Up @@ -124,7 +128,7 @@ func (s *S2IBuilder) Build() error {
} else {
push = true
}
tag := s.build.Status.OutputDockerImageReference
pushTag := s.build.Status.OutputDockerImageReference
git := s.build.Spec.Source.Git

var ref string
Expand All @@ -151,6 +155,13 @@ func (s *S2IBuilder) Build() error {
})
}

// TODO replace this with a truly random string
suffix := fmt.Sprintf("%x", time.Now().UnixNano())
// buildTag is a random tag used for building the image in such a way
// that we can refer to the built image knowing that it was built here
// and not concurrently by a different goroutine or process.
buildTag := namer.GetName(pushTag, suffix, reference.NameTotalLengthMax)

config := &s2iapi.Config{
WorkingDir: buildDir,
DockerConfig: &s2iapi.DockerConfig{Endpoint: s.dockerSocket},
Expand All @@ -166,7 +177,7 @@ func (s *S2IBuilder) Build() error {
DockerNetworkMode: getDockerNetworkMode(),

Source: sourceURI.String(),
Tag: tag,
Tag: buildTag,
ContextDir: s.build.Spec.Source.ContextDir,

Injections: injections,
Expand Down Expand Up @@ -203,7 +214,7 @@ func (s *S2IBuilder) Build() error {
// If DockerCfgPath is provided in api.Config, then attempt to read the the
// dockercfg file and get the authentication for pulling the builder image.
config.PullAuthentication, _ = dockercfg.NewHelper().GetDockerAuth(config.BuilderImage, dockercfg.PullAuthType)
config.IncrementalAuthentication, _ = dockercfg.NewHelper().GetDockerAuth(tag, dockercfg.PushAuthType)
config.IncrementalAuthentication, _ = dockercfg.NewHelper().GetDockerAuth(pushTag, dockercfg.PushAuthType)

glog.V(2).Infof("Creating a new S2I builder with build config: %#v\n", describe.DescribeConfig(config))
builder, err := s.builder.Builder(config, s2ibuild.Overrides{Downloader: download})
Expand All @@ -217,19 +228,42 @@ func (s *S2IBuilder) Build() error {
return err
}

// TODO Shall we remove this tag/image or just leave it there? If we
// don't get to tag with the pushTag below, the image will be gone.
defer removeImage(s.dockerClient, buildTag)

if err := runPostCommitHook(s.dockerClient, s.build, "s2i", buildTag); err != nil {
return err
}

var repo, tag string
if i := strings.LastIndex(pushTag, ":"); i != -1 {
repo = pushTag[:i]
tag = pushTag[i+1:]
} else {
// TODO do not panic...
panic(fmt.Sprintf("build.Status.OutputDockerImageReference is malformed: %q", pushTag))
}
if err := s.dockerClient.TagImage(buildTag, docker.TagImageOptions{
Repo: repo,
Tag: tag,
}); err != nil {
return err
}

if push {
// Get the Docker push authentication
pushAuthConfig, authPresent := dockercfg.NewHelper().GetDockerAuth(
tag,
pushTag,
dockercfg.PushAuthType,
)
if authPresent {
glog.Infof("Using provided push secret for pushing %s image", tag)
glog.Infof("Using provided push secret for pushing %s image", pushTag)
} else {
glog.Infof("No push secret provided")
}
glog.Infof("Pushing %s image ...", tag)
if err := pushImage(s.dockerClient, tag, pushAuthConfig); err != nil {
glog.Infof("Pushing %s image ...", pushTag)
if err := pushImage(s.dockerClient, pushTag, pushAuthConfig); err != nil {
// write extended error message to assist in problem resolution
msg := fmt.Sprintf("Failed to push image. Response from registry is: %v", err)
if authPresent {
Expand All @@ -244,7 +278,7 @@ func (s *S2IBuilder) Build() error {
}
return errors.New(msg)
}
glog.Infof("Successfully pushed %s", tag)
glog.Infof("Successfully pushed %s", pushTag)
glog.Flush()
}
return nil
Expand Down

0 comments on commit 3460048

Please sign in to comment.