I’ve recently spent some time refactoring Sam McGeown’s Image as Code (IaC) Codestream pipeline for VMware vRealize Automation Cloud. The following details some of the lessons learned.
First off, I found the built in Code Stream REST tasks do not have a retry. I learned this the hard way when they had issues with their cloud offering last month. At times it would get a 500 error back when making a request, resulting in a failed execution.
This forced me to look at Python custom integrations which would retry until the correct success code was returned. I was able to get the pipeline working, but it did have a lot of repetitive code, lacked the ability to limit the number of retries, and was based on Python 2.
Seeing the error of my ways, I decided to again refactor the code with a custom module (For the repetitive code), and migrate to Python 3.
The original docker image was CentOS based and did not have Python 3 installed. Instead of just installing Python 3 thus increasing the size of the image, I opted to start with the Docker Official Python 3 image. I’ll get to the build file later.
Now on to the actual refactoring. Here I wanted to combine the reused code into a custom python module. My REST calls include POST (To get a Bearer Token), GET (With and without a Filter), PATCH (To update Image Mappings), and DELETE (To delete the test Image Profile and Cloud Template).
This module snippet includes PostBearerToken_session which returns the bearToken and other headers. GetFilteredVrac_sessions returns a filtered GET request. It also limits the retries to 5 attempts.
import requests
import logging
import os
import json
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
# retry strategy to tolerate API errors. Will retry if the status_forcelist matches.
retry_strategy = Retry (
total=5,
status_forcelist=[429, 500, 502, 503, 504],
method_whitelist=["GET", "POST"],
backoff_factor=2,
raise_on_status=True,
)
adapter = HTTPAdapter(max_retries=retry_strategy)
https = requests.Session()
https.mount("https://", adapter)
vRacUrl = "https://api.mgmt.cloud.vmware.com"
def PostBearerToken_session(refreshToken):
# Post to get the bearerToken from refreshToken
# Will return the headers with Authorization populated
# Build post payload
pl = {}
pl['refreshToken'] = refreshToken.replace('\n', '')
logging.info('payload is ' + json.dumps(pl))
loginURL = vRacUrl + "/iaas/api/login"
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}
r = https.post(loginURL, json=pl, headers=headers)
responseJson = r.json()
token = "Bearer " + responseJson["token"]
headers['Authorization']=token
return headers
def GetFilteredVrac_sessions(requestUrl, headers, requestFilter):
# Get a thing using a filter
requestUrl = vRacUrl + requestUrl + requestFilter
print(requestUrl)
adapter = HTTPAdapter(max_retries=retry_strategy)
https = requests.Session()
https.mount("https://", adapter)
r = https.get(requestUrl, headers=headers)
responseJson = r.json()
return responseJson
Here is the working Code Stream Custom Integration used to test this module. It will get the headers, then send a filtered request using the Cloud Account Name. It then pulls some information out of the Payload and prints it out. (Sorry for formatting).
runtime: "python3"
code: |
import json
import requests
import os
import sys
import logging
# append /build to the path
# this is where the custom python module is copied to
sys.path.append('/build')
import vRAC
# context.py is automatically added.
from context import getInput, setOutput
def main():
def main():
refreshToken=getInput('RefreshToken')
# now with new model
# authHeaders will have all of the required headers, including the bearerToken
authHeaders=vRAC.PostBearerToken_session(refreshToken)
# test the getFunction with filter
requestUrl = "/iaas/api/cloud-accounts"
requestFilter = "?$filter=name eq '" + getInput('cloudAccountName') + "'"
# get the cloudAccount by name
cloudAccountJson=vRAC.GetFilteredVrac_sessions(requestUrl, authHeaders, requestFilter)
# get some specific data out for later
cloudAccountId = cloudAccountJson['content'][0]['id']
logging.info('cloudAccountId: ' + cloudAccountId)
if name == 'main':
main()
inputProperties: # Enter fields for input section of a task
# Password input
- name: RefreshToken
type: text
title: vRAC Refresh Token
placeHolder: 'secret/password field'
defaultValue: changeMe
required: true
bindable: true
labelMessage: vRAC RefreshToken
- name: cloudAccountName
type: text
title: Cloud Account Name
placeHolder: vc.corp.local
defaultValue: vc.corp.local
required: true
bindable: true
labelMessage: Cloud Account Name
Next the new Docker image. This uses the official Python 3 image as a starting point. The build file copies everything over (Including the custom module and requirements.txt), then installs ‘requests’.
FROM python:3
WORKDIR /build
COPY . ./
RUN pip install --no-cache-dir -r requirements.txt
Now that the frame work is ready, it’s time to create the pipeline and test it. This is well documented here Creating and using pipelines in VMware Code Stream. Update the Host field with your predefined Docker Host, set the Builder image URL (Docker Hub repo and tag), and set the Working directory to ‘/build’ (to match WORKDIR in the Dockerfile).

Running the pipeline worked and returned the requested information.

This was a fairly brief article. I really just wanted to get everything written down before the weekend. I’ll have more in the near future.