diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 322fe2fce890..8f4972103d3d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -529,7 +529,7 @@ { "ImportPath": "github.com/fsouza/go-dockerclient", "Comment": "0.2.1-357-gd197177", - "Rev": "d19717788084716e4adff0515be6289aa04bec46" + "Rev": "e1e2cc5b83662b894c6871db875c37eb3725a045" }, { "ImportPath": "github.com/getsentry/raven-go", @@ -592,9 +592,24 @@ "Rev": "7a864a042e844af638df17ebbabf8183dace556a" }, { - "ImportPath": "github.com/openshift/source-to-image/pkg/sti", + "ImportPath": "github.com/openshift/source-to-image/pkg/build/strategies", "Comment": "v0.1-4-g3e41ad5", - "Rev": "3e41ad598db9e676a09862d0e89bab5063afa926" + "Rev": "1338bff33b5c46acc02840f88a9b576a1b1fa404" + }, + { + "ImportPath": "github.com/openshift/source-to-image/pkg/git", + "Comment": "v0.1-4-g3e41ad5", + "Rev": "1338bff33b5c46acc02840f88a9b576a1b1fa404" + }, + { + "ImportPath": "github.com/openshift/source-to-image/pkg/tar", + "Comment": "v0.1-4-g3e41ad5", + "Rev": "1338bff33b5c46acc02840f88a9b576a1b1fa404" + }, + { + "ImportPath": "github.com/openshift/source-to-image/pkg/api", + "Comment": "v0.1-4-g3e41ad5", + "Rev": "1338bff33b5c46acc02840f88a9b576a1b1fa404" }, { "ImportPath": "github.com/pkg/profile", diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS index 698901d9f4c6..fd68ef86f3a8 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS @@ -6,6 +6,8 @@ Andrews Medina Artem Sidorenko Andy Goldstein Ben McCann +Brian Lalor +Burke Libbey Carlos Diaz-Padron Cezar Sa Espinola Cheah Chu Yeow @@ -31,8 +33,10 @@ Kamil Domanski Karan Misra Kim, Hirokuni Lucas Clemente +Mantas Matelis Martin Sweeney Máximo Cuadros Ortiz +Michal Fojtik Mike Dillon Mrunal Patel Omeid Matten diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go index 40d6cf3cf406..0b1c8dd65f2f 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go @@ -193,6 +193,8 @@ type Config struct { WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"` Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"` NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"` + SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"` + OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"` } // Container is the type encompasing everything about a container - its config, diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go index 7c055c5ebd95..bcf44f789fc2 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go @@ -182,8 +182,8 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) { eventState.terminate() return } + eventState.updateLastSeen(ev) go eventState.sendEvent(ev) - go eventState.updateLastSeen(ev) case err = <-eventState.errC: if err == ErrNoListeners { eventState.terminate() @@ -227,7 +227,7 @@ func (eventState *eventMonitoringState) sendEvent(event *APIEvents) { eventState.Add(1) defer eventState.Done() if eventState.isEnabled() { - if eventState.noListeners() { + if len(eventState.listeners) == 0 { eventState.errC <- ErrNoListeners return } diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/bin/fmtpolice b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/bin/fmtpolice old mode 100644 new mode 100755 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go index 4f8c72b4bf7b..679a94509049 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go @@ -524,9 +524,13 @@ func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { Config: config, } repository := r.URL.Query().Get("repo") + tag := r.URL.Query().Get("tag") s.iMut.Lock() s.images = append(s.images, image) if repository != "" { + if tag != "" { + repository += ":" + tag + } s.imgIDs[repository] = image.ID } s.iMut.Unlock() @@ -582,20 +586,24 @@ func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { } func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { - repository := r.URL.Query().Get("fromImage") + fromImageName := r.URL.Query().Get("fromImage") image := docker.Image{ ID: s.generateID(), } s.iMut.Lock() s.images = append(s.images, image) - if repository != "" { - s.imgIDs[repository] = image.ID + if fromImageName != "" { + s.imgIDs[fromImageName] = image.ID } s.iMut.Unlock() } func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { name := mux.Vars(r)["name"] + tag := r.URL.Query().Get("tag") + if tag != "" { + name += ":" + tag + } s.iMut.RLock() if _, ok := s.imgIDs[name]; !ok { s.iMut.RUnlock() @@ -619,6 +627,10 @@ func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { s.iMut.Lock() defer s.iMut.Unlock() newRepo := r.URL.Query().Get("repo") + newTag := r.URL.Query().Get("tag") + if newTag != "" { + newRepo += ":" + newTag + } s.imgIDs[newRepo] = s.imgIDs[name] w.WriteHeader(http.StatusCreated) } diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go index 8217fb1d64fa..187c51701f62 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go @@ -277,6 +277,27 @@ func TestCommitContainerComplete(t *testing.T) { } } +func TestCommitContainerWithTag(t *testing.T) { + server := DockerServer{} + server.imgIDs = make(map[string]string) + addContainers(&server, 2) + server.buildMuxer() + recorder := httptest.NewRecorder() + queryString := "container=" + server.containers[0].ID + "&repo=tsuru/python&tag=v1" + request, _ := http.NewRequest("POST", "/commit?"+queryString, nil) + server.ServeHTTP(recorder, request) + image := server.images[0] + if image.Parent != server.containers[0].Image { + t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", server.containers[0].Image, image.Parent) + } + if image.Container != server.containers[0].ID { + t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", server.containers[0].ID, image.Container) + } + if id := server.imgIDs["tsuru/python:v1"]; id != image.ID { + t.Errorf("CommitContainer: wrong ID saved for repository. Want %q. Got %q.", image.ID, id) + } +} + func TestCommitContainerInvalidRun(t *testing.T) { server := DockerServer{} addContainers(&server, 1) @@ -778,6 +799,17 @@ func TestPushImage(t *testing.T) { } } +func TestPushImageWithTag(t *testing.T) { + server := DockerServer{imgIDs: map[string]string{"tsuru/python:v1": "a123"}} + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("POST", "/images/tsuru/python/push?tag=v1", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } +} + func TestPushImageNotFound(t *testing.T) { server := DockerServer{} server.buildMuxer() @@ -803,6 +835,20 @@ func TestTagImage(t *testing.T) { } } +func TestTagImageWithRepoAndTag(t *testing.T) { + server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}} + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("POST", "/images/tsuru/python/tag?repo=tsuru/new-python&tag=v1", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusCreated { + t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) + } + if server.imgIDs["tsuru/python"] != server.imgIDs["tsuru/new-python:v1"] { + t.Errorf("TagImage: did not tag the image") + } +} + func TestTagImageNotFound(t *testing.T) { server := DockerServer{} server.buildMuxer() diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/api/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/api/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/api/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/api/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/api/script.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/api/script.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/api/script.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/api/script.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/api/types.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/api/types.go similarity index 85% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/api/types.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/api/types.go index 924f150d25fa..a548c6483a72 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/api/types.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/api/types.go @@ -57,6 +57,15 @@ type Request struct { // LayeredBuild describes if this is build which layered scripts and sources on top of BaseImage. LayeredBuild bool + + // InstallDestination allows to override the default destination of the STI + // scripts. It allows to place the scripts into application root directory + // (see ONBUILD strategy). The default value is "upload/scripts". + InstallDestination string + + // Specify a relative directory inside the application repository that should + // be used as a root directory for the application. + ContextDir string } // Result structure contains information from build process. diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/cleanup.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/cleanup.go new file mode 100644 index 000000000000..171e86e3774b --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/cleanup.go @@ -0,0 +1,31 @@ +package build + +import ( + "github.com/golang/glog" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/docker" + "github.com/openshift/source-to-image/pkg/util" +) + +// DefaultCleaner provides a cleaner for most STI build use-cases. It cleans the +// temporary directories created by STI build and it also cleans the temporary +// Docker images produced by LayeredBuild +type DefaultCleaner struct { + util.FileSystem + docker.Docker +} + +// Cleanup removes the temporary directories where the sources were stored for +// build. +func (c *DefaultCleaner) Cleanup(request *api.Request) { + if request.PreserveWorkingDir { + glog.Infof("Temporary directory '%s' will be saved, not deleted", request.WorkingDir) + } else { + glog.V(2).Infof("Removing temporary directory %s", request.WorkingDir) + c.RemoveDirectory(request.WorkingDir) + } + if request.LayeredBuild { + glog.V(2).Infof("Removing temporary image %s", request.BaseImage) + c.RemoveImage(request.BaseImage) + } +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/interfaces.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/interfaces.go new file mode 100644 index 000000000000..1748f26e2af1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/interfaces.go @@ -0,0 +1,48 @@ +package build + +import "github.com/openshift/source-to-image/pkg/api" + +// Builder is the interface that provides basic methods all implementation +// should have. +// Build method executes the build based on Request and returns the Result. +type Builder interface { + Build(*api.Request) (*api.Result, error) +} + +// Preparer provides the Prepare method for builders that need to prepare source +// code before it gets passed to the build. +type Preparer interface { + Prepare(*api.Request) error +} + +// Cleaner provides the Cleanup method for builders that need to cleanup +// temporary containers or directories after build execution finish. +type Cleaner interface { + Cleanup(*api.Request) +} + +// IncrementalBuilder provides methods that is used for builders that implements +// the 'incremental' build workflow. +// The Determine method checks if the artifacts from the previous build exists +// and if they can be used in the current build. +// The Save method stores the artifacts for the next build. +type IncrementalBuilder interface { + Determine(*api.Request) error + Save(*api.Request) error +} + +// ScriptsHandler provides an interface for executing the scripts +type ScriptsHandler interface { + Execute(api.Script, *api.Request) error +} + +// Downloader provides methods for downloading the application source code +type Downloader interface { + Download(*api.Request) error +} + +// LayeredDockerBuilder represents a minimal Docker builder interface that is +// used to execute the layered Docker build with the application source. +type LayeredDockerBuilder interface { + Builder +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/doc.go new file mode 100644 index 000000000000..a935cbee4716 --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/doc.go @@ -0,0 +1 @@ +package strategies diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/layered/layered.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/layered/layered.go new file mode 100644 index 000000000000..84ec262725ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/layered/layered.go @@ -0,0 +1,152 @@ +package layered + +import ( + "bufio" + "bytes" + "fmt" + "io" + "path/filepath" + "time" + + "github.com/golang/glog" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/build" + "github.com/openshift/source-to-image/pkg/docker" + "github.com/openshift/source-to-image/pkg/errors" + "github.com/openshift/source-to-image/pkg/tar" + "github.com/openshift/source-to-image/pkg/util" +) + +const defaultLocation = "/tmp" + +type Layered struct { + request *api.Request + docker docker.Docker + fs util.FileSystem + tar tar.Tar + scripts build.ScriptsHandler +} + +func New(request *api.Request, scripts build.ScriptsHandler) (*Layered, error) { + d, err := docker.New(request.DockerSocket) + if err != nil { + return nil, err + } + return &Layered{ + docker: d, + request: request, + fs: util.NewFileSystem(), + tar: tar.New(), + scripts: scripts, + }, nil +} + +func getLocation(request *api.Request) string { + location := request.Location + if len(location) == 0 { + location = defaultLocation + } + return location +} + +func (b *Layered) CreateDockerfile(request *api.Request) error { + buffer := bytes.Buffer{} + + user, err := b.docker.GetImageUser(b.request.BaseImage) + if err != nil { + return err + } + + locations := []string{ + filepath.Join(getLocation(request), "scripts"), + filepath.Join(getLocation(request), "src"), + } + + buffer.WriteString(fmt.Sprintf("FROM %s\n", b.request.BaseImage)) + buffer.WriteString(fmt.Sprintf("COPY scripts %s\n", locations[0])) + buffer.WriteString(fmt.Sprintf("COPY src %s\n", locations[1])) + + //TODO: We need to account for images that may not have chown. There is a proposal + // to specify the owner for COPY here: https://github.com/docker/docker/pull/9934 + if len(user) > 0 { + buffer.WriteString("USER root\n") + buffer.WriteString(fmt.Sprintf("RUN chown -R %s %s %s\n", user, locations[0], locations[1])) + buffer.WriteString(fmt.Sprintf("USER %s\n", user)) + } + + uploadDir := filepath.Join(b.request.WorkingDir, "upload") + if err := b.fs.WriteFile(filepath.Join(uploadDir, "Dockerfile"), buffer.Bytes()); err != nil { + return err + } + glog.V(2).Infof("Writing custom Dockerfile to %s", uploadDir) + return nil +} + +func (b *Layered) SourceTar(request *api.Request) (io.ReadCloser, error) { + uploadDir := filepath.Join(request.WorkingDir, "upload") + tarFileName, err := b.tar.CreateTarFile(b.request.WorkingDir, uploadDir) + if err != nil { + return nil, err + } + return b.fs.Open(tarFileName) +} + +func (b *Layered) Build(request *api.Request) (*api.Result, error) { + if err := b.CreateDockerfile(request); err != nil { + return nil, err + } + + glog.V(2).Info("Creating application source code image") + tarStream, err := b.SourceTar(request) + if err != nil { + return nil, err + } + defer tarStream.Close() + + newBaseImage := fmt.Sprintf("%s-%d", b.request.BaseImage, time.Now().UnixNano()) + outReader, outWriter := io.Pipe() + defer outReader.Close() + defer outWriter.Close() + opts := docker.BuildImageOptions{ + Name: newBaseImage, + Stdin: tarStream, + Stdout: outWriter, + } + + // goroutine to stream container's output + go func(reader io.Reader) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + glog.V(2).Info(scanner.Text()) + } + }(outReader) + + glog.V(2).Infof("Building new image %s with scripts and sources already inside", newBaseImage) + if err = b.docker.BuildImage(opts); err != nil { + return nil, err + } + + // upon successful build we need to modify current request + b.request.LayeredBuild = true + // new image name + b.request.BaseImage = newBaseImage + // the scripts are inside the image + b.request.ExternalRequiredScripts = false + b.request.ScriptsURL = "image://" + filepath.Join(getLocation(request), "scripts") + // the source is also inside the image + b.request.Location = filepath.Join(getLocation(request), "src") + + glog.V(2).Infof("Building %s using sti-enabled image", b.request.Tag) + if err := b.scripts.Execute(api.Assemble, b.request); err != nil { + switch e := err.(type) { + case errors.ContainerError: + return nil, errors.NewAssembleError(b.request.Tag, e.Output, e) + default: + return nil, err + } + } + + return &api.Result{ + Success: true, + }, nil +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/layered/layered_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/layered/layered_test.go new file mode 100644 index 000000000000..0f0d758919d6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/layered/layered_test.go @@ -0,0 +1,86 @@ +package layered + +import ( + "errors" + "regexp" + "testing" + + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/test" +) + +type FakeExecutor struct{} + +func (f *FakeExecutor) Execute(api.Script, *api.Request) error { + return nil +} + +func newFakeLayered() *Layered { + return &Layered{ + docker: &test.FakeDocker{}, + request: &api.Request{}, + fs: &test.FakeFileSystem{}, + tar: &test.FakeTar{}, + scripts: &FakeExecutor{}, + } +} + +func TestBuildOK(t *testing.T) { + l := newFakeLayered() + l.request.BaseImage = "test/image" + _, err := l.Build(l.request) + if err != nil { + t.Errorf("Unexpected error returned: %v", err) + } + if !l.request.LayeredBuild { + t.Errorf("Expected LayeredBuild to be true!") + } + if m, _ := regexp.MatchString(`test/image-\d+`, l.request.BaseImage); !m { + t.Errorf("Expected BaseImage test/image-withnumbers, but got %s", l.request.BaseImage) + } + if l.request.ExternalRequiredScripts { + t.Errorf("Expected ExternalRequiredScripts to be false!") + } + if l.request.ScriptsURL != "image:///tmp/scripts" { + t.Error("Expected ScriptsURL image:///tmp/scripts, but got %s", l.request.ScriptsURL) + } + if l.request.Location != "/tmp/src" { + t.Errorf("Expected Location /tmp/src, but got %s", l.request.Location) + } +} + +func TestBuildErrorWriteDockerfile(t *testing.T) { + l := newFakeLayered() + l.fs.(*test.FakeFileSystem).WriteFileError = errors.New("WriteDockerfileError") + _, err := l.Build(l.request) + if err == nil || err.Error() != "WriteDockerfileError" { + t.Errorf("An error was expected for WriteDockerfile, but got different: %v", err) + } +} + +func TestBuildErrorCreateTarFile(t *testing.T) { + l := newFakeLayered() + l.tar.(*test.FakeTar).CreateTarError = errors.New("CreateTarError") + _, err := l.Build(l.request) + if err == nil || err.Error() != "CreateTarError" { + t.Error("An error was expected for CreateTar, but got different: %v", err) + } +} + +func TestBuildErrorOpenTarFile(t *testing.T) { + l := newFakeLayered() + l.fs.(*test.FakeFileSystem).OpenError = errors.New("OpenTarError") + _, err := l.Build(l.request) + if err == nil || err.Error() != "OpenTarError" { + t.Errorf("An error was expected for OpenTarFile, but got different: %v", err) + } +} + +func TestBuildErrorBuildImage(t *testing.T) { + l := newFakeLayered() + l.docker.(*test.FakeDocker).BuildImageError = errors.New("BuildImageError") + _, err := l.Build(l.request) + if err == nil || err.Error() != "BuildImageError" { + t.Errorf("An error was expected for BuildImage, but got different: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/onbuild/entrypoint.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/onbuild/entrypoint.go new file mode 100644 index 000000000000..18724d6a58c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/onbuild/entrypoint.go @@ -0,0 +1,58 @@ +package onbuild + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" + "regexp" + + "github.com/golang/glog" +) + +var validEntrypoints = []*regexp.Regexp{ + regexp.MustCompile(`^run(\.sh)?$`), + regexp.MustCompile(`^start(\.sh)?$`), + regexp.MustCompile(`^exec(cute)?(\.sh)?$`), +} + +// GuessEntrypoint tries to guess the valid entrypoint from the source code +// repository. The valid entrypoints are defined above (run,start,exec,execute) +func GuessEntrypoint(sourceDir string) (string, error) { + files, err := ioutil.ReadDir(sourceDir) + if err != nil { + return "", err + } + for _, f := range files { + if f.IsDir() || !f.Mode().IsRegular() { + continue + } + if isValidEntrypoint(filepath.Join(sourceDir, f.Name())) { + glog.V(2).Infof("Found valid ENTRYPOINT: %s", f.Name()) + return f.Name(), nil + } + } + return "", errors.New("No valid entrypoint specified") +} + +// isValidEntrypoint checks if the given file exists and if it is a regular +// file. Valid ENTRYPOINT must be an executable file, so the executable bit must +// be set. +func isValidEntrypoint(path string) bool { + stat, err := os.Stat(path) + if err != nil { + return false + } + found := false + for _, pattern := range validEntrypoints { + if pattern.MatchString(stat.Name()) { + found = true + break + } + } + if !found { + return false + } + mode := stat.Mode() + return mode&0111 != 0 +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/onbuild/onbuild.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/onbuild/onbuild.go new file mode 100644 index 000000000000..d6a5f158030b --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/onbuild/onbuild.go @@ -0,0 +1,142 @@ +package onbuild + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/golang/glog" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/build" + "github.com/openshift/source-to-image/pkg/build/strategies/sti" + "github.com/openshift/source-to-image/pkg/docker" + "github.com/openshift/source-to-image/pkg/git" + "github.com/openshift/source-to-image/pkg/tar" + "github.com/openshift/source-to-image/pkg/util" +) + +// SourceHandler is a wrapper for STI strategy Downloader and Preparer which +// allows to use Download and Prepare functions from the STI strategy. +type SourceHandler struct { + build.Downloader + build.Preparer +} + +// OnBuild strategy executes the simple Docker build in case the image does not +// support STI scripts but has ONBUILD instructions recorded. +type OnBuild struct { + docker docker.Docker + git git.Git + fs util.FileSystem + tar tar.Tar + source SourceHandler + garbage build.Cleaner +} + +// New returns a new instance of OnBuild builder +func New(request *api.Request) (*OnBuild, error) { + dockerHandler, err := docker.New(request.DockerSocket) + if err != nil { + return nil, err + } + b := &OnBuild{ + docker: dockerHandler, + git: git.New(), + fs: util.NewFileSystem(), + tar: tar.New(), + } + // Use STI Prepare() and download the 'run' script optionally. + request.InstallDestination = "upload/src" + s, err := sti.New(request) + s.SetScripts([]api.Script{}, []api.Script{api.Assemble, api.Run}) + + b.source = SourceHandler{&git.Clone{b.git, b.fs}, s} + b.garbage = &build.DefaultCleaner{b.fs, b.docker} + return b, nil +} + +// SourceTar produces a tar archive containing application source and stream it +func (b *OnBuild) SourceTar(request *api.Request) (io.ReadCloser, error) { + uploadDir := filepath.Join(request.WorkingDir, "upload", "src") + tarFileName, err := b.tar.CreateTarFile(request.WorkingDir, uploadDir) + if err != nil { + return nil, err + } + return b.fs.Open(tarFileName) +} + +// Build executes the ONBUILD kind of build +func (b *OnBuild) Build(request *api.Request) (*api.Result, error) { + glog.V(2).Info("Preparing the source code for build") + // Change the installation directory for this request to store scripts inside + // the application root directory. + if err := b.source.Prepare(request); err != nil { + return nil, err + } + + glog.V(2).Info("Creating application Dockerfile") + if err := b.CreateDockerfile(request); err != nil { + return nil, err + } + + glog.V(2).Info("Creating application source code image") + tarStream, err := b.SourceTar(request) + if err != nil { + return nil, err + } + defer tarStream.Close() + + opts := docker.BuildImageOptions{ + Name: request.Tag, + Stdin: tarStream, + Stdout: os.Stdout, + } + + glog.V(2).Info("Building the application source") + if err := b.docker.BuildImage(opts); err != nil { + return nil, err + } + + glog.V(2).Info("Cleaning up temporary containers") + b.garbage.Cleanup(request) + + imageID, err := b.docker.GetImageID(opts.Name) + if err != nil { + return nil, err + } + + return &api.Result{ + Success: true, + WorkingDir: request.WorkingDir, + ImageID: imageID, + }, nil +} + +// CreateDockerfile creates the ONBUILD Dockerfile +func (b *OnBuild) CreateDockerfile(request *api.Request) error { + buffer := bytes.Buffer{} + uploadDir := filepath.Join(request.WorkingDir, "upload", "src") + buffer.WriteString(fmt.Sprintf("FROM %s\n", request.BaseImage)) + entrypoint, err := GuessEntrypoint(uploadDir) + if err != nil { + return err + } + // If there is an assemble script present, run it as part of the build process + // as the last thing. + if b.hasAssembleScript(request) { + buffer.WriteString(fmt.Sprintf("RUN sh assemble\n")) + } + // FIXME: This assumes that the WORKDIR is set to the application source root + // directory. + buffer.WriteString(fmt.Sprintf(`ENTRYPOINT ["./%s"]`+"\n", entrypoint)) + return b.fs.WriteFile(filepath.Join(uploadDir, "Dockerfile"), buffer.Bytes()) +} + +// hasAssembleScript checks if the the assemble script is available +func (b *OnBuild) hasAssembleScript(request *api.Request) bool { + assemblePath := filepath.Join(request.WorkingDir, "upload", "src", "assemble") + _, err := os.Stat(assemblePath) + return err == nil +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/sti.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/sti.go new file mode 100644 index 000000000000..a61fc303ba1d --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/sti.go @@ -0,0 +1,378 @@ +package sti + +import ( + "bufio" + "io" + "path/filepath" + "regexp" + + "github.com/golang/glog" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/build" + "github.com/openshift/source-to-image/pkg/build/strategies/layered" + "github.com/openshift/source-to-image/pkg/docker" + "github.com/openshift/source-to-image/pkg/errors" + "github.com/openshift/source-to-image/pkg/git" + "github.com/openshift/source-to-image/pkg/script" + "github.com/openshift/source-to-image/pkg/tar" + "github.com/openshift/source-to-image/pkg/util" +) + +const ( + // maxErrorOutput is the maximum length of the error output saved for processing + maxErrorOutput = 1024 + // defaultLocation is the default location of the scripts and sources in image + defaultLocation = "/tmp" +) + +var ( + // List of directories that needs to be present inside workign dir + workingDirs = []string{ + "upload/scripts", + "upload/src", + "downloads/scripts", + "downloads/defaultScripts", + } +) + +// STI strategy executes the STI build. +// For more details about STI, visit https://github.com/openshift/source-to-image +type STI struct { + request *api.Request + result *api.Result + postExecutor docker.PostExecutor + installer script.Installer + git git.Git + fs util.FileSystem + tar tar.Tar + docker docker.Docker + callbackInvoker util.CallbackInvoker + requiredScripts []api.Script + optionalScripts []api.Script + + // Interfaces + preparer build.Preparer + incremental build.IncrementalBuilder + scripts build.ScriptsHandler + source build.Downloader + garbage build.Cleaner + layered build.Builder +} + +// New returns the instance of STI builder strategy for the given request. +// If the layeredBuilder parameter is specified, then the builder provided will +// be used for the case that the base Docker image does not have 'tar' or 'bash' +// installed. +func New(req *api.Request) (*STI, error) { + docker, err := docker.New(req.DockerSocket) + if err != nil { + return nil, err + } + inst := script.NewInstaller(req.BaseImage, req.ScriptsURL, req.InstallDestination, docker) + + b := &STI{ + installer: inst, + request: req, + docker: docker, + git: git.New(), + fs: util.NewFileSystem(), + tar: tar.New(), + callbackInvoker: util.NewCallbackInvoker(), + requiredScripts: []api.Script{api.Assemble, api.Run}, + optionalScripts: []api.Script{api.SaveArtifacts}, + } + + // The sources are downloaded using the GIT downloader. + // TODO: Add more SCM in future. + b.source = &git.Clone{b.git, b.fs} + b.garbage = &build.DefaultCleaner{b.fs, b.docker} + b.layered, err = layered.New(req, b) + + // Set interfaces + b.preparer = b + b.incremental = b + b.scripts = b + b.postExecutor = b + return b, err +} + +// Build processes a Request and returns a *api.Result and an error. +// An error represents a failure performing the build rather than a failure +// of the build itself. Callers should check the Success field of the result +// to determine whether a build succeeded or not. +func (b *STI) Build(request *api.Request) (*api.Result, error) { + defer b.garbage.Cleanup(request) + + glog.Infof("Building %s", request.Tag) + if err := b.preparer.Prepare(request); err != nil { + return nil, err + } + + if err := b.incremental.Determine(request); err != nil { + return nil, err + } + + if request.Incremental { + glog.V(1).Infof("Existing image for tag %s detected for incremental build.", request.Tag) + } else { + glog.V(1).Infof("Clean build will be performed") + } + + glog.V(2).Infof("Performing source build from %s", request.Source) + if request.Incremental { + if err := b.incremental.Save(request); err != nil { + glog.Warningf("Error saving previous build artifacts: %v", err) + glog.Warning("Clean build will be performed!") + } + } + + glog.V(1).Infof("Building %s", request.Tag) + if err := b.scripts.Execute(api.Assemble, request); err != nil { + switch e := err.(type) { + case errors.ContainerError: + if !isMissingRequirements(e.Output) { + return nil, err + } + return b.layered.Build(request) + default: + return nil, err + } + } + + return b.result, nil +} + +// Prepare prepares the source code and tar for build +func (b *STI) Prepare(request *api.Request) error { + var err error + if request.WorkingDir, err = b.fs.CreateWorkingDirectory(); err != nil { + return err + } + + b.result = &api.Result{ + Success: false, + WorkingDir: request.WorkingDir, + } + + // Setup working directories + for _, v := range workingDirs { + if err := b.fs.MkdirAll(filepath.Join(request.WorkingDir, v)); err != nil { + return err + } + } + + // fetch sources, for theirs .sti/bin might contain sti scripts + if len(request.Source) > 0 { + if err = b.source.Download(request); err != nil { + return err + } + } + + // get the scripts + if request.ExternalRequiredScripts, err = b.installer.DownloadAndInstall( + b.requiredScripts, request.WorkingDir, true); err != nil { + return err + } + + if request.ExternalOptionalScripts, err = b.installer.DownloadAndInstall( + b.optionalScripts, request.WorkingDir, false); err != nil { + glog.Warningf("Failed downloading optional scripts: %v", err) + } + + return nil +} + +// SetScripts allows to overide default required and optional scripts +func (b *STI) SetScripts(required, optional []api.Script) { + b.requiredScripts = required + b.optionalScripts = optional +} + +// PostExecute allows to execute post-build actions after the Docker build +// finishes. +func (b *STI) PostExecute(containerID string, location string) error { + var ( + err error + previousImageID string + ) + + if b.request.Incremental && b.request.RemovePreviousImage { + if previousImageID, err = b.docker.GetImageID(b.request.Tag); err != nil { + glog.Errorf("Error retrieving previous image's metadata: %v", err) + } + } + + cmd := []string{} + opts := docker.CommitContainerOptions{ + Command: append(cmd, filepath.Join(location, string(api.Run))), + Env: b.generateConfigEnv(), + ContainerID: containerID, + Repository: b.request.Tag, + } + imageID, err := b.docker.CommitContainer(opts) + if err != nil { + return errors.NewBuildError(b.request.Tag, err) + } + b.result.Success = true + glog.Infof("Successfully built %s", b.request.Tag) + + b.result.ImageID = imageID + glog.V(1).Infof("Tagged %s as %s", imageID, b.request.Tag) + + if b.request.Incremental && b.request.RemovePreviousImage && previousImageID != "" { + glog.V(1).Infof("Removing previously-tagged image %s", previousImageID) + if err = b.docker.RemoveImage(previousImageID); err != nil { + glog.Errorf("Unable to remove previous image: %v", err) + } + } + + if b.request.CallbackURL != "" { + b.result.Messages = b.callbackInvoker.ExecuteCallback(b.request.CallbackURL, + b.result.Success, b.result.Messages) + } + + return nil +} + +// Determine determines if the current build supports incremental workflow. +// It checks if the previous image exists in the system and if so, then it +// verifies that the save-artifacts scripts is present. +func (b *STI) Determine(request *api.Request) (err error) { + //request.Incremental = false + + if request.Clean { + return + } + + // can only do incremental build if runtime image exists + previousImageExists, err := b.docker.IsImageInLocalRegistry(request.Tag) + if err != nil { + return + } + + // we're assuming save-artifacts to exists for embedded scripts (if not we'll + // warn a user upon container failure and proceed with clean build) + // for external save-artifacts - check its existence + saveArtifactsExists := request.ExternalOptionalScripts || + b.fs.Exists(filepath.Join(request.WorkingDir, "upload", "scripts", string(api.SaveArtifacts))) + + request.Incremental = previousImageExists && saveArtifactsExists + return nil +} + +// Save extracts and store the build artifacts from the previous build to a +// current build. +func (b *STI) Save(request *api.Request) (err error) { + artifactTmpDir := filepath.Join(request.WorkingDir, "upload", "artifacts") + if err = b.fs.Mkdir(artifactTmpDir); err != nil { + return err + } + + image := request.Tag + reader, writer := io.Pipe() + glog.V(1).Infof("Saving build artifacts from image %s to path %s", image, artifactTmpDir) + extractFunc := func() error { + defer reader.Close() + return b.tar.ExtractTarStream(artifactTmpDir, reader) + } + + opts := docker.RunContainerOptions{ + Image: image, + ExternalScripts: request.ExternalRequiredScripts, + ScriptsURL: request.ScriptsURL, + Location: request.Location, + Command: api.SaveArtifacts, + Stdout: writer, + OnStart: extractFunc, + } + err = b.docker.RunContainer(opts) + writer.Close() + + if e, ok := err.(errors.ContainerError); ok { + return errors.NewSaveArtifactsError(image, e.Output, err) + } + return err +} + +// Execute runs the specified STI script in the builder image. +func (b *STI) Execute(command api.Script, request *api.Request) error { + glog.V(2).Infof("Using image name %s", request.BaseImage) + + uploadDir := filepath.Join(request.WorkingDir, "upload") + tarFileName, err := b.tar.CreateTarFile(request.WorkingDir, uploadDir) + if err != nil { + return err + } + + tarFile, err := b.fs.Open(tarFileName) + if err != nil { + return err + } + defer tarFile.Close() + + errOutput := "" + outReader, outWriter := io.Pipe() + errReader, errWriter := io.Pipe() + defer outReader.Close() + defer outWriter.Close() + defer errReader.Close() + defer errWriter.Close() + opts := docker.RunContainerOptions{ + Image: request.BaseImage, + Stdout: outWriter, + Stderr: errWriter, + PullImage: request.ForcePull, + ExternalScripts: request.ExternalRequiredScripts, + ScriptsURL: request.ScriptsURL, + Location: request.Location, + Command: command, + Env: b.generateConfigEnv(), + PostExec: b.postExecutor, + } + if !request.LayeredBuild { + opts.Stdin = tarFile + } + // goroutine to stream container's output + go func(reader io.Reader) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + if glog.V(2) || command == api.Usage { + glog.Info(scanner.Text()) + } + } + }(outReader) + // goroutine to stream container's error + go func(reader io.Reader) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + text := scanner.Text() + if glog.V(1) { + glog.Errorf(text) + } + if len(errOutput) < maxErrorOutput { + errOutput += text + "\n" + } + } + }(errReader) + + err = b.docker.RunContainer(opts) + if e, ok := err.(errors.ContainerError); ok { + return errors.NewContainerError(request.BaseImage, e.ErrorCode, errOutput) + } + return err +} + +func (b *STI) generateConfigEnv() (configEnv []string) { + if len(b.request.Environment) > 0 { + for key, val := range b.request.Environment { + configEnv = append(configEnv, key+"="+val) + } + } + return +} + +func isMissingRequirements(text string) bool { + tar, _ := regexp.MatchString(`.*tar.*not found`, text) + sh, _ := regexp.MatchString(`.*/bin/sh.*no such file or directory`, text) + return tar || sh +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/build_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/sti_test.go similarity index 54% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/build_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/sti_test.go index d3ed69895330..303307be0436 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/build_test.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/sti_test.go @@ -6,13 +6,16 @@ import ( "reflect" "testing" - "github.com/openshift/source-to-image/pkg/sti/api" - stierr "github.com/openshift/source-to-image/pkg/sti/errors" - "github.com/openshift/source-to-image/pkg/sti/test" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/build" + stierr "github.com/openshift/source-to-image/pkg/errors" + "github.com/openshift/source-to-image/pkg/git" + "github.com/openshift/source-to-image/pkg/test" ) -type FakeBuildHandler struct { +type FakeSTI struct { CleanupCalled bool + PrepareCalled bool SetupRequired []api.Script SetupOptional []api.Script SetupError error @@ -29,66 +32,114 @@ type FakeBuildHandler struct { ExpectedError bool LayeredBuildCalled bool LayeredBuildError error + PostExecuteLocation string + PostExecuteContainerID string + PostExecuteError error } -func (f *FakeBuildHandler) cleanup() { +func newFakeBaseSTI() *STI { + return &STI{ + request: &api.Request{}, + result: &api.Result{}, + docker: &test.FakeDocker{}, + installer: &test.FakeInstaller{}, + git: &test.FakeGit{}, + fs: &test.FakeFileSystem{}, + tar: &test.FakeTar{}, + } +} + +func newFakeSTI(f *FakeSTI) *STI { + s := &STI{ + request: &api.Request{}, + result: &api.Result{}, + docker: &test.FakeDocker{}, + installer: &test.FakeInstaller{}, + git: &test.FakeGit{}, + fs: &test.FakeFileSystem{}, + tar: &test.FakeTar{}, + preparer: f, + incremental: f, + scripts: f, + garbage: f, + layered: &FakeDockerBuild{f}, + } + s.source = &git.Clone{s.git, s.fs} + return s +} + +func (f *FakeSTI) Cleanup(*api.Request) { f.CleanupCalled = true } -func (f *FakeBuildHandler) setup(required []api.Script, optional []api.Script) error { - f.SetupRequired = required - f.SetupOptional = optional - return f.SetupError +func (f *FakeSTI) Prepare(*api.Request) error { + f.PrepareCalled = true + f.SetupRequired = []api.Script{api.Assemble, api.Run} + f.SetupOptional = []api.Script{api.SaveArtifacts} + return nil } -func (f *FakeBuildHandler) determineIncremental() error { +func (f *FakeSTI) Determine(*api.Request) error { f.DetermineIncrementalCalled = true return f.DetermineIncrementalError } -func (f *FakeBuildHandler) Request() *api.Request { +func (f *FakeSTI) Request() *api.Request { return f.BuildRequest } -func (f *FakeBuildHandler) Result() *api.Result { +func (f *FakeSTI) Result() *api.Result { return f.BuildResult } -func (f *FakeBuildHandler) saveArtifacts() error { +func (f *FakeSTI) Save(*api.Request) error { f.SaveArtifactsCalled = true return f.SaveArtifactsError } -func (f *FakeBuildHandler) fetchSource() error { +func (f *FakeSTI) fetchSource() error { return f.FetchSourceError } -func (f *FakeBuildHandler) execute(command api.Script) error { +func (f *FakeSTI) Download(*api.Request) error { + return nil +} + +func (f *FakeSTI) Execute(command api.Script, r *api.Request) error { f.ExecuteCommand = command return f.ExecuteError } -func (f *FakeBuildHandler) wasExpectedError(text string) bool { +func (f *FakeSTI) wasExpectedError(text string) bool { return f.ExpectedError } -func (f *FakeBuildHandler) build() error { +func (f *FakeSTI) PostExecute(id, location string) error { + f.PostExecuteContainerID = id + f.PostExecuteLocation = location + return f.PostExecuteError +} + +type FakeDockerBuild struct { + *FakeSTI +} + +func (f *FakeDockerBuild) Build(*api.Request) (*api.Result, error) { f.LayeredBuildCalled = true - return f.LayeredBuildError + return nil, f.LayeredBuildError } func TestBuild(t *testing.T) { incrementalTest := []bool{false, true} for _, incremental := range incrementalTest { - fh := &FakeBuildHandler{ + fh := &FakeSTI{ BuildRequest: &api.Request{Incremental: incremental}, BuildResult: &api.Result{}, } - builder := Builder{ - handler: fh, - } - builder.Build() + + builder := newFakeSTI(fh) + builder.Build(&api.Request{Incremental: incremental}) // Verify the right scripts were requested if !reflect.DeepEqual(fh.SetupRequired, []api.Script{api.Assemble, api.Run}) { @@ -116,7 +167,7 @@ func TestBuild(t *testing.T) { } func TestLayeredBuild(t *testing.T) { - fh := &FakeBuildHandler{ + fh := &FakeSTI{ BuildRequest: &api.Request{ BaseImage: "testimage", }, @@ -124,10 +175,8 @@ func TestLayeredBuild(t *testing.T) { ExecuteError: stierr.NewContainerError("", 1, `/bin/sh: tar: not found`), ExpectedError: true, } - builder := Builder{ - handler: fh, - } - builder.Build() + builder := newFakeSTI(fh) + builder.Build(&api.Request{BaseImage: "testimage"}) // Verify layered build if !fh.LayeredBuildCalled { t.Errorf("Layered build was not called.") @@ -135,7 +184,7 @@ func TestLayeredBuild(t *testing.T) { } func TestBuildErrorExecute(t *testing.T) { - fh := &FakeBuildHandler{ + fh := &FakeSTI{ BuildRequest: &api.Request{ BaseImage: "testimage", }, @@ -143,10 +192,8 @@ func TestBuildErrorExecute(t *testing.T) { ExecuteError: errors.New("ExecuteError"), ExpectedError: false, } - builder := Builder{ - handler: fh, - } - _, err := builder.Build() + builder := newFakeSTI(fh) + _, err := builder.Build(&api.Request{BaseImage: "testimage"}) if err == nil || err.Error() != "ExecuteError" { t.Errorf("An error was expected, but got different %v", err) } @@ -178,31 +225,27 @@ func TestWasExpectedError(t *testing.T) { } for i, ti := range tests { - bh := &buildHandler{} - result := bh.wasExpectedError(ti.text) + result := isMissingRequirements(ti.text) if result != ti.expected { t.Errorf("(%d) Unexpected result: %v. Expected: %v", i, result, ti.expected) } } } -func testBuildHandler() *buildHandler { - requestHandler := &requestHandler{ - docker: &test.FakeDocker{}, - installer: &test.FakeInstaller{}, - git: &test.FakeGit{}, - fs: &test.FakeFileSystem{}, - tar: &test.FakeTar{}, - - request: &api.Request{}, - result: &api.Result{}, - } - buildHandler := &buildHandler{ - requestHandler: requestHandler, +func testBuildHandler() *STI { + s := &STI{ + docker: &test.FakeDocker{}, + installer: &test.FakeInstaller{}, + git: &test.FakeGit{}, + fs: &test.FakeFileSystem{}, + tar: &test.FakeTar{}, + request: &api.Request{}, + result: &api.Result{}, callbackInvoker: &test.FakeCallbackInvoker{}, } - return buildHandler + s.source = &git.Clone{s.git, s.fs} + return s } func TestPostExecute(t *testing.T) { @@ -265,7 +308,7 @@ func TestDetermineIncremental(t *testing.T) { {false, true, true, true, true}, // 1: previous image, script downloaded but no save-artifacts - {false, true, true, false, false}, + {false, true, true, true, true}, // 2-9: clean build - should always return false no matter what other flags are {true, false, false, false, false}, @@ -289,7 +332,7 @@ func TestDetermineIncremental(t *testing.T) { // 18-19: previous image, script inside the image, its existence does not matter {false, true, false, true, true}, - {false, true, false, false, true}, + {false, true, false, true, true}, } for i, ti := range tests { @@ -303,12 +346,15 @@ func TestDetermineIncremental(t *testing.T) { "/working-dir/upload/scripts/save-artifacts": true, } } - bh.determineIncremental() + bh.Determine(bh.request) if bh.request.Incremental != ti.expected { t.Errorf("(%d) Unexpected incremental result: %v. Expected: %v", i, bh.request.Incremental, ti.expected) } if !ti.clean && ti.previousImage && ti.scriptDownload { + if len(bh.fs.(*test.FakeFileSystem).ExistsFile) == 0 { + continue + } scriptChecked := bh.fs.(*test.FakeFileSystem).ExistsFile[0] expectedScript := "/working-dir/upload/scripts/save-artifacts" if scriptChecked != expectedScript { @@ -326,7 +372,7 @@ func TestSaveArtifacts(t *testing.T) { fs := bh.fs.(*test.FakeFileSystem) fd := bh.docker.(*test.FakeDocker) th := bh.tar.(*test.FakeTar) - err := bh.saveArtifacts() + err := bh.Save(bh.request) if err != nil { t.Errorf("Unexpected error when saving artifacts: %v", err) } @@ -351,7 +397,7 @@ func TestSaveArtifactsRunError(t *testing.T) { } expected := []error{ tests[0], - stierr.NewSaveArtifactsError("", tests[1]), + stierr.NewSaveArtifactsError("", "", tests[1]), } // test with tar extract error or not tarError := []bool{true, false} @@ -364,7 +410,7 @@ func TestSaveArtifactsRunError(t *testing.T) { if te { th.ExtractTarError = fmt.Errorf("tar error") } - err := bh.saveArtifacts() + err := bh.Save(bh.request) if !te && err != expected[i] { t.Errorf("Unexpected error returned from saveArtifacts: %v", err) } else if te && err != th.ExtractTarError { @@ -380,7 +426,7 @@ func TestSaveArtifactsErrorBeforeStart(t *testing.T) { expected := fmt.Errorf("run error") fd.RunContainerError = expected fd.RunContainerErrorBeforeStart = true - err := bh.saveArtifacts() + err := bh.Save(bh.request) if err != expected { t.Errorf("Unexpected error returned from saveArtifacts: %v", err) } @@ -391,7 +437,7 @@ func TestSaveArtifactsExtractError(t *testing.T) { th := bh.tar.(*test.FakeTar) expected := fmt.Errorf("extract error") th.ExtractTarError = expected - err := bh.saveArtifacts() + err := bh.Save(bh.request) if err != expected { t.Errorf("Unexpected error returned from saveArtifacts: %v", err) } @@ -442,7 +488,7 @@ func TestFetchSource(t *testing.T) { } bh.request.Source = "a-repo-source" expectedTargetDir := "/working-dir/upload/src" - bh.fetchSource() + bh.source.Download(bh.request) if ft.cloneExpected { if gh.CloneSource != "a-repo-source" { t.Errorf("Clone was not called with the expected source.") @@ -469,3 +515,201 @@ func TestFetchSource(t *testing.T) { } } } + +func TestPrepareOK(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + rh.SetScripts([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) + rh.fs.(*test.FakeFileSystem).WorkingDirResult = "/working-dir" + err := rh.Prepare(rh.request) + if err != nil { + t.Errorf("An error occurred setting up the request handler: %v", err) + } + if !rh.fs.(*test.FakeFileSystem).WorkingDirCalled { + t.Errorf("Working directory was not created.") + } + var expected []string + for _, dir := range workingDirs { + expected = append(expected, "/working-dir/"+dir) + } + mkdirs := rh.fs.(*test.FakeFileSystem).MkdirAllDir + if !reflect.DeepEqual(mkdirs, expected) { + t.Errorf("Unexpected set of MkdirAll calls: %#v", mkdirs) + } + requiredFlags := rh.installer.(*test.FakeInstaller).Required + if !reflect.DeepEqual(requiredFlags, []bool{true, false}) { + t.Errorf("Unexpected set of required flags: %#v", requiredFlags) + } + scripts := rh.installer.(*test.FakeInstaller).Scripts + if !reflect.DeepEqual(scripts[0], []api.Script{api.Assemble, api.Run}) { + t.Errorf("Unexpected set of required scripts: %#v", scripts[0]) + } + if !reflect.DeepEqual(scripts[1], []api.Script{api.SaveArtifacts}) { + t.Errorf("Unexpected set of optional scripts: %#v", scripts[1]) + } +} + +func TestPrepareErrorCreatingWorkingDir(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + rh.fs.(*test.FakeFileSystem).WorkingDirError = errors.New("WorkingDirError") + err := rh.Prepare(rh.request) + if err == nil || err.Error() != "WorkingDirError" { + t.Errorf("An error was expected for WorkingDir, but got different: %v", err) + } +} + +func TestPrepareErrorMkdirAll(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + rh.fs.(*test.FakeFileSystem).MkdirAllError = errors.New("MkdirAllError") + err := rh.Prepare(rh.request) + if err == nil || err.Error() != "MkdirAllError" { + t.Errorf("An error was expected for MkdirAll, but got different: %v", err) + } +} + +func TestPrepareErrorRequiredDownloadAndInstall(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + rh.SetScripts([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) + rh.installer.(*test.FakeInstaller).ErrScript = api.Assemble + err := rh.Prepare(rh.request) + if err == nil || err.Error() != string(api.Assemble) { + t.Errorf("An error was expected for required DownloadAndInstall, but got different: %v", err) + } +} + +func TestPrepareErrorOptionalDownloadAndInstall(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + rh.SetScripts([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) + rh.installer.(*test.FakeInstaller).ErrScript = api.SaveArtifacts + err := rh.Prepare(rh.request) + if err != nil { + t.Errorf("Unexpected error when downloading optional scripts: %v", err) + } +} + +func equalArrayContents(a []string, b []string) bool { + if len(a) != len(b) { + return false + } + for _, e := range a { + found := false + for _, f := range b { + if f == e { + found = true + } + } + if !found { + return false + } + } + return true +} + +func TestGenerateConfigEnv(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + testEnv := map[string]string{ + "Key1": "Value1", + "Key2": "Value2", + "Key3": "Value3", + } + rh.request.Environment = testEnv + result := rh.generateConfigEnv() + expected := []string{"Key1=Value1", "Key2=Value2", "Key3=Value3"} + if !equalArrayContents(result, expected) { + t.Errorf("Unexpected result. Expected: %#v. Actual: %#v", + expected, result) + } +} + +func TestExecuteOK(t *testing.T) { + rh := newFakeBaseSTI() + pe := &FakeSTI{} + rh.postExecutor = pe + rh.request.WorkingDir = "/working-dir" + rh.request.BaseImage = "test/image" + rh.request.ForcePull = true + th := rh.tar.(*test.FakeTar) + th.CreateTarResult = "/working-dir/test.tar" + fd := rh.docker.(*test.FakeDocker) + fd.RunContainerContainerID = "1234" + fd.RunContainerCmd = []string{"one", "two"} + + err := rh.Execute("test-command", rh.request) + if err != nil { + t.Errorf("Unexpected error returned: %v", err) + } + if th.CreateTarBase != "/working-dir" { + t.Errorf("Unexpected tar base directory: %s", th.CreateTarBase) + } + if th.CreateTarDir != "/working-dir/upload" { + t.Errorf("Unexpected tar directory: %s", th.CreateTarDir) + } + fh, ok := rh.fs.(*test.FakeFileSystem) + if !ok { + t.Fatalf("Unable to convert %v to FakeFilesystem", rh.fs) + } + if fh.OpenFile != "/working-dir/test.tar" { + t.Errorf("Unexpected file opened: %s", fh.OpenFile) + } + if !fh.OpenFileResult.CloseCalled { + t.Errorf("Tar file was not closed.") + } + ro := fd.RunContainerOpts + + if ro.Image != rh.request.BaseImage { + t.Errorf("Unexpected Image passed to RunContainer") + } + if ro.Stdin != fh.OpenFileResult { + t.Errorf("Unexpected input stream: %#v", fd.RunContainerOpts.Stdin) + } + if !ro.PullImage { + t.Errorf("PullImage is not true for RunContainer") + } + if ro.Command != "test-command" { + t.Errorf("Unexpected command passed to RunContainer: %s", + fd.RunContainerOpts.Command) + } + if pe.PostExecuteContainerID != "1234" { + t.Errorf("PostExecutor not called with expected ID: %s", + pe.PostExecuteContainerID) + } + if !reflect.DeepEqual(pe.PostExecuteLocation, "test-command") { + t.Errorf("PostExecutor not called with expected command: %s", pe.PostExecuteLocation) + } +} + +func TestExecuteErrorCreateTarFile(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + rh.tar.(*test.FakeTar).CreateTarError = errors.New("CreateTarError") + err := rh.Execute("test-command", rh.request) + if err == nil || err.Error() != "CreateTarError" { + t.Errorf("An error was expected for CreateTarFile, but got different: %v", err) + } +} + +func TestExecuteErrorOpenTarFile(t *testing.T) { + rh := newFakeSTI(&FakeSTI{}) + rh.fs.(*test.FakeFileSystem).OpenError = errors.New("OpenTarError") + err := rh.Execute("test-command", rh.request) + if err == nil || err.Error() != "OpenTarError" { + t.Errorf("An error was expected for OpenTarFile, but got different: %v", err) + } +} + +func TestCleanup(t *testing.T) { + rh := newFakeBaseSTI() + + rh.request.WorkingDir = "/working-dir" + preserve := []bool{false, true} + for _, p := range preserve { + rh.request.PreserveWorkingDir = p + rh.fs = &test.FakeFileSystem{} + rh.garbage = &build.DefaultCleaner{rh.fs, rh.docker} + rh.garbage.Cleanup(rh.request) + removedDir := rh.fs.(*test.FakeFileSystem).RemoveDirName + if p && removedDir != "" { + t.Errorf("Expected working directory to be preserved, but it was removed.") + } else if !p && removedDir == "" { + t.Errorf("Expected working directory to be removed, but it was preserved.") + } + } +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/usage.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/usage.go similarity index 51% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/usage.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/usage.go index a5fbbb28c407..af1c6fc10463 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/usage.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/usage.go @@ -1,39 +1,49 @@ package sti import ( - "github.com/openshift/source-to-image/pkg/sti/api" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/build" ) // UsageHandler handles a request to display usage type usageHandler interface { - cleanup() - setup(required []api.Script, optional []api.Script) error - execute(command api.Script) error + build.ScriptsHandler + build.Preparer + SetScripts([]api.Script, []api.Script) } // Usage display usage information about a particular build image type Usage struct { handler usageHandler + garbage build.Cleaner + request *api.Request } // NewUsage creates a new instance of the default Usage implementation func NewUsage(req *api.Request) (*Usage, error) { - h, err := newRequestHandler(req) + b, err := New(req) if err != nil { return nil, err } - return &Usage{handler: h}, nil + usage := Usage{ + handler: b, + request: req, + garbage: b.garbage, + } + return &usage, nil } // Show starts the builder container and invokes the usage script on it // to print usage information for the script. func (u *Usage) Show() error { - h := u.handler - defer h.cleanup() + b := u.handler + defer u.garbage.Cleanup(u.request) + + b.SetScripts([]api.Script{api.Usage}, []api.Script{}) - if err := h.setup([]api.Script{api.Usage}, []api.Script{}); err != nil { + if err := b.Prepare(u.request); err != nil { return err } - return h.execute(api.Usage) + return b.Execute(api.Usage, u.request) } diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/usage_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/usage_test.go similarity index 74% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/usage_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/usage_test.go index 95caefa2c99b..572554589c5c 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/usage_test.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/sti/usage_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/openshift/source-to-image/pkg/sti/api" + "github.com/openshift/source-to-image/pkg/api" ) type FakeUsageHandler struct { @@ -17,21 +17,32 @@ type FakeUsageHandler struct { executeError error } -func (f *FakeUsageHandler) cleanup() { +type FakeCleaner struct { + cleanupCalled bool +} + +func (f *FakeCleaner) Cleanup(*api.Request) { f.cleanupCalled = true } -func (f *FakeUsageHandler) setup(required []api.Script, optional []api.Script) error { - f.setupRequired = required - f.setupOptional = optional +func (f *FakeUsageHandler) Prepare(*api.Request) error { return f.setupError } -func (f *FakeUsageHandler) execute(command api.Script) error { +func (f *FakeUsageHandler) SetScripts(r, o []api.Script) { + f.setupRequired = r + f.setupOptional = o +} + +func (f *FakeUsageHandler) Execute(command api.Script, r *api.Request) error { f.executeCommand = command return f.executeError } +func (f *FakeUsageHandler) Download(*api.Request) error { + return nil +} + func newTestUsage() *Usage { return &Usage{ handler: &FakeUsageHandler{}, @@ -40,6 +51,8 @@ func newTestUsage() *Usage { func TestUsage(t *testing.T) { u := newTestUsage() + g := &FakeCleaner{} + u.garbage = g fh := u.handler.(*FakeUsageHandler) err := u.Show() if err != nil { @@ -54,13 +67,14 @@ func TestUsage(t *testing.T) { if fh.executeCommand != "usage" { t.Errorf("execute called with unexpected command: %#v", fh.executeCommand) } - if !fh.cleanupCalled { + if !g.cleanupCalled { t.Errorf("cleanup was not called from usage.") } } func TestUsageSetupError(t *testing.T) { u := newTestUsage() + u.garbage = &FakeCleaner{} fh := u.handler.(*FakeUsageHandler) fh.setupError = fmt.Errorf("setup error") err := u.Show() @@ -74,6 +88,7 @@ func TestUsageSetupError(t *testing.T) { func TestUsageExecuteError(t *testing.T) { u := newTestUsage() + u.garbage = &FakeCleaner{} fh := u.handler.(*FakeUsageHandler) fh.executeError = fmt.Errorf("execute error") err := u.Show() diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/strategies.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/strategies.go new file mode 100644 index 000000000000..ff7c2135f3b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/build/strategies/strategies.go @@ -0,0 +1,50 @@ +package strategies + +import ( + dockerclient "github.com/fsouza/go-dockerclient" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/build" + "github.com/openshift/source-to-image/pkg/build/strategies/onbuild" + "github.com/openshift/source-to-image/pkg/build/strategies/sti" + "github.com/openshift/source-to-image/pkg/docker" +) + +// GetStrategy decides what build strategy will be used for the STI build. +func GetStrategy(request *api.Request) (build.Builder, error) { + image, err := GetBaseImage(request) + if err != nil { + return nil, err + } + + if image.OnBuild { + return onbuild.New(request) + } + + return sti.New(request) +} + +// GetBaseImage processes the request and performs operations necessary to make +// the Docker image specified as BaseImage available locally. +// It returns informations about the base image, containing metadata necessary +// for choosing the right STI build strategy. +func GetBaseImage(request *api.Request) (*docker.PullResult, error) { + d, err := docker.New(request.DockerSocket) + result := docker.PullResult{} + if err != nil { + return nil, err + } + + var image *dockerclient.Image + if request.ForcePull { + image, err = d.PullImage(request.BaseImage) + } else { + image, err = d.CheckAndPull(request.BaseImage) + } + + if err != nil { + return nil, err + } + result.Image = image + result.OnBuild = d.IsImageOnBuild(request.BaseImage) + return &result, nil +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/config/config.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/config/config.go new file mode 100644 index 000000000000..bacbddc1c6ab --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/config/config.go @@ -0,0 +1,70 @@ +package config + +import ( + "io/ioutil" + + "encoding/json" + + "github.com/golang/glog" + "github.com/openshift/source-to-image/pkg/api" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// DefaultConfigPath specifies the default location of the STI config file +const DefaultConfigPath = ".stifile" + +// Config represents a basic serialization for the STI build options +type Config struct { + Source string `json:"source" yaml:"source"` + BaseImage string `json:"baseImage" yaml:"baseImage"` + Tag string `json:"tag" yaml:"tag"` + Flags map[string]string `json:"flags,omitempty" yaml:"flags,omitempty"` +} + +// Save persists the STI command line arguments into disk +func Save(req *api.Request, cmd *cobra.Command) { + c := Config{ + BaseImage: req.BaseImage, + Source: req.Source, + Tag: req.Tag, + Flags: make(map[string]string), + } + // Store only flags that have changed + cmd.Flags().Visit(func(f *pflag.Flag) { + c.Flags[f.Name] = f.Value.String() + }) + data, err := json.Marshal(c) + if err != nil { + glog.V(1).Infof("Unable to serialize to %s: %v", DefaultConfigPath, err) + return + } + if err := ioutil.WriteFile(DefaultConfigPath, data, 0644); err != nil { + glog.V(1).Infof("Unable to save %s: %v", DefaultConfigPath, err) + } + return +} + +// Restore loads the arguments from disk and prefill the Request +func Restore(req *api.Request, cmd *cobra.Command) { + data, err := ioutil.ReadFile(DefaultConfigPath) + if err != nil { + glog.V(1).Infof("Unable to restore %s: %v", err) + return + } + c := Config{} + if err := json.Unmarshal(data, &c); err != nil { + glog.V(1).Infof("Unable to parse %s: %v", DefaultConfigPath, err) + return + } + req.BaseImage = c.BaseImage + req.Source = c.Source + req.Tag = c.Tag + for name, value := range c.Flags { + // Do not change flags that user sets. Allow overriding of stored flags. + if cmd.Flag(name).Changed { + continue + } + cmd.Flags().Set(name, value) + } +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/create.go similarity index 89% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/create.go index 83386befb5e5..da734dadda4c 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/create.go @@ -1,11 +1,11 @@ -package sti +package create import ( "os" "text/template" "github.com/golang/glog" - "github.com/openshift/source-to-image/pkg/sti/create/templates" + "github.com/openshift/source-to-image/pkg/create/templates" ) // Bootstrap defines parameters for the template processing @@ -14,9 +14,9 @@ type Bootstrap struct { ImageName string } -// NewCreate returns a new bootstrap for giben image name and destination +// NewCreate returns a new bootstrap for given image name and destination // directory -func NewCreate(name, dst string) *Bootstrap { +func New(name, dst string) *Bootstrap { return &Bootstrap{ImageName: name, DestinationDir: dst} } diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create/templates/docker.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/templates/docker.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create/templates/docker.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/templates/docker.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create/templates/scripts.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/templates/scripts.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create/templates/scripts.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/templates/scripts.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create/templates/test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/templates/test.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/create/templates/test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/create/templates/test.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/docker.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/docker.go similarity index 87% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/docker.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/docker.go index b9847f2bf7a7..9a27df7dc8f5 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/docker.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/docker.go @@ -10,8 +10,8 @@ import ( "github.com/fsouza/go-dockerclient" "github.com/golang/glog" - "github.com/openshift/source-to-image/pkg/sti/api" - "github.com/openshift/source-to-image/pkg/sti/errors" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/errors" ) const ( @@ -24,15 +24,17 @@ const ( // build or usage commands type Docker interface { IsImageInLocalRegistry(name string) (bool, error) + IsImageOnBuild(string) bool RemoveContainer(id string) error GetScriptsURL(name string) (string, error) RunContainer(opts RunContainerOptions) error GetImageID(name string) (string, error) CommitContainer(opts CommitContainerOptions) (string, error) RemoveImage(name string) error - PullImage(name string) error + PullImage(name string) (*docker.Image, error) CheckAndPull(name string) (*docker.Image, error) BuildImage(opts BuildImageOptions) error + GetImageUser(name string) (string, error) } // Client contains all methods called on the go Docker @@ -55,10 +57,15 @@ type stiDocker struct { client Client } -type postExecutor interface { +type PostExecutor interface { PostExecute(containerID string, location string) error } +type PullResult struct { + OnBuild bool + Image *docker.Image +} + // RunContainerOptions are options passed in to the RunContainer method type RunContainerOptions struct { Image string @@ -72,7 +79,7 @@ type RunContainerOptions struct { Stdout io.Writer Stderr io.Writer OnStart func() error - PostExec postExecutor + PostExec PostExecutor } // CommitContainerOptions are options passed in to the CommitContainer method @@ -90,8 +97,8 @@ type BuildImageOptions struct { Stdout io.Writer } -// NewDocker creates a new implementation of the STI Docker interface -func NewDocker(endpoint string) (Docker, error) { +// New creates a new implementation of the STI Docker interface +func New(endpoint string) (Docker, error) { client, err := docker.NewClient(endpoint) if err != nil { return nil, err @@ -113,6 +120,30 @@ func (d *stiDocker) IsImageInLocalRegistry(name string) (bool, error) { return false, err } +// GetImageUser finds and retrieves the user associated with +// an image if one has been specified +func (d *stiDocker) GetImageUser(name string) (string, error) { + image, err := d.client.InspectImage(name) + if err != nil { + return "", errors.NewInspectImageError(name, err) + } + user := image.ContainerConfig.User + if len(user) == 0 { + user = image.Config.User + } + return user, nil +} + +// IsImageOnBuild provides information about whether the Docker image has +// OnBuild intruction recorded in the Image Config. +func (d *stiDocker) IsImageOnBuild(name string) bool { + image, err := d.client.InspectImage(name) + if err != nil { + return false + } + return len(image.Config.OnBuild) > 0 +} + // CheckAndPull pulls an image into the local registry if not present // and returns the image metadata func (d *stiDocker) CheckAndPull(name string) (image *docker.Image, err error) { @@ -120,12 +151,9 @@ func (d *stiDocker) CheckAndPull(name string) (image *docker.Image, err error) { return nil, errors.NewInspectImageError(name, err) } if image == nil { - if err = d.PullImage(name); err != nil { + if image, err = d.PullImage(name); err != nil { return nil, err } - if image, err = d.client.InspectImage(name); err != nil { - return nil, errors.NewInspectImageError(name, err) - } } else { glog.V(2).Infof("Image %s available locally", name) } @@ -133,14 +161,18 @@ func (d *stiDocker) CheckAndPull(name string) (image *docker.Image, err error) { } // PullImage pulls an image into the local registry -func (d *stiDocker) PullImage(name string) (err error) { +func (d *stiDocker) PullImage(name string) (image *docker.Image, err error) { glog.V(1).Infof("Pulling image %s", name) // TODO: Add authentication support if err = d.client.PullImage(docker.PullImageOptions{Repository: name}, docker.AuthConfiguration{}); err != nil { - return errors.NewPullImageError(name, err) + glog.V(3).Infof("An error was received from the PullImage call: %v", err) + return nil, errors.NewPullImageError(name, err) } - return nil + if image, err = d.client.InspectImage(name); err != nil { + return nil, errors.NewInspectImageError(name, err) + } + return } // RemoveContainer removes a container and its associated volumes. @@ -373,7 +405,7 @@ func (d *stiDocker) BuildImage(opts BuildImageOptions) error { dockerOpts := docker.BuildImageOptions{ Name: opts.Name, NoCache: true, - SuppressOutput: true, + SuppressOutput: false, RmTmpContainer: true, ForceRmTmpContainer: true, InputStream: opts.Stdin, diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/docker_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/docker_test.go similarity index 98% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/docker_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/docker_test.go index 6c26c358aeb8..be28b283ea2b 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/docker_test.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/docker_test.go @@ -9,9 +9,9 @@ import ( "github.com/fsouza/go-dockerclient" - "github.com/openshift/source-to-image/pkg/sti/api" - "github.com/openshift/source-to-image/pkg/sti/docker/test" - "github.com/openshift/source-to-image/pkg/sti/errors" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/docker/test" + "github.com/openshift/source-to-image/pkg/errors" ) func getDocker(client Client) *stiDocker { diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/test/client.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/test/client.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/docker/test/client.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/docker/test/client.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/errors/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/errors/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/errors/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/errors/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/errors/errors.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/errors/errors.go similarity index 81% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/errors/errors.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/errors/errors.go index 1a0f7a6798a7..f5c9db022d93 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/errors/errors.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/errors/errors.go @@ -3,7 +3,7 @@ package errors import ( "fmt" - "github.com/openshift/source-to-image/pkg/sti/api" + "github.com/openshift/source-to-image/pkg/api" ) // Common STI errors @@ -32,11 +32,11 @@ type Error struct { // ContainerError is an error returned when a container exits with a non-zero code. // ExitCode contains the exit code from the container type ContainerError struct { - Message string - ExpectedError string - ErrorCode int - Suggestion string - ExitCode int + Message string + Output string + ErrorCode int + Suggestion string + ExitCode int } // Error returns a string for a given error @@ -84,15 +84,26 @@ func NewScriptDownloadError(name api.Script, err error) error { // NewSaveArtifactsError returns a new error which indicates there was a problem // calling save-artifacts script -func NewSaveArtifactsError(name string, err error) error { +func NewSaveArtifactsError(name, output string, err error) error { return Error{ - Message: fmt.Sprintf("saving artifacts for %s failed", name), + Message: fmt.Sprintf("saving artifacts for %s failed:\n%s", name, output), Details: err, ErrorCode: ErrSaveArtifacts, Suggestion: "check the save-artifacts script for errors", } } +// NewAssembleError returns a new error which indicates there was a problem +// running assemble script +func NewAssembleError(name, output string, err error) error { + return Error{ + Message: fmt.Sprintf("assemble for %s failed:\n%s", name, output), + Details: err, + ErrorCode: ErrBuild, + Suggestion: "check the assemble script output for errors", + } +} + // NewBuildError returns a new error which indicates there was a problem // building the image func NewBuildError(name string, err error) error { @@ -100,7 +111,7 @@ func NewBuildError(name string, err error) error { Message: fmt.Sprintf("building %s failed", name), Details: err, ErrorCode: ErrBuild, - Suggestion: "check the assemble script for errors", + Suggestion: "check the build output for errors", } } @@ -161,12 +172,12 @@ func NewDefaultScriptsURLError(err error) error { // NewContainerError return a new error which indicates there was a problem // invoking command inside container -func NewContainerError(name string, code int, expected string) error { +func NewContainerError(name string, code int, output string) error { return ContainerError{ - Message: fmt.Sprintf("non-zero (%d) exit code from %s", code, name), - ExpectedError: expected, - ErrorCode: ErrSTIContainer, - Suggestion: "check the container logs for more information on the failure", - ExitCode: code, + Message: fmt.Sprintf("non-zero (%d) exit code from %s", code, name), + Output: output, + ErrorCode: ErrSTIContainer, + Suggestion: "check the container logs for more information on the failure", + ExitCode: code, } } diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/clone.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/clone.go new file mode 100644 index 000000000000..f55cbac58894 --- /dev/null +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/clone.go @@ -0,0 +1,54 @@ +package git + +import ( + "path/filepath" + + "github.com/golang/glog" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/util" +) + +type Clone struct { + Git + util.FileSystem +} + +// Download downloads the application source code from the GIT repository +// and checkout the Ref specified in the request. +func (c *Clone) Download(request *api.Request) error { + targetSourceDir := filepath.Join(request.WorkingDir, "upload", "src") + + if c.ValidCloneSpec(request.Source) { + + if len(request.ContextDir) > 0 { + targetSourceDir = filepath.Join(request.WorkingDir, "upload", "tmp") + } + glog.V(2).Infof("Cloning into %s", targetSourceDir) + if err := c.Clone(request.Source, targetSourceDir); err != nil { + glog.Errorf("Git clone failed: %+v", err) + return err + } + + if request.Ref != "" { + glog.V(1).Infof("Checking out ref %s", request.Ref) + + if err := c.Checkout(targetSourceDir, request.Ref); err != nil { + return err + } + } + + if len(request.ContextDir) > 0 { + originalTargetDir := filepath.Join(request.WorkingDir, "upload", "src") + c.RemoveDirectory(originalTargetDir) + err := c.Copy(filepath.Join(targetSourceDir, request.ContextDir), originalTargetDir) + if err != nil { + return err + } + c.RemoveDirectory(targetSourceDir) + } + + return nil + } + + return c.Copy(filepath.Join(request.Source, request.ContextDir), targetSourceDir) +} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/git.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/git.go similarity index 96% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/git.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/git.go index f8468a17c49b..08e1d4300d1b 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/git.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/git.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - "github.com/openshift/source-to-image/pkg/sti/util" + "github.com/openshift/source-to-image/pkg/util" ) // Git is an interface used by main STI code to extract/checkout git repositories @@ -17,7 +17,7 @@ type Git interface { } // NewGit returns a new instance of the default implementation of the Git interface -func NewGit() Git { +func New() Git { return &stiGit{ runner: util.NewCommandRunner(), } diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/git_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/git_test.go similarity index 95% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/git_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/git_test.go index 71b8d135e845..d66fc2396142 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/git/git_test.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/git/git_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/openshift/source-to-image/pkg/sti/test" + "github.com/openshift/source-to-image/pkg/test" ) func TestValidCloneSpec(t *testing.T) { @@ -20,7 +20,7 @@ func TestValidCloneSpec(t *testing.T) { "/home/user/code/repo.git", } - gh := NewGit() + gh := New() for _, scenario := range scenarios { result := gh.ValidCloneSpec(scenario) @@ -31,7 +31,7 @@ func TestValidCloneSpec(t *testing.T) { } func getGit() (*stiGit, *test.FakeCmdRunner) { - gh := NewGit().(*stiGit) + gh := New().(*stiGit) cr := &test.FakeCmdRunner{} gh.runner = cr diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/installer.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/installer.go similarity index 79% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/installer.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/installer.go index 4e7511bc7969..e37d58a8914f 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/installer.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/installer.go @@ -7,21 +7,24 @@ import ( "github.com/golang/glog" - "github.com/openshift/source-to-image/pkg/sti/api" - "github.com/openshift/source-to-image/pkg/sti/docker" - "github.com/openshift/source-to-image/pkg/sti/errors" - "github.com/openshift/source-to-image/pkg/sti/util" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/docker" + "github.com/openshift/source-to-image/pkg/errors" + "github.com/openshift/source-to-image/pkg/util" ) +const defaultInstallDir = "upload/scripts" + // Installer interface is responsible for installing scripts needed to run the build type Installer interface { DownloadAndInstall(scripts []api.Script, workingDir string, required bool) (bool, error) } // NewInstaller returns a new instance of the default Installer implementation -func NewInstaller(image, scriptsURL string, docker docker.Docker) Installer { +func NewInstaller(image, scriptsURL, dstDir string, docker docker.Docker) Installer { handler := &handler{ image: image, + destDir: dstDir, scriptsURL: scriptsURL, docker: docker, downloader: util.NewDownloader(), @@ -38,12 +41,13 @@ type handler struct { docker docker.Docker image string scriptsURL string + destDir string downloader util.Downloader fs util.FileSystem } type scriptHandler interface { - download(scripts []api.Script, workingDir string) (bool, error) + download(scripts []api.Script, workingDir string, required bool) (bool, error) getPath(script api.Script, workingDir string) string install(scriptPath string, workingDir string) error } @@ -58,19 +62,21 @@ type scriptInfo struct { // cannot be found, an error is returned, additionally the method returns information // whether the download actually happened. func (i *installer) DownloadAndInstall(scripts []api.Script, workingDir string, required bool) (bool, error) { - download, err := i.handler.download(scripts, workingDir) + download, err := i.handler.download(scripts, workingDir, required) if err != nil { return false, err } if !download { return false, nil } - for _, script := range scripts { scriptPath := i.handler.getPath(script, workingDir) if required && scriptPath == "" { return false, errors.NewScriptDownloadError(script, nil) } + if scriptPath == "" { + continue + } if err := i.handler.install(scriptPath, workingDir); err != nil { return false, err } @@ -78,7 +84,7 @@ func (i *installer) DownloadAndInstall(scripts []api.Script, workingDir string, return true, nil } -func (s *handler) download(scripts []api.Script, workingDir string) (bool, error) { +func (s *handler) download(scripts []api.Script, workingDir string, required bool) (bool, error) { if len(scripts) == 0 { return false, nil } @@ -108,6 +114,18 @@ func (s *handler) download(scripts []api.Script, workingDir string) (bool, error } } + // check for the scripts inside the sources + scriptsDir := filepath.Join(workingDir, "/upload/src/.sti/bin") + if s.fs.Exists(scriptsDir) { + for _, script := range scripts { + file := filepath.Join(scriptsDir, string(script)) + if s.fs.Exists(file) { + downloads[script] <- true + } + } + } + + // check for the scripts from parameter if s.scriptsURL != "" { destDir := filepath.Join(workingDir, "/downloads/scripts") for file, info := range s.prepareDownload(scripts, destDir, s.scriptsURL) { @@ -116,11 +134,11 @@ func (s *handler) download(scripts []api.Script, workingDir string) (bool, error } } + // check for the scripts from default URL defaultURL, err := s.docker.GetScriptsURL(s.image) if err != nil { return false, errors.NewDefaultScriptsURLError(err) } - if defaultURL != "" { destDir := filepath.Join(workingDir, "/downloads/defaultScripts") for file, info := range s.prepareDownload(scripts, destDir, defaultURL) { @@ -131,6 +149,11 @@ func (s *handler) download(scripts []api.Script, workingDir string) (bool, error // Wait for the script downloads to finish wg.Wait() + + // If the script is not required, ignore errors + if !required { + return true, nil + } for s, d := range downloads { if len(d) == 0 { return false, errors.NewScriptDownloadError(s, nil) @@ -175,7 +198,10 @@ func (s *handler) getPath(script api.Script, workingDir string) string { func (s *handler) install(path string, workingDir string) error { script := filepath.Base(path) - return s.fs.Rename(path, filepath.Join(workingDir, "upload/scripts", script)) + if len(s.destDir) == 0 { + s.destDir = defaultInstallDir + } + return s.fs.Rename(path, filepath.Join(workingDir, s.destDir, script)) } // prepareScriptDownload turns the script name into proper URL diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/installer_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/installer_test.go similarity index 97% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/installer_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/installer_test.go index c166678622bd..fd69baae0780 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/script/installer_test.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/script/installer_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" - "github.com/openshift/source-to-image/pkg/sti/api" - "github.com/openshift/source-to-image/pkg/sti/test" + "github.com/openshift/source-to-image/pkg/api" + "github.com/openshift/source-to-image/pkg/test" ) type FakeScriptHandler struct { @@ -22,7 +22,7 @@ type FakeScriptHandler struct { InstallError error } -func (f *FakeScriptHandler) download(scripts []api.Script, workingDir string) (bool, error) { +func (f *FakeScriptHandler) download(scripts []api.Script, workingDir string, required bool) (bool, error) { f.DownloadScripts = scripts f.DownloadWorkingDir = workingDir return f.DownloadResult, f.DownloadError @@ -169,7 +169,7 @@ func TestDownload(t *testing.T) { sh := getScriptHandler() dl := sh.downloader.(*test.FakeDownloader) sh.docker.(*test.FakeDocker).DefaultURLResult = "http://image.url/scripts" - _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir") + _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir", true) if err != nil { t.Errorf("Got unexpected error: %v", err) } @@ -220,7 +220,7 @@ func TestDownloadErrors1(t *testing.T) { "http://image.url/scripts/two": dlErr, "http://image.url/scripts/three": nil, } - _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir") + _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir", true) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -239,7 +239,7 @@ func TestDownloadErrors2(t *testing.T) { fmt.Sprintf("http://image.url/scripts/%s", api.Run): dlErr, fmt.Sprintf("http://image.url/scripts/%s", api.SaveArtifacts): nil, } - _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir") + _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir", true) if err == nil { t.Errorf("Expected an error because script could not be downloaded") } @@ -257,7 +257,7 @@ func TestDownloadChmodError(t *testing.T) { fmt.Sprintf("/working-dir/downloads/defaultScripts/%s", api.Run): nil, fmt.Sprintf("/working-dir/downloads/defaultScripts/%s", api.SaveArtifacts): nil, } - _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir") + _, err := sh.download([]api.Script{api.Assemble, api.Run, api.SaveArtifacts}, "/working-dir", true) if err == nil { t.Errorf("Expected an error because chmod returned an error.") } diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/build.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/build.go deleted file mode 100644 index beda4021eed9..000000000000 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/build.go +++ /dev/null @@ -1,222 +0,0 @@ -package sti - -import ( - "io" - "path/filepath" - "regexp" - - "github.com/golang/glog" - - "github.com/openshift/source-to-image/pkg/sti/api" - "github.com/openshift/source-to-image/pkg/sti/docker" - "github.com/openshift/source-to-image/pkg/sti/errors" - "github.com/openshift/source-to-image/pkg/sti/util" -) - -// Builder provides a simple Build interface -type Builder struct { - handler buildHandlerInterface -} - -type buildHandlerInterface interface { - cleanup() - setup(required []api.Script, optional []api.Script) error - determineIncremental() error - Request() *api.Request - Result() *api.Result - saveArtifacts() error - fetchSource() error - execute(command api.Script) error - wasExpectedError(text string) bool - build() error -} - -type buildHandler struct { - *requestHandler - callbackInvoker util.CallbackInvoker -} - -// NewBuilder returns a new Builder -func NewBuilder(req *api.Request) (*Builder, error) { - handler, err := newBuildHandler(req) - if err != nil { - return nil, err - } - return &Builder{ - handler: handler, - }, nil -} - -func newBuildHandler(req *api.Request) (*buildHandler, error) { - rh, err := newRequestHandler(req) - if err != nil { - return nil, err - } - bh := &buildHandler{ - requestHandler: rh, - callbackInvoker: util.NewCallbackInvoker(), - } - rh.postExecutor = bh - rh.errorChecker = bh - return bh, nil -} - -// Build processes a Request and returns a *api.Result and an error. -// An error represents a failure performing the build rather than a failure -// of the build itself. Callers should check the Success field of the result -// to determine whether a build succeeded or not. -func (b *Builder) Build() (*api.Result, error) { - bh := b.handler - defer bh.cleanup() - - err := bh.setup([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) - if err != nil { - return nil, err - } - - err = bh.determineIncremental() - if err != nil { - return nil, err - } - if bh.Request().Incremental { - glog.V(1).Infof("Existing image for tag %s detected for incremental build.", bh.Request().Tag) - } else { - glog.V(1).Infof("Clean build will be performed") - } - - glog.V(2).Infof("Performing source build from %s", bh.Request().Source) - if bh.Request().Incremental { - if err = bh.saveArtifacts(); err != nil { - glog.Warning("Error saving previous build artifacts: %v", err) - glog.Warning("Clean build will be performed!") - } - } - - glog.V(1).Infof("Building %s", bh.Request().Tag) - err = bh.execute(api.Assemble) - if e, ok := err.(errors.ContainerError); ok && bh.wasExpectedError(e.ExpectedError) { - glog.Warningf("Image %s does not have tar! Performing additional build to add the scripts and sources.", - bh.Request().BaseImage) - if err := bh.build(); err != nil { - return nil, err - } - glog.V(2).Infof("Building %s using sti-enabled image", bh.Request().Tag) - if err := bh.execute(api.Assemble); err != nil { - return nil, err - } - } else if err != nil { - return nil, err - } - - return bh.Result(), nil -} - -// wasExpectedError is used for determining whether the error that appeared -// authorizes us to do the additional build injecting the scripts and sources. -func (h *buildHandler) wasExpectedError(text string) bool { - tar, _ := regexp.MatchString(`.*tar.*not found`, text) - sh, _ := regexp.MatchString(`.*/bin/sh.*no such file or directory`, text) - return tar || sh -} - -func (h *buildHandler) PostExecute(containerID string, location string) error { - var ( - err error - previousImageID string - ) - if h.request.Incremental && h.request.RemovePreviousImage { - if previousImageID, err = h.docker.GetImageID(h.request.Tag); err != nil { - glog.Errorf("Error retrieving previous image's metadata: %v", err) - } - } - - cmd := []string{} - opts := docker.CommitContainerOptions{ - Command: append(cmd, filepath.Join(location, string(api.Run))), - Env: h.generateConfigEnv(), - ContainerID: containerID, - Repository: h.request.Tag, - } - imageID, err := h.docker.CommitContainer(opts) - if err != nil { - return errors.NewBuildError(h.request.Tag, err) - } - - h.result.ImageID = imageID - glog.V(1).Infof("Tagged %s as %s", imageID, h.request.Tag) - - if h.request.Incremental && h.request.RemovePreviousImage && previousImageID != "" { - glog.V(1).Infof("Removing previously-tagged image %s", previousImageID) - if err = h.docker.RemoveImage(previousImageID); err != nil { - glog.Errorf("Unable to remove previous image: %v", err) - } - } - - if h.request.CallbackURL != "" { - h.result.Messages = h.callbackInvoker.ExecuteCallback(h.request.CallbackURL, - h.result.Success, h.result.Messages) - } - - glog.Infof("Successfully built %s", h.request.Tag) - return nil -} - -func (h *buildHandler) determineIncremental() (err error) { - h.request.Incremental = false - if h.request.Clean { - return - } - - // can only do incremental build if runtime image exists - previousImageExists, err := h.docker.IsImageInLocalRegistry(h.request.Tag) - if err != nil { - return - } - - // we're assuming save-artifacts to exists for embedded scripts (if not we'll - // warn a user upon container failure and proceed with clean build) - // for external save-artifacts - check its existence - saveArtifactsExists := !h.request.ExternalOptionalScripts || - h.fs.Exists(filepath.Join(h.request.WorkingDir, "upload", "scripts", string(api.SaveArtifacts))) - h.request.Incremental = previousImageExists && saveArtifactsExists - return nil -} - -func (h *buildHandler) saveArtifacts() (err error) { - artifactTmpDir := filepath.Join(h.request.WorkingDir, "upload", "artifacts") - if err = h.fs.Mkdir(artifactTmpDir); err != nil { - return err - } - - image := h.request.Tag - reader, writer := io.Pipe() - glog.V(1).Infof("Saving build artifacts from image %s to path %s", image, artifactTmpDir) - extractFunc := func() error { - defer reader.Close() - return h.tar.ExtractTarStream(artifactTmpDir, reader) - } - - opts := docker.RunContainerOptions{ - Image: image, - ExternalScripts: h.request.ExternalRequiredScripts, - ScriptsURL: h.request.ScriptsURL, - Location: h.request.Location, - Command: api.SaveArtifacts, - Stdout: writer, - OnStart: extractFunc, - } - err = h.docker.RunContainer(opts) - writer.Close() - if _, ok := err.(errors.ContainerError); ok { - return errors.NewSaveArtifactsError(image, err) - } - return err -} - -func (h *buildHandler) Request() *api.Request { - return h.request -} - -func (h *buildHandler) Result() *api.Result { - return h.result -} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/doc.go deleted file mode 100644 index 7442e0a6f238..000000000000 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Provides functionality to build docker images from user source and a base image. - -package sti diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/executor.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/executor.go deleted file mode 100644 index ae258225fa62..000000000000 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/executor.go +++ /dev/null @@ -1,297 +0,0 @@ -package sti - -import ( - "bufio" - "bytes" - "fmt" - "io" - "path/filepath" - "time" - - "github.com/golang/glog" - - "github.com/openshift/source-to-image/pkg/sti/api" - "github.com/openshift/source-to-image/pkg/sti/docker" - "github.com/openshift/source-to-image/pkg/sti/errors" - "github.com/openshift/source-to-image/pkg/sti/git" - "github.com/openshift/source-to-image/pkg/sti/script" - "github.com/openshift/source-to-image/pkg/sti/tar" - "github.com/openshift/source-to-image/pkg/sti/util" -) - -const ( - // maxErrorOutput is the maximum length of the error output saved for processing - maxErrorOutput = 1024 - // defaultLocation is the default location of the scripts and sources in image - defaultLocation = "/tmp" -) - -// requestHandler encapsulates dependencies needed to fulfill requests. -type requestHandler struct { - request *api.Request - result *api.Result - postExecutor postExecutor - errorChecker errorChecker - installer script.Installer - git git.Git - fs util.FileSystem - docker docker.Docker - tar tar.Tar -} - -type postExecutor interface { - PostExecute(containerID string, location string) error -} - -type errorChecker interface { - wasExpectedError(text string) bool -} - -// newRequestHandler returns a new handler for a given request. -func newRequestHandler(req *api.Request) (*requestHandler, error) { - glog.V(2).Infof("Using docker socket: %s", req.DockerSocket) - - docker, err := docker.NewDocker(req.DockerSocket) - if err != nil { - return nil, err - } - - return &requestHandler{ - request: req, - docker: docker, - installer: script.NewInstaller(req.BaseImage, req.ScriptsURL, docker), - git: git.NewGit(), - fs: util.NewFileSystem(), - tar: tar.NewTar(), - }, nil -} - -func (h *requestHandler) setup(requiredScripts, optionalScripts []api.Script) (err error) { - if h.request.WorkingDir, err = h.fs.CreateWorkingDirectory(); err != nil { - return err - } - - h.result = &api.Result{ - Success: false, - WorkingDir: h.request.WorkingDir, - } - - // immediately pull the image if forcepull is true, that way later code that - // references the image will have it pre-pulled and can just inspect the image. - if h.request.ForcePull { - err = h.docker.PullImage(h.request.BaseImage) - } else { - _, err = h.docker.CheckAndPull(h.request.BaseImage) - } - if err != nil { - return - } - - // fetch sources, for theirs .sti/bin might contain sti scripts - if len(h.request.Source) > 0 { - if err = h.fetchSource(); err != nil { - return err - } - } - - dirs := []string{"upload/scripts", "downloads/scripts", "downloads/defaultScripts"} - for _, v := range dirs { - if err = h.fs.MkdirAll(filepath.Join(h.request.WorkingDir, v)); err != nil { - return err - } - } - - if h.request.ExternalRequiredScripts, err = h.installer.DownloadAndInstall( - requiredScripts, h.request.WorkingDir, true); err != nil { - return err - } - if h.request.ExternalOptionalScripts, err = h.installer.DownloadAndInstall( - optionalScripts, h.request.WorkingDir, false); err != nil { - glog.Warningf("Failed downloading optional scripts: %v", err) - } - - return nil -} - -func (h *requestHandler) generateConfigEnv() (configEnv []string) { - if len(h.request.Environment) > 0 { - for key, val := range h.request.Environment { - configEnv = append(configEnv, key+"="+val) - } - } - return -} - -func (h *requestHandler) execute(command api.Script) error { - glog.V(2).Infof("Using image name %s", h.request.BaseImage) - - uploadDir := filepath.Join(h.request.WorkingDir, "upload") - tarFileName, err := h.tar.CreateTarFile(h.request.WorkingDir, uploadDir) - if err != nil { - return err - } - - tarFile, err := h.fs.Open(tarFileName) - if err != nil { - return err - } - defer tarFile.Close() - - expectedError := "" - outReader, outWriter := io.Pipe() - errReader, errWriter := io.Pipe() - defer outReader.Close() - defer outWriter.Close() - defer errReader.Close() - defer errWriter.Close() - opts := docker.RunContainerOptions{ - Image: h.request.BaseImage, - Stdout: outWriter, - Stderr: errWriter, - PullImage: h.request.ForcePull, - ExternalScripts: h.request.ExternalRequiredScripts, - ScriptsURL: h.request.ScriptsURL, - Location: h.request.Location, - Command: command, - Env: h.generateConfigEnv(), - PostExec: h, - } - if !h.request.LayeredBuild { - opts.Stdin = tarFile - } - // goroutine to stream container's output - go func(reader io.Reader) { - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - if glog.V(2) || command == api.Usage { - glog.Info(scanner.Text()) - } - } - }(outReader) - // goroutine to stream container's error - go func(reader io.Reader) { - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - text := scanner.Text() - if glog.V(1) { - glog.Errorf(text) - } - if h.errorChecker != nil && h.errorChecker.wasExpectedError(text) && - len(expectedError) < maxErrorOutput { - expectedError += text + "; " - } - } - }(errReader) - - err = h.docker.RunContainer(opts) - if e, ok := err.(errors.ContainerError); ok { - return errors.NewContainerError(h.request.BaseImage, e.ErrorCode, expectedError) - } - return err -} - -func (h *requestHandler) build() error { - // create Dockerfile - buffer := bytes.Buffer{} - location := h.request.Location - if len(location) == 0 { - location = defaultLocation - } - buffer.WriteString(fmt.Sprintf("FROM %s\n", h.request.BaseImage)) - buffer.WriteString(fmt.Sprintf("ADD scripts %s\n", filepath.Join(location, "scripts"))) - buffer.WriteString(fmt.Sprintf("ADD src %s\n", filepath.Join(location, "src"))) - uploadDir := filepath.Join(h.request.WorkingDir, "upload") - if err := h.fs.WriteFile(filepath.Join(uploadDir, "Dockerfile"), buffer.Bytes()); err != nil { - return err - } - glog.V(2).Infof("Writing custom Dockerfile to %s", uploadDir) - - tarFileName, err := h.tar.CreateTarFile(h.request.WorkingDir, uploadDir) - if err != nil { - return err - } - tarFile, err := h.fs.Open(tarFileName) - if err != nil { - return err - } - defer tarFile.Close() - - newBaseImage := fmt.Sprintf("%s-%d", h.request.BaseImage, time.Now().UnixNano()) - outReader, outWriter := io.Pipe() - defer outReader.Close() - defer outWriter.Close() - opts := docker.BuildImageOptions{ - Name: newBaseImage, - Stdin: tarFile, - Stdout: outWriter, - } - // goroutine to stream container's output - go func(reader io.Reader) { - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - glog.V(2).Info(scanner.Text()) - } - }(outReader) - glog.V(2).Infof("Building new image %s with scripts and sources already inside", newBaseImage) - if err = h.docker.BuildImage(opts); err != nil { - return err - } - - // upon successful build we need to modify current request - h.request.LayeredBuild = true - // new image name - h.request.BaseImage = newBaseImage - // the scripts are inside the image - h.request.ExternalRequiredScripts = false - h.request.ScriptsURL = "image://" + filepath.Join(location, "scripts") - // the source is also inside the image - h.request.Location = filepath.Join(location, "src") - return nil -} - -func (h *requestHandler) PostExecute(containerID string, location string) (err error) { - h.result.Success = true - if h.postExecutor != nil { - err = h.postExecutor.PostExecute(containerID, location) - if err != nil { - glog.Errorf("An error occurred in post executor: %v", err) - } - } - return err -} - -func (h *requestHandler) cleanup() { - if h.request.PreserveWorkingDir { - glog.Infof("Temporary directory '%s' will be saved, not deleted", h.request.WorkingDir) - } else { - glog.V(2).Infof("Removing temporary directory %s", h.request.WorkingDir) - h.fs.RemoveDirectory(h.request.WorkingDir) - } - if h.request.LayeredBuild { - glog.V(2).Infof("Removing temporary image %s", h.request.BaseImage) - h.docker.RemoveImage(h.request.BaseImage) - } -} - -func (h *requestHandler) fetchSource() error { - targetSourceDir := filepath.Join(h.request.WorkingDir, "upload", "src") - glog.V(1).Infof("Downloading %s to directory %s", h.request.Source, targetSourceDir) - if h.git.ValidCloneSpec(h.request.Source) { - if err := h.git.Clone(h.request.Source, targetSourceDir); err != nil { - glog.Errorf("Git clone failed: %+v", err) - return err - } - - if h.request.Ref != "" { - glog.V(1).Infof("Checking out ref %s", h.request.Ref) - - if err := h.git.Checkout(targetSourceDir, h.request.Ref); err != nil { - return err - } - } - } else { - h.fs.Copy(h.request.Source, targetSourceDir) - } - - return nil -} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/executor_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/executor_test.go deleted file mode 100644 index 9fa11e919fea..000000000000 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/executor_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package sti - -import ( - "errors" - "fmt" - "reflect" - "regexp" - "testing" - - "github.com/openshift/source-to-image/pkg/sti/api" - "github.com/openshift/source-to-image/pkg/sti/test" -) - -func testRequestHandler() *requestHandler { - return &requestHandler{ - docker: &test.FakeDocker{}, - installer: &test.FakeInstaller{}, - git: &test.FakeGit{}, - fs: &test.FakeFileSystem{}, - tar: &test.FakeTar{}, - - request: &api.Request{}, - result: &api.Result{}, - } -} - -func TestSetupOK(t *testing.T) { - rh := testRequestHandler() - rh.fs.(*test.FakeFileSystem).WorkingDirResult = "/working-dir" - err := rh.setup([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) - if err != nil { - t.Errorf("An error occurred setting up the request handler: %v", err) - } - if !rh.fs.(*test.FakeFileSystem).WorkingDirCalled { - t.Errorf("Working directory was not created.") - } - mkdirs := rh.fs.(*test.FakeFileSystem).MkdirAllDir - if !reflect.DeepEqual( - mkdirs, - []string{"/working-dir/upload/scripts", - "/working-dir/downloads/scripts", - "/working-dir/downloads/defaultScripts"}) { - t.Errorf("Unexpected set of MkdirAll calls: %#v", mkdirs) - } - requiredFlags := rh.installer.(*test.FakeInstaller).Required - if !reflect.DeepEqual(requiredFlags, []bool{true, false}) { - t.Errorf("Unexpected set of required flags: %#v", requiredFlags) - } - scripts := rh.installer.(*test.FakeInstaller).Scripts - if !reflect.DeepEqual(scripts[0], []api.Script{api.Assemble, api.Run}) { - t.Errorf("Unexpected set of required scripts: %#v", scripts[0]) - } - if !reflect.DeepEqual(scripts[1], []api.Script{api.SaveArtifacts}) { - t.Errorf("Unexpected set of optional scripts: %#v", scripts[1]) - } -} - -func TestSetupErrorCreatingWorkingDir(t *testing.T) { - rh := testRequestHandler() - rh.fs.(*test.FakeFileSystem).WorkingDirError = errors.New("WorkingDirError") - err := rh.setup([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) - if err == nil || err.Error() != "WorkingDirError" { - t.Errorf("An error was expected for WorkingDir, but got different: %v", err) - } -} - -func TestSetupErrorMkdirAll(t *testing.T) { - rh := testRequestHandler() - rh.fs.(*test.FakeFileSystem).MkdirAllError = errors.New("MkdirAllError") - err := rh.setup([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) - if err == nil || err.Error() != "MkdirAllError" { - t.Errorf("An error was expected for MkdirAll, but got different: %v", err) - } -} - -func TestSetupErrorRequiredDownloadAndInstall(t *testing.T) { - rh := testRequestHandler() - rh.installer.(*test.FakeInstaller).ErrScript = api.Assemble - err := rh.setup([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) - if err == nil || err.Error() != string(api.Assemble) { - t.Errorf("An error was expected for required DownloadAndInstall, but got different: %v", err) - } -} - -func TestSetupErrorOptionalDownloadAndInstall(t *testing.T) { - rh := testRequestHandler() - rh.installer.(*test.FakeInstaller).ErrScript = api.SaveArtifacts - err := rh.setup([]api.Script{api.Assemble, api.Run}, []api.Script{api.SaveArtifacts}) - if err != nil { - t.Errorf("Unexpected error when downloading optional scripts: %v", err) - } -} - -func equalArrayContents(a []string, b []string) bool { - if len(a) != len(b) { - return false - } - for _, e := range a { - found := false - for _, f := range b { - if f == e { - found = true - } - } - if !found { - return false - } - } - return true -} - -func TestGenerateConfigEnv(t *testing.T) { - rh := testRequestHandler() - testEnv := map[string]string{ - "Key1": "Value1", - "Key2": "Value2", - "Key3": "Value3", - } - rh.request.Environment = testEnv - result := rh.generateConfigEnv() - expected := []string{"Key1=Value1", "Key2=Value2", "Key3=Value3"} - if !equalArrayContents(result, expected) { - t.Errorf("Unexpected result. Expected: %#v. Actual: %#v", - expected, result) - } -} - -type FakePostExecutor struct { - containerID string - location string - err error -} - -func (f *FakePostExecutor) PostExecute(id string, location string) error { - fmt.Errorf("Post execute called!!!!") - f.containerID = id - f.location = location - return f.err -} - -func TestExecuteOK(t *testing.T) { - rh := testRequestHandler() - pe := &FakePostExecutor{} - rh.postExecutor = pe - rh.request.WorkingDir = "/working-dir" - rh.request.BaseImage = "test/image" - rh.request.ForcePull = true - th := rh.tar.(*test.FakeTar) - th.CreateTarResult = "/working-dir/test.tar" - fd := rh.docker.(*test.FakeDocker) - fd.RunContainerContainerID = "1234" - fd.RunContainerCmd = []string{"one", "two"} - - err := rh.execute("test-command") - if err != nil { - t.Errorf("Unexpected error returned: %v", err) - } - if !rh.result.Success { - t.Errorf("Execute was not successful.") - } - if th.CreateTarBase != "/working-dir" { - t.Errorf("Unexpected tar base directory: %s", th.CreateTarBase) - } - if th.CreateTarDir != "/working-dir/upload" { - t.Errorf("Unexpected tar directory: %s", th.CreateTarDir) - } - fh := rh.fs.(*test.FakeFileSystem) - if fh.OpenFile != "/working-dir/test.tar" { - t.Errorf("Unexpected file opened: %s", fh.OpenFile) - } - if !fh.OpenFileResult.CloseCalled { - t.Errorf("Tar file was not closed.") - } - ro := fd.RunContainerOpts - - if ro.Image != rh.request.BaseImage { - t.Errorf("Unexpected Image passed to RunContainer") - } - if ro.Stdin != fh.OpenFileResult { - t.Errorf("Unexpected input stream: %#v", fd.RunContainerOpts.Stdin) - } - if !ro.PullImage { - t.Errorf("PullImage is not true for RunContainer") - } - if ro.Command != "test-command" { - t.Errorf("Unexpected command passed to RunContainer: %s", - fd.RunContainerOpts.Command) - } - if pe.containerID != "1234" { - t.Errorf("PostExecutor not called with expected ID: %s", - pe.containerID) - } - if !reflect.DeepEqual(pe.location, "test-command") { - t.Errorf("PostExecutor not called with expected command: %s", pe.location) - } -} - -func TestExecuteErrorCreateTarFile(t *testing.T) { - rh := testRequestHandler() - rh.tar.(*test.FakeTar).CreateTarError = errors.New("CreateTarError") - err := rh.execute("test-command") - if err == nil || err.Error() != "CreateTarError" { - t.Errorf("An error was expected for CreateTarFile, but got different: %v", err) - } -} - -func TestExecuteErrorOpenTarFile(t *testing.T) { - rh := testRequestHandler() - rh.fs.(*test.FakeFileSystem).OpenError = errors.New("OpenTarError") - err := rh.execute("test-command") - if err == nil || err.Error() != "OpenTarError" { - t.Errorf("An error was expected for OpenTarFile, but got different: %v", err) - } -} - -func TestBuildOK(t *testing.T) { - rh := testRequestHandler() - rh.request.BaseImage = "test/image" - err := rh.build() - if err != nil { - t.Errorf("Unexpected error returned: %v", err) - } - if !rh.request.LayeredBuild { - t.Errorf("Expected LayeredBuild to be true!") - } - if m, _ := regexp.MatchString(`test/image-\d+`, rh.request.BaseImage); !m { - t.Errorf("Expected BaseImage test/image-withnumbers, but got %s", rh.request.BaseImage) - } - if rh.request.ExternalRequiredScripts { - t.Errorf("Expected ExternalRequiredScripts to be false!") - } - if rh.request.ScriptsURL != "image:///tmp/scripts" { - t.Error("Expected ScriptsURL image:///tmp/scripts, but got %s", rh.request.ScriptsURL) - } - if rh.request.Location != "/tmp/src" { - t.Errorf("Expected Location /tmp/src, but got %s", rh.request.Location) - } -} - -func TestBuildErrorWriteDockerfile(t *testing.T) { - rh := testRequestHandler() - rh.fs.(*test.FakeFileSystem).WriteFileError = errors.New("WriteDockerfileError") - err := rh.build() - if err == nil || err.Error() != "WriteDockerfileError" { - t.Error("An error was expected for WriteDockerfile, but got different: %v", err) - } -} - -func TestBuildErrorCreateTarFile(t *testing.T) { - rh := testRequestHandler() - rh.tar.(*test.FakeTar).CreateTarError = errors.New("CreateTarError") - err := rh.build() - if err == nil || err.Error() != "CreateTarError" { - t.Error("An error was expected for CreateTar, but got different: %v", err) - } -} - -func TestBuildErrorOpenTarFile(t *testing.T) { - rh := testRequestHandler() - rh.fs.(*test.FakeFileSystem).OpenError = errors.New("OpenTarError") - err := rh.build() - if err == nil || err.Error() != "OpenTarError" { - t.Errorf("An error was expected for OpenTarFile, but got different: %v", err) - } -} - -func TestBuildErrorBuildImage(t *testing.T) { - rh := testRequestHandler() - rh.docker.(*test.FakeDocker).BuildImageError = errors.New("BuildImageError") - err := rh.build() - if err == nil || err.Error() != "BuildImageError" { - t.Errorf("An error was expected for BuildImage, but got different: %v", err) - } -} - -func TestCleanup(t *testing.T) { - rh := testRequestHandler() - rh.request.WorkingDir = "/working-dir" - preserve := []bool{false, true} - for _, p := range preserve { - rh.request.PreserveWorkingDir = p - rh.fs = &test.FakeFileSystem{} - rh.cleanup() - removedDir := rh.fs.(*test.FakeFileSystem).RemoveDirName - if p && removedDir != "" { - t.Errorf("Expected working directory to be preserved, but it was removed.") - } else if !p && removedDir == "" { - t.Errorf("Expected working directory to be removed, but it was preserved.") - } - } -} diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/tar.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/tar.go similarity index 98% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/tar.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/tar.go index 80d7cb010cfd..c6cf325eafa9 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/tar.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/tar.go @@ -12,7 +12,7 @@ import ( "github.com/golang/glog" - "github.com/openshift/source-to-image/pkg/sti/errors" + "github.com/openshift/source-to-image/pkg/errors" ) // defaultTimeout is the amount of time that the untar will wait for a tar @@ -38,7 +38,7 @@ type Tar interface { } // NewTar creates a new Tar -func NewTar() Tar { +func New() Tar { return &stiTar{ exclude: defaultExclusionPattern, timeout: defaultTimeout, diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/tar_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/tar_test.go similarity index 98% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/tar_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/tar_test.go index 3c1fcacea751..ca0654b2e1d6 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/tar/tar_test.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/tar/tar_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/openshift/source-to-image/pkg/sti/errors" + "github.com/openshift/source-to-image/pkg/errors" ) type fileDesc struct { @@ -122,7 +122,7 @@ func verifyTarFile(t *testing.T, filename string, files []fileDesc, links []link } func TestCreateTar(t *testing.T) { - th := NewTar() + th := New() tempDir, err := ioutil.TempDir("", "testtar") defer os.RemoveAll(tempDir) if err != nil { @@ -228,7 +228,7 @@ func TestExtractTarStream(t *testing.T) { defer os.RemoveAll(destDir) wg := sync.WaitGroup{} wg.Add(2) - th := NewTar() + th := New() go func() { defer wg.Done() @@ -252,7 +252,7 @@ func TestExtractTarStreamTimeout(t *testing.T) { defer os.RemoveAll(destDir) wg := sync.WaitGroup{} wg.Add(2) - th := NewTar() + th := New() th.(*stiTar).timeout = 10 * time.Millisecond go func() { defer wg.Done() diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/callback.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/callback.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/callback.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/callback.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/cmd.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/cmd.go similarity index 91% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/cmd.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/cmd.go index a355de74c4f9..d235d2ae53b5 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/cmd.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/cmd.go @@ -1,7 +1,7 @@ package test import ( - "github.com/openshift/source-to-image/pkg/sti/util" + "github.com/openshift/source-to-image/pkg/util" ) // FakeCmdRunner provider the fake command runner diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/docker.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/docker.go similarity index 85% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/docker.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/docker.go index a98b25714d2b..881955ac8738 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/docker.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/docker.go @@ -5,7 +5,7 @@ import ( dockerclient "github.com/fsouza/go-dockerclient" - "github.com/openshift/source-to-image/pkg/sti/docker" + "github.com/openshift/source-to-image/pkg/docker" ) // FakeDocker provides a fake docker interface @@ -26,6 +26,9 @@ type FakeDocker struct { GetImageIDImage string GetImageIDResult string GetImageIDError error + GetImageUserImage string + GetImageUserResult string + GetImageUserError error CommitContainerOpts docker.CommitContainerOptions CommitContainerResult string CommitContainerError error @@ -43,6 +46,10 @@ func (f *FakeDocker) IsImageInLocalRegistry(imageName string) (bool, error) { return f.LocalRegistryResult, f.LocalRegistryError } +func (f *FakeDocker) IsImageOnBuild(imageName string) bool { + return false +} + // RemoveContainer removes a fake Docker container func (f *FakeDocker) RemoveContainer(id string) error { f.RemoveContainerID = id @@ -78,6 +85,12 @@ func (f *FakeDocker) GetImageID(image string) (string, error) { return f.GetImageIDResult, f.GetImageIDError } +// GetImageUser returns a fake user +func (f *FakeDocker) GetImageUser(image string) (string, error) { + f.GetImageUserImage = image + return f.GetImageUserResult, f.GetImageUserError +} + // CommitContainer commits a fake Docker container func (f *FakeDocker) CommitContainer(opts docker.CommitContainerOptions) (string, error) { f.CommitContainerOpts = opts @@ -91,8 +104,8 @@ func (f *FakeDocker) RemoveImage(name string) error { } // PullImage pulls a fake docker image -func (f *FakeDocker) PullImage(imageName string) error { - return nil +func (f *FakeDocker) PullImage(imageName string) (*dockerclient.Image, error) { + return nil, nil } // CheckAndPull pulls a fake docker image diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/download.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/download.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/download.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/download.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/fs.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/fs.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/fs.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/fs.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/git.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/git.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/git.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/git.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/installer.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/installer.go similarity index 92% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/installer.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/installer.go index b165890cf2ec..2ac3f3cc44e7 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/installer.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/installer.go @@ -2,7 +2,7 @@ package test import ( "errors" - "github.com/openshift/source-to-image/pkg/sti/api" + "github.com/openshift/source-to-image/pkg/api" ) // FakeInstaller provides a fake installer diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/tar.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/tar.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/test/tar.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/test/tar.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/callback.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/callback.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/callback.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/callback.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/callback_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/callback_test.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/callback_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/callback_test.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/cmd.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/cmd.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/cmd.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/cmd.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/download.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/download.go similarity index 98% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/download.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/download.go index f14d73b3da7e..f385d72b8286 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/download.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/download.go @@ -9,7 +9,7 @@ import ( "github.com/golang/glog" - stierr "github.com/openshift/source-to-image/pkg/sti/errors" + stierr "github.com/openshift/source-to-image/pkg/errors" ) // Downloader downloads the specified URL to the target file location diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/download_test.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/download_test.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/download_test.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/download_test.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/fs.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/fs.go similarity index 96% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/fs.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/fs.go index a47310a26434..94e1f985cdae 100644 --- a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/util/fs.go +++ b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/util/fs.go @@ -8,7 +8,7 @@ import ( "github.com/golang/glog" - "github.com/openshift/source-to-image/pkg/sti/errors" + "github.com/openshift/source-to-image/pkg/errors" ) // FileSystem allows STI to work with the file system and @@ -79,6 +79,7 @@ func (h *fs) Copy(sourcePath string, targetPath string) error { targetPath = filepath.Join(targetPath, filepath.Base(sourcePath)) } + glog.V(5).Infof("cp -ad %s %s", sourcePath, targetPath) return h.runner.Run("cp", "-ad", sourcePath, targetPath) } diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/version/doc.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/version/doc.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/version/doc.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/version/doc.go diff --git a/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/version/version.go b/Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/version/version.go similarity index 100% rename from Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/sti/version/version.go rename to Godeps/_workspace/src/github.com/openshift/source-to-image/pkg/version/version.go diff --git a/pkg/build/api/types.go b/pkg/build/api/types.go index 2c247b6d9acd..dc4b3b463f25 100644 --- a/pkg/build/api/types.go +++ b/pkg/build/api/types.go @@ -83,6 +83,11 @@ const ( type BuildSource struct { Type BuildSourceType `json:"type,omitempty"` Git *GitBuildSource `json:"git,omitempty"` + + // Specify the sub-directory where the source code for the application exists. + // This allows to have buildable sources in directory other than root of + // repository. + ContextDir string `json:"contextDir,omitempty"` } // SourceRevision is the revision or commit information from the source for the build @@ -179,11 +184,6 @@ type CustomBuildStrategy struct { // DockerBuildStrategy defines input parameters specific to Docker build. type DockerBuildStrategy struct { - // ContextDir is used as the Docker build context. It is a path for a directory within the - // application source directory structure (as referenced in the BuildSource. See GitBuildSource - // for an example.) - ContextDir string `json:"contextDir,omitempty"` - // NoCache if set to true indicates that the docker build must be executed with the // --no-cache=true flag NoCache bool `json:"noCache,omitempty"` diff --git a/pkg/build/api/v1beta1/conversion.go b/pkg/build/api/v1beta1/conversion.go index 7f37f2c9fdcd..446e3e96279e 100644 --- a/pkg/build/api/v1beta1/conversion.go +++ b/pkg/build/api/v1beta1/conversion.go @@ -10,6 +10,45 @@ import ( func init() { api.Scheme.AddConversionFuncs( + // Move ContextDir in DockerBuildStrategy to BuildSource + func(in *newer.BuildParameters, out *BuildParameters, s conversion.Scope) error { + err := s.DefaultConvert(&in.Strategy, &out.Strategy, conversion.IgnoreMissingFields) + if err != nil { + return err + } + if out.Strategy.Type == DockerBuildStrategyType && in.Strategy.DockerStrategy != nil { + out.Strategy.DockerStrategy.ContextDir = in.Source.ContextDir + } + if err := s.Convert(&in.Source, &out.Source, 0); err != nil { + return err + } + if err := s.Convert(&in.Output, &out.Output, 0); err != nil { + return err + } + if err := s.Convert(&in.Revision, &out.Revision, 0); err != nil { + return err + } + return nil + }, + func(in *BuildParameters, out *newer.BuildParameters, s conversion.Scope) error { + err := s.DefaultConvert(&in.Strategy, &out.Strategy, conversion.IgnoreMissingFields) + if err != nil { + return err + } + if in.Strategy.Type == DockerBuildStrategyType && in.Strategy.DockerStrategy != nil { + out.Source.ContextDir = in.Strategy.DockerStrategy.ContextDir + } + if err := s.Convert(&in.Source, &out.Source, 0); err != nil { + return err + } + if err := s.Convert(&in.Output, &out.Output, 0); err != nil { + return err + } + if err := s.Convert(&in.Revision, &out.Revision, 0); err != nil { + return err + } + return nil + }, // Rename STIBuildStrategy.BuildImage to STIBuildStrategy.Image func(in *newer.STIBuildStrategy, out *STIBuildStrategy, s conversion.Scope) error { out.BuilderImage = in.Image diff --git a/pkg/build/api/v1beta1/types.go b/pkg/build/api/v1beta1/types.go index f6227e967064..577f9a05f17f 100644 --- a/pkg/build/api/v1beta1/types.go +++ b/pkg/build/api/v1beta1/types.go @@ -83,6 +83,11 @@ const ( type BuildSource struct { Type BuildSourceType `json:"type,omitempty"` Git *GitBuildSource `json:"git,omitempty"` + + // Specify the sub-directory where the source code for the application exists. + // This allows to have buildable sources in directory other than root of + // repository. + ContextDir string `json:"contextDir,omitempty"` } // SourceRevision is the revision or commit information from the source for the build @@ -170,9 +175,8 @@ type CustomBuildStrategy struct { // DockerBuildStrategy defines input parameters specific to Docker build. type DockerBuildStrategy struct { - // ContextDir is used as the Docker build context. It is a path for a directory within the - // application source directory structure (as referenced in the BuildSource. See GitBuildSource - // for an example.) + + // DEPRECATED: See the BuildSource type ContextDir string `json:"contextDir,omitempty"` // NoCache if set to true indicates that the docker build must be executed with the diff --git a/pkg/build/api/validation/validation_test.go b/pkg/build/api/validation/validation_test.go index 4827db06db41..1d762a020856 100644 --- a/pkg/build/api/validation/validation_test.go +++ b/pkg/build/api/validation/validation_test.go @@ -18,12 +18,11 @@ func TestBuildValdationSuccess(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "repository/data", @@ -45,12 +44,11 @@ func TestBuildValidationFailure(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "repository/data", @@ -72,12 +70,11 @@ func TestBuildConfigValidationSuccess(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{}, }, @@ -96,12 +93,11 @@ func TestBuildConfigValidationFailure(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "repository/data", @@ -122,12 +118,11 @@ func TestBuildConfigValidationOutputFailure(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "repository/data", @@ -183,12 +178,11 @@ func TestValidateBuildParameters(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "some/long/value/with/no/meaning", @@ -203,12 +197,11 @@ func TestValidateBuildParameters(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{ @@ -226,12 +219,11 @@ func TestValidateBuildParameters(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{}, @@ -246,12 +238,11 @@ func TestValidateBuildParameters(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{ @@ -269,12 +260,11 @@ func TestValidateBuildParameters(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://github.com/my/repository", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{ diff --git a/pkg/build/builder/docker.go b/pkg/build/builder/docker.go index 7a42c6457a43..7306a25a7038 100644 --- a/pkg/build/builder/docker.go +++ b/pkg/build/builder/docker.go @@ -13,8 +13,8 @@ import ( "github.com/fsouza/go-dockerclient" "github.com/openshift/origin/pkg/build/api" - "github.com/openshift/source-to-image/pkg/sti/git" - "github.com/openshift/source-to-image/pkg/sti/tar" + "github.com/openshift/source-to-image/pkg/git" + "github.com/openshift/source-to-image/pkg/tar" ) // urlCheckTimeout is the timeout used to check the source URL @@ -43,8 +43,8 @@ func NewDockerBuilder(dockerClient DockerClient, authCfg docker.AuthConfiguratio authPresent: authPresent, auth: authCfg, build: build, - git: git.NewGit(), - tar: tar.NewTar(), + git: git.New(), + tar: tar.New(), urlTimeout: urlCheckTimeout, } } @@ -136,8 +136,8 @@ func (d *DockerBuilder) fetchSource(dir string) error { // Also append the environment variables in the Dockerfile. func (d *DockerBuilder) addBuildParameters(dir string) error { dockerfilePath := filepath.Join(dir, "Dockerfile") - if d.build.Parameters.Strategy.DockerStrategy != nil && len(d.build.Parameters.Strategy.DockerStrategy.ContextDir) > 0 { - dockerfilePath = filepath.Join(dir, d.build.Parameters.Strategy.DockerStrategy.ContextDir, "Dockerfile") + if d.build.Parameters.Strategy.DockerStrategy != nil && len(d.build.Parameters.Source.ContextDir) > 0 { + dockerfilePath = filepath.Join(dir, d.build.Parameters.Source.ContextDir, "Dockerfile") } fileStat, err := os.Lstat(dockerfilePath) @@ -175,8 +175,8 @@ func (d *DockerBuilder) addBuildParameters(dir string) error { func (d *DockerBuilder) dockerBuild(dir string) error { var noCache bool if d.build.Parameters.Strategy.DockerStrategy != nil { - if d.build.Parameters.Strategy.DockerStrategy.ContextDir != "" { - dir = filepath.Join(dir, d.build.Parameters.Strategy.DockerStrategy.ContextDir) + if d.build.Parameters.Source.ContextDir != "" { + dir = filepath.Join(dir, d.build.Parameters.Source.ContextDir) } noCache = d.build.Parameters.Strategy.DockerStrategy.NoCache } diff --git a/pkg/build/builder/dockerutil.go b/pkg/build/builder/dockerutil.go index 318336acd7ea..ac363bdaf99e 100644 --- a/pkg/build/builder/dockerutil.go +++ b/pkg/build/builder/dockerutil.go @@ -4,7 +4,7 @@ import ( "os" "github.com/fsouza/go-dockerclient" - "github.com/openshift/source-to-image/pkg/sti/tar" + "github.com/openshift/source-to-image/pkg/tar" ) // DockerClient is an interface to the Docker client that contains diff --git a/pkg/build/builder/sti.go b/pkg/build/builder/sti.go index 60e33cbe5824..5fb8d2821d36 100644 --- a/pkg/build/builder/sti.go +++ b/pkg/build/builder/sti.go @@ -3,8 +3,8 @@ package builder import ( "github.com/fsouza/go-dockerclient" "github.com/golang/glog" - "github.com/openshift/source-to-image/pkg/sti" - stiapi "github.com/openshift/source-to-image/pkg/sti/api" + stiapi "github.com/openshift/source-to-image/pkg/api" + sti "github.com/openshift/source-to-image/pkg/build/strategies" "github.com/openshift/origin/pkg/build/api" ) @@ -36,6 +36,7 @@ func (s *STIBuilder) Build() error { BaseImage: s.build.Parameters.Strategy.STIStrategy.Image, DockerSocket: s.dockerSocket, Source: s.build.Parameters.Source.Git.URI, + ContextDir: s.build.Parameters.Source.ContextDir, Tag: tag, ScriptsURL: s.build.Parameters.Strategy.STIStrategy.Scripts, Environment: getBuildEnvVars(s.build), @@ -48,12 +49,12 @@ func (s *STIBuilder) Build() error { request.Ref = s.build.Parameters.Source.Git.Ref } glog.V(2).Infof("Creating a new STI builder with build request: %#v\n", request) - builder, err := sti.NewBuilder(request) + builder, err := sti.GetStrategy(request) if err != nil { return err } defer removeImage(s.dockerClient, tag) - if _, err = builder.Build(); err != nil { + if _, err = builder.Build(request); err != nil { return err } if len(s.build.Parameters.Output.DockerImageReference) != 0 { diff --git a/pkg/build/controller/controller_test.go b/pkg/build/controller/controller_test.go index 4b3ce128b541..6533a6151458 100644 --- a/pkg/build/controller/controller_test.go +++ b/pkg/build/controller/controller_test.go @@ -108,12 +108,11 @@ func mockBuildAndController(status buildapi.BuildStatus, output buildapi.BuildOu Git: &buildapi.GitBuildSource{ URI: "http://my.build.com/the/build/Dockerfile", }, + ContextDir: "contextimage", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "contextimage", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: output, }, diff --git a/pkg/build/controller/image_change_controller_test.go b/pkg/build/controller/image_change_controller_test.go index 54197a4935d3..d8a0b5f58858 100644 --- a/pkg/build/controller/image_change_controller_test.go +++ b/pkg/build/controller/image_change_controller_test.go @@ -39,8 +39,7 @@ func mockBuildConfig(baseImage, triggerImage, repoName, repoTag string) *buildap Strategy: buildapi.BuildStrategy{ Type: buildapi.DockerBuildStrategyType, DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "contextimage", - BaseImage: baseImage, + BaseImage: baseImage, }, }, }, diff --git a/pkg/build/controller/strategy/docker_test.go b/pkg/build/controller/strategy/docker_test.go index 4666c12cf3d6..888b8ed43837 100644 --- a/pkg/build/controller/strategy/docker_test.go +++ b/pkg/build/controller/strategy/docker_test.go @@ -66,10 +66,11 @@ func mockDockerBuild() *buildapi.Build { Git: &buildapi.GitBuildSource{ URI: "http://my.build.com/the/dockerbuild/Dockerfile", }, + ContextDir: "my/test/dir", }, Strategy: buildapi.BuildStrategy{ Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ContextDir: "my/test/dir"}, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "docker-registry/repository/dockerBuild", diff --git a/pkg/build/util/generate_test.go b/pkg/build/util/generate_test.go index 503557a0134c..d5f1582b8f96 100644 --- a/pkg/build/util/generate_test.go +++ b/pkg/build/util/generate_test.go @@ -484,8 +484,7 @@ func mockDockerStrategy() buildapi.BuildStrategy { return buildapi.BuildStrategy{ Type: buildapi.DockerBuildStrategyType, DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "/test/dir", - NoCache: true, + NoCache: true, }, } } diff --git a/pkg/cmd/cli/describe/describer.go b/pkg/cmd/cli/describe/describer.go index 577ff4ff942d..7c781def0d55 100644 --- a/pkg/cmd/cli/describe/describer.go +++ b/pkg/cmd/cli/describe/describer.go @@ -68,9 +68,6 @@ func (d *BuildDescriber) DescribeParameters(p buildapi.BuildParameters, out *tab formatString(out, "Strategy", p.Strategy.Type) switch p.Strategy.Type { case buildapi.DockerBuildStrategyType: - if p.Strategy.DockerStrategy != nil && len(p.Strategy.DockerStrategy.ContextDir) == 0 { - formatString(out, "Context Directory", p.Strategy.DockerStrategy.ContextDir) - } if p.Strategy.DockerStrategy != nil && p.Strategy.DockerStrategy.NoCache { formatString(out, "No Cache", "yes") } @@ -97,6 +94,9 @@ func (d *BuildDescriber) DescribeParameters(p buildapi.BuildParameters, out *tab if len(p.Source.Git.Ref) > 0 { formatString(out, "Ref", p.Source.Git.Ref) } + if len(p.Source.ContextDir) > 0 { + formatString(out, "ContextDir", p.Source.ContextDir) + } } if p.Output.To != nil { if p.Output.To.Namespace != "" { diff --git a/pkg/generate/app/app.go b/pkg/generate/app/app.go index 315425b8f90f..bbb7d1ac73eb 100644 --- a/pkg/generate/app/app.go +++ b/pkg/generate/app/app.go @@ -92,10 +92,11 @@ func nameFromGitURL(url *url.URL) (string, bool) { // SourceRef is a reference to a build source type SourceRef struct { - URL *url.URL - Ref string - Dir string - Name string + URL *url.URL + Ref string + Dir string + Name string + ContextDir string } // SuggestName returns a name derived from the source URL @@ -114,6 +115,7 @@ func (r *SourceRef) BuildSource() (*buildapi.BuildSource, []buildapi.BuildTrigge URI: r.URL.String(), Ref: r.Ref, }, + ContextDir: r.ContextDir, }, []buildapi.BuildTriggerPolicy{ { Type: buildapi.GithubWebHookBuildTriggerType, @@ -134,7 +136,6 @@ func (r *SourceRef) BuildSource() (*buildapi.BuildSource, []buildapi.BuildTrigge type BuildStrategyRef struct { IsDockerBuild bool Base *ImageRef - DockerContext string } // BuildStrategy builds an OpenShift BuildStrategy from a BuildStrategyRef @@ -143,11 +144,6 @@ func (s *BuildStrategyRef) BuildStrategy() (*buildapi.BuildStrategy, []buildapi. strategy := &buildapi.BuildStrategy{ Type: buildapi.DockerBuildStrategyType, } - if len(s.DockerContext) > 0 { - strategy.DockerStrategy = &buildapi.DockerBuildStrategy{ - ContextDir: s.DockerContext, - } - } return strategy, s.Base.BuildTriggers() } diff --git a/pkg/generate/app/sourcelookup.go b/pkg/generate/app/sourcelookup.go index 6677e9b78262..341b968a62e4 100644 --- a/pkg/generate/app/sourcelookup.go +++ b/pkg/generate/app/sourcelookup.go @@ -178,11 +178,11 @@ func StrategyAndSourceForRepository(repo *SourceRepository) (*BuildStrategyRef, } strategy := &BuildStrategyRef{ IsDockerBuild: repo.IsDockerBuild(), - DockerContext: "", } source := &SourceRef{ - URL: &repo.url, - Ref: repo.url.Fragment, + URL: &repo.url, + Ref: repo.url.Fragment, + ContextDir: "", } return strategy, source, nil } diff --git a/pkg/generate/generator/strategyref.go b/pkg/generate/generator/strategyref.go index 60af02a83275..38a6b141e81c 100644 --- a/pkg/generate/generator/strategyref.go +++ b/pkg/generate/generator/strategyref.go @@ -80,6 +80,10 @@ func (g *BuildStrategyRefGenerator) FromSourceRefAndDockerContext(srcRef app.Sou } } + if len(context) > 0 { + srcRef.ContextDir = context + } + // Look for Dockerfile in repository file, err := os.Open(filepath.Join(srcRef.Dir, context, "Dockerfile")) if err != nil { @@ -102,16 +106,15 @@ func (g *BuildStrategyRefGenerator) FromSourceRefAndDockerContext(srcRef app.Sou return nil, err } - return g.FromDockerContextAndParent(context, parentRef) + return g.FromDockerContextAndParent(parentRef) } // FromContextAndParent generates a build strategy ref from a context path and parent image name -func (g *BuildStrategyRefGenerator) FromDockerContextAndParent(context string, parentRef *app.ImageRef) (*app.BuildStrategyRef, error) { +func (g *BuildStrategyRefGenerator) FromDockerContextAndParent(parentRef *app.ImageRef) (*app.BuildStrategyRef, error) { return &app.BuildStrategyRef{ IsDockerBuild: true, Base: parentRef, - DockerContext: context, }, nil } diff --git a/pkg/generate/generator/strategyref_test.go b/pkg/generate/generator/strategyref_test.go index 7a5963c4da3e..dc4abb088c6b 100644 --- a/pkg/generate/generator/strategyref_test.go +++ b/pkg/generate/generator/strategyref_test.go @@ -54,7 +54,7 @@ func TestFromDockerContextAndParent(t *testing.T) { if err != nil { t.Errorf("Unexpected error: %v", err) } - strategy, err := g.FromDockerContextAndParent("docker/context", imgRef) + strategy, err := g.FromDockerContextAndParent(imgRef) if err != nil { t.Errorf("Unexpected error: %v", err) } diff --git a/test/integration/buildcfgclient_test.go b/test/integration/buildcfgclient_test.go index 665e2e32c5b9..e64d6854af04 100644 --- a/test/integration/buildcfgclient_test.go +++ b/test/integration/buildcfgclient_test.go @@ -133,12 +133,11 @@ func mockBuildConfig() *buildapi.BuildConfig { Git: &buildapi.GitBuildSource{ URI: "http://my.docker/build", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "namespace/builtimage", @@ -174,12 +173,11 @@ func TestBuildConfigClient(t *testing.T) { Git: &buildapi.GitBuildSource{ URI: "http://my.docker/build", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "namespace/builtimage", diff --git a/test/integration/buildclient_test.go b/test/integration/buildclient_test.go index b549fed4f730..c07fe4454ebd 100644 --- a/test/integration/buildclient_test.go +++ b/test/integration/buildclient_test.go @@ -129,12 +129,11 @@ func mockBuild() *buildapi.Build { Git: &buildapi.GitBuildSource{ URI: "http://my.docker/build", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ - Type: buildapi.DockerBuildStrategyType, - DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - }, + Type: buildapi.DockerBuildStrategyType, + DockerStrategy: &buildapi.DockerBuildStrategy{}, }, Output: buildapi.BuildOutput{ DockerImageReference: "namespace/builtimage", diff --git a/test/integration/imagechange_buildtrigger_test.go b/test/integration/imagechange_buildtrigger_test.go index bbf94e204a98..57cbf599ee22 100644 --- a/test/integration/imagechange_buildtrigger_test.go +++ b/test/integration/imagechange_buildtrigger_test.go @@ -92,12 +92,12 @@ func imageChangeBuildConfig() *buildapi.BuildConfig { Git: &buildapi.GitBuildSource{ URI: "git://github.com/openshift/ruby-hello-world.git", }, + ContextDir: "contextimage", }, Strategy: buildapi.BuildStrategy{ Type: buildapi.DockerBuildStrategyType, DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "contextimage", - BaseImage: "registry:8080/openshift/test-image", + BaseImage: "registry:8080/openshift/test-image", }, }, Output: buildapi.BuildOutput{ diff --git a/test/integration/webhookgithub_test.go b/test/integration/webhookgithub_test.go index c5a78dce8090..313bbec7e273 100644 --- a/test/integration/webhookgithub_test.go +++ b/test/integration/webhookgithub_test.go @@ -256,12 +256,12 @@ func mockBuildConfigParms(imageName, imageRepo, imageTag string) *buildapi.Build Git: &buildapi.GitBuildSource{ URI: "http://my.docker/build", }, + ContextDir: "context", }, Strategy: buildapi.BuildStrategy{ Type: buildapi.DockerBuildStrategyType, DockerStrategy: &buildapi.DockerBuildStrategy{ - ContextDir: "context", - BaseImage: imageName, + BaseImage: imageName, }, }, Output: buildapi.BuildOutput{