CI Operator Internals

A detailed view of some aspects of the ci-operator implementation, intended for developers working on its source code.

This document describes in detail some aspects of the implementation of ci-operator, with the intent of serving as an auxiliary guide for developers working with the openshift/ci-tools repository.

Execution graph

ci-operator is at its core a task scheduling program. The input configuration is processed and used to build a task graph, which is then executed until completion, failure, or interruption. Thus, the execution flow of ci-operator can be divided in these major phases:

  1. input processing
  2. task graph creation
  3. task graph execution
  4. cleanup

In the code base, these phases correspond to the following modules in the pkg directory:

  • api: Go types used by all phases
  • load: I/O operations for types
  • registry: configuration resolution using the step registry
  • config: input configuration processing
  • validation: input configuration validation
  • defaults: mapping from inputs to tasks
  • steps: definitions for each task type, task execution

Step graph

The data structure common to all phases is the step graph. Each individual task in ci-operator is called a step, and the dependency relations between steps are represented by the graph. It determines the execution order and concurrency of steps, and allows unneeded steps to be pruned.

The basic types for steps and graphs can be found in pkg/api/graph.go: Step is the interface implemented by all step types, StepLink defines dependencies between steps, and StepNode is the structure that composes the graph itself (being a directed acyclic graph, it is represented as a list of all the root nodes).

The translation from the input configuration happens in pkg/defaults/defaults.go. The public entry point is the FromConfig function, which is passed the ci-operator configuration, command-line arguments, and other types of input and generates a list of steps. The dependencies declared by each step as their inputs and outputs is then processed by BuildGraph in pkg/api/graph.go. Finally, the result is pruned by BuildPartialGraph in the same file based on which steps were requested via the --target arguments.

The execution of the graph starts with the list of root nodes: they are all started in parallel. As each finishes, dependent steps (following the edges of the graph) are started. At the end, post-steps are executed in the same manner.

In case of error, the sub-graph formed by the dependencies of the failed step is skipped. In case of interruption, a shared context is signaled and steps are allowed to continue. This is done in order to ensure proper cleanup is performed (e.g. post steps are executed, in the case of multi-stage tests). Steps usually return an error in this case, but otherwise execution proceeds as before.

Step types

This section describes each task type in pkg/steps: their purpose, the configuration section (or sections) that generate them, their inputs and outputs, etc.

With respect to the “generated by” items in this section, note that advanced users familiar with these types can also add any of them directly using the raw_steps field in the configuration file.

Build Steps

GitSourceStep

  • Source: git_source.go
  • Generated by: build_root.project_image
  • Configuration: ProjectDirectoryImageBuildInputs
  • Inputs: none
  • Outputs: the pipeline:root image

GitSourceStep is generated when the configuration’s build_root specifies that an image must be built from a Dockerfile in the repository under test (see the build root image section for details). It performs the image build and updates the corresponding pipeline ImageStream.

InputImageTagStep

  • Source: input_image_tag.go
  • Generated by:
    • build_root.from_repository
    • build_root.image_stream_tag
    • base_images
    • base_rpm_images
    • tests[*].steps.{pre,test,post}[*].from_image
  • Configuration: InputImageTagStepConfiguration
  • Inputs: none
  • Outputs: a tag in the pipeline ImageStream

InputImageTagStep is analogous to the oc tag command: it creates an ImageStreamTag in the pipeline ImageStream based on its input configuration.

The name of the resulting tag depends on the originator of the step:

  • build_root.*: the root tag is always the target
  • base_images: the tag is the alias defined in the configuration
  • base_rpm_images: as base_images, but -without-rpms is appended to the alias (the real tag will be created by RPMImageInjectionStep)
  • multi-stage tests: the name is derived from the input tag

These names must be unique across the entire configuration: this is enforced by static validation.

SourceStep

  • Source: source.go
  • Generated by: refs or extra_refs in $JOB_SPEC
  • Configuration: SourceStepConfiguration
  • Inputs: the pipeline:root image
  • Outputs: the pipeline:src image

SourceStep takes the image defined in build_root and clones the source code of the repository under test into it. The resulting image is tagged as src in the pipeline ImageStream. This cloning process uses the clonerefs tool from kubernetes/test-infra.

