diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7dd7f2c9..73f807eb 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -133,12 +133,7 @@ output, with parallel execution of steps leading to interwoven log output: 2018/10/08 05:29:15 Building machine-config-operator 2018/10/08 05:29:15 Building machine-config-controller 2018/10/08 05:31:47 Build machine-config-server succeeded after 2m28s -2018/10/08 05:31:47 Tagging machine-config-server into stable 2018/10/08 05:31:56 Build machine-config-operator succeeded after 2m37s -2018/10/08 05:31:56 Tagging machine-config-operator into stable 2018/10/08 05:31:56 Build machine-config-daemon succeeded after 2m37s -2018/10/08 05:31:56 Tagging machine-config-daemon into stable 2018/10/08 05:32:00 Build machine-config-controller succeeded after 2m41s -2018/10/08 05:32:00 Tagging machine-config-controller into stable -2018/10/08 05:32:00 All images ready ``` \ No newline at end of file diff --git a/cmd/ci-operator/main.go b/cmd/ci-operator/main.go index 12a8447d..6e154337 100644 --- a/cmd/ci-operator/main.go +++ b/cmd/ci-operator/main.go @@ -9,6 +9,7 @@ import ( "errors" "flag" "fmt" + "io" "io/ioutil" "log" "os" @@ -182,6 +183,7 @@ type options struct { verbose bool help bool dry bool + print bool writeParams string artifactDir string @@ -219,6 +221,7 @@ func bindOptions(flag *flag.FlagSet) *options { flag.StringVar(&opt.configSpecPath, "config", "", "The configuration file. If not specified the CONFIG_SPEC environment variable will be used.") flag.Var(&opt.targets, "target", "One or more targets in the configuration to build. Only steps that are required for this target will be run.") flag.BoolVar(&opt.dry, "dry-run", opt.dry, "Print the steps that would be run and the objects that would be created without executing any steps") + flag.BoolVar(&opt.print, "print-graph", opt.print, "Print a directed graph of the build steps and exit. Intended for use with the golang digraph utility.") // add to the graph of things we run or create flag.Var(&opt.templatePaths, "template", "A set of paths to optional templates to add as stages to this job. Each template is expected to contain at least one restart=Never pod. Parameters are filled from environment or from the automatic parameters generated by the operator.") @@ -399,6 +402,13 @@ func (o *options) Run() error { return fmt.Errorf("unable to write metadata.json for build: %v", err) } + if o.print { + if err := printDigraph(os.Stdout, buildSteps); err != nil { + return fmt.Errorf("could not print graph: %v", err) + } + return nil + } + // convert the full graph into the subset we must run nodes, err := api.BuildPartialGraph(buildSteps, o.targets.values) if err != nil { @@ -665,9 +675,6 @@ func (o *options) initializeNamespace() error { }) } - if len(o.secrets) > 0 { - log.Printf("Populating secrets for test") - } for _, secret := range o.secrets { _, err := client.Secrets(o.namespace).Create(secret) if kerrors.IsAlreadyExists(err) { @@ -922,6 +929,22 @@ func topologicalSort(nodes []*api.StepNode) ([]*api.StepNode, error) { return sortedNodes, nil } +func printDigraph(w io.Writer, steps []api.Step) error { + for _, step := range steps { + for _, other := range steps { + if step == other { + continue + } + if api.HasAnyLinks(step.Requires(), other.Creates()) { + if _, err := fmt.Fprintf(w, "%s %s\n", step.Name(), other.Name()); err != nil { + return err + } + } + } + } + return nil +} + func printExecutionOrder(nodes []*api.StepNode) error { ordered, err := topologicalSort(nodes) if err != nil { diff --git a/pkg/api/graph.go b/pkg/api/graph.go index 13221cd4..54a37701 100644 --- a/pkg/api/graph.go +++ b/pkg/api/graph.go @@ -242,7 +242,9 @@ func BuildPartialGraph(steps []Step, names []string) ([]*StepNode, error) { var required []StepLink candidates := make([]bool, len(steps)) + var allNames []string for i, step := range steps { + allNames = append(allNames, step.Name()) for j, name := range names { if name != step.Name() { continue @@ -254,7 +256,7 @@ func BuildPartialGraph(steps []Step, names []string) ([]*StepNode, error) { } } if len(names) > 0 { - return nil, fmt.Errorf("the following names were not found in the config or were duplicates: %s", strings.Join(names, ", ")) + return nil, fmt.Errorf("the following names were not found in the config or were duplicates: %s (from %s)", strings.Join(names, ", "), strings.Join(allNames, ", ")) } // identify all other steps that provide any links required by the current set diff --git a/pkg/api/parameters.go b/pkg/api/parameters.go index bdb06d1b..938a77e3 100644 --- a/pkg/api/parameters.go +++ b/pkg/api/parameters.go @@ -6,6 +6,48 @@ import ( "sync" ) +// Parameters allows a step to read values set by other steps. +type Parameters interface { + Has(name string) bool + HasInput(name string) bool + Get(name string) (string, error) + Links(name string) []StepLink +} + +type overrideParameters struct { + params Parameters + overrides map[string]string +} + +func (p *overrideParameters) Has(name string) bool { + if _, ok := p.overrides[name]; ok { + return true + } + return p.params.Has(name) +} + +func (p *overrideParameters) HasInput(name string) bool { + return p.params.HasInput(name) +} + +func (p *overrideParameters) Get(name string) (string, error) { + if value, ok := p.overrides[name]; ok { + return value, nil + } + return p.params.Get(name) +} + +func (p *overrideParameters) Links(name string) []StepLink { + return p.params.Links(name) +} + +func NewOverrideParameters(params Parameters, overrides map[string]string) Parameters { + return &overrideParameters{ + params: params, + overrides: overrides, + } +} + type DeferredParameters struct { lock sync.Mutex fns ParameterMap @@ -61,6 +103,17 @@ func (p *DeferredParameters) Add(name string, link StepLink, fn func() (string, } } +// HasInput returns true if the named parameter is an input from outside the graph, rather +// than provided either by the graph caller or another node. +func (p *DeferredParameters) HasInput(name string) bool { + p.lock.Lock() + defer p.lock.Unlock() + _, ok := os.LookupEnv(name) + return ok +} + +// Has returns true if the named parameter exists. Use HasInput if you need to know whether +// the value comes from outside the graph. func (p *DeferredParameters) Has(name string) bool { p.lock.Lock() defer p.lock.Unlock() @@ -75,9 +128,6 @@ func (p *DeferredParameters) Has(name string) bool { func (p *DeferredParameters) Links(name string) []StepLink { p.lock.Lock() defer p.lock.Unlock() - if _, ok := os.LookupEnv(name); ok { - return nil - } return p.links[name] } @@ -85,10 +135,7 @@ func (p *DeferredParameters) AllLinks() []StepLink { p.lock.Lock() defer p.lock.Unlock() var links []StepLink - for name, v := range p.links { - if _, ok := os.LookupEnv(name); ok { - continue - } + for _, v := range p.links { links = append(links, v...) } return links diff --git a/pkg/defaults/defaults.go b/pkg/defaults/defaults.go index f636dffd..f549977b 100644 --- a/pkg/defaults/defaults.go +++ b/pkg/defaults/defaults.go @@ -5,9 +5,10 @@ import ( "fmt" "log" "net/url" - "os" "strings" + "github.com/openshift/ci-operator/pkg/steps/clusterinstall" + appsclientset "k8s.io/client-go/kubernetes/typed/apps/v1" coreclientset "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" @@ -52,6 +53,7 @@ func FromConfig( var templateClient steps.TemplateClient var configMapGetter coreclientset.ConfigMapsGetter var serviceGetter coreclientset.ServicesGetter + var secretGetter coreclientset.SecretsGetter var podClient steps.PodClient if clusterConfig != nil { @@ -90,6 +92,7 @@ func FromConfig( } serviceGetter = coreGetter configMapGetter = coreGetter + secretGetter = coreGetter podClient = steps.NewPodClient(coreGetter, clusterConfig, coreGetter.RESTClient()) } @@ -101,7 +104,7 @@ func FromConfig( params.Add("NAMESPACE", nil, func() (string, error) { return jobSpec.Namespace, nil }) var imageStepLinks []api.StepLink - var releaseStep, initialReleaseStep api.Step + var hasReleaseStep bool for _, rawStep := range stepConfigsForBuild(config, jobSpec) { var step api.Step var stepLinks []api.StepLink @@ -141,20 +144,31 @@ func FromConfig( step = release.ReleaseImagesTagStep(*rawStep.ReleaseImagesTagStepConfiguration, srcClient, imageClient, routeGetter, configMapGetter, params, jobSpec) stepLinks = append(stepLinks, step.Creates()...) - releaseStep = release.AssembleReleaseStep(true, *rawStep.ReleaseImagesTagStepConfiguration, config.Resources, podClient, imageClient, artifactDir, jobSpec) - checkForFullyQualifiedStep(releaseStep, params) + hasReleaseStep = true + + releaseStep := release.AssembleReleaseStep(true, *rawStep.ReleaseImagesTagStepConfiguration, params, config.Resources, podClient, imageClient, artifactDir, jobSpec) + addProvidesForStep(releaseStep, params) + buildSteps = append(buildSteps, releaseStep) - initialReleaseStep = release.AssembleReleaseStep(false, *rawStep.ReleaseImagesTagStepConfiguration, config.Resources, podClient, imageClient, artifactDir, jobSpec) - checkForFullyQualifiedStep(initialReleaseStep, params) - // initial release is always added + initialReleaseStep := release.AssembleReleaseStep(false, *rawStep.ReleaseImagesTagStepConfiguration, params, config.Resources, podClient, imageClient, artifactDir, jobSpec) + addProvidesForStep(initialReleaseStep, params) buildSteps = append(buildSteps, initialReleaseStep) + } else if rawStep.TestStepConfiguration != nil && rawStep.TestStepConfiguration.OpenshiftInstallerClusterTestConfiguration != nil && rawStep.TestStepConfiguration.OpenshiftInstallerClusterTestConfiguration.Upgrade { + var err error + step, err = clusterinstall.E2ETestStep(*rawStep.TestStepConfiguration.OpenshiftInstallerClusterTestConfiguration, *rawStep.TestStepConfiguration, params, podClient, templateClient, secretGetter, artifactDir, jobSpec) + if err != nil { + return nil, nil, fmt.Errorf("unable to create end to end test step: %v", err) + } + } else if rawStep.TestStepConfiguration != nil { step = steps.TestStep(*rawStep.TestStepConfiguration, config.Resources, podClient, artifactDir, jobSpec) } step, ok := checkForFullyQualifiedStep(step, params) - if !ok { + if ok { + log.Printf("Task %s is satisfied by environment variables and will be skipped", step.Name()) + } else { imageStepLinks = append(imageStepLinks, stepLinks...) } buildSteps = append(buildSteps, step) @@ -169,9 +183,7 @@ func FromConfig( buildSteps = append(buildSteps, steps.WriteParametersStep(params, paramFile)) } - if releaseStep != nil { - buildSteps = append(buildSteps, releaseStep) - } else { + if !hasReleaseStep { buildSteps = append(buildSteps, release.StableImagesTagStep(imageClient, jobSpec)) } @@ -195,14 +207,23 @@ func FromConfig( return buildSteps, postSteps, nil } +// addProvidesForStep adds any required parameters to the deferred parameters map. +// Use this when a step may still need to run even if all parameters are provided +// by the caller as environment variables. +func addProvidesForStep(step api.Step, params *api.DeferredParameters) { + provides, link := step.Provides() + for name, fn := range provides { + params.Add(name, link, fn) + } +} + // checkForFullyQualifiedStep if all output parameters of this step are part of the // environment, replace the step with a shim that automatically provides those variables. // Returns true if the step was replaced. func checkForFullyQualifiedStep(step api.Step, params *api.DeferredParameters) (api.Step, bool) { provides, link := step.Provides() - if values, ok := envHasAllParameters(provides); ok { - log.Printf("Task %s is satisfied by environment variables and will be skipped", step.Name()) + if values, ok := paramsHasAllParametersAsInput(params, provides); ok { step = steps.NewInputEnvironmentStep(step.Name(), values, step.Creates()) for k, v := range values { params.Set(k, v) @@ -388,7 +409,10 @@ func stepConfigsForBuild(config *api.ReleaseBuildConfiguration, jobSpec *api.Job for i := range config.Tests { test := &config.Tests[i] - if test.ContainerTestConfiguration != nil { + switch { + case test.ContainerTestConfiguration != nil: + buildSteps = append(buildSteps, api.StepConfiguration{TestStepConfiguration: test}) + case test.OpenshiftInstallerClusterTestConfiguration != nil && test.OpenshiftInstallerClusterTestConfiguration.Upgrade: buildSteps = append(buildSteps, api.StepConfiguration{TestStepConfiguration: test}) } } @@ -430,19 +454,22 @@ func createStepConfigForGitSource(target api.ProjectDirectoryImageBuildInputs, j } } -func envHasAllParameters(params map[string]func() (string, error)) (map[string]string, bool) { +func paramsHasAllParametersAsInput(p api.Parameters, params map[string]func() (string, error)) (map[string]string, bool) { if len(params) == 0 { return nil, false } var values map[string]string for k := range params { - v, ok := os.LookupEnv(k) - if !ok { + if !p.HasInput(k) { return nil, false } if values == nil { values = make(map[string]string) } + v, err := p.Get(k) + if err != nil { + return nil, false + } values[k] = v } return values, true diff --git a/pkg/steps/clusterinstall/clusterinstall.go b/pkg/steps/clusterinstall/clusterinstall.go new file mode 100644 index 00000000..ec0a92ae --- /dev/null +++ b/pkg/steps/clusterinstall/clusterinstall.go @@ -0,0 +1,160 @@ +package clusterinstall + +import ( + "context" + "fmt" + "strings" + + "github.com/ghodss/yaml" + + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + coreclientset "k8s.io/client-go/kubernetes/typed/core/v1" + + templateapi "github.com/openshift/api/template/v1" + + "github.com/openshift/ci-operator/pkg/api" + "github.com/openshift/ci-operator/pkg/junit" + "github.com/openshift/ci-operator/pkg/steps" +) + +type e2eTestStep struct { + config api.OpenshiftInstallerClusterTestConfiguration + testConfig api.TestStepConfiguration + + secretClient coreclientset.SecretsGetter + jobSpec *api.JobSpec + + step api.Step + nestedSubTests +} + +type nestedSubTests interface { + SubTests() []*junit.TestCase +} + +// E2ETestStep installs a cluster and then runs end-to-end tests against it. +func E2ETestStep( + config api.OpenshiftInstallerClusterTestConfiguration, + testConfig api.TestStepConfiguration, + params api.Parameters, + podClient steps.PodClient, + templateClient steps.TemplateClient, + secretClient coreclientset.SecretsGetter, + artifactDir string, + jobSpec *api.JobSpec, +) (api.Step, error) { + var template *templateapi.Template + if err := yaml.Unmarshal([]byte(installTemplateE2E), &template); err != nil { + panic(fmt.Errorf("the embedded template is invalid: %v", err)) + } + + template.Name = testConfig.As + + if config.Upgrade { + overrides := make(map[string]string) + for i := range template.Parameters { + p := &template.Parameters[i] + switch p.Name { + case "JOB_NAME_SAFE": + if !params.HasInput(p.Name) { + overrides[p.Name] = testConfig.As + } + case "TEST_COMMAND": + p.Value = testConfig.Commands + case "CLUSTER_TYPE": + p.Value = strings.Split(string(config.ClusterProfile), "-")[0] + } + } + + // ensure we depend on the release image + name := "RELEASE_IMAGE_INITIAL" + template.Parameters = append(template.Parameters, templateapi.Parameter{ + Required: true, + Name: name, + }) + + // ensure the installer image points to the initial state + name = "IMAGE_INSTALLER" + if !params.HasInput(name) { + overrides[name] = "stable-initial:installer" + } + template.Parameters = append(template.Parameters, templateapi.Parameter{ + Required: true, + Name: name, + }) + + // set install initial release true for use in the template + name = "INSTALL_INITIAL_RELEASE" + template.Parameters = append(template.Parameters, templateapi.Parameter{ + Required: true, + Name: name, + Value: "true", + }) + + params = api.NewOverrideParameters(params, overrides) + } + + step := steps.TemplateExecutionStep(template, params, podClient, templateClient, artifactDir, jobSpec) + subTests, ok := step.(nestedSubTests) + if !ok { + return nil, fmt.Errorf("unexpected %T", step) + } + + return &e2eTestStep{ + config: config, + testConfig: testConfig, + + secretClient: secretClient, + jobSpec: jobSpec, + + step: step, + nestedSubTests: subTests, + }, nil +} + +func (s *e2eTestStep) checkPrereqs() error { + return nil +} + +func (s *e2eTestStep) Inputs(ctx context.Context, dry bool) (api.InputDefinition, error) { + return nil, nil +} + +func (s *e2eTestStep) Run(ctx context.Context, dry bool) error { + if dry { + return nil + } + if _, err := s.secretClient.Secrets(s.jobSpec.Namespace).Get(fmt.Sprintf("%s-cluster-profile", s.testConfig.As), meta.GetOptions{}); err != nil { + return fmt.Errorf("could not find required secret: %v", err) + } + return s.step.Run(ctx, dry) +} + +func (s *e2eTestStep) Done() (bool, error) { + return false, nil +} + +func (s *e2eTestStep) Requires() []api.StepLink { + links := s.step.Requires() + if s.config.Upgrade { + links = append([]api.StepLink{api.ReleasePayloadImageLink(api.PipelineImageStreamTagReference("initial"))}, links...) + } + return links +} + +func (s *e2eTestStep) Creates() []api.StepLink { + return nil +} + +func (s *e2eTestStep) Provides() (api.ParameterMap, api.StepLink) { + return nil, nil +} + +func (s *e2eTestStep) Name() string { return s.testConfig.As } + +func (s *e2eTestStep) Description() string { + if s.config.Upgrade { + return fmt.Sprintf("Run cluster install and upgrade %s", s.testConfig.As) + } + return fmt.Sprintf("Run cluster install %s", s.testConfig.As) +} diff --git a/pkg/steps/clusterinstall/template.go b/pkg/steps/clusterinstall/template.go new file mode 100644 index 00000000..3a736a1a --- /dev/null +++ b/pkg/steps/clusterinstall/template.go @@ -0,0 +1,478 @@ +package clusterinstall + +const installTemplateE2E = ` +kind: Template +apiVersion: template.openshift.io/v1 + +parameters: +- name: JOB_NAME_SAFE + required: true +- name: JOB_NAME_HASH + required: true +- name: NAMESPACE + required: true +- name: IMAGE_INSTALLER + required: true +- name: IMAGE_TESTS + required: true +- name: CLUSTER_TYPE + required: true +- name: TEST_COMMAND + required: true +- name: RELEASE_IMAGE_LATEST + required: true + +objects: + +# We want the cluster to be able to access these images +- kind: RoleBinding + apiVersion: authorization.openshift.io/v1 + metadata: + name: ${JOB_NAME_SAFE}-image-puller + namespace: ${NAMESPACE} + roleRef: + name: system:image-puller + subjects: + - kind: SystemGroup + name: system:unauthenticated + - kind: SystemGroup + name: system:authenticated + +# Give edit access to a known bot +- kind: RoleBinding + apiVersion: authorization.openshift.io/v1 + metadata: + name: ${JOB_NAME_SAFE}-namespace-editors + namespace: ${NAMESPACE} + roleRef: + name: edit + subjects: + - kind: ServiceAccount + namespace: ci + name: ci-chat-bot + +# The e2e pod spins up a cluster, runs e2e tests, and then cleans up the cluster. +- kind: Pod + apiVersion: v1 + metadata: + name: ${JOB_NAME_SAFE} + namespace: ${NAMESPACE} + annotations: + # we want to gather the teardown logs no matter what + ci-operator.openshift.io/wait-for-container-artifacts: teardown + ci-operator.openshift.io/save-container-logs: "true" + ci-operator.openshift.io/container-sub-tests: "setup,test,teardown" + spec: + restartPolicy: Never + activeDeadlineSeconds: 14400 + terminationGracePeriodSeconds: 900 + volumes: + - name: artifacts + emptyDir: {} + - name: shared-tmp + emptyDir: {} + - name: cluster-profile + secret: + secretName: ${JOB_NAME_SAFE}-cluster-profile + + containers: + + # Once the cluster is up, executes shared tests + - name: test + image: ${IMAGE_TESTS} + terminationMessagePolicy: FallbackToLogsOnError + resources: + requests: + cpu: 1 + memory: 300Mi + limits: + memory: 3Gi + volumeMounts: + - name: shared-tmp + mountPath: /tmp/shared + - name: cluster-profile + mountPath: /tmp/cluster + - name: artifacts + mountPath: /tmp/artifacts + env: + - name: AWS_SHARED_CREDENTIALS_FILE + value: /tmp/cluster/.awscred + - name: ARTIFACT_DIR + value: /tmp/artifacts + - name: HOME + value: /tmp/home + - name: KUBECONFIG + value: /tmp/artifacts/installer/auth/kubeconfig + command: + - /bin/bash + - -c + - | + #!/bin/bash + set -euo pipefail + + export PATH=/usr/libexec/origin:$PATH + + trap 'touch /tmp/shared/exit' EXIT + trap 'kill $(jobs -p); exit 0' TERM + + mkdir -p "${HOME}" + + # wait for the API to come up + while true; do + if [[ -f /tmp/shared/exit ]]; then + echo "Another process exited" 2>&1 + exit 1 + fi + if [[ ! -f /tmp/shared/setup-success ]]; then + sleep 15 & wait + continue + fi + # don't let clients impact the global kubeconfig + cp "${KUBECONFIG}" /tmp/admin.kubeconfig + export KUBECONFIG=/tmp/admin.kubeconfig + break + done + + until oc --insecure-skip-tls-verify wait clusterversion/version --for condition=available 2>/dev/null; do + sleep 10 & wait + done + + # set up cloud-provider-specific env vars + export KUBE_SSH_BASTION="$( oc --insecure-skip-tls-verify get node -l node-role.kubernetes.io/master -o 'jsonpath={.items[0].status.addresses[?(@.type=="ExternalIP")].address}' ):22" + export KUBE_SSH_KEY_PATH=/tmp/cluster/ssh-privatekey + if [[ "${CLUSTER_TYPE}" == "gcp" ]]; then + export GOOGLE_APPLICATION_CREDENTIALS="/tmp/cluster/gce.json" + export KUBE_SSH_USER=cloud-user + mkdir -p ~/.ssh + cp /tmp/cluster/ssh-privatekey ~/.ssh/google_compute_engine || true + export PROVIDER_ARGS='-provider=gce -gce-zone=us-east1-c -gce-project=openshift-gce-devel-ci' + export TEST_PROVIDER='{"type":"gce","zone":"us-east1-c","projectid":"openshift-gce-devel-ci"}' + elif [[ "${CLUSTER_TYPE}" == "aws" ]]; then + mkdir -p ~/.ssh + cp /tmp/cluster/ssh-privatekey ~/.ssh/kube_aws_rsa || true + export PROVIDER_ARGS="-provider=aws -gce-zone=us-east-1" + # TODO: make openshift-tests auto-discover this from cluster config + export TEST_PROVIDER='{"type":"aws","region":"us-east-1","zone":"us-east-1a","multizone":true,"multimaster":true}' + export KUBE_SSH_USER=core + elif [[ "${CLUSTER_TYPE}" == "openstack" ]]; then + mkdir -p ~/.ssh + cp /tmp/cluster/ssh-privatekey ~/.ssh/kube_openstack_rsa || true + fi + + mkdir -p /tmp/output + cd /tmp/output + + function run-upgrade-tests() { + openshift-tests run-upgrade "${TEST_SUITE}" --to-image "${RELEASE_IMAGE_LATEST}" \ + --provider "${TEST_PROVIDER:-}" -o /tmp/artifacts/e2e.log --junit-dir /tmp/artifacts/junit + exit 0 + } + + function run-tests() { + openshift-tests run "${TEST_SUITE}" \ + --provider "${TEST_PROVIDER:-}" -o /tmp/artifacts/e2e.log --junit-dir /tmp/artifacts/junit + exit 0 + } + + ${TEST_COMMAND} + + # Runs an install + - name: setup + image: ${IMAGE_INSTALLER} + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - name: shared-tmp + mountPath: /tmp + - name: cluster-profile + mountPath: /etc/openshift-installer + - name: artifacts + mountPath: /tmp/artifacts + env: + - name: TYPE + value: ${CLUSTER_TYPE} + - name: AWS_SHARED_CREDENTIALS_FILE + value: /etc/openshift-installer/.awscred + - name: AWS_REGION + value: us-east-1 + - name: CLUSTER_NAME + value: ${NAMESPACE}-${JOB_NAME_HASH} + - name: BASE_DOMAIN + value: origin-ci-int-aws.dev.rhcloud.com + - name: SSH_PUB_KEY_PATH + value: /etc/openshift-installer/ssh-publickey + - name: PULL_SECRET_PATH + value: /etc/openshift-installer/pull-secret + - name: OPENSHIFT_INSTALL_RELEASE_IMAGE_OVERRIDE + value: ${RELEASE_IMAGE_LATEST} + - name: OPENSTACK_IMAGE + value: rhcos + - name: OPENSTACK_REGION + value: RegionOne + - name: OPENSTACK_EXTERNAL_NETWORK + value: public + - name: OS_CLOUD + value: openstack-cloud + - name: OS_CLIENT_CONFIG_FILE + value: /etc/openshift-installer/clouds.yaml + - name: USER + value: test + - name: HOME + value: /tmp + - name: INSTALL_INITIAL_RELEASE + - name: RELEASE_IMAGE_INITIAL + command: + - /bin/sh + - -c + - | + #!/bin/sh + trap 'rc=$?; if test "${rc}" -eq 0; then touch /tmp/setup-success; else touch /tmp/exit; fi; exit "${rc}"' EXIT + trap 'CHILDREN=$(jobs -p); if test -n "${CHILDREN}"; then kill ${CHILDREN} && wait; fi' TERM + + cp "$(command -v openshift-install)" /tmp + mkdir /tmp/artifacts/installer + + if [[ -n "${INSTALL_INITIAL_RELEASE}" && -n "${RELEASE_IMAGE_INITIAL}" ]]; then + echo "Installing from initial release ${RELEASE_IMAGE_INITIAL}" + OPENSHIFT_INSTALL_RELEASE_IMAGE_OVERRIDE="${RELEASE_IMAGE_INITIAL}" + else + echo "Installing from release ${RELEASE_IMAGE_LATEST}" + fi + + export EXPIRATION_DATE=$(date -d '4 hours' --iso=minutes --utc) + export CLUSTER_ID=$(uuidgen --random) + export SSH_PUB_KEY=$(cat "${SSH_PUB_KEY_PATH}") + export PULL_SECRET=$(cat "${PULL_SECRET_PATH}") + + if [[ "${CLUSTER_TYPE}" == "aws" ]]; then + cat > /tmp/artifacts/installer/install-config.yaml << EOF + apiVersion: v1beta3 + baseDomain: ${BASE_DOMAIN} + clusterID: ${CLUSTER_ID} + metadata: + name: ${CLUSTER_NAME} + networking: + clusterNetworks: + - cidr: 10.128.0.0/14 + hostSubnetLength: 9 + machineCIDR: 10.0.0.0/16 + serviceCIDR: 172.30.0.0/16 + type: OpenShiftSDN + platform: + aws: + region: ${AWS_REGION} + userTags: + expirationDate: ${EXPIRATION_DATE} + pullSecret: > + ${PULL_SECRET} + sshKey: | + ${SSH_PUB_KEY} + EOF + elif [[ "${CLUSTER_TYPE}" == "openstack" ]]; then + cat > /tmp/artifacts/installer/install-config.yaml << EOF + apiVersion: v1beta3 + baseDomain: ${BASE_DOMAIN} + clusterID: ${CLUSTER_ID} + metadata: + name: ${CLUSTER_NAME} + networking: + clusterNetworks: + - cidr: 10.128.0.0/14 + hostSubnetLength: 9 + machineCIDR: 10.0.0.0/16 + serviceCIDR: 172.30.0.0/16 + type: OpenShiftSDN + platform: + openstack: + baseImage: ${OPENSTACK_IMAGE} + cloud: ${OS_CLOUD} + externalNetwork: ${OPENSTACK_EXTERNAL_NETWORK} + region: ${OPENSTACK_REGION} + pullSecret: > + ${PULL_SECRET} + sshKey: | + ${SSH_PUB_KEY} + EOF + else + echo "Unsupported cluster type '${CLUSTER_NAME}'" + exit 1 + fi + + TF_LOG=debug openshift-install --dir=/tmp/artifacts/installer create cluster & + wait "$!" + + # Performs cleanup of all created resources + - name: teardown + image: ${IMAGE_TESTS} + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - name: shared-tmp + mountPath: /tmp/shared + - name: cluster-profile + mountPath: /etc/openshift-installer + - name: artifacts + mountPath: /tmp/artifacts + env: + - name: INSTANCE_PREFIX + value: ${NAMESPACE}-${JOB_NAME_HASH} + - name: TYPE + value: ${CLUSTER_TYPE} + - name: KUBECONFIG + value: /tmp/artifacts/installer/auth/kubeconfig + command: + - /bin/bash + - -c + - | + #!/bin/bash + function queue() { + local TARGET="${1}" + shift + local LIVE="$(jobs | wc -l)" + while [[ "${LIVE}" -ge 45 ]]; do + sleep 1 + LIVE="$(jobs | wc -l)" + done + echo "${@}" + if [[ -n "${FILTER}" ]]; then + "${@}" | "${FILTER}" >"${TARGET}" & + else + "${@}" >"${TARGET}" & + fi + } + + function teardown() { + set +e + touch /tmp/shared/exit + export PATH=$PATH:/tmp/shared + + echo "Gathering artifacts ..." + mkdir -p /tmp/artifacts/pods /tmp/artifacts/nodes /tmp/artifacts/metrics /tmp/artifacts/bootstrap /tmp/artifacts/network + + + if [ -f /tmp/artifacts/installer/terraform.tfstate ] + then + # we don't have jq, so the python equivalent of + # jq '.modules[].resources."aws_instance.bootstrap".primary.attributes."public_ip" | select(.)' + bootstrap_ip=$(python -c \ + 'import sys, json; d=reduce(lambda x,y: dict(x.items() + y.items()), map(lambda x: x["resources"], json.load(sys.stdin)["modules"])); k="aws_instance.bootstrap"; print d[k]["primary"]["attributes"]["public_ip"] if k in d else ""' \ + < /tmp/artifacts/installer/terraform.tfstate + ) + + if [ -n "${bootstrap_ip}" ] + then + for service in bootkube openshift kubelet crio + do + queue "/tmp/artifacts/bootstrap/${service}.service" curl \ + --insecure \ + --silent \ + --connect-timeout 5 \ + --retry 3 \ + --cert /tmp/artifacts/installer/tls/journal-gatewayd.crt \ + --key /tmp/artifacts/installer/tls/journal-gatewayd.key \ + --url "https://${bootstrap_ip}:19531/entries?_SYSTEMD_UNIT=${service}.service" + done + fi + else + echo "No terraform statefile found. Skipping collection of bootstrap logs." + fi + + oc --insecure-skip-tls-verify --request-timeout=5s get nodes -o jsonpath --template '{range .items[*]}{.metadata.name}{"\n"}{end}' > /tmp/nodes + oc --insecure-skip-tls-verify --request-timeout=5s get pods --all-namespaces --template '{{ range .items }}{{ $name := .metadata.name }}{{ $ns := .metadata.namespace }}{{ range .spec.containers }}-n {{ $ns }} {{ $name }} -c {{ .name }}{{ "\n" }}{{ end }}{{ range .spec.initContainers }}-n {{ $ns }} {{ $name }} -c {{ .name }}{{ "\n" }}{{ end }}{{ end }}' > /tmp/containers + oc --insecure-skip-tls-verify --request-timeout=5s get pods -l openshift.io/component=api --all-namespaces --template '{{ range .items }}-n {{ .metadata.namespace }} {{ .metadata.name }}{{ "\n" }}{{ end }}' > /tmp/pods-api + + queue /tmp/artifacts/apiservices.json oc --insecure-skip-tls-verify --request-timeout=5s get apiservices -o json + queue /tmp/artifacts/clusteroperators.json oc --insecure-skip-tls-verify --request-timeout=5s get clusteroperators -o json + queue /tmp/artifacts/clusterversion.json oc --insecure-skip-tls-verify --request-timeout=5s get clusterversion -o json + queue /tmp/artifacts/configmaps.json oc --insecure-skip-tls-verify --request-timeout=5s get configmaps --all-namespaces -o json + queue /tmp/artifacts/csr.json oc --insecure-skip-tls-verify --request-timeout=5s get csr -o json + queue /tmp/artifacts/endpoints.json oc --insecure-skip-tls-verify --request-timeout=5s get endpoints --all-namespaces -o json + queue /tmp/artifacts/events.json oc --insecure-skip-tls-verify --request-timeout=5s get events --all-namespaces -o json + queue /tmp/artifacts/kubeapiserver.json oc --insecure-skip-tls-verify --request-timeout=5s get kubeapiserver -o json + queue /tmp/artifacts/kubecontrollermanager.json oc --insecure-skip-tls-verify --request-timeout=5s get kubecontrollermanager -o json + queue /tmp/artifacts/machineconfigpools.json oc --insecure-skip-tls-verify --request-timeout=5s get machineconfigpools -o json + queue /tmp/artifacts/machineconfigs.json oc --insecure-skip-tls-verify --request-timeout=5s get machineconfigs -o json + queue /tmp/artifacts/namespaces.json oc --insecure-skip-tls-verify --request-timeout=5s get namespaces -o json + queue /tmp/artifacts/nodes.json oc --insecure-skip-tls-verify --request-timeout=5s get nodes -o json + queue /tmp/artifacts/openshiftapiserver.json oc --insecure-skip-tls-verify --request-timeout=5s get openshiftapiserver -o json + queue /tmp/artifacts/pods.json oc --insecure-skip-tls-verify --request-timeout=5s get pods --all-namespaces -o json + queue /tmp/artifacts/rolebindings.json oc --insecure-skip-tls-verify --request-timeout=5s get rolebindings --all-namespaces -o json + queue /tmp/artifacts/roles.json oc --insecure-skip-tls-verify --request-timeout=5s get roles --all-namespaces -o json + queue /tmp/artifacts/secrets.json oc --insecure-skip-tls-verify --request-timeout=5s get secrets --all-namespaces -o json + queue /tmp/artifacts/services.json oc --insecure-skip-tls-verify --request-timeout=5s get services --all-namespaces -o json + + FILTER=gzip queue /tmp/artifacts/openapi.json.gz oc --insecure-skip-tls-verify --request-timeout=5s get --raw /openapi/v2 + + # gather nodes first in parallel since they may contain the most relevant debugging info + while IFS= read -r i; do + mkdir -p /tmp/artifacts/nodes/$i + queue /tmp/artifacts/nodes/$i/heap oc --insecure-skip-tls-verify get --request-timeout=20s --raw /api/v1/nodes/$i/proxy/debug/pprof/heap + done < /tmp/nodes + + if oc --insecure-skip-tls-verify adm node-logs -h &>/dev/null; then + # starting in 4.0 we can query node logs directly + FILTER=gzip queue /tmp/artifacts/nodes/masters-journal.gz oc --insecure-skip-tls-verify adm node-logs --role=master --unify=false + FILTER=gzip queue /tmp/artifacts/nodes/workers-journal.gz oc --insecure-skip-tls-verify adm node-logs --role=worker --unify=false + else + while IFS= read -r i; do + FILTER=gzip queue /tmp/artifacts/nodes/$i/messages.gz oc --insecure-skip-tls-verify get --request-timeout=20s --raw /api/v1/nodes/$i/proxy/logs/messages + oc --insecure-skip-tls-verify get --request-timeout=20s --raw /api/v1/nodes/$i/proxy/logs/journal | sed -e 's|.*href="\(.*\)".*|\1|;t;d' > /tmp/journals + while IFS= read -r j; do + FILTER=gzip queue /tmp/artifacts/nodes/$i/journal.gz oc --insecure-skip-tls-verify get --request-timeout=20s --raw /api/v1/nodes/$i/proxy/logs/journal/${j}system.journal + done < /tmp/journals + FILTER=gzip queue /tmp/artifacts/nodes/$i/secure.gz oc --insecure-skip-tls-verify get --request-timeout=20s --raw /api/v1/nodes/$i/proxy/logs/secure + FILTER=gzip queue /tmp/artifacts/nodes/$i/audit.gz oc --insecure-skip-tls-verify get --request-timeout=20s --raw /api/v1/nodes/$i/proxy/logs/audit + done < /tmp/nodes + fi + + # Snapshot iptables-save on each node for debugging possible kube-proxy issues + oc --insecure-skip-tls-verify get --request-timeout=20s -n openshift-sdn -l app=sdn pods --template '{{ range .items }}{{ .metadata.name }}{{ "\n" }}{{ end }}' > /tmp/sdn-pods + while IFS= read -r i; do + queue /tmp/artifacts/network/iptables-save-$i oc --insecure-skip-tls-verify rsh --timeout=20 -n openshift-sdn -c sdn $i iptables-save -c + done < /tmp/sdn-pods + + while IFS= read -r i; do + file="$( echo "$i" | cut -d ' ' -f 3 | tr -s ' ' '_' )" + queue /tmp/artifacts/metrics/${file}-heap oc --insecure-skip-tls-verify exec $i -- /bin/bash -c 'oc --insecure-skip-tls-verify get --raw /debug/pprof/heap --server "https://$( hostname ):8443" --config /etc/origin/master/admin.kubeconfig' + queue /tmp/artifacts/metrics/${file}-controllers-heap oc --insecure-skip-tls-verify exec $i -- /bin/bash -c 'oc --insecure-skip-tls-verify get --raw /debug/pprof/heap --server "https://$( hostname ):8444" --config /etc/origin/master/admin.kubeconfig' + done < /tmp/pods-api + + while IFS= read -r i; do + file="$( echo "$i" | cut -d ' ' -f 2,3,5 | tr -s ' ' '_' )" + FILTER=gzip queue /tmp/artifacts/pods/${file}.log.gz oc --insecure-skip-tls-verify logs --request-timeout=20s $i + FILTER=gzip queue /tmp/artifacts/pods/${file}_previous.log.gz oc --insecure-skip-tls-verify logs --request-timeout=20s -p $i + done < /tmp/containers + + echo "Gathering kube-apiserver audit.log ..." + oc --insecure-skip-tls-verify adm node-logs --role=master --path=kube-apiserver/ > /tmp/kube-audit-logs + while IFS=$'\n' read -r line; do + IFS=' ' read -ra log <<< "${line}" + FILTER=gzip queue /tmp/artifacts/nodes/"${log[0]}"-"${log[1]}".gz oc --insecure-skip-tls-verify adm node-logs "${log[0]}" --path=kube-apiserver/"${log[1]}" + done < /tmp/kube-audit-logs + + echo "Gathering openshift-apiserver audit.log ..." + oc --insecure-skip-tls-verify adm node-logs --role=master --path=openshift-apiserver/ > /tmp/openshift-audit-logs + while IFS=$'\n' read -r line; do + IFS=' ' read -ra log <<< "${line}" + FILTER=gzip queue /tmp/artifacts/nodes/"${log[0]}"-"${log[1]}".gz oc --insecure-skip-tls-verify adm node-logs "${log[0]}" --path=openshift-apiserver/"${log[1]}" + done < /tmp/openshift-audit-logs + + echo "Snapshotting prometheus (may take 15s) ..." + queue /tmp/artifacts/metrics/prometheus.tar.gz oc --insecure-skip-tls-verify exec -n openshift-monitoring prometheus-k8s-0 -- tar cvzf - -C /prometheus . + + echo "Waiting for logs ..." + wait + + echo "Deprovisioning cluster ..." + export AWS_SHARED_CREDENTIALS_FILE=/etc/openshift-installer/.awscred + openshift-install --dir /tmp/artifacts/installer destroy cluster + } + + trap 'teardown' EXIT + trap 'kill $(jobs -p); exit 0' TERM + + for i in $(seq 1 180); do + if [[ -f /tmp/shared/exit ]]; then + exit 0 + fi + sleep 60 & wait + done +` diff --git a/pkg/steps/images_ready.go b/pkg/steps/images_ready.go index c625ef35..97a2bfd9 100644 --- a/pkg/steps/images_ready.go +++ b/pkg/steps/images_ready.go @@ -2,7 +2,6 @@ package steps import ( "context" - "log" "github.com/openshift/ci-operator/pkg/api" ) @@ -16,7 +15,6 @@ func (s *imagesReadyStep) Inputs(ctx context.Context, dry bool) (api.InputDefini } func (s *imagesReadyStep) Run(ctx context.Context, dry bool) error { - log.Printf("All images ready") return nil } diff --git a/pkg/steps/release/create_release.go b/pkg/steps/release/create_release.go index 29b2d002..ffbeda0f 100644 --- a/pkg/steps/release/create_release.go +++ b/pkg/steps/release/create_release.go @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" "log" - "os" "path/filepath" "strings" "time" @@ -49,6 +48,8 @@ import ( type assembleReleaseStep struct { config api.ReleaseTagConfiguration latest bool + params api.Parameters + releaseSpec string resources api.ResourceConfiguration imageClient imageclientset.ImageV1Interface podClient steps.PodClient @@ -58,23 +59,79 @@ type assembleReleaseStep struct { } func (s *assembleReleaseStep) Inputs(ctx context.Context, dry bool) (api.InputDefinition, error) { - if val := os.Getenv(s.envVar()); len(val) > 0 { - return api.InputDefinition{val}, nil + if val, _ := s.params.Get(s.envVar()); len(val) > 0 { + result, err := s.imageClient.ImageStreamImports(s.config.Namespace).Create(&imageapi.ImageStreamImport{ + ObjectMeta: meta.ObjectMeta{ + Name: "release-import", + }, + Spec: imageapi.ImageStreamImportSpec{ + Images: []imageapi.ImageImportSpec{ + { + From: coreapi.ObjectReference{ + Kind: "DockerImage", + Name: val, + }, + }, + }, + }, + }) + if err != nil { + if errors.IsForbidden(err) { + // the ci-operator expects to have POST /imagestreamimports in the namespace of the tag spec + log.Printf("warning: Unable to lock %s to an image digest pull spec, you don't have permission to access the necessary API.", s.envVar()) + return api.InputDefinition{val}, nil + } + return nil, err + } + image := result.Status.Images[0] + if image.Image == nil { + log.Printf("warning: Unable to lock %s to an image digest pull spec due to an import error (%s): %s.", s.envVar(), image.Status.Reason, image.Status.Message) + return api.InputDefinition{val}, nil + } + log.Printf("Resolved release:%s %s", s.tag(), image.Image.DockerImageReference) + s.releaseSpec = image.Image.DockerImageReference + return api.InputDefinition{image.Image.Name}, nil } return nil, nil } func (s *assembleReleaseStep) Run(ctx context.Context, dry bool) error { + if dry { + return nil + } + tag := s.tag() streamName := s.streamName() + // ensure the image stream exists + release, err := s.imageClient.ImageStreams(s.jobSpec.Namespace).Create(&imageapi.ImageStream{ + ObjectMeta: meta.ObjectMeta{ + Name: "release", + }, + }) + if err != nil { + if !errors.IsAlreadyExists(err) { + return err + } + release, err = s.imageClient.ImageStreams(s.jobSpec.Namespace).Get("release", meta.GetOptions{}) + if err != nil { + return err + } + } + // if the user specified an input env var, we tag it in instead of generating it - providedImage, ok := os.LookupEnv(s.envVar()) - if ok { + if s.params.HasInput(s.envVar()) { + providedImage, err := s.params.Get(s.envVar()) + if err != nil { + return fmt.Errorf("cannot retrieve %s: %v", s.envVar(), err) + } if len(providedImage) == 0 { - log.Printf("No %s release image necessary", tag) + log.Printf("No %s release image necessary because empty input variable provided", tag) return nil } + if len(s.releaseSpec) > 0 { + providedImage = s.releaseSpec + } return s.importFromReleaseImage(ctx, dry, providedImage) } @@ -97,25 +154,11 @@ func (s *assembleReleaseStep) Run(ctx context.Context, dry bool) error { return fmt.Errorf("no 'cli' image was tagged into the %s stream, that image is required for building a release", streamName) } - release, err := s.imageClient.ImageStreams(s.jobSpec.Namespace).Create(&imageapi.ImageStream{ - ObjectMeta: meta.ObjectMeta{ - Name: "release", - }, - }) - if err != nil { - if !errors.IsAlreadyExists(err) { - return err - } - release, err = s.imageClient.ImageStreams(s.jobSpec.Namespace).Get("release", meta.GetOptions{}) - if err != nil { - return err - } - } - destination := fmt.Sprintf("%s:%s", release.Status.PublicDockerImageRepository, tag) log.Printf("Create release image %s", destination) podConfig := steps.PodStepConfiguration{ - As: fmt.Sprintf("release-%s", tag), + SkipLogs: true, + As: fmt.Sprintf("release-%s", tag), From: api.ImageStreamTagReference{ Name: api.StableImageStream, Tag: "cli", @@ -159,9 +202,11 @@ func (s *assembleReleaseStep) importFromReleaseImage(ctx context.Context, dry bo return nil } + log.Printf("Importing release image %s", tag) + start := time.Now() - // create the stable image stream with lookup policy + // create the stable image stream with lookup policy so we have a place to put our imported images _, err := s.imageClient.ImageStreams(s.jobSpec.Namespace).Create(&imageapi.ImageStream{ ObjectMeta: meta.ObjectMeta{ Name: streamName, @@ -345,19 +390,19 @@ oc adm release extract --from=%q --file=image-references > /tmp/artifacts/%s return fmt.Errorf("unable to update stable image stream with release tags: %v", err) } - log.Printf("Imported %s to release:%s and updated %s images in %s", providedImage, tag, streamName, time.Now().Sub(start).Truncate(time.Second)) + log.Printf("Imported %s to release:%s in %s", providedImage, tag, time.Now().Sub(start).Truncate(time.Second)) return nil } func (s *assembleReleaseStep) Done() (bool, error) { - // TODO: define done - return true, nil + return false, nil } func (s *assembleReleaseStep) Requires() []api.StepLink { - // if our prereq is provided, we don't need any prereqs - if len(os.Getenv(s.envVar())) > 0 { - return nil + // if our prereq is provided, we only depend on the stable and stable-initial + // image streams to be populated + if s.params.HasInput(s.envVar()) { + return []api.StepLink{api.ReleaseImagesLink()} } if s.latest { return []api.StepLink{api.ImagesReadyLink()} @@ -403,6 +448,13 @@ func (s *assembleReleaseStep) Provides() (api.ParameterMap, api.StepLink) { } else { return "", fmt.Errorf("image stream %s has no accessible image registry value", "release") } + ref, image := findStatusTag(is, tag) + if len(image) > 0 { + return fmt.Sprintf("%s@%s", registry, image), nil + } + if ref == nil && findSpecTag(is, tag) == nil { + return "", nil + } return fmt.Sprintf("%s:%s", registry, tag), nil }, }, api.ReleasePayloadImageLink(api.PipelineImageStreamTagReference(tag)) @@ -421,10 +473,11 @@ func (s *assembleReleaseStep) Description() string { // AssembleReleaseStep builds a new update payload image based on the cluster version operator // and the operators defined in the release configuration. -func AssembleReleaseStep(latest bool, config api.ReleaseTagConfiguration, resources api.ResourceConfiguration, podClient steps.PodClient, imageClient imageclientset.ImageV1Interface, artifactDir string, jobSpec *api.JobSpec) api.Step { +func AssembleReleaseStep(latest bool, config api.ReleaseTagConfiguration, params api.Parameters, resources api.ResourceConfiguration, podClient steps.PodClient, imageClient imageclientset.ImageV1Interface, artifactDir string, jobSpec *api.JobSpec) api.Step { return &assembleReleaseStep{ config: config, latest: latest, + params: params, resources: resources, podClient: podClient, imageClient: imageClient, diff --git a/pkg/steps/release/release_images.go b/pkg/steps/release/release_images.go index c5300920..bd15f53f 100644 --- a/pkg/steps/release/release_images.go +++ b/pkg/steps/release/release_images.go @@ -94,6 +94,16 @@ type releaseImagesTagStep struct { jobSpec *api.JobSpec } +func findSpecTag(is *imageapi.ImageStream, tag string) *coreapi.ObjectReference { + for _, t := range is.Spec.Tags { + if t.Name != tag { + continue + } + return t.From + } + return nil +} + func findStatusTag(is *imageapi.ImageStream, tag string) (*coreapi.ObjectReference, string) { for _, t := range is.Status.Tags { if t.Tag != tag { @@ -213,7 +223,7 @@ func (s *releaseImagesTagStep) Run(ctx context.Context, dry bool) error { if !ok { continue } - s.params.Set(componentToParamName(tag.Name), spec) + s.params.Set("IMAGE_"+componentToParamName(tag.Name), spec) } return nil diff --git a/pkg/steps/template.go b/pkg/steps/template.go index 83438782..9b613a61 100644 --- a/pkg/steps/template.go +++ b/pkg/steps/template.go @@ -14,8 +14,6 @@ import ( "k8s.io/client-go/rest" - templateapi "github.com/openshift/api/template/v1" - templateclientset "github.com/openshift/client-go/template/clientset/versioned/typed/template/v1" coreapi "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" meta "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -24,13 +22,16 @@ import ( "k8s.io/apimachinery/pkg/watch" coreclientset "k8s.io/client-go/kubernetes/typed/core/v1" + templateapi "github.com/openshift/api/template/v1" + templateclientset "github.com/openshift/client-go/template/clientset/versioned/typed/template/v1" + "github.com/openshift/ci-operator/pkg/api" "github.com/openshift/ci-operator/pkg/junit" ) type templateExecutionStep struct { template *templateapi.Template - params *api.DeferredParameters + params api.Parameters templateClient TemplateClient podClient PodClient artifactDir string @@ -81,7 +82,9 @@ func (s *templateExecutionStep) Run(ctx context.Context, dry bool) error { } } - addArtifactsToTemplate(s.template) + if len(s.artifactDir) > 0 { + addArtifactsToTemplate(s.template) + } if dry { j, _ := json.MarshalIndent(s.template, "", " ") @@ -204,7 +207,8 @@ func (s *templateExecutionStep) Requires() []api.StepLink { var links []api.StepLink for _, p := range s.template.Parameters { if s.params.Has(p.Name) { - links = append(links, s.params.Links(p.Name)...) + paramLinks := s.params.Links(p.Name) + links = append(links, paramLinks...) continue } if strings.HasPrefix(p.Name, "IMAGE_") { @@ -229,7 +233,7 @@ func (s *templateExecutionStep) Description() string { return fmt.Sprintf("Run template %s", s.template.Name) } -func TemplateExecutionStep(template *templateapi.Template, params *api.DeferredParameters, podClient PodClient, templateClient TemplateClient, artifactDir string, jobSpec *api.JobSpec) api.Step { +func TemplateExecutionStep(template *templateapi.Template, params api.Parameters, podClient PodClient, templateClient TemplateClient, artifactDir string, jobSpec *api.JobSpec) api.Step { return &templateExecutionStep{ template: template, params: params,