PipelineLib/Tutorial/TestingHelloNode using multiple stages

From Wikitech

Welcome to Testing Hello Node (with multiple stages), a slightly more advanced PipelineLib tutorial than the previous Testing Hello Node tutorial.

Prerequisites

Learning objectives

At the end of this tutorial, you wll understand:

  1. How to define more re-usable Blubber variants.
  2. How to split up discrete CI build, test, and lint tasks into multiple pipeline stages that execute serially.
  3. How to pass arguments to image variants.
  4. How to utilize output from one pipeline stage in subsequent stages.

Tutorial

The basic steps to this tutorial will be to:

  1. Set up the example repo.
  2. Modify the repo's existing Blubber configuration to provide a more generic test runner variant.
  3. Define a pipeline stage that builds the test-runner variant.
  4. Define additional pipeline stages that execute the test-runner's lint and test scripts.
  5. Submit your change to Gerrit.
  6. Observe how Jenkins executes the project's multi-stage test pipeline.
  7. Clean up.

Set up the example repo

If you've already set up the repo as part of the previous tutorial, you can safely skip this step.
dev@laptop:~$ git clone ssh://gerrit.wikimedia.org:29418/blubber-doc/example/helloworldoid blubber-doc/example/helloworldoid
dev@laptop:~$ cd blubber-doc/example/helloworldoid
dev@laptop:helloworldoid$ curl -o .git/hooks/commit-msg https://gerrit.wikimedia.org/r/tools/hooks/commit-msg
dev@laptop:helloworldoid$ chmod +x .git/hooks/commit-msg

Modify the existing test variant to run linters

In the previous tutorial, we instructed our CI system to execute the project's test variant. Let's look again at that variant in .pipeline/blubber.yaml.

dev@laptop:helloworldoid$ grep -A 2 ' test:' .pipeline/blubber.yaml
  test:
    includes: [build]
    entrypoint: [npm, test]

Again, it's a really simple variant that simply runs npm test, which in NPM speak is really just a shortcut for npm run-script test.

Does the project perhaps have another script defined in package.json for running its linters?

dev@laptop:helloworldoid$ grep -A 4 scripts package.json
  "scripts": {
    "lint": "eslint --ignore-path .gitignore .",
    "systemtest": "mocha ./test/system.js",
    "test": "mocha ./test/unit.js"
  },

Indeed it does, and the script is creatively named lint.

At this point, we have options. One would be to copy/paste the existing test variant and change [npm, test] to [npm, run-script, lint]. But what if there are other scripts to run in the future—like that temptatious systemtest one? We'd eventually end up with a bunch of strikingly similar image variants for doing almost the same thing aside from differences between a single argument.

Let's instead modify the existing test variant to be able to run any project script. In .pipeline/blubber.yaml, change this.

  test:
    includes: [build]
    entrypoint: [npm, test]

To this.

  script:
    includes: [build]
    entrypoint: [npm, run-script]

A very generic variant called script for running any script defined in package.json. Wee.

Define a stage to build the script runner

On to CI orchestration! It's what we're really here for after all.

Similar to how we defined our single build-and-run-tests stage in the previous tutorial, we're going to open up .pipeline/config.yaml and define a single stage for now.

dev@laptop:helloworldoid$ vim .pipeline/config.yaml # or emacs; flame on
pipelines:
  test:
    blubberfile: blubber.yaml
    stages:
      - name: build
        build: script

Line by line, we are:

  1. Defining a new pipeline named test.
  2. Telling CI where our image variants are defined.
  3. Defining a new stage in our pipeline called build.
  4. Specifying that we'd like that stage to build our generic script image variant.
Note that unlike in the previous tutorial, there's no run: true below line 6. We don't want to execute the script-runner variant in this stage, just build it.

Define additional test and lint stages

Now that we have a stage that will build our generic script-runner image, let's create two more stages for the dual purposes of running our unit tests and running our linter using that image variant.

pipelines:
  test:
    blubberfile: blubber.yaml
    stages:
      - name: build
        build: script
      - name: test
        run:
          image: '${build.imageID}'
          arguments: [test]
      - name: lint
        run:
          image: '${build.imageID}'
          arguments: [lint]

Line by line, we are:

  1. Defining a new stage in our pipeline called test.
  2. Specifying that this stage should run the image built in the build stage (script) and pass it the argument test.
  3. Defining a new stage in our pipeline called lint.
  4. Specifying that this stage should run the image built in the build stage (script) and pass it the argument lint.
You've probably already noticed the goofy looking ${build.imageID} placeholders. You can read a lot more about those in PiplineLib concepts, but essentially they are replaced with the ID of the image built in the build stage.

Submit our change and watch our pipeline execute

Now that we have a .pipeline/config.yaml that will tell our CI system to (serially) execute both our project's unit-test suite and linter using a common script-runner image, let's push up our change up to Gerrit and watch Jenkins run our three-stage test pipeline.

dev@laptop:helloworldoid$ git add .pipeline/{blubber,config}.yaml
dev@laptop:helloworldoid$ git commit -m 'tutorial2: Configure CI to run tests and linters'
dev@laptop:helloworldoid$ git push origin HEAD:refs/for/master

Head over to the helloworldoid-pipeline-test job to see its progress!

Clean up and move on

Help us to keep Gerrit tidy by abandoning your change!

If you're ready to learn more about PipelineLib, move on to the next tutorial, Testing Hello Node (using parallel_execution). Otherwise, you can reset your local working copy to origin/master or commit your changes in a branch—whatever you like!