How To Automate CI/CD Process for Your NetSuite App

Blog
The author image who wrote the blog article
By
Keir Kettle

[Grab your FREE NetSuite productivity GUIDE for 2024]

In my previous blog post, I promised to give some more information on how we’ve set up Bitbucket Pipelines to automate the CI/CD process for our SDF SuiteApp project. 


When we started out we followed the guidance around using the CLI or a IDE plugin for deploying to a specific account.


But soon, we wanted to include environment specific configuration - e.g. when we pushed our SuiteApp to a QA account, we wanted it to point at our QA API. Later, when we were at the point of releasing our app, we discovered that deployment was a manual process using the NetSuite SuiteApp Control Centre. 


This all meant that each deployment was an environment-specific multi-step process and thus was prone to errors. Imagine the horror releasing your app to the world, only to find out that the configuration is pointing to your QA API!


We use Atlassian’s Bitbucket as our remote code repository and one of the features is the ability to automate CI/CD using “Bitbucket Pipelines”. Github has a similar feature called Github Actions as does Gitlab, called Gitlab CI/CD. 

While the guidance here is specific to Bitbucket, it shouldn’t be too much trouble to adapt for Github or Gitlab.

Step 1: Prepare your project to be built via the command line

The first step is to be able to build your project via the command line. This only applies if you are compiling your code (e.g. from TypeScript), including config, or dependant on any steps run by hand or via a IDE command to prepare your project for deployment usually. 


If your project is dependent on multiple steps, it’s a good idea to alias those in your package.json file to a single command - e.g.

npm run build
		

or

npm run build:qa
	

You can skip this step if this doesn’t apply to you. 

Step 2: Dockerize the SuiteCloud CLI

The next step is to Dockerize the SuiteCloud CLI. Once you have an image containing the CLI, you can setup actions to watch your repo and use this image to build and deploy your project.


I’ve prepared the following dockerfile based on Amazon Corretto (but you can use any JDK base image) for running the CLI via docker. It’s fairly straight forward - it installs Node via Yum, then installs the SuiteCloud CLI via NPM. 

FROM arm64v8/amazoncorretto:11-al2-jdk

USER root
RUN yum update -y && \ 
    yum install -y curl && \ 
    curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - && \
    yum install -y nodejs
RUN npm install -g --acceptSuiteCloudSDKLicense @oracle/suitecloud-cli

WORKDIR /app
	

Now I can run this Docker image in interactive mode using the following command -

docker run --rm -it -v sdf-data:/root/.SDF:z -v suitecloud-sdk-data:/root
/.suitecloud-sdk:z -v $(pwd)/com.yourcompany.yoursuiteapp:/root/src -w /
root/src sdfdocker:latest suitecloud
	

Or even better is to alias this command - I’ve set up the following package.json: 

“scripts”: {
    “sdf”:  “docker run --rm -it -v sdf-data:/root/.SDF:z -v
suitecloud-sdk-data:/root/.suitecloud-sdk:z -v $(pwd)/com.yourcompany.
yoursuiteapp:/root/src -w /root/src sdfdocker:latest suitecloud”}
	

This allows me to run the image like so

‘npm run sdf project:deploy’
	

Step 3: Test the image and push for later use

Now you can test that the image works as expected. Once you are happy with the image - push the image to a remote image repository (like DockerHub) for use later. 

Step 4: Set up the pipelines

The next job is to decide when you’d like to build and deploy the project.   

If you’ve adopted a ‘gitflow’ branching model, then any commit to the ‘develop’ branch should represent a completed feature to be included in the next release, and any commit tagged on the main (or master) branch represents a discrete version of the project, in which case I’d suggest something like this: 

- Build and deploy any commit to ‘develop’ branch to your QA accounts

- Build and generate a ZIP archive of the project when any commit is tagged on the main (or master) branch (which can be manually uploaded to the SuiteApp Control Centre). 

Bitbucket Pipelines uses a .yml file to describe these (see the example below). You can see I’ve stored the credentials in Bitbucket and reference them with ‘$xxx’.  I’ve also used a prebuilt step ‘bitbucket-upload-file’ to upload my zip file to a bitbucket for download later - there are similar ‘actions’ available for Github and Gitlab. 

image: node:16
pipelines:
  branches:
    develop:
      - step:
          name: Deploy to QA
          deployment: staging
          image: yourdockerimage:latest # This would reference your 
          previously created docker image
          caches:
            - node
          script: # These are the manual commands to build and deploy 
          your SuiteApp
            - npm install
            - npm run build:qa-trailing
            - suitecloud account:savetoken --account $NS_QA_
            TRAILING_ACCOUNT --authid qa-trailing --tokenid $NS_QA_
            TRAILING_TOKENID --tokensecret $NS_QA_TRAILING_TOKENSECRET
            - suitecloud project:deploy
            - npm run clean
            - npm run build:qa-leading
            - suitecloud account:savetoken --account $NS_QA_LEADING_
            ACCOUNT --authid qa-leading --tokenid $NS_QA_LEADING_
            TOKENID --tokensecret $NS_QA_LEADING_TOKENSECRET
            - suitecloud project:deploy
  tags:
    '*':
      - step:
          name: Build master for new app version
          caches:
            - node
          script:
            - npm install
            - npm run deploy:prod #The output of this is a zip file 
            in the build folder
          artifacts:
            - build/**
      - step:
          deployment: production
          script:
            - pipe: atlassian/bitbucket-upload-file:0.3.2
              variables:
                BITBUCKET_USERNAME: $BITBUCKET_USERNAME
                BITBUCKET_APP_PASSWORD: $BITBUCKET_APP_PASSWORD
                FILENAME: 'build/com.getbusy.workiro-*.zip'
                

I hope this helps!