diff --git a/assets/app/scripts/controllers/create.js b/assets/app/scripts/controllers/create.js index 7efe6ad6e518..2edff31e80da 100644 --- a/assets/app/scripts/controllers/create.js +++ b/assets/app/scripts/controllers/create.js @@ -14,7 +14,7 @@ angular.module('openshiftConsole') $scope.templatesByTag = {}; - $scope.sourceURLPattern = /^(ftp|http|https|git):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; + $scope.sourceURLPattern = /^((ftp|http|https|git):\/\/(\w+:{0,1}\w*@)|git@)?([^\s@]+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; DataService.list("templates", $scope, function(templates) { $scope.projectTemplates = templates.by("metadata.name"); diff --git a/assets/test/spec/controllers/create.js b/assets/test/spec/controllers/create.js new file mode 100644 index 000000000000..f3312319e89e --- /dev/null +++ b/assets/test/spec/controllers/create.js @@ -0,0 +1,70 @@ +"use strict"; + +describe("CreateController", function(){ + var controller, form; + var $scope = { + projectTemplates: {}, + openshiftTemplates: {}, + templatesByTag: {} + }; + + beforeEach(function(){ + inject(function(_$controller_){ + // The injector unwraps the underscores (_) from around the parameter names when matching + controller = _$controller_("CreateController", { + $scope: $scope, + DataService: { + list: function(templates){} + } + }); + }); + }); + + + it("valid http URL", function(){ + var match = 'http://example.com/dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + it("valid http URL, without http part", function(){ + var match = 'example.com/dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + + it("valid http URL with user and password", function(){ + var match = 'http://user:pass@example.com/dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + it("valid http URL with port", function(){ + var match = 'http://example.com:8080/dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + it("valid http URL with port, user and password", function(){ + var match = 'http://user:pass@example.com:8080/dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + it("valid https URL", function(){ + var match = 'https://example.com/dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + it("valid ftp URL", function(){ + var match = 'ftp://example.com/dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + it("valid git+ssh URL", function(){ + var match = 'git@example.com:dir1/dir2'.match($scope.sourceURLPattern) + expect(match).not.toBeNull(); + }); + + it("invalid git+ssh URL (double @@)", function(){ + var match = 'git@@example.com:dir1/dir2'.match($scope.sourceURLPattern) + expect(match).toBeNull(); + }); + +}); diff --git a/pkg/assets/bindata.go b/pkg/assets/bindata.go index 0ed8dcdfd0c7..ce31be7a385d 100644 --- a/pkg/assets/bindata.go +++ b/pkg/assets/bindata.go @@ -16383,7 +16383,7 @@ namespace:"openshift" }), g.info("openshift image repos", a.openshiftImageRepos); }); } ]), angular.module("openshiftConsole").controller("CreateController", [ "$scope", "DataService", "$filter", "LabelFilter", "$location", "Logger", function(a, b, c, d, e, f) { -a.projectTemplates = {}, a.openshiftTemplates = {}, a.templatesByTag = {}, a.sourceURLPattern = /^(ftp|http|https|git):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, b.list("templates", a, function(b) { +a.projectTemplates = {}, a.openshiftTemplates = {}, a.templatesByTag = {}, a.sourceURLPattern = /^((ftp|http|https|git):\/\/(\w+:{0,1}\w*@)|git@)?([^\s@]+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, b.list("templates", a, function(b) { a.projectTemplates = b.by("metadata.name"), g(), f.info("project templates", a.projectTemplates); }), b.list("templates", { namespace:"openshift" diff --git a/pkg/build/api/types.go b/pkg/build/api/types.go index 01e4d910c58f..ffc4d31cd499 100644 --- a/pkg/build/api/types.go +++ b/pkg/build/api/types.go @@ -111,6 +111,13 @@ type BuildSource struct { // This allows to have buildable sources in directory other than root of // repository. ContextDir string `json:"contextDir,omitempty"` + + // SourceSecretName is the name of a Secret that would be used for setting + // up the authentication for cloning private repository. + // The secret contains valid credentials for remote repository, where the + // data's key represent the authentication method to be used and value is + // the base64 encoded credentials. Supported auth methods are: ssh-privatekey. + SourceSecretName string } // SourceRevision is the revision or commit information from the source for the build @@ -240,7 +247,7 @@ type BuildOutput struct { // a Docker image repository to push to. Failure to find the To will result in a build error. To *kapi.ObjectReference `json:"to,omitempty"` - // pushSecretName is the name of a Secret that would be used for setting + // PushSecretName is the name of a Secret that would be used for setting // up the authentication for executing the Docker push to authentication // enabled Docker Registry (or Docker Hub). PushSecretName string `json:"pushSecretName,omitempty"` diff --git a/pkg/build/api/v1beta1/types.go b/pkg/build/api/v1beta1/types.go index 0e410e201dad..ef2428b6ca12 100644 --- a/pkg/build/api/v1beta1/types.go +++ b/pkg/build/api/v1beta1/types.go @@ -111,6 +111,13 @@ type BuildSource struct { // This allows to have buildable sources in directory other than root of // repository. ContextDir string `json:"contextDir,omitempty"` + + // SourceSecretName is the name of a Secret that would be used for setting + // up the authentication for cloning private repository. + // The secret contains valid credentials for remote repository, where the + // secret's data key represent the authentication method to be used and value is + // the base64 encoded credentials. Supported auth methods are: ssh-privatekey. + SourceSecretName string `json:"sourceSecretName,omitempty" description:"supported auth methods are: ssh-privatekey` } // SourceRevision is the revision or commit information from the source for the build @@ -278,7 +285,7 @@ type BuildOutput struct { // a Docker image repository to push to. To *kapi.ObjectReference `json:"to,omitempty"` - // pushSecretName is the name of a Secret that would be used for setting + // PushSecretName is the name of a Secret that would be used for setting // up the authentication for executing the Docker push to authentication // enabled Docker Registry (or Docker Hub). PushSecretName string `json:"pushSecretName,omitempty"` diff --git a/pkg/build/api/v1beta3/types.go b/pkg/build/api/v1beta3/types.go index 0176c89ffffa..f92f5c0df7f5 100644 --- a/pkg/build/api/v1beta3/types.go +++ b/pkg/build/api/v1beta3/types.go @@ -117,6 +117,13 @@ type BuildSource struct { // This allows to have buildable sources in directory other than root of // repository. ContextDir string `json:"contextDir,omitempty"` + + // SourceSecretName is the name of a Secret that would be used for setting + // up the authentication for cloning private repository. + // The secret contains valid credentials for remote repository, where the + // data's key represent the authentication method to be used and value is + // the base64 encoded credentials. Supported auth methods are: ssh-privatekey. + SourceSecretName string `json:"sourceSecretName,omitempty" description:"supported auth methods are: ssh-privatekey` } // SourceRevision is the revision or commit information from the source for the build diff --git a/pkg/build/builder/cmd/builder.go b/pkg/build/builder/cmd/builder.go index 6a8262bb9b3a..6cc1cded1a26 100644 --- a/pkg/build/builder/cmd/builder.go +++ b/pkg/build/builder/cmd/builder.go @@ -1,7 +1,11 @@ package cmd import ( + "fmt" + "io/ioutil" "os" + "os/exec" + "path/filepath" "github.com/fsouza/go-dockerclient" "github.com/golang/glog" @@ -9,6 +13,7 @@ import ( "github.com/openshift/origin/pkg/build/api" bld "github.com/openshift/origin/pkg/build/builder" "github.com/openshift/origin/pkg/build/builder/cmd/dockercfg" + "github.com/openshift/origin/pkg/build/builder/cmd/scmauth" dockerutil "github.com/openshift/origin/pkg/cmd/util/docker" image "github.com/openshift/origin/pkg/image/api" ) @@ -19,6 +24,7 @@ const DockerCfgFile = ".dockercfg" type builder interface { Build() error } + type factoryFunc func( client bld.DockerClient, dockerSocket string, @@ -26,7 +32,9 @@ type factoryFunc func( authPresent bool, build *api.Build) builder -func run(builderFactory factoryFunc) { +// run is responsible for preparing environment for actual build. +// It accepts factoryFunc and an ordered array of SCMAuths. +func run(builderFactory factoryFunc, scmAuths []scmauth.SCMAuth) { client, endpoint, err := dockerutil.NewHelper().GetClient() if err != nil { glog.Fatalf("Error obtaining docker client: %v", err) @@ -57,6 +65,11 @@ func run(builderFactory factoryFunc) { dockercfg.PullAuthType, ) } + if len(build.Parameters.Source.SourceSecretName) > 0 { + if err := setupSourceSecret(build.Parameters.Source.SourceSecretName, scmAuths); err != nil { + glog.Fatalf("Cannot setup secret file for accessing private repository: %v", err) + } + } b := builderFactory(client, endpoint, authcfg, authPresent, &build) if err = b.Build(); err != nil { glog.Fatalf("Build error: %v", err) @@ -67,16 +80,83 @@ func run(builderFactory factoryFunc) { } +// fixSecretPermissions loweres access permissions to very low acceptable level +// TODO: this method should be removed as soon as secrets permissions are fixed upstream +func fixSecretPermissions() error { + secretTmpDir, err := ioutil.TempDir("", "tmpsecret") + if err != nil { + return err + } + cmd := exec.Command("cp", "-R", ".", secretTmpDir) + cmd.Dir = os.Getenv("SOURCE_SECRET_PATH") + if err := cmd.Run(); err != nil { + return err + } + secretFiles, err := ioutil.ReadDir(secretTmpDir) + if err != nil { + return err + } + for _, file := range secretFiles { + if err := os.Chmod(filepath.Join(secretTmpDir, file.Name()), 0600); err != nil { + return err + } + } + os.Setenv("SOURCE_SECRET_PATH", secretTmpDir) + return nil +} + +func setupSourceSecret(sourceSecretName string, scmAuths []scmauth.SCMAuth) error { + fixSecretPermissions() + sourceSecretDir := os.Getenv("SOURCE_SECRET_PATH") + files, err := ioutil.ReadDir(sourceSecretDir) + if err != nil { + return err + } + found := false + +SCMAuthLoop: + for _, scmAuth := range scmAuths { + glog.V(3).Infof("Checking for '%s' in secret '%s'", scmAuth.Name(), sourceSecretName) + for _, file := range files { + if file.Name() == scmAuth.Name() { + glog.Infof("Using '%s' from secret '%s'", scmAuth.Name(), sourceSecretName) + if err := scmAuth.Setup(sourceSecretDir); err != nil { + glog.Warningf("Error setting up '%s': %v", scmAuth.Name(), err) + continue + } + found = true + break SCMAuthLoop + } + } + } + if !found { + return fmt.Errorf("the provided secret '%s' did not have any of the supported keys %v", + sourceSecretName, getSCMNames(scmAuths)) + } + return nil +} + +func getSCMNames(scmAuths []scmauth.SCMAuth) string { + var names string + for _, scmAuth := range scmAuths { + if len(names) > 0 { + names += ", " + } + names += scmAuth.Name() + } + return names +} + // RunDockerBuild creates a docker builder and runs its build func RunDockerBuild() { run(func(client bld.DockerClient, sock string, auth docker.AuthConfiguration, present bool, build *api.Build) builder { return bld.NewDockerBuilder(client, auth, present, build) - }) + }, []scmauth.SCMAuth{&scmauth.SSHPrivateKey{}}) } // RunSTIBuild creates a STI builder and runs its build func RunSTIBuild() { run(func(client bld.DockerClient, sock string, auth docker.AuthConfiguration, present bool, build *api.Build) builder { return bld.NewSTIBuilder(client, sock, auth, present, build) - }) + }, []scmauth.SCMAuth{&scmauth.SSHPrivateKey{}}) } diff --git a/pkg/build/builder/cmd/dockercfg/cfg.go b/pkg/build/builder/cmd/dockercfg/cfg.go index bb199c25b342..12c9113323b3 100644 --- a/pkg/build/builder/cmd/dockercfg/cfg.go +++ b/pkg/build/builder/cmd/dockercfg/cfg.go @@ -47,7 +47,7 @@ func (h *Helper) GetDockerAuth(registry, authType string) (docker.AuthConfigurat dockercfgPath = getDockercfgFile(pathForAuthType) } if _, err := os.Stat(dockercfgPath); err != nil { - glog.V(3).Infof("%s: %v", dockercfgPath, err) + glog.V(3).Infof("Problem accessing %s: %v", dockercfgPath, err) return authCfg, false } cfg, err := readDockercfg(dockercfgPath) diff --git a/pkg/build/builder/cmd/scmauth/scmauth.go b/pkg/build/builder/cmd/scmauth/scmauth.go new file mode 100644 index 000000000000..ed76fc4ea6e1 --- /dev/null +++ b/pkg/build/builder/cmd/scmauth/scmauth.go @@ -0,0 +1,9 @@ +package scmauth + +// SCMAuth is an interface implemented by different authentication providers +// which are responsible for setting up the credentials to be used when accessing +// private repository. +type SCMAuth interface { + Name() string + Setup(baseDir string) error +} diff --git a/pkg/build/builder/cmd/scmauth/sshkey.go b/pkg/build/builder/cmd/scmauth/sshkey.go new file mode 100644 index 000000000000..270ea7ec25f9 --- /dev/null +++ b/pkg/build/builder/cmd/scmauth/sshkey.go @@ -0,0 +1,40 @@ +package scmauth + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +const SSHPrivateKeyMethodName = "ssh-privatekey" + +// SSHPrivateKey implements SCMAuth interface for using SSH private keys. +type SSHPrivateKey struct{} + +// Setup creates a wrapper script for SSH command to be able to use the provided +// SSH key while accessing private repository. +func (_ SSHPrivateKey) Setup(baseDir string) error { + script, err := ioutil.TempFile("", "gitssh") + if err != nil { + return err + } + defer script.Close() + if err := script.Chmod(0711); err != nil { + return err + } + if _, err := script.WriteString("#!/bin/sh\nssh -i " + + filepath.Join(baseDir, SSHPrivateKeyMethodName) + + " -o StrictHostKeyChecking=false \"$@\"\n"); err != nil { + return err + } + // set environment variable to tell git to use the SSH wrapper + if err := os.Setenv("GIT_SSH", script.Name()); err != nil { + return err + } + return nil +} + +// Name returns the name of this auth method. +func (_ SSHPrivateKey) Name() string { + return SSHPrivateKeyMethodName +} diff --git a/pkg/build/controller/strategy/custom.go b/pkg/build/controller/strategy/custom.go index d6970dc62081..689574f46604 100644 --- a/pkg/build/controller/strategy/custom.go +++ b/pkg/build/controller/strategy/custom.go @@ -79,5 +79,6 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod, setupDockerSocket(pod) setupDockerSecrets(pod, build.Parameters.Output.PushSecretName) } + setupSourceSecrets(pod, build.Parameters.Source.SourceSecretName) return pod, nil } diff --git a/pkg/build/controller/strategy/custom_test.go b/pkg/build/controller/strategy/custom_test.go index 52c245e19519..74688b1f5d6e 100644 --- a/pkg/build/controller/strategy/custom_test.go +++ b/pkg/build/controller/strategy/custom_test.go @@ -53,21 +53,19 @@ func TestCustomCreateBuildPod(t *testing.T) { if actual.Spec.RestartPolicy != kapi.RestartPolicyNever { t.Errorf("Expected never, got %#v", actual.Spec.RestartPolicy) } - if len(container.VolumeMounts) != 2 { - t.Fatalf("Expected 2 volumes in container, got %d", len(container.VolumeMounts)) + if len(container.VolumeMounts) != 3 { + t.Fatalf("Expected 3 volumes in container, got %d", len(container.VolumeMounts)) } - if container.VolumeMounts[0].MountPath != dockerSocketPath { - t.Fatalf("Expected %s in first VolumeMount, got %s", dockerSocketPath, container.VolumeMounts[0].MountPath) - } - if container.VolumeMounts[1].MountPath != dockerPushSecretMountPath { - t.Fatalf("Expected %s in first VolumeMount, got %s", dockerPushSecretMountPath, container.VolumeMounts[1].MountPath) + for i, expected := range []string{dockerSocketPath, dockerPushSecretMountPath, sourceSecretMountPath} { + if container.VolumeMounts[i].MountPath != expected { + t.Fatalf("Expected %s in VolumeMount[%d], got %s", expected, i, container.VolumeMounts[i].MountPath) + } } if !kapi.Semantic.DeepEqual(container.Resources, expected.Parameters.Resources) { t.Fatalf("Expected actual=expected, %v != %v", container.Resources, expected.Parameters.Resources) } - - if len(actual.Spec.Volumes) != 2 { - t.Fatalf("Expected 2 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 3 { + t.Fatalf("Expected 3 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } buildJSON, _ := v1beta1.Codec.Encode(expected) errorCases := map[int][]string{ @@ -110,6 +108,7 @@ func mockCustomBuild() *buildapi.Build { URI: "http://my.build.com/the/dockerbuild/Dockerfile", Ref: "master", }, + SourceSecretName: "secretFoo", }, Strategy: buildapi.BuildStrategy{ Type: buildapi.CustomBuildStrategyType, diff --git a/pkg/build/controller/strategy/docker.go b/pkg/build/controller/strategy/docker.go index 3923d19e3db1..94f89cc22f6a 100644 --- a/pkg/build/controller/strategy/docker.go +++ b/pkg/build/controller/strategy/docker.go @@ -55,5 +55,6 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod, setupDockerSocket(pod) setupDockerSecrets(pod, build.Parameters.Output.PushSecretName) + setupSourceSecrets(pod, build.Parameters.Source.SourceSecretName) return pod, nil } diff --git a/pkg/build/controller/strategy/docker_test.go b/pkg/build/controller/strategy/docker_test.go index 58baf00ebe5f..a80f14c1f28a 100644 --- a/pkg/build/controller/strategy/docker_test.go +++ b/pkg/build/controller/strategy/docker_test.go @@ -48,20 +48,19 @@ func TestDockerCreateBuildPod(t *testing.T) { if actual.Spec.RestartPolicy != kapi.RestartPolicyNever { t.Errorf("Expected never, got %#v", actual.Spec.RestartPolicy) } - if len(container.VolumeMounts) != 2 { - t.Fatalf("Expected 2 volumes in container, got %d", len(container.VolumeMounts)) + if len(container.Env) != 4 { + t.Fatalf("Expected 4 elements in Env table, got %d", len(container.Env)) } - if container.VolumeMounts[0].MountPath != dockerSocketPath { - t.Fatalf("Expected %s in first VolumeMount, got %s", dockerSocketPath, container.VolumeMounts[0].MountPath) + if len(container.VolumeMounts) != 3 { + t.Fatalf("Expected 3 volumes in container, got %d", len(container.VolumeMounts)) } - if container.VolumeMounts[1].MountPath != dockerPushSecretMountPath { - t.Fatalf("Expected %s in first VolumeMount, got %s", dockerPushSecretMountPath, container.VolumeMounts[1].MountPath) - } - if len(actual.Spec.Volumes) != 2 { - t.Fatalf("Expected 2 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + for i, expected := range []string{dockerSocketPath, dockerPushSecretMountPath, sourceSecretMountPath} { + if container.VolumeMounts[i].MountPath != expected { + t.Fatalf("Expected %s in VolumeMount[%d], got %s", expected, i, container.VolumeMounts[i].MountPath) + } } - if len(container.Env) != 3 { - t.Fatalf("Expected 3 elements in Env table, got %d", len(container.Env)) + if len(actual.Spec.Volumes) != 3 { + t.Fatalf("Expected 3 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } if !kapi.Semantic.DeepEqual(container.Resources, expected.Parameters.Resources) { t.Fatalf("Expected actual=expected, %v != %v", container.Resources, expected.Parameters.Resources) @@ -93,7 +92,8 @@ func mockDockerBuild() *buildapi.Build { Git: &buildapi.GitBuildSource{ URI: "http://my.build.com/the/dockerbuild/Dockerfile", }, - ContextDir: "my/test/dir", + ContextDir: "my/test/dir", + SourceSecretName: "secretFoo", }, Strategy: buildapi.BuildStrategy{ Type: buildapi.DockerBuildStrategyType, diff --git a/pkg/build/controller/strategy/sti.go b/pkg/build/controller/strategy/sti.go index b0a931fd1d3d..b4adf5c86809 100644 --- a/pkg/build/controller/strategy/sti.go +++ b/pkg/build/controller/strategy/sti.go @@ -78,5 +78,6 @@ func (bs *STIBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod, er setupDockerSocket(pod) setupDockerSecrets(pod, build.Parameters.Output.PushSecretName) + setupSourceSecrets(pod, build.Parameters.Source.SourceSecretName) return pod, nil } diff --git a/pkg/build/controller/strategy/sti_test.go b/pkg/build/controller/strategy/sti_test.go index 178b189734cb..c69dbad8badb 100644 --- a/pkg/build/controller/strategy/sti_test.go +++ b/pkg/build/controller/strategy/sti_test.go @@ -56,21 +56,20 @@ func TestSTICreateBuildPod(t *testing.T) { t.Errorf("Expected never, got %#v", actual.Spec.RestartPolicy) } // strategy ENV is not copied into the container environment, so only - // expect 5 not 6 values. - if len(container.Env) != 5 { - t.Fatalf("Expected 5 elements in Env table, got %d", len(container.Env)) + // expect 6 not 7 values. + if len(container.Env) != 6 { + t.Fatalf("Expected 6 elements in Env table, got %d", len(container.Env)) } - if len(container.VolumeMounts) != 2 { - t.Fatalf("Expected 2 volumes in container, got %d", len(container.VolumeMounts)) + if len(container.VolumeMounts) != 3 { + t.Fatalf("Expected 3 volumes in container, got %d", len(container.VolumeMounts)) } - if container.VolumeMounts[0].MountPath != dockerSocketPath { - t.Fatalf("Expected %s in first VolumeMount, got %s", dockerSocketPath, container.VolumeMounts[0].MountPath) - } - if container.VolumeMounts[1].MountPath != dockerPushSecretMountPath { - t.Fatalf("Expected %s in first VolumeMount, got %s", dockerPushSecretMountPath, container.VolumeMounts[1].MountPath) + for i, expected := range []string{dockerSocketPath, dockerPushSecretMountPath, sourceSecretMountPath} { + if container.VolumeMounts[i].MountPath != expected { + t.Fatalf("Expected %s in VolumeMount[%d], got %s", expected, i, container.VolumeMounts[i].MountPath) + } } - if len(actual.Spec.Volumes) != 2 { - t.Fatalf("Expected 2 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 3 { + t.Fatalf("Expected 3 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } if !kapi.Semantic.DeepEqual(container.Resources, expected.Parameters.Resources) { t.Fatalf("Expected actual=expected, %v != %v", container.Resources, expected.Parameters.Resources) @@ -111,6 +110,7 @@ func mockSTIBuild() *buildapi.Build { Git: &buildapi.GitBuildSource{ URI: "http://my.build.com/the/stibuild/Dockerfile", }, + SourceSecretName: "fooSecret", }, Strategy: buildapi.BuildStrategy{ Type: buildapi.STIBuildStrategyType, diff --git a/pkg/build/controller/strategy/util.go b/pkg/build/controller/strategy/util.go index a1c9a8c2e41a..5e97f958c0ca 100644 --- a/pkg/build/controller/strategy/util.go +++ b/pkg/build/controller/strategy/util.go @@ -17,6 +17,7 @@ const ( // TODO: The pull secrets is the same as push secret for now. // This will be replaced using Service Account. dockerPullSecretMountPath = dockerPushSecretMountPath + sourceSecretMountPath = "/var/run/secrets/source" ) var whitelistEnvVarNames = []string{"BUILD_LOGLEVEL"} @@ -71,36 +72,55 @@ func setupBuildEnv(build *buildapi.Build, pod *kapi.Pod) error { return nil } -// setupDockerSecrets mounts Docker Registry secrets into Pod running the build, -// allowing Docker to authenticate against private registries or Docker Hub. -func setupDockerSecrets(pod *kapi.Pod, pushSecret string) { - if len(pushSecret) == 0 { - return - } - +// mountSecretVolume is a helper method responsible for actual mounting secret +// volumes into a pod. +func mountSecretVolume(pod *kapi.Pod, secretName, mountPath string) { volume := kapi.Volume{ - Name: pushSecret, + Name: secretName, VolumeSource: kapi.VolumeSource{ Secret: &kapi.SecretVolumeSource{ - SecretName: pushSecret, + SecretName: secretName, }, }, } volumeMount := kapi.VolumeMount{ - Name: pushSecret, - MountPath: dockerPushSecretMountPath, + Name: secretName, + MountPath: mountPath, ReadOnly: true, } - - glog.V(3).Infof("Installed %s as docker push secret in Pod %s", volumeMount.MountPath, pod.Name) pod.Spec.Volumes = append(pod.Spec.Volumes, volume) pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, volumeMount) +} + +// setupDockerSecrets mounts Docker Registry secrets into Pod running the build, +// allowing Docker to authenticate against private registries or Docker Hub. +func setupDockerSecrets(pod *kapi.Pod, pushSecret string) { + if len(pushSecret) == 0 { + return + } + + mountSecretVolume(pod, pushSecret, dockerPushSecretMountPath) + glog.V(3).Infof("Installed %s as docker push secret in Pod %s", dockerPushSecretMountPath, pod.Name) pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{ {Name: "PUSH_DOCKERCFG_PATH", Value: filepath.Join(dockerPushSecretMountPath, "dockercfg")}, {Name: "PULL_DOCKERCFG_PATH", Value: filepath.Join(dockerPullSecretMountPath, "dockercfg")}, }...) } +// setupSourceSecrets mounts SSH key used for accesing private SCM to clone +// application source code during build. +func setupSourceSecrets(pod *kapi.Pod, sourceSecret string) { + if len(sourceSecret) == 0 { + return + } + + mountSecretVolume(pod, sourceSecret, sourceSecretMountPath) + glog.V(3).Infof("Installed source secrets in %s, in Pod %s", sourceSecretMountPath, pod.Name) + pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{ + {Name: "SOURCE_SECRET_PATH", Value: sourceSecretMountPath}, + }...) +} + // mergeTrustedEnvWithoutDuplicates merges two environment lists without having // duplicate items in the output list. Only trusted environment variables // will be merged. diff --git a/pkg/build/controller/strategy/util_test.go b/pkg/build/controller/strategy/util_test.go index 031171c547bf..c5339ac57f8b 100644 --- a/pkg/build/controller/strategy/util_test.go +++ b/pkg/build/controller/strategy/util_test.go @@ -57,7 +57,6 @@ func TestSetupDockerSocketHostSocket(t *testing.T) { } func isVolumeSourceEmpty(volumeSource kapi.VolumeSource) bool { - if volumeSource.EmptyDir == nil && volumeSource.HostPath == nil && volumeSource.GCEPersistentDisk == nil && diff --git a/pkg/cmd/cli/describe/describer.go b/pkg/cmd/cli/describe/describer.go index 277abe162fb3..fa8db64c37c5 100644 --- a/pkg/cmd/cli/describe/describer.go +++ b/pkg/cmd/cli/describe/describer.go @@ -173,6 +173,9 @@ func describeBuildParameters(p buildapi.BuildParameters, out *tabwriter.Writer) if len(p.Source.ContextDir) > 0 { formatString(out, "ContextDir", p.Source.ContextDir) } + if len(p.Source.SourceSecretName) > 0 { + formatString(out, "Source Secret", p.Source.SourceSecretName) + } } if p.Output.To != nil { tag := imageapi.DefaultImageTag diff --git a/pkg/generate/app/cmd/newapp_test.go b/pkg/generate/app/cmd/newapp_test.go index e5478ea7fc65..dfdef130a216 100644 --- a/pkg/generate/app/cmd/newapp_test.go +++ b/pkg/generate/app/cmd/newapp_test.go @@ -98,6 +98,15 @@ func TestValidate(t *testing.T) { env: map[string]string{}, parms: map[string]string{}, }, + "git+sshsourcerepos": { + cfg: AppConfig{ + SourceRepositories: []string{"git@github.com:openshift/ruby-hello-world.git"}, + }, + componentValues: []string{}, + sourceRepoLocations: []string{"git@github.com:openshift/ruby-hello-world.git"}, + env: map[string]string{}, + parms: map[string]string{}, + }, "envs": { cfg: AppConfig{ Environment: util.StringList{"one=first", "two=second", "three=third"}, diff --git a/pkg/generate/app/sourcelookup.go b/pkg/generate/app/sourcelookup.go index a266ecb6e29b..33f1fb205f4b 100644 --- a/pkg/generate/app/sourcelookup.go +++ b/pkg/generate/app/sourcelookup.go @@ -46,6 +46,7 @@ func NewSourceRepository(s string) (*SourceRepository, error) { if err != nil { return nil, err } + return &SourceRepository{ location: s, url: *location, diff --git a/pkg/generate/git/git.go b/pkg/generate/git/git.go index 8cb080f64f2c..d2d87b01d6a4 100644 --- a/pkg/generate/git/git.go +++ b/pkg/generate/git/git.go @@ -17,41 +17,30 @@ import ( // - file // - git func ParseRepository(s string) (*url.URL, error) { - switch { - case strings.HasPrefix(s, "git@"): - base := "git://" + strings.TrimPrefix(s, "git@") - url, err := url.Parse(base) - if err != nil { - return nil, err - } - return url, nil + uri, err := url.Parse(s) + if err != nil { + return nil, err + } - default: - uri, err := url.Parse(s) + if uri.Scheme == "" && !strings.HasPrefix(uri.Path, "git@") { + path := s + ref := "" + segments := strings.SplitN(path, "#", 2) + if len(segments) == 2 { + path, ref = segments[0], segments[1] + } + path, err := filepath.Abs(path) if err != nil { return nil, err } - - if uri.Scheme == "" { - path := s - ref := "" - segments := strings.SplitN(path, "#", 2) - if len(segments) == 2 { - path, ref = segments[0], segments[1] - } - path, err := filepath.Abs(path) - if err != nil { - return nil, err - } - uri = &url.URL{ - Scheme: "file", - Path: path, - Fragment: ref, - } + uri = &url.URL{ + Scheme: "file", + Path: path, + Fragment: ref, } - - return uri, nil } + + return uri, nil } // NameFromRepositoryURL suggests a name for a repository URL based on the last