Forum Posts

Apr 15, 2019
Automate build, test and deployment using Bitbucket Pipelines 📷 Motivation to invest in CI/CD Modern development practices are reducing batch sizes of work significantly to, e.g., speed up time to market or get customer feedback earlier. Classic release cadences of once or twice a year are not applicable anymore to keep pace with the market in more and more industries. Instead releases are done in extrem cases every 11.6 seconds at amazon (status as of 2011). Building releases by hand, executing manual tests and handing over code to operations via ticket systems who then try to deploy by hand, are obviously not working any more. Continuous Integration (CI) is a development practice which requires developers to integrate their code into a shared repository frequently, usually several times a day. Every check-in is verified by building the code and executing tests to detect problems early. [Compare: ThoughtWorks] Continuous Deplyoment (CD) is a strategy to automatically deploy the code to production after all automated tests were passed during the CI phase. [Compare: Techtarget] The 2017 State of DevOps Report motivates why CI/CD is beneficial to apply in comparison to classic approaches: Quality: 5x lower change failure rate Delivery: 440x faster lead time from commit to deploy Delivery: 46x more frequent code deployments Happy teams: 44% more time spent on new features and code 📷Excerpt of 2017 State of DevOps Report Infograph The automation of build, test and deployment are the most typical steps which can be automated. In this article we will automatically build, test and deploy an Angular 6 application to Firebase hosting on every commit to the master branch. To automate the process we use Bitbucket Pipelines. In earlier articles I already demonstrated CI/CD using Bitbucket Pipelines for 1) CakePHP via FTP to shared hosting server and 2) CakePHP to Heroku. Outline Create Bitbucket Repository Create Angular 6 demo application Create Firebase project Configure Bitbucket Pipeline Prerequisites Bitbucket and Firebase account Git and Node.js 10.x installed on your local development machine 1. Create a Bitbucket repository First we create a new Bitbucket repository. You can find my demo repository here: 📷Create new Bitbucket repository 2. Initialize Angular application For this demonstration we create our Angular application with the Angular CLI. If you have not installed the CLI yet, use the following command to do so: npm install -g @angular/cli If you have already installed an older version of the angular CLI, use the following command to upgrade to the latest version as described here: npm uninstall -g @angular/cli npm cache verify npm install -g @angular/cli@latest Then we can create our Angular skeleton application: ng new angular-firebase-ci-demo If you are interested in what you just created start it with the build in server: cd angular-firebase-ci-demo ng serve -o The command should automatically start your browser and navigate to http://localhost:4200/. Configure Test with Puppeteer The default test behavior is to start Chrome and execute the tests. On our CI environment we do not have Chrome installed and we do not want to start an GUI based application anyway. Therefore, we utilize the puppeteer package which includes chrome and which we can then start in a headless mode which means without GUI. First, we include a dev-dependency to puppeteer: npm install --save-dev puppeteer Then we configure our karma.conf.js to use ChromeHeadless instead of Chrome. Additionally, we need to use the — no-sandbox option, to make it work on our CI environment. Further, we only want to let the test cases run once which we can configure with the flag singleRun. # ./angular-firebase-ci-demo/src/karma.conf.js const puppeteer = require('puppeteer'); process.env.CHROME_BIN = puppeteer.executablePath(); module.exports = function (config) { config.set({ (...) browsers: [ 'ChromeHeadlessNoSandbox' ], customLaunchers: { ChromeHeadlessNoSandbox: { base: 'ChromeHeadless', flags: ['--no-sandbox'] } }, singleRun: true }); }; Commit to Bitbucket Now we can share our local code with the remote repository in Bitbucket. You can find the git URL in the Bitbucket repository overview. For me the commands look like this: # switch to project folder in ./angular-firebase-ci-demo git remote add origin git push origin master The code should be visible in our Bitbucket repository now. 3. Create Firebase Application Create a new project on 📷New Firebase Project Install Firebase Tools As we want to deploy to firebase we need the firebase tools. Let’s add them to our dev-dependencies and also install them globally on our machine: npm install --save-dev firebase-tools npm install -g firebase-tools Add build and deployment command In our package.json we add the following two commands for production build and deployment. We will use them later in our pipeline. "scripts": { "build-prod": "ng build --prod", "deploy": "firebase deploy --token $FIREBASE_TOKEN --non-interactive" }, Add configuration for Firebase General Firebase project configuration: # ./angular-firebase-ci-demo/.firebaserc { "projects": { "default": "angular-firebase-ci-demo" } } Firebase hosting configuration. Let’s only deploy what is inside the dist folder which is created during the production build. # ./angular-firebase-ci-demo/firebase.json { "hosting": { "public": "dist/angular-firebase-ci-demo", "rewrites": [ { "source": "**", "destination": "/index.html" } ] } } Commit Firebase Configuration We need to push all changes now to our master branch. git add -A git commit -m "firebase config" git commit git push origin master (4) Setup Bitbucket Pipeline Configure Pipeline We navigate to our repository on Bitbucket and navigate to Pipelines in the left navigation menu. The first time we click on it, it may take some time until we see something (~ 5–10 seconds). We scroll down the page and choose JavaScript. Replace the content of the auto-generated bitbucket-pipelines.yml file with the following content and commit the file: Note: We will get some errors as we have not configured the environment variables yet. Explanation of the configured pipeline Withimage: node:10 we tell our pipeline which docker container should be used for the build. For Angular 6 we require Node 10.x. Using branches: master: we define, that the pipeline should run as soon as we check in code to the master branch. With caches: -node we cache all node_modules which are downloaded during the installation of dependencies. This will speed up the subsequent runs. To track deployment we use the command deployment: production which tells Bitbucket to consider this Pipeline as a production deployment. Find more information here on Bitbucket Deployments. The last part are the scripts in the script: section. Install all required modules which are described in package.json Install required libraries to run chrome on the docker image as discussed in puppeteer’s troubleshooting documentation Execute test cases Build production ready artifacts Deploy artifacts to Firebase If any command fails, the whole pipeline fails. That means, a failed test will prevent from deploying to the server (which is good as we do not want to break the production). Set environment variables in Bitbucket In our pipeline script we have defined the environment variable FIREBASE_TOKEN which is used to auth ourselves against Firebase. We get the token by typing on our command line the following: firebase login:ci The token we put into out pipeline in Settings > Pipeline > Repository variables. Important: Never store passwords or other confidential information in git. Instead make use of, e.g., securely stored environment variables for passwords. 📷 Now commit to master or rerun the pipeline, and you will see a deployment to Firebase. Alternative Docker Image The pipeline is currently “poisoned” with a statement to install required libraries for chrome. Without these libraries chrome is not working and quits with the following error: Cannot start ChromeHeadless /opt/atlassian/pipelines/agent/build/node_modules/puppeteer/.local-chromium/linux-609904/chrome-linux/chrome: error while loading shared libraries: cannot open shared object file: No such file or directory A cleaner approach is to use a custom docker container, which includes these libraries. This is more friendly to maintain and speeds up the pipeline. How this could look like is described in puppeteer’s troubleshooting documentation. Final thoughts Additionally, we could define test or staging environments to which we deploy after code has been merged to a specific branch. Credit to Kai Niklas
More actions