From 867cc26b34c04f1169b1254ac4a09a6b7021be8c Mon Sep 17 00:00:00 2001 From: Adam Kaplan Date: Mon, 7 May 2018 13:33:47 -0400 Subject: [PATCH] oc: add configmaps to build via cli --- contrib/completions/bash/oc | 2 + contrib/completions/zsh/oc | 2 + pkg/oc/cli/cmd/newbuild.go | 4 ++ pkg/oc/cli/describe/describer.go | 8 ++++ pkg/oc/generate/app/app.go | 2 + pkg/oc/generate/app/app_test.go | 20 ++++++++ pkg/oc/generate/app/sourcelookup.go | 46 +++++++++++++++++- pkg/oc/generate/app/sourcelookup_test.go | 60 ++++++++++++++++++++++++ pkg/oc/generate/cmd/newapp.go | 9 +++- test/integration/newapp_test.go | 37 +++++++++++---- 10 files changed, 179 insertions(+), 11 deletions(-) diff --git a/contrib/completions/bash/oc b/contrib/completions/bash/oc index a5b73b1e650a..6f924f1041a6 100644 --- a/contrib/completions/bash/oc +++ b/contrib/completions/bash/oc @@ -13466,6 +13466,8 @@ _oc_new-build() local_nonpersistent_flags+=("--binary") flags+=("--build-arg=") local_nonpersistent_flags+=("--build-arg=") + flags+=("--build-config-map=") + local_nonpersistent_flags+=("--build-config-map=") flags+=("--build-secret=") local_nonpersistent_flags+=("--build-secret=") flags+=("--code=") diff --git a/contrib/completions/zsh/oc b/contrib/completions/zsh/oc index 6bc5dd97aca7..c47021537a06 100644 --- a/contrib/completions/zsh/oc +++ b/contrib/completions/zsh/oc @@ -13608,6 +13608,8 @@ _oc_new-build() local_nonpersistent_flags+=("--binary") flags+=("--build-arg=") local_nonpersistent_flags+=("--build-arg=") + flags+=("--build-config-map=") + local_nonpersistent_flags+=("--build-config-map=") flags+=("--build-secret=") local_nonpersistent_flags+=("--build-secret=") flags+=("--code=") diff --git a/pkg/oc/cli/cmd/newbuild.go b/pkg/oc/cli/cmd/newbuild.go index 0b5ebc56bb35..c36d6b6d9c3b 100644 --- a/pkg/oc/cli/cmd/newbuild.go +++ b/pkg/oc/cli/cmd/newbuild.go @@ -60,6 +60,9 @@ var ( # Create a build config from a remote repository and inject the npmrc into a build %[1]s %[2]s https://github.com/openshift/ruby-hello-world --build-secret npmrc:.npmrc + # Create a build config from a remote repository and inject environment data into a build + %[1]s %[2]s https://github.com/openshift/ruby-hello-world --build-config-map env:config + # Create a build config that gets its input from a remote repository and another Docker image %[1]s %[2]s https://github.com/openshift/ruby-hello-world --source-image=openshift/jenkins-1-centos7 --source-image-path=/var/lib/jenkins:tmp`) @@ -112,6 +115,7 @@ func NewCmdNewBuild(name, baseName string, f *clientcmd.Factory, in io.Reader, o cmd.Flags().MarkDeprecated("image", "use --image-stream instead") cmd.Flags().StringSliceVarP(&config.ImageStreams, "image-stream", "i", config.ImageStreams, "Name of an image stream to to use as a builder.") cmd.Flags().StringSliceVar(&config.DockerImages, "docker-image", config.DockerImages, "Name of a Docker image to use as a builder.") + cmd.Flags().StringSliceVar(&config.ConfigMaps, "build-config-map", config.ConfigMaps, "ConfigMap and destination to use as an input for the build.") cmd.Flags().StringSliceVar(&config.Secrets, "build-secret", config.Secrets, "Secret and destination to use as an input for the build.") cmd.Flags().StringVar(&config.SourceSecret, "source-secret", "", "The name of an existing secret that should be used for cloning a private git repository.") cmd.Flags().StringVar(&config.PushSecret, "push-secret", "", "The name of an existing secret that should be used for pushing the output image.") diff --git a/pkg/oc/cli/describe/describer.go b/pkg/oc/cli/describe/describer.go index c03fed6593ec..6198fe4379d7 100644 --- a/pkg/oc/cli/describe/describer.go +++ b/pkg/oc/cli/describe/describer.go @@ -344,6 +344,14 @@ func describeCommonSpec(p buildapi.CommonSpec, out *tabwriter.Writer) { } formatString(out, "Build Secrets", strings.Join(result, ",")) } + if len(p.Source.ConfigMaps) > 0 { + result := []string{} + for _, c := range p.Source.ConfigMaps { + result = append(result, fmt.Sprintf("%s->%s", c.ConfigMap.Name, filepath.Clean(c.DestinationDir))) + } + formatString(out, "Build ConfigMaps", strings.Join(result, ",")) + } + if len(p.Source.Images) == 1 && len(p.Source.Images[0].Paths) == 1 { noneType = false image := p.Source.Images[0] diff --git a/pkg/oc/generate/app/app.go b/pkg/oc/generate/app/app.go index 9662e33bfdf1..71b2c117091a 100644 --- a/pkg/oc/generate/app/app.go +++ b/pkg/oc/generate/app/app.go @@ -112,6 +112,7 @@ type SourceRef struct { Name string ContextDir string Secrets []buildapi.SecretBuildSource + ConfigMaps []buildapi.ConfigMapBuildSource SourceImage *ImageRef ImageSourcePath string @@ -153,6 +154,7 @@ func (r *SourceRef) BuildSource() (*buildapi.BuildSource, []buildapi.BuildTrigge } source := &buildapi.BuildSource{} source.Secrets = r.Secrets + source.ConfigMaps = r.ConfigMaps if len(r.DockerfileContents) != 0 { source.Dockerfile = &r.DockerfileContents diff --git a/pkg/oc/generate/app/app_test.go b/pkg/oc/generate/app/app_test.go index beade1949fc4..54c6b8a250c2 100644 --- a/pkg/oc/generate/app/app_test.go +++ b/pkg/oc/generate/app/app_test.go @@ -96,6 +96,26 @@ func TestBuildConfigWithSecrets(t *testing.T) { } } +func TestBuildConfigWithConfigMaps(t *testing.T) { + url, err := git.Parse("https://github.com/openshift/origin.git") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + source := &SourceRef{URL: url, ConfigMaps: []buildapi.ConfigMapBuildSource{ + {ConfigMap: kapi.LocalObjectReference{Name: "foo"}, DestinationDir: "/var"}, + {ConfigMap: kapi.LocalObjectReference{Name: "bar"}}, + }} + build := &BuildRef{Source: source} + config, err := build.BuildConfig() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + configMaps := config.Spec.Source.ConfigMaps + if got := len(configMaps); got != 2 { + t.Errorf("expected 2 source configMaps in build config, got %d", got) + } +} + func TestBuildConfigBinaryWithImageSource(t *testing.T) { source := &SourceRef{ Name: "binarybuild", diff --git a/pkg/oc/generate/app/sourcelookup.go b/pkg/oc/generate/app/sourcelookup.go index a40cddccf4ad..43350c91a86f 100644 --- a/pkg/oc/generate/app/sourcelookup.go +++ b/pkg/oc/generate/app/sourcelookup.go @@ -101,6 +101,7 @@ type SourceRepository struct { remoteURL *s2igit.URL contextDir string secrets []buildapi.SecretBuildSource + configMaps []buildapi.ConfigMapBuildSource info *SourceRepositoryInfo sourceImage ComponentReference sourceImageFrom string @@ -357,7 +358,12 @@ func (r *SourceRepository) ContextDir() string { return r.contextDir } -// Secrets returns the secrets +// ConfigMaps returns the configMap build sources +func (r *SourceRepository) ConfigMaps() []buildapi.ConfigMapBuildSource { + return r.configMaps +} + +// Secrets returns the secret build sources func (r *SourceRepository) Secrets() []buildapi.SecretBuildSource { return r.secrets } @@ -390,6 +396,43 @@ func (r *SourceRepository) AddDockerfile(contents string) error { return nil } +// AddBuildConfigMaps adds the defined configMaps into the build. The input format for +// the secrets is ":". The destinationDir is +// optional and when not specified the default is the current working directory. +func (r *SourceRepository) AddBuildConfigMaps(configMaps []string) error { + injections := s2iapi.VolumeList{} + r.configMaps = []buildapi.ConfigMapBuildSource{} + for _, in := range configMaps { + if err := injections.Set(in); err != nil { + return err + } + } + configMapExists := func(name string) bool { + for _, c := range r.configMaps { + if c.ConfigMap.Name == name { + return true + } + } + return false + } + for _, in := range injections { + if r.GetStrategy() == generate.StrategyDocker && filepath.IsAbs(in.Destination) { + return fmt.Errorf("for the docker strategy, the configMap destination directory %q must be a relative path", in.Destination) + } + if len(validation.ValidateConfigMapName(in.Source, false)) != 0 { + return fmt.Errorf("the %q must be a valid configMap name", in.Source) + } + if configMapExists(in.Source) { + return fmt.Errorf("the %q configMap can be used just once", in.Source) + } + r.configMaps = append(r.configMaps, buildapi.ConfigMapBuildSource{ + ConfigMap: kapi.LocalObjectReference{Name: in.Source}, + DestinationDir: in.Destination, + }) + } + return nil +} + // AddBuildSecrets adds the defined secrets into a build. The input format for // the secrets is ":". The destinationDir is // optional and when not specified the default is the current working directory. @@ -540,6 +583,7 @@ func StrategyAndSourceForRepository(repo *SourceRepository, image *ImageRef) (*B source := &SourceRef{ Binary: repo.binary, Secrets: repo.secrets, + ConfigMaps: repo.configMaps, RequiresAuth: repo.requiresAuth, } diff --git a/pkg/oc/generate/app/sourcelookup_test.go b/pkg/oc/generate/app/sourcelookup_test.go index 2b853ff3fc59..17cdb697d9d5 100644 --- a/pkg/oc/generate/app/sourcelookup_test.go +++ b/pkg/oc/generate/app/sourcelookup_test.go @@ -65,3 +65,63 @@ func TestAddBuildSecrets(t *testing.T) { } } } + +func TestAddBuildConfigMaps(t *testing.T) { + type result struct{ name, dest string } + type tc struct { + in []string + expect []result + } + table := []tc{ + { + in: []string{"config1"}, + expect: []result{{name: "config1", dest: "."}}, + }, + { + in: []string{"config1", "config1"}, + }, + { + in: []string{"config1:/var/lib/foo"}, + expect: []result{{name: "config1", dest: "/var/lib/foo"}}, + }, + { + in: []string{"config1", "config2:/foo"}, + expect: []result{ + { + name: "config1", + dest: ".", + }, + { + name: "config2", + dest: "/foo", + }, + }, + }, + } + repo := &SourceRepository{} + repo.strategy = generate.StrategyDocker + if err := repo.AddBuildSecrets([]string{"config1:/absolute/path"}); err == nil { + t.Errorf("expected error for docker strategy when destDir is absolute") + } + for _, item := range table { + repo := &SourceRepository{} + err := repo.AddBuildSecrets(item.in) + if err != nil && len(item.expect) != 0 { + t.Errorf("unexpected error: %v", err) + continue + } + for _, expect := range item.expect { + got := repo.Secrets() + found := false + for _, s := range got { + if s.Secret.Name == expect.name && s.DestinationDir == expect.dest { + found = true + break + } + } + if !found { + t.Errorf("expected %+v secret in %#v not found", expect, got) + } + } + } +} diff --git a/pkg/oc/generate/cmd/newapp.go b/pkg/oc/generate/cmd/newapp.go index fc0e9df925b0..7bd4d4def557 100644 --- a/pkg/oc/generate/cmd/newapp.go +++ b/pkg/oc/generate/cmd/newapp.go @@ -83,7 +83,8 @@ type GenerationInputs struct { SourceImage string SourceImagePath string - Secrets []string + Secrets []string + ConfigMaps []string AllowMissingImageStreamTags bool @@ -438,11 +439,15 @@ func (c *AppConfig) buildPipelines(components app.ComponentReferences, environme switch { case refInput.ExpectToBuild: glog.V(4).Infof("will add %q secrets into a build for a source build of %q", strings.Join(c.Secrets, ","), refInput.Uses) - if err := refInput.Uses.AddBuildSecrets(c.Secrets); err != nil { return nil, fmt.Errorf("unable to add build secrets %q: %v", strings.Join(c.Secrets, ","), err) } + glog.V(4).Infof("will add %q configMaps into a build for a source build of %q", strings.Join(c.ConfigMaps, ","), refInput.Uses) + if err = refInput.Uses.AddBuildConfigMaps(c.ConfigMaps); err != nil { + return nil, fmt.Errorf("unable to add build configMaps %q: %v", strings.Join(c.Secrets, ","), err) + } + if refInput.Uses.GetStrategy() == generate.StrategyDocker { numDockerBuilds++ } diff --git a/test/integration/newapp_test.go b/test/integration/newapp_test.go index 3ebc4d015aa7..70e57c8e51a5 100644 --- a/test/integration/newapp_test.go +++ b/test/integration/newapp_test.go @@ -1355,6 +1355,7 @@ func TestNewAppRunBuilds(t *testing.T) { ContextDir: "openshift/pipeline", Git: &buildapi.GitBuildSource{URI: "https://github.com/openshift/nodejs-ex"}, Secrets: []buildapi.SecretBuildSource{}, + ConfigMaps: []buildapi.ConfigMapBuildSource{}, }) { return fmt.Errorf("invalid bc.Spec.Source, got %#v", bc.Spec.Source) } @@ -1395,6 +1396,7 @@ func TestNewAppRunBuilds(t *testing.T) { ContextDir: "openshift/pipeline", Git: &buildapi.GitBuildSource{URI: "https://github.com/openshift/nodejs-ex"}, Secrets: []buildapi.SecretBuildSource{}, + ConfigMaps: []buildapi.ConfigMapBuildSource{}, }) { return fmt.Errorf("invalid bc.Spec.Source, got %#v", bc.Spec.Source.Git) } @@ -1826,11 +1828,12 @@ func TestNewAppBuildConfigEnvVarsAndSecrets(t *testing.T) { okRouteClient := &routefake.Clientset{} tests := []struct { - name string - config *cmd.AppConfig - expected []kapi.EnvVar - expectedSecrets map[string]string - expectedErr error + name string + config *cmd.AppConfig + expected []kapi.EnvVar + expectedSecrets map[string]string + expectedConfigMaps map[string]string + expectedErr error }{ { name: "explicit environment variables for buildConfig and deploymentConfig", @@ -1843,6 +1846,7 @@ func TestNewAppBuildConfigEnvVarsAndSecrets(t *testing.T) { OutputDocker: true, Environment: []string{"BUILD_ENV_1=env_value_1", "BUILD_ENV_2=env_value_2"}, Secrets: []string{"foo:/var", "bar"}, + ConfigMaps: []string{"this:/tmp", "that"}, }, Resolvers: cmd.Resolvers{ @@ -1859,9 +1863,10 @@ func TestNewAppBuildConfigEnvVarsAndSecrets(t *testing.T) { RouteClient: okRouteClient.Route(), OriginNamespace: "default", }, - expected: []kapi.EnvVar{}, - expectedSecrets: map[string]string{"foo": "/var", "bar": "."}, - expectedErr: nil, + expected: []kapi.EnvVar{}, + expectedSecrets: map[string]string{"foo": "/var", "bar": "."}, + expectedConfigMaps: map[string]string{"this": "/tmp", "that": "."}, + expectedErr: nil, }, } @@ -1875,11 +1880,13 @@ func TestNewAppBuildConfigEnvVarsAndSecrets(t *testing.T) { } got := []kapi.EnvVar{} gotSecrets := []buildapi.SecretBuildSource{} + gotConfigMaps := []buildapi.ConfigMapBuildSource{} for _, obj := range res.List.Items { switch tp := obj.(type) { case *buildapi.BuildConfig: got = tp.Spec.Strategy.SourceStrategy.Env gotSecrets = tp.Spec.Source.Secrets + gotConfigMaps = tp.Spec.Source.ConfigMaps break } } @@ -1898,6 +1905,20 @@ func TestNewAppBuildConfigEnvVarsAndSecrets(t *testing.T) { } } + for configName, destDir := range test.expectedConfigMaps { + found := false + for _, got := range gotConfigMaps { + if got.ConfigMap.Name == configName && got.DestinationDir == destDir { + found = true + continue + } + } + if !found { + t.Errorf("expected configMap %q and destination %q, got %#v", configName, destDir, gotConfigMaps) + continue + } + } + if !reflect.DeepEqual(test.expected, got) { t.Errorf("%s: unexpected output. Expected: %#v, Got: %#v", test.name, test.expected, got) continue