PipelineImageCacheStep

  • Source: pipeline_image_cache.go
  • Generated by:
    • binary_build_commands
    • test_binary_build_commands
    • rpm_build_commands
  • Configuration: PipelineImageCacheStepConfiguration
  • Inputs:
    • binary_build_commands: the pipeline:source image
    • test_binary_build_commands: the pipeline:source image
    • rpm_build_commands: the pipeline:bin image, or pipeline:src if no binary_build_commands are defined
  • Outputs:
    • binary_build_commands: the pipeline:bin image
    • test_binary_build_commands: the pipeline:test-bin image
    • rpm_build_commands: the pipeline:rpms image

PipelineImageCacheStep performs an image build at a predefined place in the pipeline based on commands defined in each of the configuration fields mentioned above.

RPMServerStep

  • Source: rpm_server.go
  • Generated by: rpm_build_commands
  • Configuration: RPMServeStepConfiguration
  • Inputs: the pipeline:rpms image
  • Outputs: an RPM web server used by RPMImageInjectionStep

RPMServerStep creates a Deployment and Route for a web server that serves the RPM packages contained in the pipeline:rpms image.

RPMImageInjectionStep

  • Source: rpm_injection.go
  • Generated by: base_rpm_images
  • Configuration: RPMImageInjectionStepConfiguration
  • Inputs:
    • the image in the pipeline ImageStream created by InputImageTagStep, named by appending -without-rpms to the alias defined in the configuration file
    • the RPM server created by RPMServerStep
  • Outputs: an image in the pipeline ImageStream, named by the alias defined in the configuration file

RPMImageInjectionStep builds a new image from the base image tagged by InputImageTagStep. The new image has the public endpoint of the route created by RPMServerStep in its list of RPM servers.

ProjectDirectoryImageBuildStep

  • Source: project_image.go
  • Generated by:
    • images
    • operator.bundles
  • Configuration: ProjectDirectoryImageBuildStepConfiguration
  • Inputs:
    • the pipeline:src image
    • images: the pipeline images referenced in from and inputs, if defined
    • operator.bundles:
      • bundle image: the pipeline:src-bundle image
      • index image: the corresponding ci-index-*-gen pipeline image
  • Outputs: an image in the pipeline ImageStream

ProjectDirectoryImageBuildStep performs an image build using either an inline Dockerfile or one from the repository’s source code.

OutputImageTagStep

  • Source: output_image_tag.go
  • Generated by: images
  • Configuration: OutputImageTagStepConfiguration
  • Inputs:
    • the pipeline image referenced in from
    • the populated stable ImageStream
  • Outputs: a tag in the stable ImageStream

OutputImageTagStep tags images that were built earlier in the pipeline into the stable ImageStream so that the test environment reflects the state of the release after the changes are integrated.

ImagesReadyStep

  • Source: images_ready.go
  • Generated by: every configuration
  • Inputs: all steps that import or build images
  • Outputs: the signal that all image imports and builds have finished

ImagesReadyStep is a synthetic step added to the graph to generate the correct dependencies between steps that import or build images and the ones that consume all images.

Release Steps

ReleaseImagesTagStep

  • Source: release_images.go
  • Generated by: tag_specification or releases.integration
  • Configuration: ReleaseImagesTagStepConfiguration
  • Inputs: none
  • Outputs: the populated initial and stable ImageStreams

ReleaseImagesTagStep creates the initial and stable ImageStreams and imports all tags from the configured input.

ImportReleaseStep

  • Source: import_release.go
  • Generated by:
    • tag_specification or releases.integration when a RELEASE_IMAGE_INITIAL or RELEASE_IMAGE_LATEST environment variable is present
    • releases
  • Configuration: ReleaseImagesTagStepConfiguration/ResolvedReleaseImagesStepConfiguration
  • Inputs: via tag_specification or releases.integration:
  • Outputs:
    • the populated corresponding stable* ImageStream
    • the corresponding release payload image

ImportReleaseStep populates a stable ImageStream with tags from an existing ImageStream and creates a release payload image based on it. The name of the stable ImageStream and the release payload ImageStreamTag depends on which release is being imported:

  • tag_specification: initial or latest
  • releases: the name in the configuration

AssembleReleaseStep

  • Source: create_release.go
  • Generated by: tag_specification or releases.integration when not overridden by an environment variable
  • Configuration: ReleaseImagesTagStepConfiguration
  • Inputs:
    • for latest: the ImagesReadyStep step
    • otherwise: the corresponding stable* ImageStream
  • Outputs: the corresponding release payload image

AssembleReleaseStep creates a release payload image from an existing stable ImageStream.

