Resources

Arrow Image

Blog & News

Arrow Image

Implement a CI/CD Pipeline using Frame APIs

Implement a CI/CD Pipeline using Frame APIs

Continuous Deployment (CD) is a software development practice which aims to ensure that code changes or software updates can be safely and quickly deployed to a production environment. CD is only part of a common industry practice called Continuous Integration / Continuous Delivery/Deployment (CI/CD). The goal of CI/CD is to automate the build, test, and deployment of software applications, by using a set of tools and practices.

News & Blog

WRITTEN BY

Justin Emilio

Senior Solutions Architect

April 5, 2023

TABLE OF CONTENT

When considering the scope of CI/CD and the focus of this document, Frame provides a perfect platform to implement an automated CD process through various Frame APIs and toolsets.

In this blog post, we will explore a simple scripted example for implementing CD in a CI/CD workflow using Frame Admin API (along with a few best practices for the Frame Admin APIs). We will cover the steps for integrating Frame Admin API into an automated CI/CD workflow, as well as best practices for configuring your Frame account with these automations in mind. Whether you're new to CD or looking to improve/automate your existing workflow, this post will provide valuable insights and practical advice.

Frame CD Deployment Components

Before we go into the details, it is important to understand the different components and terminology that will be used for this process and discussed in this blog. The three required components are listed below:

Pre-Session Script* Resides on the Sandbox Workload VM* Executes local automation software (custom, Jenkins, Buddy tools, etc)* Communicates status to the Deployment Management Service (Optional)

CI / CD practices aim to automate the process of building, testing, and deploying software. In order to achieve this automation, many businesses use dedicated CI/CD tools like Jenkins, CircleCI, Terraform, and others. These tools provide a range of features such as version control integration, automated testing, and deployment pipelines that make it easy for developers to build, test, and deploy their code quickly and consistently. The Deployment Manager Service is defined as simply being the customer's desired choice of a CI/CD suite that will be used to trigger the Frame Admin API script.

However, it is also possible to implement CI/CD without using any particular software suite. This can be done by creating custom scripts (which are essentially just a series of command-line instructions that can be executed in sequence to perform the update task).

The timing of the Frame Admin API trigger is simply how often you want the automated deployment to occur (usually after a Git commit/push, or release of an updated and tested version of the target software). To keep the example code simple and straightforward, I will not be using a Deployment Manager Service to trigger the Frame Admin API script. It is just assumed that you will execute the Frame Admin API Script when you need the automated deployment to occur.

As already mentioned, we will not focus on any one of these dedicated tools and are opting to create a simple custom script to accomplish all of our automation needs.The purpose of this article is not to teach you how to use any of the dedicated tools; instead, we will show you how to implement very specific Continuous Delivery steps using the documented Frame Admin APIs and scripting resources. You are expected to understand your own software and environment enough to fill in the holes (which is noted where appropriate).

It's also important to note that there are many downsides to using scripts without any of the previously mentioned dedicated software suites. For one, it can be more time-consuming to set up and maintain custom scripts, rather than using a dedicated tool with pre-built features designed for many different workflows. Additionally, it may be more difficult to scale this process with custom scripts as your business grows (or if the number of developers and project scope increases).

The Frame Admin API Script is a script that is executed on any remote compute platform that calls multiple Admin API endpoints to orchestrate and execute the automation we intend to deploy. This script can be located on anything from your local laptop or desktop, to being hosted on a cloud platform.

The language being used for this script is also up to you for determining what makes the most sense for the solution as a whole. Any language that can make remote HTTPS GET and POST calls can be used. The HTTPS request does include a signed header that will be used for authenticating the API calls that we make (which might require crypto/hashing libraries). More information about the signed header is available in our documentation page Making API Calls.

In the example code, I will be using will be written in Python for clarity, simplicity, and readability.

The Pre-Session Script is a Powershell (.ps1) script that will be located on the Sandbox for the Frame account that you want to update. The example code I provide will be entirely in Powershell, but you can use any other language that is triggered by a Powershell FGA Script. A Frame FGA Script is a special script that gets triggered at very specific times relative to the lifecycle of a Frame Session, or VM boot cycle. You can read more about FGA Scripts and the many different hooks/events that you can use to trigger Powershell scripts in our FGA Scripting documentation.

Where the Components Reside

Both the Deployment Manager Service and Admin API Script can be hosted anywhere remote to the Frame Platform. In most cases, customers would opt to have these integrated into their current infrastructure or cloud platform that hosts many of their other computing and networked services (hosting, networking, etc.). This isn't meant to suggest that you can't place these on the Frame Platform either. It's very possible to host the Deployment Manager Service and/or the Admin API Script on the Frame Platform. As a matter of fact, the testing and development of the solution for this blog workflow was hosted on a Utility server for the same account that I ran the automations on.

