CI/CD Meets Orchestration: Gitflow Example using Cloudify and GitHub Actions

In my previous articles, I described Cloudify’s integration with Jenkins, as well as with CircleCI and GitHub Actions. These integrations provide easy access to Cloudify from within some of the most popular CI/CD tools in the market.

In this article, I will demonstrate using Cloudify’s integrations within the context of real-world development work. The demonstration will use GitHub Actions, however the same concepts can be applied to any supported CI/CD platform.

Note: before proceeding, you may want to brush up on Gitflow. A good description can be found here.

You can also watch the demo here:

Example Scenario

  • The master branch contains the latest known stable code
  • The develop branch functions as a “release staging area”, where feature branches (see below) are merged into before releasing to production
  • Feature branches, named feature/*, containing code for specific features and fixes

What we would like to achieve is the following:

  • Whenever there’s a push to a feature branch, we would like to create a new environment using Cloudify on a test tenant, deploy our application to it, run tests, and then delete the environment.
  • Whenever a feature branch is merged into develop, we would like to update an existing Cloudify deployment, which contains our ongoing release staging code.

Note: you can find the example scenario and repository on GitHub.

Setup

  • CLOUDIFY_HOST: IP or hostname of Cloudify Manager.
  • CLOUDIFY_USERNAME: User to authenticate to Cloudify Manager with.
  • CLOUDIFY_PASSWORD: Password for the user specified by CLOUDIFY_USERNAME.
  • CLOUDIFY_SSL: Set to false if Cloudify Manager is not SSL-protected (default is true).
  • CLOUDIFY_SSL_TRUST_ALL: Set to true if Cloudify Manager’s certificate should be trusted blindly (Note: do not do this without understanding the risks involved).
  • CLOUDIFY_TENANT: Set to the name of the Cloudify tenant where the integration Cloudify environment should exist.
  • CLOUDIFY_TEST_TENANT: Set to the name of the Cloudify tenant where environments should be created for feature branches. (This may be equal to CLOUDIFY_TENANT)

Writing the GitHub Workflows

  • feature.yml will contain the workflow to run for feature branches
  • integration.yml will contain the workflow to run when feature branches are merged into the develop branch

(Both files should exist in .github/workflows, as per GitHub’s conventions)

Let’s look at feature.yml:

Note the on -> push -> branches directive, instructing GitHub to run this workflow whenever there’s a push to any branch that adheres to the feature/* name mask.

  • We assume that the Cloudify blueprint, used to set up the test environment, is located in the cloudify subdirectory. In reality, it may exist anywhere.
  • We delete the environment right after creating it. In reality, you would place any GitHub Actions you’d like in order to execute your tests.
  • Note that we name our Cloudify environments product-feature-$GITHUB_RUN_ID, as $GITHUB_RUN_ID is automatically provided by GitHub as a string containing digits only. This is just an example; in reality, you may choose any other name (typically, using GitHub-provided environment variables), provided that it conforms with Cloudify’s blueprint naming rules (letters, digits, -, ., and _).
  • Note the env section, describing the Cloudify environment. We provide the CLOUDIFY_TEST_TENANT secret as a value for CLOUDIFY_TENANT, as we’d like these environments to be created in our test Cloudify tenant.

Next, let’s look at integration.yml:

The algorithm, in high level:

  • Upload the blueprint we have in this commit.
  • If the application’s deployment can’t be found, then create it and install it, based on the blueprint we had just uploaded. Otherwise, update the existing deployment using that same blueprint.

Let’s Go

We start with a repository containing a simple blueprint, and only one branch: master. The blueprint is:

At this point, develop doesn’t exist yet. Let’s create it and push it to GitHub:

git checkout -b develop
git push -u origin HEAD

This results in the develop branch being created and pushed to GitHub. As this triggers a push event, our integration.yml workflow will fire up:

Image for post
Image for post

If we expand the “Install or update deployment” step, we will see that a new deployment was created. That makes sense, as the application didn’t exist; the initial push to develop was meant to create it.

Image for post
Image for post

Let’s see Cloudify Manager’s administration console:

Image for post
Image for post
Image for post
Image for post

Our deployment is there.

Now, let’s create a feature branch off develop, and call it feature/bugfix:

cfy checkout -b feature/bugfix

Now, we’ll change the blueprint by adding a new node template:

Then, we commit and push the feature branch to GitHub:

git add .
git commit -m "Added node template"
git push -u origin HEAD

Since we pushed to a branch of the pattern feature/*, the push will trigger the feature.yml workflow. This workflow creates a new deployment on the test tenant, installs the application, potentially runs tests, and then deletes the environment.

Image for post
Image for post

Lastly, let’s merge the feature branch into develop, signifying the end of the development of our feature/bugfix feature branch:

git checkout develop
git merge feature/bugfix
git push

As we pushed to develop, this triggers the integration.yml workflow. This time, however, the deployment exists on Cloudify Manager; we expect it to be updated. And indeed:

Image for post
Image for post

If we expand the “Install or update deployment” step, we will see that the deployment was indeed updated, and the old blueprint (that is, the blueprint with which the deployment was associated) was deleted:

Image for post
Image for post

Checking Cloudify Manager’s administration console…

Image for post
Image for post

Conclusion

  • Managing test environments during features’ development
  • Updating existing Cloudify-managed applications during integration

The blueprint that we used was very simple, as the Cloudify blueprint was not the focus here. In reality, this blueprint can be arbitrarily complex, involving virtual machines, network resources, Kubernetes resources and virtually any resource that can be managed by Cloudify.

Written by

Traveller, writer, musician, software architect; not necessarily in that order

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store