StableImagesTagStep

  • Source: release_images.go
  • Generated by: configurations without tag_specification or releases
  • Inputs: none
  • Outputs: the stable ImageStream

StableImagesTagStep is a simple step which creates an empty stable ImageStream if no other step does it.

Operator Steps

BundleSourceStep

  • Source: bundle_source.go
  • Generated by: operator
  • Configuration: BundleSourceStepConfiguration
  • Inputs:
    • the pipeline:src image
    • each image referenced in operator.substitutions
  • Outputs: the pipeline:src-bundle image

BundleSourceStep builds an image based on the pipeline:src image with the replacements defined in the operator.substitutions field applied to all operator manifests. Pullspecs in the manifests are replaced with references to the images imported to or built in the test namespace.

IndexGeneratorStep

  • Source: index_generator.go
  • Generated by: operator.bundles
  • Configuration: IndexGeneratorStepConfiguration
  • Inputs:
    • the image referenced by base_index, if defined
    • named bundles: the corresponding pipeline image
    • unnamed bundles: all unnamed bundle pipeline images
  • Outputs: the corresponding ci-index*-gen pipeline image

IndexGeneratorStep builds an image to be used used as the base image for the final bundle index. One step is created for each named bundle in operator.bundles while a single step is created for all unnamed bundles.

Auxiliary Steps

PodStep

PodStep is never added to the step graph. It is intended to be used by other steps that need to execute a Pod as part of their implementation.

LeaseStep

  • Source: lease.go
  • Generated by:
    • template tests that declare the special USE_LEASE_CLIENT parameter
    • multi-stage tests that either
      • declare a cluster profile
      • have a leases section or include any registry component that does
      • openshift_installer tests with upgrade: true
  • Inputs: see description
  • Outputs: see description

LeaseStep wraps an existing step in the graph and adds resource lease acquisition and release around its execution. Inputs and outputs are the same as the inner step.

ClusterClaimStep

  • Source: cluster_claim.go
  • Generated by: tests[*].cluster_claim
  • Configuration: ClusterClaim
  • Inputs: see description
  • Outputs: see description

ClusterClaimStep wraps an existing step in the graph and adds cluster claim acquisition and release around its execution. Inputs and outputs are the same as the inner step.

InputEnvironmentStep

  • Source: input_env.go
  • Generated by: specific environment variables
  • Inputs: none
  • Outputs: see description

InputEnvironmentStep replaces another step when all of its outputs are provided as environment variable. The original step is not executed and all of its output signals are emitted.

Test Steps

TestStep

  • Source: pod.go
  • Generated by: tests[*].container
  • Configuration: TestStepConfiguration
  • Inputs:
    • with from: the pipeline image referenced in from
    • otherwise: the ImagesReadyStep step
  • Outputs: none

TestStep executes a simple container test.

MultiStageTestStep

  • Source: multi_stage.go
  • Generated by: tests[*].steps
  • Configuration: TestStepConfiguration
  • Inputs:
    • the images referenced in from, from_image, and dependencies in each step (the configuration is fully resolved at this point), which can be either pipeline or release images
    • the stable ImageStream referenced by the cli field in steps, if defined
    • the latest release payload image if a cluster profile is defined
  • Outputs: none

MultiStageTestStep executes a multi-stage test.

TemplateExecutionStep

  • Source: template.go
  • Generated by: --template arguments
  • Inputs: determined by the template parameters
  • Outputs: none

TemplateExecutionStep executes one of the legacy tests based on Openshift templates. It is mentioned for completeness but has been replaced by multi-stage tests.

E2ETestStep

  • Source: clusterinstall.go
  • Generated by: tests[*].openshift_installer.upgrade
  • Configuration: TestStepConfiguration
  • Inputs:
    • see TemplateExecutionStep
    • the initial release payload image
  • Outputs: none

E2ETestStep is part of a discontinued attempt to improve template tests. It is mentioned for completeness but has been replaced by multi-stage tests.

Output Steps

WriteParametersStep

  • Source: write_params.go
  • Generated by: --write-params argument
  • Inputs: all steps
  • Output: none

WriteParametersStep is a debug step used to dump the input parameters to template tests.

PromotionStep

  • Source: promote.go
  • Generated by: --promote argument
  • Configuration: PromotionConfiguration
  • Inputs: all steps
  • Outputs: none

PromotionStep is used in post-submit jobs to export the newly-built repository images to the configured ImageStream. See the promotion section for details.