From 9be6877c3d62a3f48e9bc0bbe882be5544ab0e26 Mon Sep 17 00:00:00 2001 From: Adam Kaplan Date: Thu, 7 Jun 2018 15:55:32 -0400 Subject: [PATCH] build: add ConfigMap build sources RFE/bug 1540978 --- pkg/build/apis/build/validation/validation.go | 20 +++ .../apis/build/validation/validation_test.go | 135 ++++++++++++++- pkg/build/builder/common.go | 46 +++++ pkg/build/builder/docker.go | 110 +++++++----- pkg/build/builder/docker_test.go | 2 +- pkg/build/builder/sti.go | 37 +++- pkg/build/builder/sti_test.go | 52 ++++++ pkg/build/controller/strategy/custom_test.go | 16 +- pkg/build/controller/strategy/docker.go | 1 + pkg/build/controller/strategy/docker_test.go | 24 ++- pkg/build/controller/strategy/sti.go | 1 + pkg/build/controller/strategy/sti_test.go | 26 ++- pkg/build/controller/strategy/util.go | 76 ++++++-- pkg/build/controller/strategy/util_test.go | 86 +++++++++- test/extended/builds/secrets.go | 58 ++++--- test/extended/testdata/bindata.go | 162 +++++++++++++++++- .../testdata/builds/build-secrets/Dockerfile | 6 +- .../s2i-binary-dir/.s2i/bin/assemble | 20 ++- .../build-secrets/s2i-binary-dir/.s2i/bin/run | 43 ++++- .../build-secrets/test-configmap-2.json | 13 ++ .../builds/build-secrets/test-configmap.json | 12 ++ .../build-secrets/test-docker-build.json | 13 ++ .../builds/build-secrets/test-s2i-build.json | 14 ++ 23 files changed, 844 insertions(+), 129 deletions(-) create mode 100644 test/extended/testdata/builds/build-secrets/test-configmap-2.json create mode 100644 test/extended/testdata/builds/build-secrets/test-configmap.json diff --git a/pkg/build/apis/build/validation/validation.go b/pkg/build/apis/build/validation/validation.go index 0c93d5bc2f93..fd35ff4f2d35 100644 --- a/pkg/build/apis/build/validation/validation.go +++ b/pkg/build/apis/build/validation/validation.go @@ -218,6 +218,7 @@ func validateSource(input *buildapi.BuildSource, isCustomStrategy, isDockerStrat } allErrs = append(allErrs, validateSecrets(input.Secrets, isDockerStrategy, fldPath.Child("secrets"))...) + allErrs = append(allErrs, validateConfigMaps(input.ConfigMaps, isDockerStrategy, fldPath.Child("configMaps"))...) allErrs = append(allErrs, validateSecretRef(input.SourceSecret, fldPath.Child("sourceSecret"))...) @@ -275,6 +276,25 @@ func validateGitSource(git *buildapi.GitBuildSource, fldPath *field.Path) field. return allErrs } +func validateConfigMaps(configs []buildapi.ConfigMapBuildSource, isDockerStrategy bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, c := range configs { + if len(c.ConfigMap.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("configMap"), "")) + } + if reasons := validation.ValidateConfigMapName(c.ConfigMap.Name, false); len(reasons) != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("configMap"), c, "must be valid configMap name")) + } + if strings.HasPrefix(path.Clean(c.DestinationDir), "..") { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("destinationDir"), c.DestinationDir, "destination dir cannot start with '..'")) + } + if isDockerStrategy && filepath.IsAbs(c.DestinationDir) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("destinationDir"), c.DestinationDir, "for the docker strategy the destinationDir has to be relative path")) + } + } + return allErrs +} + func validateSecrets(secrets []buildapi.SecretBuildSource, isDockerStrategy bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for i, s := range secrets { diff --git a/pkg/build/apis/build/validation/validation_test.go b/pkg/build/apis/build/validation/validation_test.go index ee0a3fc1258e..4f2977e163de 100644 --- a/pkg/build/apis/build/validation/validation_test.go +++ b/pkg/build/apis/build/validation/validation_test.go @@ -820,11 +820,14 @@ func TestValidateSource(t *testing.T) { dockerfile := "FROM something" invalidProxyAddress := "some!@#$%^&*()url" errorCases := []struct { - t field.ErrorType - path string - source *buildapi.BuildSource - ok bool - multiple bool + t field.ErrorType + path string + source *buildapi.BuildSource + ok bool + multiple bool + customStrategy bool + dockerStrategy bool + jenkinsStrategy bool }{ // 0 { @@ -1189,10 +1192,130 @@ func TestValidateSource(t *testing.T) { }, }, }, + // 25 - invalid configMap name + { + t: field.ErrorTypeInvalid, + path: "configMaps[0].configMap", + source: &buildapi.BuildSource{ + ConfigMaps: []buildapi.ConfigMapBuildSource{ + { + ConfigMap: kapi.LocalObjectReference{ + Name: "A@ba!dn#me", + }, + DestinationDir: "./some/relative/path", + }, + }, + }, + }, + // 26 - invalid relative path + { + t: field.ErrorTypeInvalid, + path: "configMaps[0].destinationDir", + source: &buildapi.BuildSource{ + ConfigMaps: []buildapi.ConfigMapBuildSource{ + { + ConfigMap: kapi.LocalObjectReference{ + Name: "good-secret-name", + }, + DestinationDir: "../bad/parent/path", + }, + }, + }, + }, + // 27 - invalid abs path with Docker strategy + { + t: field.ErrorTypeInvalid, + path: "configMaps[0].destinationDir", + dockerStrategy: true, + source: &buildapi.BuildSource{ + ConfigMaps: []buildapi.ConfigMapBuildSource{ + { + ConfigMap: kapi.LocalObjectReference{ + Name: "good-secret-name", + }, + DestinationDir: "/var/log/something", + }, + }, + }, + }, + // 28 - ok abs path without Docker strategy + { + ok: true, + source: &buildapi.BuildSource{ + ConfigMaps: []buildapi.ConfigMapBuildSource{ + { + ConfigMap: kapi.LocalObjectReference{ + Name: "good-secret-name", + }, + DestinationDir: "/var/log/something", + }, + }, + }, + }, + // 29 - invalid secret name + { + t: field.ErrorTypeInvalid, + path: "secrets[0].secret", + source: &buildapi.BuildSource{ + Secrets: []buildapi.SecretBuildSource{ + { + Secret: kapi.LocalObjectReference{ + Name: "A@ba!dn#me", + }, + DestinationDir: "./some/relative/path", + }, + }, + }, + }, + // 30 - invalid secret relative path + { + t: field.ErrorTypeInvalid, + path: "secrets[0].destinationDir", + source: &buildapi.BuildSource{ + Secrets: []buildapi.SecretBuildSource{ + { + Secret: kapi.LocalObjectReference{ + Name: "good-secret-name", + }, + DestinationDir: "../bad/parent/path", + }, + }, + }, + }, + // 31 - invalid abs path with Docker strategy + { + t: field.ErrorTypeInvalid, + path: "secrets[0].destinationDir", + dockerStrategy: true, + source: &buildapi.BuildSource{ + Secrets: []buildapi.SecretBuildSource{ + { + Secret: kapi.LocalObjectReference{ + Name: "good-secret-name", + }, + DestinationDir: "/var/log/something", + }, + }, + }, + }, + // 32 - ok abs path without Docker strategy + { + ok: true, + source: &buildapi.BuildSource{ + Secrets: []buildapi.SecretBuildSource{ + { + Secret: kapi.LocalObjectReference{ + Name: "good-secret-name", + }, + DestinationDir: "/var/log/something", + }, + }, + }, + }, } for i, tc := range errorCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - errors := validateSource(tc.source, false, false, false, nil) + errors := validateSource(tc.source, tc.customStrategy, tc.dockerStrategy, tc.jenkinsStrategy, nil) switch len(errors) { case 0: if !tc.ok { diff --git a/pkg/build/builder/common.go b/pkg/build/builder/common.go index d4aec577d0a0..1f67b4c59a48 100644 --- a/pkg/build/builder/common.go +++ b/pkg/build/builder/common.go @@ -14,6 +14,7 @@ import ( "strings" "time" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" @@ -66,6 +67,51 @@ type GitClient interface { GetInfo(location string) (*git.SourceInfo, []error) } +// localObjectBuildSource is a build source that is copied into a build from a Kubernetes +// key-value store, such as a `Secret` or `ConfigMap`. +type localObjectBuildSource interface { + // LocalObjectRef returns a reference to a local Kubernetes object by name. + LocalObjectRef() corev1.LocalObjectReference + // DestinationPath returns the directory where the files from the build source should be + // available for the build time. + // For the Source build strategy, these will be injected into a container + // where the assemble script runs. + // For the Docker build strategy, these will be copied into the build + // directory, where the Dockerfile is located, so users can ADD or COPY them + // during docker build. + DestinationPath() string + // IsSecret returns `true` if the build source is a `Secret` containing sensitive data. + IsSecret() bool +} + +type configMapSource buildapiv1.ConfigMapBuildSource + +func (c configMapSource) LocalObjectRef() corev1.LocalObjectReference { + return c.ConfigMap +} + +func (c configMapSource) DestinationPath() string { + return c.DestinationDir +} + +func (c configMapSource) IsSecret() bool { + return false +} + +type secretSource buildapiv1.SecretBuildSource + +func (s secretSource) LocalObjectRef() corev1.LocalObjectReference { + return s.Secret +} + +func (s secretSource) DestinationPath() string { + return s.DestinationDir +} + +func (s secretSource) IsSecret() bool { + return true +} + // buildInfo returns a slice of KeyValue pairs with build metadata to be // inserted into Docker images produced by build. func buildInfo(build *buildapiv1.Build, sourceInfo *git.SourceInfo) []KeyValue { diff --git a/pkg/build/builder/docker.go b/pkg/build/builder/docker.go index 9f27ad7f3913..6a4798e67e7a 100644 --- a/pkg/build/builder/docker.go +++ b/pkg/build/builder/docker.go @@ -137,7 +137,7 @@ func (d *DockerBuilder) Build() error { } startTime := metav1.Now() - err = d.dockerBuild(buildDir, buildTag, d.build.Spec.Source.Secrets) + err = d.dockerBuild(buildDir, buildTag) timing.RecordNewStep(ctx, buildapiv1.StageBuild, buildapiv1.StepDockerBuild, startTime, metav1.Now()) @@ -205,59 +205,82 @@ func (d *DockerBuilder) Build() error { return nil } +// copyConfigMaps copies all files from the directory where the configMap is +// mounted in the builder pod to a directory where the is the Dockerfile, so +// users can ADD or COPY the files inside their Dockerfile. +func (d *DockerBuilder) copyConfigMaps(configs []buildapiv1.ConfigMapBuildSource, targetDir string) error { + var err error + for _, c := range configs { + err = d.copyLocalObject(configMapSource(c), strategy.ConfigMapBuildSourceBaseMountPath, targetDir) + if err != nil { + return err + } + } + return nil +} + // copySecrets copies all files from the directory where the secret is // mounted in the builder pod to a directory where the is the Dockerfile, so // users can ADD or COPY the files inside their Dockerfile. -func (d *DockerBuilder) copySecrets(secrets []buildapiv1.SecretBuildSource, buildDir string) error { +func (d *DockerBuilder) copySecrets(secrets []buildapiv1.SecretBuildSource, targetDir string) error { + var err error for _, s := range secrets { - dstDir := filepath.Join(buildDir, s.DestinationDir) - if err := os.MkdirAll(dstDir, 0777); err != nil { + err = d.copyLocalObject(secretSource(s), strategy.SecretBuildSourceBaseMountPath, targetDir) + if err != nil { return err } - glog.V(3).Infof("Copying files from the build secret %q to %q", s.Secret.Name, dstDir) - - // Secrets contain nested directories and fairly baroque links. To prevent extra data being - // copied, perform the following steps: - // - // 1. Only top level files and directories within the secret directory are candidates - // 2. Any item starting with '..' is ignored - // 3. Destination directories are created first with 0777 - // 4. Use the '-L' option to cp to copy only contents. - // - srcDir := filepath.Join(strategy.SecretBuildSourceBaseMountPath, s.Secret.Name) - if err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if srcDir == path { - return nil - } + } + return nil +} - // skip any contents that begin with ".." - if strings.HasPrefix(filepath.Base(path), "..") { - if info.IsDir() { - return filepath.SkipDir - } - return nil - } +func (d *DockerBuilder) copyLocalObject(s localObjectBuildSource, sourceDir, targetDir string) error { + dstDir := filepath.Join(targetDir, s.DestinationPath()) + if err := os.MkdirAll(dstDir, 0777); err != nil { + return err + } + glog.V(3).Infof("Copying files from the build source %q to %q", s.LocalObjectRef().Name, dstDir) + + // Build sources contain nested directories and fairly baroque links. To prevent extra data being + // copied, perform the following steps: + // + // 1. Only top level files and directories within the secret directory are candidates + // 2. Any item starting with '..' is ignored + // 3. Destination directories are created first with 0777 + // 4. Use the '-L' option to cp to copy only contents. + // + srcDir := filepath.Join(sourceDir, s.LocalObjectRef().Name) + if err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if srcDir == path { + return nil + } - // ensure all directories are traversable + // skip any contents that begin with ".." + if strings.HasPrefix(filepath.Base(path), "..") { if info.IsDir() { - if err := os.MkdirAll(dstDir, 0777); err != nil { - return err - } + return filepath.SkipDir } - out, err := exec.Command("cp", "-vLRf", path, dstDir+"/").Output() - if err != nil { - glog.V(4).Infof("Secret %q failed to copy: %q", s.Secret.Name, string(out)) + return nil + } + + // ensure all directories are traversable + if info.IsDir() { + if err := os.MkdirAll(dstDir, 0777); err != nil { return err } - // See what is copied when debugging. - glog.V(5).Infof("Result of secret copy %s\n%s", s.Secret.Name, string(out)) - return nil - }); err != nil { + } + out, err := exec.Command("cp", "-vLRf", path, dstDir+"/").Output() + if err != nil { + glog.V(4).Infof("Build source %q failed to copy: %q", s.LocalObjectRef().Name, string(out)) return err } + // See what is copied when debugging. + glog.V(5).Infof("Result of build source copy %s\n%s", s.LocalObjectRef().Name, string(out)) + return nil + }); err != nil { + return err } return nil } @@ -282,7 +305,7 @@ func (d *DockerBuilder) setupPullSecret() (*docker.AuthConfigurations, error) { } // dockerBuild performs a docker build on the source that has been retrieved -func (d *DockerBuilder) dockerBuild(dir string, tag string, secrets []buildapiv1.SecretBuildSource) error { +func (d *DockerBuilder) dockerBuild(dir string, tag string) error { var noCache bool var forcePull bool var buildArgs []docker.BuildArg @@ -304,7 +327,10 @@ func (d *DockerBuilder) dockerBuild(dir string, tag string, secrets []buildapiv1 if err != nil { return err } - if err := d.copySecrets(secrets, dir); err != nil { + if err := d.copySecrets(d.build.Spec.Source.Secrets, dir); err != nil { + return err + } + if err = d.copyConfigMaps(d.build.Spec.Source.ConfigMaps, dir); err != nil { return err } diff --git a/pkg/build/builder/docker_test.go b/pkg/build/builder/docker_test.go index 310719404df9..8aa9bde799f2 100644 --- a/pkg/build/builder/docker_test.go +++ b/pkg/build/builder/docker_test.go @@ -285,7 +285,7 @@ func TestDockerfilePath(t *testing.T) { } // check that the docker client is called with the right Dockerfile parameter - if err = dockerBuilder.dockerBuild(buildDir, "", []buildapiv1.SecretBuildSource{}); err != nil { + if err = dockerBuilder.dockerBuild(buildDir, ""); err != nil { t.Errorf("failed to build: %v", err) continue } diff --git a/pkg/build/builder/sti.go b/pkg/build/builder/sti.go index 40fad46605dc..6088c5c71206 100644 --- a/pkg/build/builder/sti.go +++ b/pkg/build/builder/sti.go @@ -99,6 +99,33 @@ func newS2IBuilder(dockerClient DockerClient, dockerSocket string, buildsClient } } +// injectConfigMaps creates an s2i `VolumeSpec` from each provided `ConfigMapBuildSource` +func injectConfigMaps(configMaps []buildapiv1.ConfigMapBuildSource) []s2iapi.VolumeSpec { + vols := make([]s2iapi.VolumeSpec, len(configMaps)) + for i, c := range configMaps { + vols[i] = makeVolumeSpec(configMapSource(c), strategy.ConfigMapBuildSourceBaseMountPath) + } + return vols +} + +// injectSecrets creates an s2i `VolumeSpec` from each provided `SecretBuildSource` +func injectSecrets(secrets []buildapiv1.SecretBuildSource) []s2iapi.VolumeSpec { + vols := make([]s2iapi.VolumeSpec, len(secrets)) + for i, s := range secrets { + vols[i] = makeVolumeSpec(secretSource(s), strategy.SecretBuildSourceBaseMountPath) + } + return vols +} + +func makeVolumeSpec(src localObjectBuildSource, mountPath string) s2iapi.VolumeSpec { + glog.V(3).Infof("Injecting build source %q into a build into %q", src.LocalObjectRef().Name, filepath.Clean(src.DestinationPath())) + return s2iapi.VolumeSpec{ + Source: filepath.Join(mountPath, src.LocalObjectRef().Name), + Destination: src.DestinationPath(), + Keep: !src.IsSecret(), + } +} + // Build executes S2I build based on configured builder, S2I builder factory // and S2I config validator func (s *S2IBuilder) Build() error { @@ -133,14 +160,8 @@ func (s *S2IBuilder) Build() error { s2iSourceInfo = toS2ISourceInfo(sourceInfo) } injections := s2iapi.VolumeList{} - for _, s := range s.build.Spec.Source.Secrets { - glog.V(3).Infof("Injecting secret %q into a build into %q", s.Secret.Name, filepath.Clean(s.DestinationDir)) - secretSourcePath := filepath.Join(strategy.SecretBuildSourceBaseMountPath, s.Secret.Name) - injections = append(injections, s2iapi.VolumeSpec{ - Source: secretSourcePath, - Destination: s.DestinationDir, - }) - } + injections = append(injections, injectSecrets(s.build.Spec.Source.Secrets)...) + injections = append(injections, injectConfigMaps(s.build.Spec.Source.ConfigMaps)...) buildTag := randomBuildTag(s.build.Namespace, s.build.Name) scriptDownloadProxyConfig, err := scriptProxyConfig(s.build) diff --git a/pkg/build/builder/sti_test.go b/pkg/build/builder/sti_test.go index cd74e3617d9b..b98c8ab70800 100644 --- a/pkg/build/builder/sti_test.go +++ b/pkg/build/builder/sti_test.go @@ -159,6 +159,58 @@ func TestCopyToVolumeList(t *testing.T) { } } +func TestInjectSecrets(t *testing.T) { + secrets := []buildapiv1.SecretBuildSource{ + { + Secret: corev1.LocalObjectReference{ + Name: "secret1", + }, + DestinationDir: "/tmp", + }, + { + Secret: corev1.LocalObjectReference{ + Name: "secret2", + }, + }, + } + output := injectSecrets(secrets) + for i, v := range output { + secret := secrets[i] + if v.Keep { + t.Errorf("secret volume %s should not have been kept", secret.Secret.Name) + } + if secret.DestinationDir != v.Destination { + t.Errorf("expected secret %s to be mounted to %s, got %s", secret.Secret.Name, secret.DestinationDir, v.Destination) + } + } +} + +func TestInjectConfigMaps(t *testing.T) { + configMaps := []buildapiv1.ConfigMapBuildSource{ + { + ConfigMap: corev1.LocalObjectReference{ + Name: "configMap1", + }, + DestinationDir: "/tmp", + }, + { + ConfigMap: corev1.LocalObjectReference{ + Name: "configMap2", + }, + }, + } + output := injectConfigMaps(configMaps) + for i, v := range output { + configMap := configMaps[i] + if !v.Keep { + t.Errorf("configMap volume %s should have been kept", configMap.ConfigMap.Name) + } + if configMap.DestinationDir != v.Destination { + t.Errorf("expected configMap %s to be mounted to %s, got %s", configMap.ConfigMap.Name, configMap.DestinationDir, v.Destination) + } + } +} + func TestBuildEnvVars(t *testing.T) { // In order not complicate this function, the ordering of the expected // EnvironmentList structure and the one that is returned must match, diff --git a/pkg/build/controller/strategy/custom_test.go b/pkg/build/controller/strategy/custom_test.go index e3b89ff8d2b6..a7170368c7aa 100644 --- a/pkg/build/controller/strategy/custom_test.go +++ b/pkg/build/controller/strategy/custom_test.go @@ -60,8 +60,8 @@ func TestCustomCreateBuildPod(t *testing.T) { if actual.Spec.RestartPolicy != v1.RestartPolicyNever { t.Errorf("Expected never, got %#v", actual.Spec.RestartPolicy) } - if len(container.VolumeMounts) != 3 { - t.Fatalf("Expected 3 volumes in container, got %d", len(container.VolumeMounts)) + if len(container.VolumeMounts) != 4 { + t.Fatalf("Expected 4 volumes in container, got %d", len(container.VolumeMounts)) } if *actual.Spec.ActiveDeadlineSeconds != 60 { t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds) @@ -74,8 +74,8 @@ func TestCustomCreateBuildPod(t *testing.T) { if !kapihelper.Semantic.DeepEqual(container.Resources, util.CopyApiResourcesToV1Resources(&build.Spec.Resources)) { t.Fatalf("Expected actual=expected, %v != %v", container.Resources, build.Spec.Resources) } - if len(actual.Spec.Volumes) != 3 { - t.Fatalf("Expected 3 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 4 { + t.Fatalf("Expected 4 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } buildJSON, _ := runtime.Encode(legacyscheme.Codecs.LegacyCodec(buildapi.LegacySchemeGroupVersion), build) errorCases := map[int][]string{ @@ -212,6 +212,14 @@ func mockCustomBuild(forcePull, emptySource bool) *buildapi.Build { }, ExposeDockerSocket: true, ForcePull: forcePull, + Secrets: []buildapi.SecretSpec{ + { + SecretSource: kapi.LocalObjectReference{ + Name: "secret", + }, + MountPath: "secret", + }, + }, }, }, Output: buildapi.BuildOutput{ diff --git a/pkg/build/controller/strategy/docker.go b/pkg/build/controller/strategy/docker.go index 5875f2e7c13e..500aeda25757 100644 --- a/pkg/build/controller/strategy/docker.go +++ b/pkg/build/controller/strategy/docker.go @@ -168,5 +168,6 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildapi.Build) (*v1.Pod, e // location into the working directory. // TODO: consider moving this into the git-clone container and doing the secret copying there instead. setupInputSecrets(pod, &pod.Spec.Containers[0], build.Spec.Source.Secrets) + setupInputConfigMaps(pod, &pod.Spec.Containers[0], build.Spec.Source.ConfigMaps) return pod, nil } diff --git a/pkg/build/controller/strategy/docker_test.go b/pkg/build/controller/strategy/docker_test.go index 87d085ab820e..30784480ea47 100644 --- a/pkg/build/controller/strategy/docker_test.go +++ b/pkg/build/controller/strategy/docker_test.go @@ -65,8 +65,8 @@ func TestDockerCreateBuildPod(t *testing.T) { } // the pod has 6 volumes but the git source secret is not mounted into the main container. - if len(container.VolumeMounts) != 5 { - t.Fatalf("Expected 5 volumes in container, got %d", len(container.VolumeMounts)) + if len(container.VolumeMounts) != 7 { + t.Fatalf("Expected 7 volumes in container, got %d", len(container.VolumeMounts)) } if *actual.Spec.ActiveDeadlineSeconds != 60 { t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds) @@ -76,8 +76,8 @@ func TestDockerCreateBuildPod(t *testing.T) { t.Fatalf("Expected %s in VolumeMount[%d], got %s", expected, i, container.VolumeMounts[i].MountPath) } } - if len(actual.Spec.Volumes) != 6 { - t.Fatalf("Expected 6 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 8 { + t.Fatalf("Expected 8 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } if !kapihelper.Semantic.DeepEqual(container.Resources, util.CopyApiResourcesToV1Resources(&build.Spec.Resources)) { t.Fatalf("Expected actual=expected, %v != %v", container.Resources, build.Spec.Resources) @@ -149,6 +149,22 @@ func mockDockerBuild() *buildapi.Build { }, ContextDir: "my/test/dir", SourceSecret: &kapi.LocalObjectReference{Name: "secretFoo"}, + Secrets: []buildapi.SecretBuildSource{ + { + Secret: kapi.LocalObjectReference{ + Name: "super-secret", + }, + DestinationDir: "a/path/for/secret", + }, + }, + ConfigMaps: []buildapi.ConfigMapBuildSource{ + { + ConfigMap: kapi.LocalObjectReference{ + Name: "build-config", + }, + DestinationDir: "a/path/for/config", + }, + }, }, Strategy: buildapi.BuildStrategy{ DockerStrategy: &buildapi.DockerBuildStrategy{ diff --git a/pkg/build/controller/strategy/sti.go b/pkg/build/controller/strategy/sti.go index b41fe5a14e8d..b5e61ce153bc 100644 --- a/pkg/build/controller/strategy/sti.go +++ b/pkg/build/controller/strategy/sti.go @@ -188,6 +188,7 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildapi.Build) (*v1.Pod, e // location into the working directory. // TODO: consider moving this into the git-clone container and doing the secret copying there instead. setupInputSecrets(pod, &pod.Spec.Containers[0], build.Spec.Source.Secrets) + setupInputConfigMaps(pod, &pod.Spec.Containers[0], build.Spec.Source.ConfigMaps) return pod, nil } diff --git a/pkg/build/controller/strategy/sti_test.go b/pkg/build/controller/strategy/sti_test.go index 60f64f8826a6..a1272325fd25 100644 --- a/pkg/build/controller/strategy/sti_test.go +++ b/pkg/build/controller/strategy/sti_test.go @@ -103,17 +103,17 @@ func testSTICreateBuildPod(t *testing.T, rootAllowed bool) { t.Errorf("Expected environment keys:\n%v\ngot keys\n%v", expectedKeys, gotKeys) } - // the pod has 6 volumes but the git source secret is not mounted into the main container. - if len(container.VolumeMounts) != 5 { - t.Fatalf("Expected 5 volumes in container, got %d", len(container.VolumeMounts)) + // the pod has 8 volumes but the git source secret is not mounted into the main container. + if len(container.VolumeMounts) != 7 { + t.Fatalf("Expected 7 volumes in container, got %d", len(container.VolumeMounts)) } for i, expected := range []string{buildutil.BuildWorkDirMount, dockerSocketPath, "/var/run/crio/crio.sock", DockerPushSecretMountPath, DockerPullSecretMountPath} { 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) != 6 { - t.Fatalf("Expected 6 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 8 { + t.Fatalf("Expected 8 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } if *actual.Spec.ActiveDeadlineSeconds != 60 { t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds) @@ -208,6 +208,22 @@ func mockSTIBuild() *buildapi.Build { }, ContextDir: "foo", SourceSecret: &kapi.LocalObjectReference{Name: "fooSecret"}, + Secrets: []buildapi.SecretBuildSource{ + { + Secret: kapi.LocalObjectReference{ + Name: "secret", + }, + DestinationDir: "/tmp", + }, + }, + ConfigMaps: []buildapi.ConfigMapBuildSource{ + { + ConfigMap: kapi.LocalObjectReference{ + Name: "configmap", + }, + DestinationDir: "relpath", + }, + }, }, Strategy: buildapi.BuildStrategy{ SourceStrategy: &buildapi.SourceBuildStrategy{ diff --git a/pkg/build/controller/strategy/util.go b/pkg/build/controller/strategy/util.go index a705d87015e0..e2e5cee69ff8 100644 --- a/pkg/build/controller/strategy/util.go +++ b/pkg/build/controller/strategy/util.go @@ -13,6 +13,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kvalidation "k8s.io/apimachinery/pkg/util/validation" kapi "k8s.io/kubernetes/pkg/apis/core" + kext "k8s.io/kubernetes/pkg/apis/extensions" buildapiv1 "github.com/openshift/api/build/v1" "github.com/openshift/origin/pkg/api/apihelpers" @@ -25,10 +26,11 @@ const ( dockerSocketPath = "/var/run/docker.sock" sourceSecretMountPath = "/var/run/secrets/openshift.io/source" - DockerPushSecretMountPath = "/var/run/secrets/openshift.io/push" - DockerPullSecretMountPath = "/var/run/secrets/openshift.io/pull" - SecretBuildSourceBaseMountPath = "/var/run/secrets/openshift.io/build" - SourceImagePullSecretMountPath = "/var/run/secrets/openshift.io/source-image" + DockerPushSecretMountPath = "/var/run/secrets/openshift.io/push" + DockerPullSecretMountPath = "/var/run/secrets/openshift.io/pull" + ConfigMapBuildSourceBaseMountPath = "/var/run/configs/openshift.io/build" + SecretBuildSourceBaseMountPath = "/var/run/secrets/openshift.io/build" + SourceImagePullSecretMountPath = "/var/run/secrets/openshift.io/source-image" // ExtractImageContentContainer is the name of the container that will // pull down input images and extract their content for input to the build. @@ -122,10 +124,26 @@ func setupCrioSocket(pod *v1.Pod) { crioSocketVolumeMount) } +// mountConfigMapVolume is a helper method responsible for actual mounting configMap +// volumes into a pod. +func mountConfigMapVolume(pod *v1.Pod, container *v1.Container, configMapName, mountPath, volumeSuffix string) { + mountVolume(pod, container, configMapName, mountPath, volumeSuffix, kext.ConfigMap) +} + // mountSecretVolume is a helper method responsible for actual mounting secret // volumes into a pod. func mountSecretVolume(pod *v1.Pod, container *v1.Container, secretName, mountPath, volumeSuffix string) { - volumeName := apihelpers.GetName(secretName, volumeSuffix, kvalidation.DNS1123LabelMaxLength) + mountVolume(pod, container, secretName, mountPath, volumeSuffix, kext.Secret) +} + +// mountVolume is a helper method responsible for mounting volumes into a pod. +// The following file system types for the volume are supported: +// +// 1. ConfigMap +// 2. EmptyDir +// 3. Secret +func mountVolume(pod *v1.Pod, container *v1.Container, objName, mountPath, volumeSuffix string, fsType kext.FSType) { + volumeName := apihelpers.GetName(objName, volumeSuffix, kvalidation.DNS1123LabelMaxLength) // coerce from RFC1123 subdomain to RFC1123 label. volumeName = strings.Replace(volumeName, ".", "-", -1) @@ -139,15 +157,7 @@ func mountSecretVolume(pod *v1.Pod, container *v1.Container, secretName, mountPa } mode := int32(0600) if !volumeExists { - volume := v1.Volume{ - Name: volumeName, - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: secretName, - DefaultMode: &mode, - }, - }, - } + volume := makeVolume(volumeName, objName, mode, fsType) pod.Spec.Volumes = append(pod.Spec.Volumes, volume) } @@ -159,6 +169,35 @@ func mountSecretVolume(pod *v1.Pod, container *v1.Container, secretName, mountPa container.VolumeMounts = append(container.VolumeMounts, volumeMount) } +func makeVolume(volumeName, refName string, mode int32, fsType kext.FSType) v1.Volume { + // TODO: Add support for key-based paths for secrets and configMaps? + vol := v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{}, + } + switch fsType { + case kext.ConfigMap: + vol.VolumeSource.ConfigMap = &v1.ConfigMapVolumeSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: refName, + }, + DefaultMode: &mode, + } + case kext.EmptyDir: + vol.VolumeSource.EmptyDir = &v1.EmptyDirVolumeSource{} + case kext.Secret: + vol.VolumeSource.Secret = &v1.SecretVolumeSource{ + SecretName: refName, + DefaultMode: &mode, + } + default: + glog.V(3).Infof("File system %s is not supported for volumes. Using empty directory instead.", fsType) + vol.VolumeSource.EmptyDir = &v1.EmptyDirVolumeSource{} + } + + return vol +} + // setupDockerSecrets mounts Docker Registry secrets into Pod running the build, // allowing Docker to authenticate against private registries or Docker Hub. func setupDockerSecrets(pod *v1.Pod, container *v1.Container, pushSecret, pullSecret *kapi.LocalObjectReference, imageSources []buildapi.ImageSource) { @@ -205,6 +244,15 @@ func setupSourceSecrets(pod *v1.Pod, container *v1.Container, sourceSecret *kapi }...) } +// setupInputConfigMaps mounts the configMaps referenced by the ConfigMapBuildSource +// into a builder container. +func setupInputConfigMaps(pod *v1.Pod, container *v1.Container, configs []buildapi.ConfigMapBuildSource) { + for _, c := range configs { + mountConfigMapVolume(pod, container, c.ConfigMap.Name, filepath.Join(ConfigMapBuildSourceBaseMountPath, c.ConfigMap.Name), "build") + glog.V(3).Infof("%s will be used as a build config in %s", c.ConfigMap.Name, ConfigMapBuildSourceBaseMountPath) + } +} + // setupInputSecrets mounts the secrets referenced by the SecretBuildSource // into a builder container. func setupInputSecrets(pod *v1.Pod, container *v1.Container, secrets []buildapi.SecretBuildSource) { diff --git a/pkg/build/controller/strategy/util_test.go b/pkg/build/controller/strategy/util_test.go index 5b6756c80557..321ab167bbed 100644 --- a/pkg/build/controller/strategy/util_test.go +++ b/pkg/build/controller/strategy/util_test.go @@ -72,13 +72,7 @@ func isVolumeSourceEmpty(volumeSource v1.VolumeSource) bool { } func TestSetupDockerSecrets(t *testing.T) { - pod := v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - {}, - }, - }, - } + pod := emptyPod() pushSecret := &kapi.LocalObjectReference{ Name: "my.pushSecret.with.full.stops.and.longer.than.sixty.three.characters", @@ -135,6 +129,16 @@ func TestSetupDockerSecrets(t *testing.T) { } } +func emptyPod() v1.Pod { + return v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {}, + }, + }, + } +} + func TestCopyEnvVarSlice(t *testing.T) { s1 := []v1.EnvVar{{Name: "FOO", Value: "bar"}, {Name: "BAZ", Value: "qux"}} s2 := copyEnvVarSlice(s1) @@ -167,3 +171,71 @@ func checkAliasing(t *testing.T, pod *v1.Pod) { m[p] = true } } + +// TODO: Add tests for mounting secrets and configMaps +func TestMountConfigsAndSecrets(t *testing.T) { + pod := emptyPod() + configs := []buildapi.ConfigMapBuildSource{ + { + ConfigMap: kapi.LocalObjectReference{ + Name: "my.config.with.full.stops.and.longer.than.sixty.three.characters", + }, + DestinationDir: "./a/rel/path", + }, + { + ConfigMap: kapi.LocalObjectReference{ + Name: "config", + }, + DestinationDir: "some/path", + }, + } + secrets := []buildapi.SecretBuildSource{ + { + Secret: kapi.LocalObjectReference{ + Name: "my.secret.with.full.stops.and.longer.than.sixty.three.characters", + }, + DestinationDir: "./a/secret/path", + }, + { + Secret: kapi.LocalObjectReference{ + Name: "super-secret", + }, + DestinationDir: "secret/path", + }, + } + setupInputConfigMaps(&pod, &pod.Spec.Containers[0], configs) + setupInputSecrets(&pod, &pod.Spec.Containers[0], secrets) + if len(pod.Spec.Volumes) != 4 { + t.Fatalf("Expected 4 volumes, got: %#v", pod.Spec.Volumes) + } + + seenName := map[string]bool{} + for _, v := range pod.Spec.Volumes { + if seenName[v.Name] { + t.Errorf("Duplicate volume name %s", v.Name) + } + seenName[v.Name] = true + t.Logf("Saw volume %s", v.Name) + } + seenMount := map[string]bool{} + for _, m := range pod.Spec.Containers[0].VolumeMounts { + if seenMount[m.Name] { + t.Errorf("Duplicate volume mount name %s", m.Name) + } + seenMount[m.Name] = true + } + expectedVols := []string{ + "my-config-with-full-stops-and-longer-than-sixty--1935b127-build", + "config-build", + "my-secret-with-full-stops-and-longer-than-sixty--2f06b2d9-build", + "super-secret-build", + } + for _, vol := range expectedVols { + if !seenName[vol] { + t.Errorf("volume %s was not seen", vol) + } + if !seenMount[vol] { + t.Errorf("volumemount %s was not seen", vol) + } + } +} diff --git a/test/extended/builds/secrets.go b/test/extended/builds/secrets.go index c1422ab3a1d7..3d16e5c37bbe 100644 --- a/test/extended/builds/secrets.go +++ b/test/extended/builds/secrets.go @@ -14,15 +14,17 @@ import ( var _ = g.Describe("[Feature:Builds][Slow] can use build secrets", func() { defer g.GinkgoRecover() var ( - buildSecretBaseDir = exutil.FixturePath("testdata", "builds", "build-secrets") - secretsFixture = filepath.Join(buildSecretBaseDir, "test-secret.json") - secondSecretsFixture = filepath.Join(buildSecretBaseDir, "test-secret-2.json") - isFixture = filepath.Join(buildSecretBaseDir, "test-is.json") - dockerBuildFixture = filepath.Join(buildSecretBaseDir, "test-docker-build.json") - dockerBuildDockerfile = filepath.Join(buildSecretBaseDir, "Dockerfile") - sourceBuildFixture = filepath.Join(buildSecretBaseDir, "test-s2i-build.json") - sourceBuildBinDir = filepath.Join(buildSecretBaseDir, "s2i-binary-dir") - oc = exutil.NewCLI("build-secrets", exutil.KubeConfigPath()) + buildSecretBaseDir = exutil.FixturePath("testdata", "builds", "build-secrets") + secretsFixture = filepath.Join(buildSecretBaseDir, "test-secret.json") + secondSecretsFixture = filepath.Join(buildSecretBaseDir, "test-secret-2.json") + configMapFixture = filepath.Join(buildSecretBaseDir, "test-configmap.json") + secondConfigMapFixture = filepath.Join(buildSecretBaseDir, "test-configmap-2.json") + isFixture = filepath.Join(buildSecretBaseDir, "test-is.json") + dockerBuildFixture = filepath.Join(buildSecretBaseDir, "test-docker-build.json") + dockerBuildDockerfile = filepath.Join(buildSecretBaseDir, "Dockerfile") + sourceBuildFixture = filepath.Join(buildSecretBaseDir, "test-s2i-build.json") + sourceBuildBinDir = filepath.Join(buildSecretBaseDir, "s2i-binary-dir") + oc = exutil.NewCLI("build-secrets", exutil.KubeConfigPath()) ) g.Context("", func() { @@ -37,21 +39,27 @@ var _ = g.Describe("[Feature:Builds][Slow] can use build secrets", func() { } }) - g.Describe("build with secrets", func() { + g.Describe("build with secrets and configMaps", func() { - g.It("should contain secrets during the source strategy build", func() { - g.By("creating secret fixtures") + g.BeforeEach(func() { + g.By("creating secret and configMap fixtures") err := oc.Run("create").Args("-f", secretsFixture).Execute() o.Expect(err).NotTo(o.HaveOccurred()) err = oc.Run("create").Args("-f", secondSecretsFixture).Execute() o.Expect(err).NotTo(o.HaveOccurred()) + err = oc.Run("create").Args("-f", configMapFixture).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + err = oc.Run("create").Args("-f", secondConfigMapFixture).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) g.By("creating test image stream") err = oc.Run("create").Args("-f", isFixture).Execute() o.Expect(err).NotTo(o.HaveOccurred()) + }) + g.It("should contain secrets during the source strategy build", func() { g.By("creating test build config") - err = oc.Run("create").Args("-f", sourceBuildFixture).Execute() + err := oc.Run("create").Args("-f", sourceBuildFixture).Execute() o.Expect(err).NotTo(o.HaveOccurred()) g.By("starting the test source build") @@ -62,7 +70,7 @@ var _ = g.Describe("[Feature:Builds][Slow] can use build secrets", func() { image, err := exutil.GetDockerImageReference(oc.ImageClient().Image().ImageStreams(oc.Namespace()), "test", "latest") o.Expect(err).NotTo(o.HaveOccurred()) - g.By("verifying the build secrets were available during build and not present in the output image") + g.By("verifying the build sources were available during build and secrets were not present in the output image") pod := exutil.GetPodForContainer(kapiv1.Container{Name: "test", Image: image}) oc.KubeFramework().TestContainerOutput("test-build-secret-source", pod, 0, []string{ "testsecret/secret1=secret1", @@ -71,22 +79,18 @@ var _ = g.Describe("[Feature:Builds][Slow] can use build secrets", func() { "testsecret2/secret1=secret1", "testsecret2/secret2=secret2", "testsecret2/secret3=secret3", + "testconfig/foo=bar", + "testconfig/red=hat", + "testconfig/this=that", + "testconfig2/foo=bar", + "testconfig2/red=hat", + "testconfig2/this=that", }) }) g.It("should contain secrets during the docker strategy build", func() { - g.By("creating secret fixtures") - err := oc.Run("create").Args("-f", secretsFixture).Execute() - o.Expect(err).NotTo(o.HaveOccurred()) - err = oc.Run("create").Args("-f", secondSecretsFixture).Execute() - o.Expect(err).NotTo(o.HaveOccurred()) - - g.By("creating test image stream") - err = oc.Run("create").Args("-f", isFixture).Execute() - o.Expect(err).NotTo(o.HaveOccurred()) - g.By("creating test build config") - err = oc.Run("create").Args("-f", dockerBuildFixture).Execute() + err := oc.Run("create").Args("-f", dockerBuildFixture).Execute() o.Expect(err).NotTo(o.HaveOccurred()) g.By("starting the test docker build") @@ -97,11 +101,13 @@ var _ = g.Describe("[Feature:Builds][Slow] can use build secrets", func() { image, err := exutil.GetDockerImageReference(oc.ImageClient().Image().ImageStreams(oc.Namespace()), "test", "latest") o.Expect(err).NotTo(o.HaveOccurred()) - g.By("verifying the secrets are present in container output") + g.By("verifying the build sources are present in container output") pod := exutil.GetPodForContainer(kapiv1.Container{Name: "test", Image: image}) oc.KubeFramework().TestContainerOutput("test-build-secret-docker", pod, 0, []string{ "secret1=secret1", "relative-secret2=secret2", + "foo=bar", + "relative-this=that", }) }) }) diff --git a/test/extended/testdata/bindata.go b/test/extended/testdata/bindata.go index e98b13b7bde6..ec2dd9637f2d 100644 --- a/test/extended/testdata/bindata.go +++ b/test/extended/testdata/bindata.go @@ -20,6 +20,8 @@ // test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/run // test/extended/testdata/builds/build-secrets/s2i-binary-dir/Gemfile // test/extended/testdata/builds/build-secrets/s2i-binary-dir/config.ru +// test/extended/testdata/builds/build-secrets/test-configmap-2.json +// test/extended/testdata/builds/build-secrets/test-configmap.json // test/extended/testdata/builds/build-secrets/test-docker-build.json // test/extended/testdata/builds/build-secrets/test-is.json // test/extended/testdata/builds/build-secrets/test-s2i-build.json @@ -812,11 +814,15 @@ var _testExtendedTestdataBuildsBuildSecretsDockerfile = []byte(`FROM centos/ruby USER root ADD ./secret-dir /secrets COPY ./secret2 / +ADD ./config-dir /configs +COPY ./this / -# Create a shell script that will output secrets when the image is run +# Create a shell script that will output secrets and configMaps when the image is run RUN echo '#!/bin/sh' > /secret_report.sh RUN echo '(test -f /secrets/secret1 && echo -n "secret1=" && cat /secrets/secret1)' >> /secret_report.sh RUN echo '(test -f /secret2 && echo -n "relative-secret2=" && cat /secret2)' >> /secret_report.sh +RUN echo '(test -f /configs/foo && echo -n "foo=" && cat /configs/foo)' >> /secret_report.sh +RUN echo '(test -f /this && echo -n "relative-this=" && cat /this)' >> /secret_report.sh RUN chmod 755 /secret_report.sh CMD ["/bin/sh", "-c", "/secret_report.sh"] @@ -857,7 +863,26 @@ if [[ -f secret1 ]]; then else echo "Unable to locate testsecret2 fixture files" exit 2 -fi `) +fi + +mkdir -p "${HOME}/testconfig" +if [[ -f /tmp/configmap/foo ]]; then + # Copy three configMap entries defined in configmap1 fixture to directory + cp /tmp/configmap/* "${HOME}/testconfig" +else + echo "Unable to locate test-configmap fixture files" + exit 3 +fi + +mkdir -p "${HOME}/testconfig2" +if [[ -f configmap2/foo ]]; then + # Copy three configMap entries defined in configmap2 fixture to directory + cp configmap2/* "${HOME}/testconfig2" +else + echo "Unable to locate test-configmap-2 fixture files" + exit 4 +fi +`) func testExtendedTestdataBuildsBuildSecretsS2iBinaryDirS2iBinAssembleBytes() ([]byte, error) { return _testExtendedTestdataBuildsBuildSecretsS2iBinaryDirS2iBinAssemble, nil @@ -877,16 +902,51 @@ func testExtendedTestdataBuildsBuildSecretsS2iBinaryDirS2iBinAssemble() (*asset, var _testExtendedTestdataBuildsBuildSecretsS2iBinaryDirS2iBinRun = []byte(`#!/bin/bash # Ensure none of the build config inject secrets still exist in the file system -for s in /tmp/secret? secret?; do - if [[ -s "${s}" ]]; then - echo "Found secret file which should have been removed: ${s}" + +secrets=(secret1 secret2 secret3) +configMaps=(foo this red) + +function checkSecret() { + dir=$1 + file=$2 + if [[ -a "${dir}/${file}" ]]; then + if [[ -s "${dir}/${file}" ]]; then + echo "Found secret file which should have been truncated: ${dir}/${file}" + exit 1 + fi + else + echo "Secret file ${file} is missing from ${dir}." exit 1 fi +} + +function checkConfigMap() { + dir=$1 + file=$2 + if [[ -a "${dir}/${file}" ]]; then + if [[ ! -s "${dir}/${file}" ]]; then + echo "Found empty configMap file which should not have been truncated: ${dir}/${file}" + exit 1 + fi + else + echo "ConfigMap file ${file} is missing from ${dir}." + exit 1 + fi +} + +for s in ${secrets[@]}; do + checkSecret "/tmp" $s + checkSecret "." $s +done + +for c in ${configMaps[@]}; do + checkConfigMap "/tmp/configmap" $c + checkConfigMap "configmap2" $c done # Print out the secrets copied into the image during assemble cd "${HOME}" -for s in testsecret/* testsecret2/*; do +for s in testsecret/* testsecret2/* testconfig/* testconfig2/*; do echo -n "${s}=" && cat "${s}" done`) @@ -943,6 +1003,65 @@ func testExtendedTestdataBuildsBuildSecretsS2iBinaryDirConfigRu() (*asset, error return a, nil } +var _testExtendedTestdataBuildsBuildSecretsTestConfigmap2Json = []byte(`{ + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "test-configmap-2", + "creationTimestamp": null + }, + "data": { + "foo": "bar\n", + "this": "that\n", + "red": "hat\n" + } +} +`) + +func testExtendedTestdataBuildsBuildSecretsTestConfigmap2JsonBytes() ([]byte, error) { + return _testExtendedTestdataBuildsBuildSecretsTestConfigmap2Json, nil +} + +func testExtendedTestdataBuildsBuildSecretsTestConfigmap2Json() (*asset, error) { + bytes, err := testExtendedTestdataBuildsBuildSecretsTestConfigmap2JsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "test/extended/testdata/builds/build-secrets/test-configmap-2.json", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _testExtendedTestdataBuildsBuildSecretsTestConfigmapJson = []byte(`{ + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "test-configmap" + }, + "data": { + "foo": "bar\n", + "this": "that\n", + "red": "hat\n" + } +} +`) + +func testExtendedTestdataBuildsBuildSecretsTestConfigmapJsonBytes() ([]byte, error) { + return _testExtendedTestdataBuildsBuildSecretsTestConfigmapJson, nil +} + +func testExtendedTestdataBuildsBuildSecretsTestConfigmapJson() (*asset, error) { + bytes, err := testExtendedTestdataBuildsBuildSecretsTestConfigmapJsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "test/extended/testdata/builds/build-secrets/test-configmap.json", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _testExtendedTestdataBuildsBuildSecretsTestDockerBuildJson = []byte(`{ "kind": "BuildConfig", "apiVersion": "v1", @@ -969,6 +1088,19 @@ var _testExtendedTestdataBuildsBuildSecretsTestDockerBuildJson = []byte(`{ "name": "testsecret2" } } + ], + "configMaps": [ + { + "configMap": { + "name": "test-configmap" + }, + "destinationDir": "config-dir" + }, + { + "configMap": { + "name": "test-configmap-2" + } + } ] }, "strategy": { @@ -1060,6 +1192,20 @@ var _testExtendedTestdataBuildsBuildSecretsTestS2iBuildJson = []byte(`{ "name": "testsecret2" } } + ], + "configMaps": [ + { + "configMap": { + "name": "test-configmap" + }, + "destinationDir": "/tmp/configmap" + }, + { + "configMap": { + "name": "test-configmap-2" + }, + "destinationDir": "configmap2" + } ] }, "strategy": { @@ -33164,6 +33310,8 @@ var _bindata = map[string]func() (*asset, error){ "test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/run": testExtendedTestdataBuildsBuildSecretsS2iBinaryDirS2iBinRun, "test/extended/testdata/builds/build-secrets/s2i-binary-dir/Gemfile": testExtendedTestdataBuildsBuildSecretsS2iBinaryDirGemfile, "test/extended/testdata/builds/build-secrets/s2i-binary-dir/config.ru": testExtendedTestdataBuildsBuildSecretsS2iBinaryDirConfigRu, + "test/extended/testdata/builds/build-secrets/test-configmap-2.json": testExtendedTestdataBuildsBuildSecretsTestConfigmap2Json, + "test/extended/testdata/builds/build-secrets/test-configmap.json": testExtendedTestdataBuildsBuildSecretsTestConfigmapJson, "test/extended/testdata/builds/build-secrets/test-docker-build.json": testExtendedTestdataBuildsBuildSecretsTestDockerBuildJson, "test/extended/testdata/builds/build-secrets/test-is.json": testExtendedTestdataBuildsBuildSecretsTestIsJson, "test/extended/testdata/builds/build-secrets/test-s2i-build.json": testExtendedTestdataBuildsBuildSecretsTestS2iBuildJson, @@ -33614,6 +33762,8 @@ var _bintree = &bintree{nil, map[string]*bintree{ "Gemfile": &bintree{testExtendedTestdataBuildsBuildSecretsS2iBinaryDirGemfile, map[string]*bintree{}}, "config.ru": &bintree{testExtendedTestdataBuildsBuildSecretsS2iBinaryDirConfigRu, map[string]*bintree{}}, }}, + "test-configmap-2.json": &bintree{testExtendedTestdataBuildsBuildSecretsTestConfigmap2Json, map[string]*bintree{}}, + "test-configmap.json": &bintree{testExtendedTestdataBuildsBuildSecretsTestConfigmapJson, map[string]*bintree{}}, "test-docker-build.json": &bintree{testExtendedTestdataBuildsBuildSecretsTestDockerBuildJson, map[string]*bintree{}}, "test-is.json": &bintree{testExtendedTestdataBuildsBuildSecretsTestIsJson, map[string]*bintree{}}, "test-s2i-build.json": &bintree{testExtendedTestdataBuildsBuildSecretsTestS2iBuildJson, map[string]*bintree{}}, diff --git a/test/extended/testdata/builds/build-secrets/Dockerfile b/test/extended/testdata/builds/build-secrets/Dockerfile index 954e63b7805e..5dd19e8934f6 100644 --- a/test/extended/testdata/builds/build-secrets/Dockerfile +++ b/test/extended/testdata/builds/build-secrets/Dockerfile @@ -3,11 +3,15 @@ FROM centos/ruby-22-centos7 USER root ADD ./secret-dir /secrets COPY ./secret2 / +ADD ./config-dir /configs +COPY ./this / -# Create a shell script that will output secrets when the image is run +# Create a shell script that will output secrets and configMaps when the image is run RUN echo '#!/bin/sh' > /secret_report.sh RUN echo '(test -f /secrets/secret1 && echo -n "secret1=" && cat /secrets/secret1)' >> /secret_report.sh RUN echo '(test -f /secret2 && echo -n "relative-secret2=" && cat /secret2)' >> /secret_report.sh +RUN echo '(test -f /configs/foo && echo -n "foo=" && cat /configs/foo)' >> /secret_report.sh +RUN echo '(test -f /this && echo -n "relative-this=" && cat /this)' >> /secret_report.sh RUN chmod 755 /secret_report.sh CMD ["/bin/sh", "-c", "/secret_report.sh"] diff --git a/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/assemble b/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/assemble index e8b980a4f66c..2436b0dd3581 100755 --- a/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/assemble +++ b/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/assemble @@ -18,4 +18,22 @@ if [[ -f secret1 ]]; then else echo "Unable to locate testsecret2 fixture files" exit 2 -fi \ No newline at end of file +fi + +mkdir -p "${HOME}/testconfig" +if [[ -f /tmp/configmap/foo ]]; then + # Copy three configMap entries defined in configmap1 fixture to directory + cp /tmp/configmap/* "${HOME}/testconfig" +else + echo "Unable to locate test-configmap fixture files" + exit 3 +fi + +mkdir -p "${HOME}/testconfig2" +if [[ -f configmap2/foo ]]; then + # Copy three configMap entries defined in configmap2 fixture to directory + cp configmap2/* "${HOME}/testconfig2" +else + echo "Unable to locate test-configmap-2 fixture files" + exit 4 +fi diff --git a/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/run b/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/run index e75f25589c96..a945474f6d24 100755 --- a/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/run +++ b/test/extended/testdata/builds/build-secrets/s2i-binary-dir/.s2i/bin/run @@ -1,15 +1,50 @@ #!/bin/bash # Ensure none of the build config inject secrets still exist in the file system -for s in /tmp/secret? secret?; do - if [[ -s "${s}" ]]; then - echo "Found secret file which should have been removed: ${s}" + +secrets=(secret1 secret2 secret3) +configMaps=(foo this red) + +function checkSecret() { + dir=$1 + file=$2 + if [[ -a "${dir}/${file}" ]]; then + if [[ -s "${dir}/${file}" ]]; then + echo "Found secret file which should have been truncated: ${dir}/${file}" + exit 1 + fi + else + echo "Secret file ${file} is missing from ${dir}." + exit 1 + fi +} + +function checkConfigMap() { + dir=$1 + file=$2 + if [[ -a "${dir}/${file}" ]]; then + if [[ ! -s "${dir}/${file}" ]]; then + echo "Found empty configMap file which should not have been truncated: ${dir}/${file}" + exit 1 + fi + else + echo "ConfigMap file ${file} is missing from ${dir}." exit 1 fi +} + +for s in ${secrets[@]}; do + checkSecret "/tmp" $s + checkSecret "." $s +done + +for c in ${configMaps[@]}; do + checkConfigMap "/tmp/configmap" $c + checkConfigMap "configmap2" $c done # Print out the secrets copied into the image during assemble cd "${HOME}" -for s in testsecret/* testsecret2/*; do +for s in testsecret/* testsecret2/* testconfig/* testconfig2/*; do echo -n "${s}=" && cat "${s}" done \ No newline at end of file diff --git a/test/extended/testdata/builds/build-secrets/test-configmap-2.json b/test/extended/testdata/builds/build-secrets/test-configmap-2.json new file mode 100644 index 000000000000..faa6c34b7064 --- /dev/null +++ b/test/extended/testdata/builds/build-secrets/test-configmap-2.json @@ -0,0 +1,13 @@ +{ + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "test-configmap-2", + "creationTimestamp": null + }, + "data": { + "foo": "bar\n", + "this": "that\n", + "red": "hat\n" + } +} diff --git a/test/extended/testdata/builds/build-secrets/test-configmap.json b/test/extended/testdata/builds/build-secrets/test-configmap.json new file mode 100644 index 000000000000..9e847458a71d --- /dev/null +++ b/test/extended/testdata/builds/build-secrets/test-configmap.json @@ -0,0 +1,12 @@ +{ + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "test-configmap" + }, + "data": { + "foo": "bar\n", + "this": "that\n", + "red": "hat\n" + } +} diff --git a/test/extended/testdata/builds/build-secrets/test-docker-build.json b/test/extended/testdata/builds/build-secrets/test-docker-build.json index dd7fff5af0bd..459da9c164a5 100644 --- a/test/extended/testdata/builds/build-secrets/test-docker-build.json +++ b/test/extended/testdata/builds/build-secrets/test-docker-build.json @@ -24,6 +24,19 @@ "name": "testsecret2" } } + ], + "configMaps": [ + { + "configMap": { + "name": "test-configmap" + }, + "destinationDir": "config-dir" + }, + { + "configMap": { + "name": "test-configmap-2" + } + } ] }, "strategy": { diff --git a/test/extended/testdata/builds/build-secrets/test-s2i-build.json b/test/extended/testdata/builds/build-secrets/test-s2i-build.json index 8a5d61c588f8..4ab3aa659956 100644 --- a/test/extended/testdata/builds/build-secrets/test-s2i-build.json +++ b/test/extended/testdata/builds/build-secrets/test-s2i-build.json @@ -24,6 +24,20 @@ "name": "testsecret2" } } + ], + "configMaps": [ + { + "configMap": { + "name": "test-configmap" + }, + "destinationDir": "/tmp/configmap" + }, + { + "configMap": { + "name": "test-configmap-2" + }, + "destinationDir": "configmap2" + } ] }, "strategy": {