Additional information about Utility Servers can be found here in our Utility Servers documentation page.

The Automated Flow

So, now that we've covered what components are involved, let's talk about what we're actually going to implement. A common question I get from customers is "How do I automate the process of updating my Sandbox to the latest version of XXXXXX software and publish to production?" For the sake of this article, we actually want to update our installation of the GIMP Image Editor for each of our end users in the production pool.

Without automation, you would have to go through this entire process manually through Frame Console and within the Sandbox. Even though Frame's Console user interface is intuitive and easy to use, the entire installation and publishing process could take hours executing updates and waiting around for application installers to finish.

What we want to do is script the process so that we don't have to go through all of these manual steps. As mentioned earlier, we will need to set up some server-side scripts (Python examples) and Windows PowerShell scripts to automate the update in the Sandbox.

The Python scripts will find, start, and stop the Sandbox for a specific Frame Account. Once the Sandbox has booted and is ready, we will start a "headless" session automatically to block any other administrators from gaining access to the Sandbox while the PowerShell script is executing. Doing this ensures the Sandbox is completely dedicated to our update procedures.

While the session is starting, a Pre-Session PowerShell script will start a silent update of the GIMP 2.X software. These PowerShell scripts are where you have the freedom to accomplish whatever sort of software installs/customization/update you may need for your automations. For illustrative purposes, I will keep this script small. You can read more about our scripting capabilities and features in our documentation.

While the session is running, our Python script will poll the status of the session we started and begin the publish process as soon as it is complete. It is important to note that you will likely validate that the installation / update process completed successfully before starting a publish. That validation step is out of the scope of this blog and will not be covered here.

Below is a sequence diagram that covers a high level overview of what we intend to accomplish.

To simplify this even further, we can group this into four steps (we'll break down each one of these later):

  1. Configure your Frame Account for automation and gather the required configuration information.
  2. Find and start the Sandbox from Frame Admin API.
  3. Install/update the GIMP 2.X software on this Sandbox to the latest version of GIMP Image Editor.
  4. Publish the Sandbox to production.

Steps 1, 2, and 4 are all going to be implemented in one Python script. Step 3 is a PowerShell script that is located on the Sandbox.

The sequence diagram used above is a little bit closer to what you would expect in the real world (checking to see if the current version of the software is already up to date). We will not be checking for new versions in this example code. We will just use a very particular version of the GIMP software to install/update (to keep the example code short).

In the next section, we will go over the details for each of these four simplified steps.

Procedural Steps for Our Automation

Configure your Account for Automation and Gather Required Info

Items to gather:

  • Account ID
  • Frame API client_id and client_secret (for secured/signed HTTP header requests)
  • Terminal Configuration ID for the Sandbox
  • Secure Anonymous Provider ID (to generate the token to start a headless session)

For this step, we need to know the specific Frame account we want to work with and its associated account_id (Contact Frame Support and ask for the details you need). Once you know the Frame Account ID, you will be able to query and modify the Sandbox's state (start, stop, check if it's running/stopped). You'll also be able to retrieve the Terminal Configuration ID that is required to start a session (used in Step 3 below). The Terminal Configuration ID specifies which Launchpad and instance type to use for the update session (in this case it's just going to be our Sandbox).

To access the Frame Admin API, you must have a client_id and client_secret generated by a Frame API Provider. Also, we will be using a Secure Anonymous Provider to generate tokens to start a session with a very particular identifying email address (detailed documentation on the API and Secure Anonymous Providers in our Secure Anonymous Tokens documentation -- use this to get the client_id and client_secret).

Here is a snippet of Python code that uses the Account ID to gather the Sandbox Terminal Configuration ID:

#!/bin/env python
import hashlib
import hmac
import time
import requests

#This is a supplementary script used to gather more information about the
#Sandbox and the account to use for automation. As soon as you have this
#information, you don't need to use this script any more.
#Write the output to a file for later use

account_name = "Justin - Blog Demo"
account_id = "xxxx-xxxx-xxxx-xxxx-xxxx"
client_id = "xxxx-xxxx-xxxx-xxxx-xxxx.img.frame.nutanix.com"
client_secret = b"xxxxxxxx"

#Pool ID or Terminal Configuration ID for your account's Sandbox
terminal_configuration_id = "e0402b07-xxxx-471e-9aec-fdf7c0408390"

#Specify the secure anonymous provider ID that was setup for this account
secure_anon_provider = "secure-anon-xxxx-xxxxx-xxxxx-xxxxx-xxxxxxxxxxx"

