You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
5.9 KiB

pipeline: Builder and stage implementation Provides `PipelineBuilder` for reading `.pipeline/config.yaml` and mapping user-defined pipelines, stages, and execution graphs to actual Jenkins Pipeline stage definitions. Provides `Pipeline` class that constructs a "stack" of `PipelineStage` objects from the user-provided configs, each with its own `NodeContext` for binding output values to names and consuming bound values from previous stages. Provides `PipelineStage` that contains core stage step implementations based on the existing `service-pipeline` JJB job definition in `integration/config`. A closure is returned by each stage for passing off to Jenkins Pipeline stage definitions by the builder. Steps have a fixed order within a given stage: build, run, publish, deploy, exports. This allows for concise definition of a stage that performs multiple steps, and deterministic behavior of default configuration that references locally bound output values (e.g. the default configuration of `image:` for an `publish: { type: image }` publish entry is `${.imageID}`, referencing the image built in the current stage's `build` step.) If the user needs to change ordering, they can simply break the stage out into multiple stages. See the `Pipeline` class for currently supported configuration. Note that the aforementioned context system allows for user's to make use of the same value bindings that step implementations use internally. They can also use the `exports` configuration field to bind new values. To illustrate the minimally required configuration, the following would approximate the current `service-pipeline-test-and-publish` JJB job for a project named "foo". pipelines: foo: directory: src/foo stages: - name: test # builds/runs "test" variant - name: candidate build: production publish: image: true deploy: # currently only the "ci" cluster chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true And to illustrate how the "candidate" stage in this example could be expressed as multiple stages using references to the output names that steps bind/export: pipelines: foo: directory: src/foo stages: - name: tested - name: built build: production - name: published publish: image: id: '${built.imageID}' exports: image: '${.imageFullName}:${.imageTag}' - name: staged deploy: image: '${published.image}' chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true Bug: T210267 Change-Id: I5a41d0d33ed7e9174db6178ab7921f5143296c75
5 years ago
pipeline: Builder and stage implementation Provides `PipelineBuilder` for reading `.pipeline/config.yaml` and mapping user-defined pipelines, stages, and execution graphs to actual Jenkins Pipeline stage definitions. Provides `Pipeline` class that constructs a "stack" of `PipelineStage` objects from the user-provided configs, each with its own `NodeContext` for binding output values to names and consuming bound values from previous stages. Provides `PipelineStage` that contains core stage step implementations based on the existing `service-pipeline` JJB job definition in `integration/config`. A closure is returned by each stage for passing off to Jenkins Pipeline stage definitions by the builder. Steps have a fixed order within a given stage: build, run, publish, deploy, exports. This allows for concise definition of a stage that performs multiple steps, and deterministic behavior of default configuration that references locally bound output values (e.g. the default configuration of `image:` for an `publish: { type: image }` publish entry is `${.imageID}`, referencing the image built in the current stage's `build` step.) If the user needs to change ordering, they can simply break the stage out into multiple stages. See the `Pipeline` class for currently supported configuration. Note that the aforementioned context system allows for user's to make use of the same value bindings that step implementations use internally. They can also use the `exports` configuration field to bind new values. To illustrate the minimally required configuration, the following would approximate the current `service-pipeline-test-and-publish` JJB job for a project named "foo". pipelines: foo: directory: src/foo stages: - name: test # builds/runs "test" variant - name: candidate build: production publish: image: true deploy: # currently only the "ci" cluster chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true And to illustrate how the "candidate" stage in this example could be expressed as multiple stages using references to the output names that steps bind/export: pipelines: foo: directory: src/foo stages: - name: tested - name: built build: production - name: published publish: image: id: '${built.imageID}' exports: image: '${.imageFullName}:${.imageTag}' - name: staged deploy: image: '${published.image}' chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true Bug: T210267 Change-Id: I5a41d0d33ed7e9174db6178ab7921f5143296c75
5 years ago
pipeline: Builder and stage implementation Provides `PipelineBuilder` for reading `.pipeline/config.yaml` and mapping user-defined pipelines, stages, and execution graphs to actual Jenkins Pipeline stage definitions. Provides `Pipeline` class that constructs a "stack" of `PipelineStage` objects from the user-provided configs, each with its own `NodeContext` for binding output values to names and consuming bound values from previous stages. Provides `PipelineStage` that contains core stage step implementations based on the existing `service-pipeline` JJB job definition in `integration/config`. A closure is returned by each stage for passing off to Jenkins Pipeline stage definitions by the builder. Steps have a fixed order within a given stage: build, run, publish, deploy, exports. This allows for concise definition of a stage that performs multiple steps, and deterministic behavior of default configuration that references locally bound output values (e.g. the default configuration of `image:` for an `publish: { type: image }` publish entry is `${.imageID}`, referencing the image built in the current stage's `build` step.) If the user needs to change ordering, they can simply break the stage out into multiple stages. See the `Pipeline` class for currently supported configuration. Note that the aforementioned context system allows for user's to make use of the same value bindings that step implementations use internally. They can also use the `exports` configuration field to bind new values. To illustrate the minimally required configuration, the following would approximate the current `service-pipeline-test-and-publish` JJB job for a project named "foo". pipelines: foo: directory: src/foo stages: - name: test # builds/runs "test" variant - name: candidate build: production publish: image: true deploy: # currently only the "ci" cluster chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true And to illustrate how the "candidate" stage in this example could be expressed as multiple stages using references to the output names that steps bind/export: pipelines: foo: directory: src/foo stages: - name: tested - name: built build: production - name: published publish: image: id: '${built.imageID}' exports: image: '${.imageFullName}:${.imageTag}' - name: staged deploy: image: '${published.image}' chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true Bug: T210267 Change-Id: I5a41d0d33ed7e9174db6178ab7921f5143296c75
5 years ago
pipeline: Builder and stage implementation Provides `PipelineBuilder` for reading `.pipeline/config.yaml` and mapping user-defined pipelines, stages, and execution graphs to actual Jenkins Pipeline stage definitions. Provides `Pipeline` class that constructs a "stack" of `PipelineStage` objects from the user-provided configs, each with its own `NodeContext` for binding output values to names and consuming bound values from previous stages. Provides `PipelineStage` that contains core stage step implementations based on the existing `service-pipeline` JJB job definition in `integration/config`. A closure is returned by each stage for passing off to Jenkins Pipeline stage definitions by the builder. Steps have a fixed order within a given stage: build, run, publish, deploy, exports. This allows for concise definition of a stage that performs multiple steps, and deterministic behavior of default configuration that references locally bound output values (e.g. the default configuration of `image:` for an `publish: { type: image }` publish entry is `${.imageID}`, referencing the image built in the current stage's `build` step.) If the user needs to change ordering, they can simply break the stage out into multiple stages. See the `Pipeline` class for currently supported configuration. Note that the aforementioned context system allows for user's to make use of the same value bindings that step implementations use internally. They can also use the `exports` configuration field to bind new values. To illustrate the minimally required configuration, the following would approximate the current `service-pipeline-test-and-publish` JJB job for a project named "foo". pipelines: foo: directory: src/foo stages: - name: test # builds/runs "test" variant - name: candidate build: production publish: image: true deploy: # currently only the "ci" cluster chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true And to illustrate how the "candidate" stage in this example could be expressed as multiple stages using references to the output names that steps bind/export: pipelines: foo: directory: src/foo stages: - name: tested - name: built build: production - name: published publish: image: id: '${built.imageID}' exports: image: '${.imageFullName}:${.imageTag}' - name: staged deploy: image: '${published.image}' chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true Bug: T210267 Change-Id: I5a41d0d33ed7e9174db6178ab7921f5143296c75
5 years ago
pipeline: Builder and stage implementation Provides `PipelineBuilder` for reading `.pipeline/config.yaml` and mapping user-defined pipelines, stages, and execution graphs to actual Jenkins Pipeline stage definitions. Provides `Pipeline` class that constructs a "stack" of `PipelineStage` objects from the user-provided configs, each with its own `NodeContext` for binding output values to names and consuming bound values from previous stages. Provides `PipelineStage` that contains core stage step implementations based on the existing `service-pipeline` JJB job definition in `integration/config`. A closure is returned by each stage for passing off to Jenkins Pipeline stage definitions by the builder. Steps have a fixed order within a given stage: build, run, publish, deploy, exports. This allows for concise definition of a stage that performs multiple steps, and deterministic behavior of default configuration that references locally bound output values (e.g. the default configuration of `image:` for an `publish: { type: image }` publish entry is `${.imageID}`, referencing the image built in the current stage's `build` step.) If the user needs to change ordering, they can simply break the stage out into multiple stages. See the `Pipeline` class for currently supported configuration. Note that the aforementioned context system allows for user's to make use of the same value bindings that step implementations use internally. They can also use the `exports` configuration field to bind new values. To illustrate the minimally required configuration, the following would approximate the current `service-pipeline-test-and-publish` JJB job for a project named "foo". pipelines: foo: directory: src/foo stages: - name: test # builds/runs "test" variant - name: candidate build: production publish: image: true deploy: # currently only the "ci" cluster chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true And to illustrate how the "candidate" stage in this example could be expressed as multiple stages using references to the output names that steps bind/export: pipelines: foo: directory: src/foo stages: - name: tested - name: built build: production - name: published publish: image: id: '${built.imageID}' exports: image: '${.imageFullName}:${.imageTag}' - name: staged deploy: image: '${published.image}' chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true Bug: T210267 Change-Id: I5a41d0d33ed7e9174db6178ab7921f5143296c75
5 years ago
pipeline: Builder and stage implementation Provides `PipelineBuilder` for reading `.pipeline/config.yaml` and mapping user-defined pipelines, stages, and execution graphs to actual Jenkins Pipeline stage definitions. Provides `Pipeline` class that constructs a "stack" of `PipelineStage` objects from the user-provided configs, each with its own `NodeContext` for binding output values to names and consuming bound values from previous stages. Provides `PipelineStage` that contains core stage step implementations based on the existing `service-pipeline` JJB job definition in `integration/config`. A closure is returned by each stage for passing off to Jenkins Pipeline stage definitions by the builder. Steps have a fixed order within a given stage: build, run, publish, deploy, exports. This allows for concise definition of a stage that performs multiple steps, and deterministic behavior of default configuration that references locally bound output values (e.g. the default configuration of `image:` for an `publish: { type: image }` publish entry is `${.imageID}`, referencing the image built in the current stage's `build` step.) If the user needs to change ordering, they can simply break the stage out into multiple stages. See the `Pipeline` class for currently supported configuration. Note that the aforementioned context system allows for user's to make use of the same value bindings that step implementations use internally. They can also use the `exports` configuration field to bind new values. To illustrate the minimally required configuration, the following would approximate the current `service-pipeline-test-and-publish` JJB job for a project named "foo". pipelines: foo: directory: src/foo stages: - name: test # builds/runs "test" variant - name: candidate build: production publish: image: true deploy: # currently only the "ci" cluster chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true And to illustrate how the "candidate" stage in this example could be expressed as multiple stages using references to the output names that steps bind/export: pipelines: foo: directory: src/foo stages: - name: tested - name: built build: production - name: published publish: image: id: '${built.imageID}' exports: image: '${.imageFullName}:${.imageTag}' - name: staged deploy: image: '${published.image}' chart: https://releases.wikimedia.org/charts/foo-0.0.1.tgz test: true Bug: T210267 Change-Id: I5a41d0d33ed7e9174db6178ab7921f5143296c75
5 years ago
  1. package org.wikimedia.integration
  2. import org.codehaus.groovy.GroovyException
  3. import static org.wikimedia.integration.PipelineStage.*
  4. import org.wikimedia.integration.ExecutionContext
  5. import org.wikimedia.integration.ExecutionGraph
  6. import org.wikimedia.integration.PipelineStage
  7. import org.wikimedia.integration.PipelineRunner
  8. /**
  9. * Defines a Jenkins Workflow based on a given configuration.
  10. *
  11. * The given configuration should look like this:
  12. *
  13. * <pre><code>
  14. * pipelines:
  15. * serviceOne:
  16. * blubberfile: serviceOne/blubber.yaml # default based on service name for the dir
  17. * directory: src/serviceOne
  18. * execution: # directed graph of stages to run
  19. * - [unit, candidate] # each arc is represented horizontally
  20. * - [lint, candidate]
  21. * - [candidate, staging, production] # common segments of arcs can be defined separately too
  22. * stages: # stage defintions
  23. * - name: unit # stage name (required)
  24. * build: phpunit # build an image variant
  25. * run: "${.imageID}" # run the built image
  26. * publish:
  27. * files: # publish select artifact files from the built/run image
  28. * paths: ["foo/*", "bar"] # copy files {foo/*,bar} from the image fs to ./artifacts/{foo/*,bar}
  29. * - name: lint # default (build/run "lint" variant, no artifacts, etc.)
  30. * - name: candidate
  31. * build: production
  32. * publish:
  33. * image: # publish built image to our docker registry
  34. * id: "${.imageID}" # image reference
  35. * name: "${setup.project}" # image name
  36. * tag: "${setup.timestamp}-${.stage}" # primary tag
  37. * tags: [candidate] # additional tags
  38. * exports: # export stage values under new names
  39. * image: "${.imageFullName}:${.imageTag}" # new variable name and interpolated value
  40. * - name: staging
  41. * deploy: # deploy image to a cluster
  42. * image: "${candidate.image}" # image name:tag reference
  43. * cluster: ci # default "ci" k8s cluster
  44. * chart: http://helm/chart # helm chart to use for deployment
  45. * test: true # run `helm test` on deployment
  46. * - name: production
  47. * deploy:
  48. * cluster: production
  49. * chart: http://helm/chart
  50. * serviceTwo:
  51. * directory: src/serviceTwo
  52. * </code></pre>
  53. */
  54. class Pipeline implements Serializable {
  55. String name
  56. String blubberfile
  57. String directory
  58. String dockerRegistry
  59. String dockerRegistryInternal
  60. private Map stagesConfig
  61. private List<List> execution
  62. /**
  63. * Constructs a new pipeline with the given name and configuration.
  64. */
  65. Pipeline(String pipelineName, Map config) {
  66. name = pipelineName
  67. blubberfile = config.blubberfile ?: "${name}/blubber.yaml"
  68. directory = config.directory ?: "."
  69. stagesConfig = config.stages.collectEntries{
  70. [(it.name): PipelineStage.defaultConfig(it)]
  71. }
  72. execution = config.execution ?: [config.stages.collect { it.name }]
  73. }
  74. /**
  75. * Returns a set of node labels that will be required for this pipeline to
  76. * function correctly.
  77. */
  78. Set getRequiredNodeLabels() {
  79. def labels = [] as Set
  80. for (def nodes in stack()) {
  81. for (def node in nodes) {
  82. labels += node.getRequiredNodeLabels()
  83. }
  84. }
  85. labels
  86. }
  87. /**
  88. * Returns the pipeline's stage stack bound with an execution context.
  89. */
  90. List stack() {
  91. def graph = setup() + (new ExecutionGraph(execution)) + teardown()
  92. def context = new ExecutionContext(graph)
  93. graph.stack().collect {
  94. it.collect { stageName ->
  95. createStage(stageName, context.ofNode(stageName))
  96. }
  97. }
  98. }
  99. /**
  100. * Returns a {@link PipelineRunner} for this pipeline and the given workflow
  101. * script object.
  102. */
  103. PipelineRunner runner(ws) {
  104. def settings = [
  105. blubberConfig: blubberfile,
  106. kubeConfig: "/etc/kubernetes/ci-staging.config",
  107. ]
  108. if (dockerRegistry) {
  109. settings["registry"] = dockerRegistry
  110. }
  111. if (dockerRegistryInternal) {
  112. settings["registryInternal"] = dockerRegistryInternal
  113. }
  114. def runner = new PipelineRunner(settings, ws)
  115. // make the PipelineRunner configPath relative to the pipeline's directory
  116. def prefix = "../" * directory.split('/').count { !(it in ["", "."]) }
  117. runner.configPath = prefix + runner.configPath
  118. runner
  119. }
  120. /**
  121. * Validates the pipeline configuration, throwing a {@link ValidationException}
  122. * if anything is amiss.
  123. */
  124. void validate() throws ValidationException {
  125. def errors = []
  126. // TODO expand validation
  127. if (PipelineStage.SETUP in stagesConfig) {
  128. errors += "${PipelineStage.SETUP} is a reserved stage name"
  129. }
  130. if (PipelineStage.TEARDOWN in stagesConfig) {
  131. errors += "${PipelineStage.TEARDOWN} is a reserved stage name"
  132. }
  133. if (errors) {
  134. throw new ValidationException(errors: errors)
  135. }
  136. }
  137. private ExecutionGraph setup() {
  138. new ExecutionGraph([[PipelineStage.SETUP]])
  139. }
  140. private ExecutionGraph teardown() {
  141. new ExecutionGraph([[PipelineStage.TEARDOWN]])
  142. }
  143. private PipelineStage createStage(stageName, context) {
  144. new PipelineStage(
  145. this,
  146. stageName,
  147. stagesConfig[stageName] ? stagesConfig[stageName] : [:],
  148. context,
  149. )
  150. }
  151. class ValidationException extends GroovyException {
  152. def errors
  153. String getMessage() {
  154. def msgs = errors.collect { " - ${it}" }.join("\n")
  155. "Pipeline configuration validation failed:\n${msgs}"
  156. }
  157. }
  158. }