Deploying with approval-based workflows
Fullstack Developer and Tech Author
When it comes to deploying features to production servers, most organizations operate using very strict guidelines. Many times, deployments to production require an approval step from a team lead or engineering manager.
In this tutorial, I’ll show you how to deploy an API to both a staging and a production environment, authorized by an approver on your team. You will create a continuous integration and continuous delivery (CI/CD) workflow that deploys the API to the staging environment, then runs automated tests using the Postman CLI tool, Newman. If the tests pass, the API is ready to be deployed. After a team member provides the approval, a deployment job will run to deploy the API to production. If the job is not approved, the API will not be deployed to production.
Prerequisites
To complete this tutorial, you will need:
- Node.js installed on your system
- Postman for Desktop installed on your system. You can download it here.
- A Heroku account
- A CircleCI account
- A GitHub account
When these items are installed and set up, you can begin the tutorial.
Cloning the API project
To begin, you will need to clone the API project. You will be working with a simple Node.js API application with a root endpoint and two other endpoints for creating and fetching users. Clone the project by running this command:
git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/deploying-api-with-approvals-heroku.git
When the cloning process is complete, go to the root of the project and install the dependencies:
cd deploying-api-with-approvals-heroku
npm install
Next, run the application:
npm start
The application will start listening on a default port of 3000
.
Open Postman and make a GET
request to the http://localhost:3000/users/get
endpoint. This should return an array of users.
Your API is ready to deploy.
Setting up staging
and deploy
environments on Heroku
The next task is to create the deployment environments. You will be creating one for staging
(where the application’s updates will be deployed, for testing) and deploy
(the production environment). You will create these environments on Heroku for this tutorial, but you can use the hosting platform you prefer.
Go to your Heroku account dashboard and click New, then Create new app. On the app creation page, create the staging
application environment.
Now, repeat the same process to create the production app.
The environments are now set up to for deploying the application. Finally, on Heroku, get your API key from the Account tab on the Account Settings page.
Setting up API tests with Postman Collections
After it is deployed to the staging
environment, you want the API to be tested. For this tutorial, you will use Postman to set up API tests that can be automated.
The first step is to create a dedicated environment for the API requests using Postman.
From the Postman desktop, click the Environments icon and then click Create new environment, you can following the instructions here. The Manage Environments dialog shows any environments that are already in place. Click Add to create a new environment.
In the new environment dialog, enter a name for the new environment. Fill in the staging environment API base URL, (https://users-api-staging-ed33a8dbfbbc.herokuapp.com/
) as an environment variable (api_url
). What you enter in INITIAL VALUE
is duplicated for CURRENT VALUE
. Keep it like that; using CURRENT VALUE
is beyond the scope of this tutorial.
Click Save to finish creating the environment. Switch to the new environment using the dropdown at the top right of your screen.
Create a collection
The next step is to create a Postman Collection for the user endpoints of the API you will be testing.
Click Collections from the left sidebar. Then click Create Collection.
On the New Collection dialog, fill in the name (Users-API-Collection
) of your collection. You can also add a description if you want to add more information about the collection.
Click Create to finish setting up the collection. The new collection is immediately shown on the left sidebar under Collections.
Adding requests to the collection
Now it is time to add requests to your collection. Our API consists of two endpoints:
/users/get
(GET): Fetches a list of user profiles/users/create
(POST): Creates a new user profile
You will be adding a request for each endpoint. To get started, click the flyout menu beside the collection (the arrow icon).
Click Add requests. On the New Request dialog, create a request for the /users/get
endpoint. Using the api_url
variable you created, enter the endpoint for the request in the address bar (/users/get
). Make sure to select GET as the request method. Click Save. This will save it to the Users-API-Collection you created earlier.
Next, create a request for the /users/create
endpoint. You need to get random values so that the POST
request can create test users. For that, you need a Pre-request
script.
Open the Pre-request Script tab and add the following script:
let random = +new Date();
pm.globals.set("name", `Test-User-${random}`);
This script uses the current timestamp to randomly create names for each fired request. The random name
variable is set as a global variable for the request instance.
Now write the request body with the dynamic name
variable.
A dynamic name
parameter will be used for each request to the /users/create
endpoint.
Adding tests
It is time to add some tests to the requests you just created.
Click the /users/get
request (in the left sidebar) to make sure it is loaded. Click the Tests tab. In the window, add:
pm.test("Request is successful with a status code of 200", function () {
pm.response.to.have.status(200);
});
pm.test("Check that it returns an array", function () {
var jsonData = pm.response.json();
pm.expect(jsonData).to.be.an("array");
});
The first test checks that the request returns successfully with a status code of 200
. The second test makes sure that it also returns an array.
Click the user creation request Test tab to add:
pm.test("User creation was successful", function () {
pm.expect(pm.response.code).to.be.oneOf([200, 201, 202]);
});
pm.test("Confirm response message", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.message).to.eql("User successfully registered");
});
The first test checks that user creation is successful by asserting a status code of either 200
, 201
or 202
. The second test makes sure that the correct response message is returned.
Note: Be sure to click Save whenever you make a change to either of the requests in this collection.
Configuring automated testing with Newman
You can either have your collections exported as JSON from Postman and saved in your repository, or you can directly call the Postman API with your collection ID and API key and run Newman against the latest version of the collection. For this tutorial, you will export the collections as JSON.
NOTE: Remember to update your collection whenever you make changes to your API
Once the file is exported. Ensure that its save in an accessible folder, prefarably in the root directory of your project.
Also, download your Postman environment by clicking the Manage Environments cog icon. Click export to get the file. The filename will be something like My-Remote-API-Testing.json
(depending on how you named the environment). Add this environment file to the root of your project.
Next, you will use Postman’s CLI tool, Newman, to automate how the tests run.
Next, install the Newman CLI tool to automate your tests by issuing the following command at the root of your project:
npm install --save-dev newman
Replace the test script in package.json
with:
"scripts": {
...
"test": "npx newman run Users-API-Collection.postman_collection.json -e My-Remote-API-Testing.postman_environment.json"
}
This test script uses npx
to run newman
against the collection’s file. Replace Users-API-Collection.postman_collection.json
and My-Remote-API-Testing.postman_environment.json
with your collections and environment’s filename, respectively, if its different. Newman runs the tests defined in the collection against the requests and picks up any pre-request scripts that have been defined.
Your tests are set up for automation.
Automating test and deployment
As stated at the beginning of this tutorial, your goal is to create an approval-based workflow in which an application is:
- Deployed to a staging environment
- Tested on the staging address and (if tests pass)
- The process waits for a team member to click an approval button in the CircleCI console and
- The
deploy
job is triggered and deploys the application to the production environment.
Our next task is to write a deployment pipeline script that performs those steps. Start by creating a folder named .circleci
at the root of the project. Add a configuration file named config.yml
inside the folder you just created. Enter the following code:
version: 2.1
orbs:
node: circleci/node@5.1.1
jobs:
stage:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- node/install
- run: npm install
- run:
name: Deploy app to Heroku.
command: |
git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_STAGING_APP_NAME.git main
test:
working_directory: ~/repo
docker:
- image: cimg/node:21.4.0
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
- run:
name: Run tests
command: npm run test
deploy:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- node/install
- run: npm install
- run:
name: Deploy app to Heroku
command: |
git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_PRODUCTION_APP_NAME.git main
workflows:
stage_test_approve_deploy:
jobs:
- stage
- test:
requires:
- stage
- hold:
type: approval
requires:
- test
- deploy:
requires:
- hold
That is quite a script, right? Let me break it down and add some detail. You start by creating 3 jobs
:
stage
: Checks out the project code from the repo and uses the Heroku CircleCI orb to deploy the application to the staging environment.test
: Installs the project dependencies needed to have thenewman
CLI installed. It then runs thetest
script inpackage.json
, invokingnewman
to run the tests in the collection against the staging application that was just deployed.deploy
: Deploys the application to the production environment on Heroku.
Finally, the script defines the stage_test_deploy
workflow. This workflow runs the stage
job. Once that job is run successfully, the test
job runs tests against the application that was just deployed. If the test
job is successful meaning all tests on the staging application passed, the workflow then runs a special hold
job. The hold
job pauses the workflow and waits for approval to be made before it continues. The hold
job can be given any name you prefer. The important feature of this job is that its type
is set to approval
. This type indicates that it is an intermediary job, which means its main function is to pause the workflow until approval is complete.
A member of the organization with the authority to approve clicks an approval button on the CircleCI console. That click causes the deploy
job to continue, deploying the application to the production environment.
Note: Anybody with write access to your project’s repository can approve a job. You can further limit who is able to run specific jobs by using a restricted context in subsequent jobs to the approval job. While anybody with write access can issue an approval, the subsequent job will only run if the approver is in the context’s security group.
Next, begin the test automation by pushing your project to GitHub.
Note: The cloned project may throw an error about having already been initialized as a git repo (or one that a remote repo is already contained). If this happens, run rm -rf .git
to delete any existing git
artifacts. Then re-initialize it with git init
.
Connecting your project to CircleCI
Log into your CircleCI account. If you signed up with your GitHub account, all your repositories will be available on your project’s dashboard. Search for the deploying-api-with-approvals-heroku
project:
Click Set Up Project. You will be prompted about whether you have already defined the configuration file for CircleCI within your project. Enter the branch name (for the tutorial, you are using main). Click the Set Up Project button to complete the process.
The first job will fail. This is because it requires the Heroku credentials.
To set up environment variables to deploy the application to both environments (staging and production) on Heroku.
click the Project Settings button, then from the side-menu, click Environment Variables.
Go ahead and add the following variables:
Add these variables:
HEROKU_STAGING_APP_NAME
: The Heroku app name for the staging environment (in this caseusers-api-staging
)HEROKU_PRODUCTION_APP_NAME
: The Heroku app name for the production environment (in this caseusers-api-production
)HEROKU_API_KEY
: Your Heroku API key
Now you are ready to deploy to both the staging and production environments on Heroku.
Click Rerun Workflow from Start.
The currently active job
is running while the others wait.
Once the test
job is done, you will see the hold
job pause the workflow for approval.
Click the hold
job to open the approval dialog.
Click Approve to continue the workflow. The workflow will resume and the deploy
job will run to push the application to production.
To review the steps in the process and their duration, just click the workflow (stage_test_deploy
) link.
You can also click each build for details about how it ran.
Great work! Your tutorial project is approved.
Conclusion
This tutorial has shown you how CircleCI makes it easy to implement approval structures in automated continuous integration processes. If approvals are part of your deployment processes, you can apply what you have learned here to other workflows, even to more complex scenarios.
For further learning on this topic, read Implementing access control policies in CI/CD pipelines and Adding approval jobs to your CI pipeline.
Happy coding!