# Create signature for the signed HTTP Requests
timestamp = int(time.time())
to_sign = "%s%s" % (timestamp, client_id)
signature = hmac.new(client_secret, to_sign.encode('utf-8'), hashlib.sha256).hexdigest()

# Prepare http request headers
headers = {
    "X-Frame-ClientId": client_id,
    "X-Frame-Timestamp": str(timestamp),
    "X-Frame-Signature": signature
}

#******** All code above this line will be used for every Python script we use below. This sets up the signed
#******** HTTP headers and specifies all required identifiers for generating the token, starting sessions, etc

def write_to_file(string, filename):
    with open(filename, 'w') as file:
        file.write(string)

# Make request to retrieve all of the Pools for a given account_id
r = requests.get("https://api.console.nutanix.com/v1/accounts/%s/pools" % (account_id), headers=headers)

account_sandbox_info = ""

#Gather the terminal_configuration_id for the Sandbox pool only. The 'pool_id' for the sandbox
#is the same thing as the terminal_configuration_id

for pool in r.json():
    if pool['kind'] == 'sandbox':
        account_sandbox_info = "Terminal Configuration ID for the Sandbox on account '" + account_name + " (" + account_id + ")': \r\n"
        account_sandbox_info += '"' + str(pool['id']) + '"'
        print(account_sandbox_info)

#Output to a text file for later use
write_to_file(account_sandbox_info,"account_sandbox_info.txt")

Next, we need to configure the Frame Account to work with our automation process.

In the Frame Console interface, we need to update the Sandbox "Session Settings" for our automations. We're going to add some information into the session settings so that we can run a session in a "headless" state without waiting for a client or a browser to connect. We're actually just increasing the timeout required to wait for a connection from the client/browser to connect

Inside the session settings for the Sandbox, under "Advanced Server Arguments", add the following argument: -contout 7600 (as shown below).

Without going into too much detail, what we're doing here is telling our session to stay open for a few hours so that our scripts have a long enough time to execute and modify the Windows environment to our needs (-contout 7600 -- you can set this value, which is in seconds, to whatever you need).

Find and Start an Account's Sandbox from API

After you have all of the information that was acquired from the previous script, we need to check whether or not a session is already running on the selected server (Sandbox). If there is already a session running, we will abort the script, since we don't want to interfere with another admin's activities.

Once we determine the Sandbox is free from sessions, the script will start a "headless" session. A "headless" session is a session that is started from a backend script and not from a browser. Again, we are doing this to make sure that the Sandbox is dedicated to the automation process and no one can interrupt our scripts during the time period that the session is running.

It's important to note that we will be generating a token to start a valid Frame session with a very special, yet arbitrary, email address that will be referenced in our PowerShell script. The email address we specify in this step will be used by the PowerShell pre-session script to determine that we want an automation to occur. We don't want the automation to occur if the Sandbox session was started any other way.

After the session has started, our Python script will then query the session status to determine if the Pre-Session script has finished. To do this, we will poll the session status and wait for the session to be "CLOSED". Once the session is closed, we are going to assume the Pre-Session PowerShell script was successful and completed as expected.

Here is the code for how you would generate a token and start a "headless" session. The script tracks the session ID for use in the next step (Polling the Session Status):

#Use the code above to generate the signed header for use by the 'requests' library


#Generate a token from the Secure Anonymous Provider associated with the account we're working on.
#This is the step where we specify which email address we will use for the session authentication. We are going to use
# "sandbox_update@automation.net" and check for this in the Powershell pre-session script
#You could also include optional params such as first_name, last_name, or metadata (However, 'email' is the only required parameter for this flow)
body = {
    "email" : "justin@techvayne.com"
}

# Make request to generate the token
r = requests.post(
    "https://api.console.nutanix.com/v1/accounts/%s/secure-anonymous/%s/tokens"
    % (account_id, secure_anon_provider),
    headers=headers,
    json=body)

#The returned body is the token. The .json() method will return just the string for the token in this case.
token = r.json()

#Next, lets start the session using the token we just received
data = {
    "terminal_configuration_id": terminal_configuration_id,
    "user_data": user_data
}

url = "https://api.console.nutanix.com/v1/sessions/start"

# Make request to start the session with the token
#Note that we are not using the same headers as required by the other Admin API calls
#We can use the token generated previously to authenticate this call rather than the signed headers
#This is important since the token contains information about the "Email" address that was used to start the automation
r = requests.post(url, data=data, headers={"Authorization": "Bearer " + token})

session = r.json()
#Get the session ID from the response. We're going to use this to poll the status of the session and determine
#when the session and install has completed
session_id = session['id']

