27

Imagine you have the following Pipeline:

Job A (deploy) -> Job B (test) -> Job C (remove test deployment)

The pipeline should deploy a test image and test it after a successful deployment. After the test I want to run a cleanup script regardless of the test output, but only if the test image (Job A) was deployed.

To summarize this: I want Gitlab to execute Job C only if Job A succeeds, but after Job B.

Things that won't work:

  • when: on-failure (Job A or Job B could failed, but only Job A is important)
  • when: always (maybe Job A failed which causes Job C to fail)
  • when: on-success (requires all jobs to succeed)

I know that GitLab has a feature called DAG Pipelines which allow you to specify multiple dependencies on other jobs with the needs keyword, but sadly the when keyword is always scoped to all prior jobs. So you are not able to say something like:

when:
    on-success: job-a
    always: job-b

Do I miss something or is there no way to achieve such a behaviour?

Sebi2020
  • 1,966
  • 1
  • 23
  • 40

4 Answers4

47

The needs DAG field can be used to conditionally execute the cleanup (Job C), if Job B fails or succeeds, but NOT when it is skipped because Job A failed.

Create 2 cleanup jobs that match the following boolean conditions:

  • (Job A succeeds and Job B succeeds): If all previous tasks succeed (Job A and Job B), we can run the cleanup with when: on_success. However, this will not trigger if Job A succeeds and Job B fails.
  • (Job A succeeds and Job B fails): To circumvent the previous scenario with an untriggered cleanup (Job C), we make use of the fact that if Job B fails, this implies that Job A succeeded in the pipeline. By creating a duplicate cleanup task and specifying a needs tag on Job B and when: on_failure, the cleanup task will only run if Job A succeeds and Job B fails.

To reiterate: a cleanup job will run if (Job A succeeds and Job B succeeds) or (Job A succeeds and Job B fails), which by boolean expression reduction is equivalent to (Job A succeeds).

An obvious caveat here is that there are now 2 cleanup jobs that are displayed in the pipeline; however, they are mutually exclusive and only one could ever be executed.

Here is a sample configuration:

stages:
  - deploy
  - test
  - cleanup

deploy_job:
  stage: deploy
  script:
    - echo Deployed
    - "true"
  when: always

test_job:
  stage: test
  script:
    - echo Executing tests
    - "true"
  when: on_success

# a YAML anchor reduces repetition
.cleanup_job: &cleanup_job
  stage: cleanup
  script:
    - echo Cleaned up deployment

cleanup_deployment_success:
  when: on_success
  <<: *cleanup_job

cleanup_deployment_failure:
  needs: ["test_job"]
  when: on_failure
  <<: *cleanup_job

With various intentional fail conditions, the following pipeline states are produced:

  • failed pipeline: Job A succeeds and Job B fails
  • failed pipeline: Job A fails and Job B is skipped
  • passed pipeline: Job A succeeds and Job B succeeds

Logically, this indicates that regardless of whether Job B succeeded or failed, Job C runs if Job A succeeded. Furthermore, the failure state is preserved in the overall pipeline.

