Promoting Content
This section explains how to use the Connect Server APIs to download code from content in one location and deploy to another. This workflow can help take application changes from a staging environment and deploy them into production.
The Connect Server API Reference contains documentation for each of the endpoints used in these recipes.
The Promoting Content recipe uses bash
snippets and relies on curl
to perform HTTP requests. These recipes use the CONNECT_SERVER
and CONNECT_API_KEY
environment variables introduced in the Getting Started section of this cookbook.
Scenario
Here is one scenario that can take advantage of this Promoting Content recipe; your organization may have similar separation of permissions and environments.
Your data scientists write applications and reports. They rapidly iterate, experiment, and share updates. Content updates are deployed to a Posit Connect staging environment. This environment lets the team share in-progress work without altering your business critical production content.
The Posit Connect production environment hosts content that is visible to your customers and stakeholders. Deployment into production is done by a deployment engineer - not the data scientist.
The data science team develops updates to an application, which are then peer reviewed, tested, and approved for production use. The application developers do not have permissions to update content in the production environment. They hand-off to the deployment engineer, who has production privileges. The deployment engineer downloads the exact bundle archive from the staging environment and deploys into production.
Before Starting
You will need the following pieces of information about your staging and production environments before attempting to download a bundle archive:
Staging environment
STAGING_SERVER
- The base URL for your Posit Connect staging environment, such ashttps://connect-staging.company.com/
.STAGING_CONTENT_GUID
- The source content GUID within your staging environment.STAGING_API_KEY
- A Posit Connect API Key within your staging environment. The user associated with this API Key must be a collaborator for your source staging content.
Production environment
PROD_SERVER
- The base URL for your Posit Connect production environment, such ashttps://connect.company.com/
.PROD_CONTENT_GUID
- The target content GUID within your production environment. This workflow assumes the target content already exists. Use the Creating Content recipe to create a new content item.PROD_API_KEY
- A Posit Connect API Key within your production environment. The user associated with this API Key must be a collaborator (or owner) of your target production content.
Additional information
- The
STAGING_SERVER
andPROD_SERVER
values appear elsewhere asCONNECT_SERVER
. - The
STAGING_API_KEY
andPROD_API_KEY
areCONNECT_API_KEY
elsewhere. - The
STAGING_CONTENT_GUID
andPROD_CONTENT_GUID
values areCONTENT_GUID
in other recipes.
Do not accidentally mix-and-match your staging/production configuration. API Keys for staging will not be recognized in your production environment.
Workflow
The content promotion workflow includes three steps:
- Download the archive file for a source bundle from the staging environment.
- Upload the archive file into the production environment.
- Deploy the new production bundle.
Bundle Download (staging)
The GET /v1/content/{guid}
endpoint returns information about a single content item and indicates the active bundle with its bundle_id
field.
curl --silent --show-error -L --max-redirs 0 --fail \
-H "Authorization: Key ${STAGING_API_KEY}" \
"${STAGING_SERVER}__api__/v1/content/${STAGING_CONTENT_GUID}"
# => {
# => "guid": "b99b9b77-a8ae-4ecd-93aa-3c23baf9cefe",
# => "title": "staging content",
# => ...
# => "bundle_id": "584",
# => ...
# => }
Extract the bundle ID from the JSON response and assign it to a STAGING_BUNDLE_ID
environment variable:
export STAGING_BUNDLE_ID="584"
We will use this bundle ID to download its archive file using the GET /v1/content/{guid}/bundles/{id}/download
bundle download endpoint.
curl --silent --show-error -L --max-redirs 0 --fail -J -O \
-H "Authorization: Key ${STAGING_API_KEY}" \
"${STAGING_SERVER}__api__/v1/content/${STAGING_CONTENT_GUID}/bundles/${STAGING_BUNDLE_ID}/download"
Posit Connect suggests the filename bundle-${STAGING_BUNDLE_ID}.tar.gz
in the Content-Disposition
HTTP response header. The -J -O
options tell curl
to save the downloaded archive file using that filename.
Let’s define an environment variable named STAGING_BUNDLE_FILE
containing name of the downloaded archive file.
# bundle-584.tar.gz
export STAGING_BUNDLE_FILE="bundle-${STAGING_BUNDLE_ID}.tar.gz"
Bundle Upload (production)
Given our staging bundle archive identified by the environment variable STAGING_BUNDLE_FILE
, we can follow the Uploading Bundles recipe.
This is the one command where we are mixing “staging” and “production” variables. The archive file we obtained from the staging environment is being uploaded to production using the POST /v1/content/{guid}/bundles
upload content bundle endpoint.
curl --silent --show-error -L --max-redirs 0 --fail -X POST \
-H "Authorization: Key ${PROD_API_KEY}" \
--data-binary @"${STAGING_BUNDLE_FILE}" \
"${PROD_SERVER}__api__/v1/content/${PROD_CONTENT_GUID}/bundles"
# => {"bundle_id":"242","bundle_size":162991}
Extract the bundle ID from the upload response and assign it to a PROD_BUNDLE_ID
environment variable:
export PROD_BUNDLE_ID="242"
Bundle Deploy (production)
Given our target production bundle identified by the environment variable PROD_BUNDLE_ID
, we can follow the Deploying a Bundle recipe. This uses the POST /v1/content/{guid}/deploy
deploy content bundle endpoint.
# Build the JSON input naming the bundle to deploy.
export DATA='{"bundle_id":"'"${PROD_BUNDLE_ID}"'"}'
# Trigger a deployment.
curl --silent --show-error -L --max-redirs 0 --fail -X POST \
-H "Authorization: Key ${PROD_API_KEY}" \
--data "${DATA}" \
"${PROD_SERVER}__api__/v1/content/${PROD_CONTENT_GUID}/deploy"
# => {"task_id":"t0yiLB6bd6RKlesX"}
You can monitor the progress of this deployment by polling against the GET /v1/tasks/{id}
get task endpoint, as explained in the Task Polling recipe. Remember to poll against the PROD_SERVER
URL and not your staging environment.
Blue-Green Deployments
Once a piece of content is live in production, updates should be done in a manner that does not cause downtime. You can use a blue-green deployment strategy to ensure that the production version of your content is always available.
Workflow
Following the Deploying Content recipe to deploy your content. We will refer to this as the
green
deployment.Assign a vanity URL to the content. Users will access the content through this vanity URL.
Deploy the updated version of your content as a new, separate deployment. We will refer to this as the
blue
deployment.Test the
blue
deployment via itscontent_url
, rather than the vanity URL (which still routes users to thegreen
deployment). If theblue
deployment doesn’t work correctly, you can fix it while users are still accessing thegreen
deployment.After successful testing, reassign the vanity URL to point to the
blue
deployment. Users are now accessing the updated content when they visit the vanity URL.
If something goes wrong with the blue
deployment after you’ve re-assigned the vanity URL, you can simply re-assign the URL to the green
deployment again while you troubleshoot the blue
deployment.
Assigning a Vanity URL
After deploying your content initially, assign a vanity URL to your content.
VANITY_NAME='sales-forecast'
DATA='{
"path": "'${VANITY_NAME}'"
}'
curl --silent --show-error -L --max-redirs 0 --fail -X PUT \
-H "Authorization: Key ${CONNECT_API_KEY}" \
--data-binary "${DATA}" \
"${CONNECT_SERVER}__api__/v1/content/${CONTENT_GUID}/vanity"
# => {
# => "content_guid":"b99b9b77-a8ae-4ecd-93aa-3c23baf9cefe",
# => "path":"sales-forecast",
# => "created_at": "2020-10-01T12:34:56-0400"
# => }
Switching Deployments
When you are ready to switch from one deployment to the other, assign the vanity URL to the other deployment. The force
parameter indicates that the existing vanity URL should be reassigned.
CONTENT_GUID='...' # blue or green deployment GUID
VANITY_NAME='sales-forecast'
DATA='{
"path": "'${VANITY_NAME}'"
"force": "true"
}'
curl --silent --show-error -L --max-redirs 0 --fail -X PUT \
-H "Authorization: Key ${CONNECT_API_KEY}" \
--data-binary "${DATA}" \
"${CONNECT_SERVER}__api__/v1/content/${CONTENT_GUID}/vanity"
# => {
# => "content_guid":"ccbd1a41-90a0-4b7b-89c7-16dd9ad47eb5",
# => "path":"sales-forecast",
# => "created_at": "2020-10-01T13:31:42-0400"
# => }