#Check to see if the session is still open. If it's closed, we
#are going to assume the automated installed is successful and will resume the script
session_closed = False
while session_closed == False:
    # Make request to determine session info
    new_url = "https://api.console.nutanix.com/v1/accounts/%s/sessions/%s" % (account_id, session_id)
    session_info= requests.get(new_url, headers=headers) #Make sure you're using the signed headers from earlier
    session_info = session_info.json()
    if session_info['status'] == 'closed':
        session_closed = True
    time.sleep(3) #Wait 3 seconds before checking the session status again

As mentioned earlier, we then need to poll the session status to validate if the session is still running. The next steps in the script should not occur until the session has completed (We don't want to publish before the automation has finished everything it needs to do).

Install/Update the GIMP 2.X Software on this Sandbox

On your Sandbox, you will need to have a pre-session script and definition.yml configured properly as stated HERE.

Here is the Powershell code to automate the update process:


# This powershell code gets executed at the beginning of every session start.
# We do not want the automated install to run unless the session was started by
# the email address we used when generating the token for the headless session.
# We chose the random (And invalid) email address "sandbox_update@automation.net".
# Note, this email does not need to be valid.
if ($env:FRAME_USER_EMAIL -eq "sandbox_update@automation.net") {
    Write-Host "FRAME_USER_EMAIL is 'sandbox_update@automation.net'. Starting the update process"

    # This is the code that executes the GIMP installer in silent/headless mode
    # This script assumes that the installer is located on the C:\ root directory
    Start-Process -FilePath "c:\gimp-2.10.x-setup.exe" -ArgumentList "/VERYSILENT", "/NORESTART" -Wait

} else {
    Write-Host "FRAME_USER_EMAIL is not 'sandbox_update@automation.net'. This is likely a normal user session and should not execute any of the update process."
}

This pre-session script does nothing more than determine if the session was started by the automation script and install the new version of GIMP (e.g., validate the session's email address, and run the installer). At the end of the script, we simply send a reboot to the Windows OS to force a session to close. You can also use the Admin API to close the session.

Publish the Sandbox to Production

At this point, we assume that all went well in the Pre-Session PowerShell script. We then need to start the publish process on this account. When the publish has started, we start yet another API poll loop to determine the publish state.

Once the publish is "FINISHED", we can assume everything went as expected and close our script. At this point, the automation has completed and your production instances should have the latest and greatest GIMP software installed. You can skip this step of waiting for the publish to complete if you have no other tasks after the publish has started. However, you might want to use other automations to test the freshly deployed production pool, or simply send a notification email to interested parties that the automation is complete.

#Publish the sandbox to production
publish_url = "https://api.console.nutanix.com/v1/accounts/%s/publish" % (account_id)
publish = requests.post(new_url, headers=headers) #Make sure you're using the signed headers from earlier

#Script is complete

Conclusion

We have finally completed the automation of the GIMP installer and confirmed that the publish process has completed. You now have a choice to take this to the next level and integrate these ideas into your current CI / CD suite (to scale this to the intended level of your design).

There are many other Frame Admin APIs that can be used to automate all of the features and services that Frame provides. Using this blog as a guide, I hope you have a better understanding of how powerful the Frame Amin APIs can be for many other use cases.

Don't forget to check out many of our other interesting blogs relating to Frame's Admin APIs and automation of your accounts:

Links to other Admin API blog posts

Contact Us

Need help with your Frame deployment, have an idea for a new use case, or just want to learn more about Frame?

Email us at frame-sales@dizzion.com and one of our experts will reach out!

About the Author

Dizzion

Dizzion was founded in 2011 with a visionary mission to redefine the way the world works.

In an era of legacy Virtual Desktop Infrastructure (VDI), Dizzion set out to challenge the status quo by making it simple for all customers to transform their workspace experience. By building a powerful automation and services platform on top of the VMware stack, Dizzion delivered virtual desktops as a service before Desktop as a Service (DaaS) even existed.

Justin Emilio

Senior Solutions Architect

Justin Emilio is a Senior Solutions Architect with Frame.

More about the author

NOTE

A "headless" session is defined as a session that runs without a client viewer/browser.

NOTE

The typical formats for an Account ID and Terminal Config ID will look similar to the following:

Account ID: 57fa46fc-xxxx-484e-xxxx-cc7032c1811a
TerminalConfigId: e0402b07-xxxx-471e-9aec-fdf7c0408390

Subscribe to our newsletter

Register for our newsletter now to unlock the full potential of Dizzion's Resource Library. Don't miss out on the latest industry insights – sign up today!