concision
  • 6,029
  • 11
  • 29
  • Wouldn't this cause the pipeline to be in passed state, even if the tests have failed? I want the pipeline to be in failed state if one job fails. – Sebi2020 Oct 05 '20 at 09:19
  • @Sebi2020 Unfortunately, it seems so. If a job fails but the pipeline "passes", an orange-ish exclamation point with "passed" is used instead of a green checkmark "passed". I have updated my answer to demonstrate what that looks like. The `when` condition triggers are not very elaborate in GitLab CI. I will take another look and see if I can resolve the pipeline fail state, but I have some serious doubts. – concision Oct 05 '20 at 09:24
  • @Sebi2020 I updated my answer to exploit DAGs and the `needs` tag. Let me know if this satisfies your constraints/conditions. – concision Oct 05 '20 at 10:24
  • This is a workaround, i already have had this idea. but I have hoped that there is a cleaner way to achieve that, because this needs duplication of the same job just to circumvent the absent "when job scope". – Sebi2020 Oct 05 '20 at 10:46
  • 1
    @Sebi2020 Unfortunately, GitLab's CI system is _very_ rigid it its approach to pipelines. For this specified 3 stage pipeline, the only caveat here is a duplicate skipped cleanup job shows up in the pipeline. If I find another solution, I will update my answer. However, after reading documentation for over an hour, I am not particularly convinced there will be a cleaner solution until GitLab adds more advanced matching for the `when` tag. More often than not, most of my GitLab CI configurations end up with dirty solutions/hacks due to their disinterest in supporting non-trivial use cases. – concision Oct 05 '20 at 10:54
  • This is close to what I need - @concision have you found a way to have the stage flow pictured in the 1st image (pass > fail > pass) and yet mark the pipeline as passed? – GrayedFox Jan 30 '23 at 23:02
  • @GrayedFox If there is, I am not aware of it. However, you may be able to hack something together where instead of failing job B, mark it passed and set a flag that job C can use to determine if it should NO-OP. – concision Jan 30 '23 at 23:31
  • 1
    Great minds my friend. Currently attempting to use artifacts:when:always to store the exit code of the test script and output that to a build.env file so I can read the result in the next job. If it works I will post an answer here too. I like GitLab but my lord this is janky. – GrayedFox Jan 31 '23 at 00:45
  • What if I need to do this for "container_scanning" job for container scans provided by GitLab security? For example, the image tag that I use for the built image in the package stage depends on the commit SHA for a regular commit and the tag for a release. With container scanning, I must define the job name as "container_scanning" and specify the docker image and its tag (commit SHA or commit tag) as the `DOCKER_IMAGE` variable of the job. What should I do in this scenario? – Alex Feb 15 '23 at 04:54
5

The needs DAG field can be used to conditionally execute the cleanup (Job C), if Job B fails or succeeds, but NOT when it is skipped because Job A failed.

That might have changed with GitLab 13.11 (April 2021)

Optional DAG ('needs:') jobs in CI/CD pipelines

The directed acyclic graph (DAG) in GitLab CI/CD lets you use the needs syntax to configure a job to start earlier than its stage (as soon as dependent jobs complete). > We also have the rules, only, or except keywords, which determine if a job is added to a pipeline at all.

Unfortunately, if you combine needs with these other keywords, it’s possible that your pipeline could fail when a dependent job does not get added to a pipeline.

In this release, we are adding the optional keyword to the needs syntax for DAG jobs.

  • If a dependent job is marked as optional but not present in the pipeline, the needs job ignores it.
  • If the job is optional and present in the pipeline, the needs job waits for it to finish before starting.

This makes it much easier to safely combine rules, only, and except with the growing popularity of DAG.

https://about.gitlab.com/images/13_11/optional.png -- Optional DAG ('needs:') jobs in CI/CD pipelines

See Documentation and Issue.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
0

I would like to add another alternative. I am currently looking to implement this exact use case for e2e-tests and my preference will probably be to delay the reporting of test results by using artifacts. The setup would be something like this:

Job A (deploy) -> Job B (run test, collect results into artifact) -> Job C (undeploy) -> Job D (publish test results from artifact)

Job B would be configured to always be successful, and only save its findings as an artifact (e.g. JUnit xml). Job D would then publish the results and the pipeline would fail here if tests were unsuccessful. This should preserve pipeline failure state, even if Job C and Job D run unconditionally with the previously mentioned technique of duplicating the Job with multiple when: conditions.

0

You can make use of allow_failure: true

https://docs.gitlab.com/ee/ci/yaml/#allow_failure

JOB_B:
  stage: test
  allow_failure: true

This will continue the pipeline regardless the outcome of JOB B. If JOB A fails JOB B and JOB C will be skipped.

wasserholz
  • 1,960
  • 2
  • 20
  • 26
  • This _could_ add some confusion to the pipeline. Per the documentation: "If `allow_failure: true` is set, the job is always considered successful, and later jobs with `when: on_failure` don’t start if this job fails." – javafueled Aug 25 '23 at 12:46