From ba5b2f5525d74df36c5e6f639f632833d9eaedd2 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Mon, 12 Oct 2020 19:42:08 -0700 Subject: [PATCH] ci-operator: allow users to inject the oc cli into test steps Signed-off-by: Steve Kuznetsov --- pkg/api/types.go | 3 ++ pkg/steps/multi_stage.go | 55 +++++++++++++++++++++++++- test/e2e/multi-stage.sh | 1 + test/e2e/multi-stage/dependencies.yaml | 11 ++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index ff8a4548bb9..f9b0d010e5e 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -600,6 +600,9 @@ type LiteralTestStep struct { // flag is set to true in MultiStageTestConfiguration. This option is // applicable to `post` steps. OptionalOnSuccess *bool `json:"optional_on_success,omitempty"` + // Cli is the (optional) name of the release from which the `oc` binary + // will be injected into this step. + Cli string `json:"cli,omitempty"` } // StepParameter is a variable set by the test, with an optional default. diff --git a/pkg/steps/multi_stage.go b/pkg/steps/multi_stage.go index 3746eb63f26..1c2b035fe0a 100644 --- a/pkg/steps/multi_stage.go +++ b/pkg/steps/multi_stage.go @@ -34,6 +34,12 @@ const ( SecretMountEnv = "SHARED_DIR" // ClusterProfileMountEnv is the env we use to expose the cluster profile dir ClusterProfileMountEnv = "CLUSTER_PROFILE_DIR" + // CliMountPath is where we mount the cli in a pod + CliMountPath = "/cli" + // CliEnv if the env we use to expose the path to the cli + CliEnv = "CLI_DIR" + // CommandPrefix is the prefix we add to a user's commands + CommandPrefix = "#!/bin/bash\nset -eu\n" ) var envForProfile = []string{ @@ -197,6 +203,12 @@ func (s *multiStageTestStep) Requires() (ret []api.StepLink) { imageStream, name, _ := s.config.DependencyParts(dependency) ret = append(ret, api.LinkForImage(imageStream, name)) } + + if step.Cli != "" { + dependency := api.StepDependency{Name: fmt.Sprintf("%s:cli", api.ReleaseStreamFor(step.Cli))} + imageStream, name, _ := s.config.DependencyParts(dependency) + ret = append(ret, api.LinkForImage(imageStream, name)) + } } for link := range internalLinks { ret = append(ret, api.InternalImageLink(link)) @@ -363,7 +375,7 @@ func (s *multiStageTestStep) generatePods(steps []api.LiteralTestStep, env []cor continue } name := fmt.Sprintf("%s-%s", s.name, step.As) - pod, err := generateBasePod(s.jobSpec, name, "test", []string{"/bin/bash", "-c", "#!/bin/bash\nset -eu\n" + step.Commands}, image, resources, step.ArtifactDir) + pod, err := generateBasePod(s.jobSpec, name, "test", []string{"/bin/bash", "-c", CommandPrefix + step.Commands}, image, resources, step.ArtifactDir) if err != nil { errs = append(errs, err) continue @@ -399,6 +411,9 @@ func (s *multiStageTestStep) generatePods(steps []api.LiteralTestStep, env []cor {Name: "KUBECONFIG", Value: filepath.Join(SecretMountPath, "kubeconfig")}, }...) } + if step.Cli != "" { + addCliInjector(step.Cli, pod) + } addSecret(s.name, pod) addCredentials(step.Credentials, pod) ret = append(ret, *pod) @@ -520,6 +535,44 @@ func addProfile(name string, profile api.ClusterProfile, pod *coreapi.Pod) { }}...) } +func addCliInjector(release string, pod *coreapi.Pod) { + volumeName := "cli" + pod.Spec.Volumes = append(pod.Spec.Volumes, coreapi.Volume{ + Name: volumeName, + VolumeSource: coreapi.VolumeSource{ + EmptyDir: &coreapi.EmptyDirVolumeSource{}, + }, + }) + pod.Spec.InitContainers = append(pod.Spec.InitContainers, coreapi.Container{ + Name: "inject-cli", + Image: fmt.Sprintf("%s:cli", api.ReleaseStreamFor(release)), + Command: []string{"/bin/cp"}, + Args: []string{"/usr/bin/oc", CliMountPath}, + VolumeMounts: []coreapi.VolumeMount{{ + Name: volumeName, + MountPath: CliMountPath, + }}, + }) + container := &pod.Spec.Containers[0] + var args []string + for _, arg := range container.Args { + if strings.HasPrefix(arg, CommandPrefix) { + args = append(args, fmt.Sprintf("%s%s\n%s", CommandPrefix, `export PATH="${PATH}:${CLI_DIR}"`, strings.TrimPrefix(arg, CommandPrefix))) + } else { + args = append(args, arg) + } + } + container.Args = args + container.VolumeMounts = append(container.VolumeMounts, coreapi.VolumeMount{ + Name: volumeName, + MountPath: CliMountPath, + }) + container.Env = append(container.Env, coreapi.EnvVar{ + Name: CliEnv, + Value: CliMountPath, + }) +} + func (s *multiStageTestStep) runPods(ctx context.Context, pods []coreapi.Pod, shortCircuit bool) error { done := ctx.Done() namePrefix := s.name + "-" diff --git a/test/e2e/multi-stage.sh b/test/e2e/multi-stage.sh index 5d43eb2ed87..ba3e7a34f44 100755 --- a/test/e2e/multi-stage.sh +++ b/test/e2e/multi-stage.sh @@ -41,5 +41,6 @@ os::test::junit::declare_suite_start "e2e/multi-stage/dependencies" export JOB_SPEC='{"type":"postsubmit","job":"branch-ci-openshift-ci-tools-master-ci-operator-e2e","buildid":"0","prowjobid":"uuid","refs":{"org":"openshift","repo":"ci-tools","base_ref":"master","base_sha":"6d231cc37652e85e0f0e25c21088b73d644d89ad","pulls":[]}}' os::cmd::expect_success "ci-operator ${namespace} --artifact-dir ${BASETMPDIR} --resolver-address http://127.0.0.1:8080 --target with-dependencies --unresolved-config ${suite_dir}/dependencies.yaml" +os::cmd::expect_success "ci-operator ${namespace} --artifact-dir ${BASETMPDIR} --resolver-address http://127.0.0.1:8080 --target with-cli --unresolved-config ${suite_dir}/dependencies.yaml" os::integration::configresolver::check_log os::test::junit::declare_suite_end diff --git a/test/e2e/multi-stage/dependencies.yaml b/test/e2e/multi-stage/dependencies.yaml index a5332bec322..156ff23c26a 100644 --- a/test/e2e/multi-stage/dependencies.yaml +++ b/test/e2e/multi-stage/dependencies.yaml @@ -70,6 +70,17 @@ tests: env: "COMMAND" - name: "release:custom" env: "RELEASE" + - as: with-cli + steps: + test: + - as: use-cli + commands: oc adm policy add-role-to-user --help + from: os + cli: custom + resources: + requests: + cpu: 100m + memory: 200Mi zz_generated_metadata: branch: master org: test