Robert Ninness
ServiceNow Employee

Inspired by Christine's article on GitLab, I thought I would share my musings with Azure DevOps on the same topic.

CI: Continuous Integration

Depends who you talk to, but CI mostly refers to testing and validation of new code to be included in a software project. The most common way to do this is via a pull request either from a branch on the same repo, or from a fork in the context of community development.

My pipeline differs slightly from Christine's. Here I am also connecting the test instance to source control, by importing the application. This has an advantage that I can directly check out the development branch in test and run ATF. If there is an issue with the build, instead of publishing a new version of the app, I can simply pull the new changes into test and re-run ATF. It's not like we are "paying by the version number", but it does means we are only publishing an app to the app store if it is ready to go further than test. This saves an administrative task in App Repo to delete broken app versions. If you manually install or upgrade the app in Prod, you won't have a long dropdown of app versions.

Triggering build validation

In ADO you can set up branch policies to restrict commits on the primary branch. Only validated and approved pull requests will be allowed to merge code. Direct commits are restricted.

find_real_file.png

find_real_file.png

"Build" is a rather loose term here when it comes to ServiceNow as there is not really a compiled executable to be built in a traditional sense. In addition, the primary reason for CI is integration of new code into existing code. How do we validate that the new code doesn't break existing code? We test.

The most important phase in this pipeline is running ATF (we should add a YAML formatter for the script block):

pool:
  vmImage: 'ubuntu-latest'

trigger:
- master

variables:
  primary_branch: 'main'
  test: 'appe_cicdtest'
  app: 'd2876156db1a60107727ecebd396197c'
  repo: '150523581b1c15101ec01f49274bcbe0'
  test_suite: 'f9f279dadb1a60107727ecebd3961900'
  branch: $(Build.SourceBranch)
  branch_name: $(Build.SourceBranchName)
  origin: $(System.PullRequest.SourceBranch)
  origin_name: $[replace(variables['System.PullRequest.SourceBranch'], 'refs/heads/', '')]

stages:
- stage: Test
  condition: ne(variables['Build.SourceBranch'], 'refs/heads/master')
  jobs:
  - job: CheckoutInTest_RunTest
    steps:
    - task: ServiceNow-CICD-SC-Apply@1
      inputs:
        connectedServiceName: $(test)
        appSysId: $(app)
        branchName: $(primary_branch)
    - task: ServiceNow-CICD-SC-Apply@1
      inputs:
        connectedServiceName: $(test)
        appSysId: $(app)
        branchName: $(origin_name)
    - task: ServiceNow-CICD-TestSuite-Run@1
      inputs:
        connectedServiceName: $(test)
        testSuiteSysId: '$(test_suite)'
        browserName: 'any'

Notes on source control with ServicenNow

When using source control and ServiceNow, there is a limitation of one branch checked out at a time on an instance. This makes sense because an instances can't hold two versions of an app at the same time. To do this on a laptop, with traditional software, you would have two different directories with the repo cloned twice. Two separate instances of the code and two separate runtimes.

This nuance with source control means that if a new branch is created in the dev environment, it will not yet be available in test until we "apply remote changes". There is no way in the CICD API to perform a fetch or Refresh (but there is in the studio VSC api /api/sn_devstudio/v1/vcs/apps/{appId}/repos/{repoId}/refresh) This is why in my example there are two Apply steps, once on the primary branch, (which will run a fetch, checkout main and pull) then a second apply to checkout the branch which triggered this pull request. If we tried to checkout the new branch, an error would be thrown because test doesn't know about the branch yet, weird I know but it's something to just work around for now.

Is this real CI validation?

If there is one downside to this method, it's that the branch we are testing is not the "fully integrated" branch. Most hosted source control providers will present a kind of staging branch for CI validation to happen on. These are named in ADO something like refs/pull/21/merge ServiceNow doesn't track this reference. You cannot check this out in your test instance. Instead we have to checkout the origin branch. Provided the origin branch is up to date with the target branch, we are validating the most recent code from both. If the origin branch is behind (someone has committed to the target branch in the meantime), the tests may miss something.

This also limits the usefulness of CI pipelines in general, wether built in ADO, GitHub Actions or GitLab etc. One of the nice things about pull request validation is that each time the target branch is updated, ADO will re-trigger the validation pipeline on the pull request. If we are just checking out the origin branch, we are running validation on the same code each time the build is re-triggered, testing nothing new.

Regardless, there may still be some value in setting up a CI pipeline, but that's up to the development team and their ways of working.

That's all I have time for right now. Tomorrow I'll try to write up the CD portion of my pipeline. I'll edit this blog with a link to it when I get around to it.