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:
In our example scenario, we have a Git repository in which:
masterbranch contains the latest known stable code
developbranch 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.
First, we will set up our GitHub repository with a few secrets, which will contain general information about our Cloudify environment. In GitHub, click Settings and then Secrets to get to the Secrets screen. Then add the following secrets:
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_SSL: Set to
falseif Cloudify Manager is not SSL-protected (default is
CLOUDIFY_SSL_TRUST_ALL: Set to
trueif 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
Writing the GitHub Workflows
We will write two GitHub workflows:
feature.ymlwill contain the workflow to run for feature branches
integration.ymlwill contain the workflow to run when feature branches are merged into the
(Both files should exist in
.github/workflows, as per GitHub’s conventions)
Let’s look at
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
cloudifysubdirectory. 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
$GITHUB_RUN_IDis 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,
- Note the
envsection, describing the Cloudify environment. We provide the
CLOUDIFY_TEST_TENANTsecret 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
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.
OK, now let’s give this a try.
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:
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.
Let’s see Cloudify Manager’s administration console:
Our deployment is there.
Now, let’s create a feature branch off
develop, and call it
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.
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
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:
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:
Checking Cloudify Manager’s administration console…
This article demonstrated the use of Cloudify’s integration with GitHub Actions, for the purpose of:
- 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.