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
In our example scenario, we have a Git repository in which:
- 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
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 byCLOUDIFY_USERNAME
.CLOUDIFY_SSL
: Set tofalse
if Cloudify Manager is not SSL-protected (default istrue
).CLOUDIFY_SSL_TRUST_ALL
: Set totrue
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 toCLOUDIFY_TENANT
)
Writing the GitHub Workflows
We will write two GitHub workflows:
feature.yml
will contain the workflow to run for feature branchesintegration.yml
will contain the workflow to run when feature branches are merged into thedevelop
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 theCLOUDIFY_TEST_TENANT
secret as a value forCLOUDIFY_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
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 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.

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:

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…

Conclusion
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.