Blog

Akamai as Code with Jsonnet

April 28, 2021 · by Anthony Hogg ·

In this article, we’ll discuss how Jsonnet can be used to improve the Akamai configuration developer experience by providing a superior templating experience. We’ll also showcase the new Akamai Jsonnet CLI, a simple utility to quickly convert an existing Akamai configuration to Jsonnet. Before we dive into managing Akamai configuration as code, let’s review configuration deployment and configuration representation.

At its core, configuration deployment is about manifesting a text representation of a configuration on the Akamai platform. There are many ways this can be done, depending on the workflows and technical ecosystem. Configuration deployment is the “Ops” in Akamai DevOps and has been an area of intense focus in the past few years during the race to meet customer automation requirements. On the other hand, configuration representation focuses on development environment integration, code assist and reuse, code quality, modularity, and change management. It’s the “Dev” in Akamai DevOps.

About Jsonnet

Jsonnet is a superset of JSON, so valid JSON is also valid Jsonnet. Jsonnet adds support for comments, variables, and control flow (conditionals and loops), and it comes with a standard library of functions that help with common computations. An import statement includes an external Jsonnet source file that enables code reuse and encourages modularity.

The design decisions that underpin Jsonnet make it an ideal choice for JSON-based configuration representations in general, and Akamai configurations in particular.

Why Jsonnet 

Jsonnet may work well for you if you’re managing your Akamai configurations as code. However, you might be tempted to manage your configurations as JSON. JSON works well with simple configurations or if you have very few. On the other hand, if you’ve ever wished that you could use a simple loop to repeat a given behavior multiple times (like a long list of redirects), or if you need to repeat the same configuration fragment across many configurations (like a pinned origin certificate, an organization-wide CORS behavior, or a caching recipe), you should consider using a template engine.

There are many template engines to choose from, and they fall into two categories: text templating (Jinja2, Handlebars, Mustache, and even PHP) and data templating (Jsonnet, Cuelang, and Dhall). All of them provide common programming constructs like loops and variables, but for configuration management, data templating is superior for at least one key reason: it’s aware of the syntax of the language that it’s generating. This means that, if the template executes correctly, the output is guaranteed to be valid JSON. Contrast this with generating JSON with Jinja, where the template can execute successfully but might commonly introduce trailing commas or misquoted field names and values.

The Jsonnet website has a page comparing Jsonnet to other configuration management approaches. It's a great read that helps you understand if Jsonnet could be a good fit, and it goes into more detail about a few other useful properties.

Jsonnet will feel familiar to anyone with even a little Python or JavaScript experience. Because it’s a superset of JSON, it really looks like JSON, and JSON can be gradually converted to Jsonnet.

Now’s a good time to dig into some concrete examples.

CP Code Mapping

Akamai configurations commonly attribute a CP Code to specific traffic segments, typically for reporting purposes. It’s one of the few situations where we’re forced to repeat ourselves and where Jsonnet shines.

In JSON, this type of configuration looks as follows:

code block

There is a lot of repetition here.

With Jsonnet, we can express the same configuration by reducing the boilerplate to a common template and repeating it using a loop.

code block

In addition to the loop, we’ve also removed optional double quotes around field names.

While this sort of simplification can be accomplished using an engine like Jinja2, we would need to make sure our variables are interpolated to correct JSON, and we would also need to insert commas between list items. With Jsonnet, we have something much more natural that guarantees correct JSON output or a render-time error. 

Preproduction Logic

Best practice dictates that preprod environments be served through Akamai, which helps catch errors early and forces more stakeholders to test changes against the entire stack from the get go.

It also forces us to manage multiple (practically identical) versions of a configuration. The popular “Gold Config” pattern creates a single configuration that can be cloned without changes from environment to environment. When specific functionality is required on a subset of environments only, run-time logic determines the current environment and gates accordingly.

The following configuration JSON does just that; it identifies if we’re in preproduction, and if we are, it activates a Request Control Cloudlet policy.

code block

The problem with this approach is that it mixes what should be build-time logic with run-time logic. On the production configuration, the preproduction logic is never run - it is essentially dead code.

These are simple examples, but in more complex ones, sheer size becomes a problem and it becomes much harder to reason about what the configuration is doing.

Assuming we have a production property template in Jsonnet, we could simply create a preproduction template like this:

code block

With this approach, the preproduction logic isn’t present in the production configuration, which makes it much simpler to manage and understand because it only concerns itself with run-time logic pertinent for production traffic.

Using External Variables

In the previous example, we showed how to create a derivative template by overriding another. While this is powerful, a preproduction environment would likely differ in many more ways than just the presence of an extra rule.

Typically, the origin server and default CP Code should also change. There are a few ways to accomplish this, but using Jsonnet external variables is very effective.

Let’s put all of our environment-specific values in a separate Jsonnet file.

code block

We can now define a single template capable of building a configuration for any environment (simplified for clarity).

code block

Rendering the configuration now looks like this:

jsonnet \

  --ext-code-file env=env/preprod.jsonnet \

  templates/example.com.jsonnet > build/preprod.example.com.json

With --ext-code-file, we’re evaluating env/preprod.jsonnet and making it available to templates/example.com.jsonnet as the external variable env.

Akamai CLI for Jsonnet

Switching from JSON to Jsonnet is an incremental process, especially on larger configurations. The CLI was written to jumpstart the initial task of splitting a configuration into one file per rule and making the generated code a little more fluent.

Dependencies

You will need Python 3 and jsonnetfmt. jsonnetfmt comes with the main Jsonnet interpreter, and we recommend using https://github.com/google/go-jsonnet. Each component covers installation in-depth. If running Windows, we recommend using the Windows Subsystem for Linux.

In the instructions below, we’ll use the Property Manager CLI to retrieve our Contract ID and Product ID.

All dependencies are also installed in the Akamai Development Environment; if you have Docker, this might be the easiest way to get started!

Finally, you’ll need an edgerc file with valid Property Manager API credentials. Instructions for setting these up can be found on the Property Manager API documentation page.

Installing the CLI

akamai install akamai-contrib/cli-jsonnet

Retrieving the Product ID

Because the Jsonnet CLI is aware of the JSON schema of the Property Manager configuration format, it needs to be able to retrieve the right schema for the delivery product you’re using, which depends on the contract.

First, list your contracts.

akamai property-manager list-contracts

╒═══════════════╤════════════════════╕

│"Contract ID"  │"Contract Type Name"│

╞═══════════════╪════════════════════╡

│"ctr_C-ABC1234"│"DIRECT_CUSTOMER"   │

└───────────────┴────────────────────┘

Now, list the products associated with the appropriate contract.

akamai property-manager list-products --contractId ctr_C-ABC1234

╒═══════════════════╤═══════════════════════╕

│"Product Name"     │"Product ID"           │

╞═══════════════════╪═══════════════════════╡

│"SPM"              │"prd_SPM"              │

└───────────────────┴───────────────────────┘

Pulling an Existing Property as Jsonnet

First, create a folder to contain the configuration.

mkdir -p jsonnet-rocks

cd jsonnet-rocks

Then retrieve the configuration by name, specifying the productId.

akamai jsonnet papi property --propertyName jsonnet-rocks --productId SPM

Once complete, your folder contents should look similar to this (varying based on the configuration structure).

$ tree

.

├── jsonnet-rocks

│   ├── rules

│   │   ├── Offload

│   │   │   ├── Assets_Flexible_Cache_Id.jsonnet

│   │   │   ├── CSS_and_Javascript.jsonnet

│   │   │   ├── Static_objects.jsonnet

│   │   │   └── Uncacheable_Responses.jsonnet

│   │   ├── Offload.jsonnet

│   │   ├── Performance

│   │   │   ├── Compressible_Objects.jsonnet

│   │   │   └── JPEG_Images.jsonnet

│   │   └── Performance.jsonnet

│   ├── rules.jsonnet

│   └── variables.jsonnet

├── jsonnet-rocks.jsonnet

└── papi

    └── SPM

        └── latest.libsonnet

If you open one of the files (for example, Offload.jsonnet), you’ll see something like this:

code block

Each file imports a Jsonnet library exposing all the behaviors and criteria as templates. As IDE integration for Jsonnet improves, this will bring better autocomplete. These templates also simplify structure. Rule components are output in the order you expect: name, comments, criteria, behaviors and children.

// standard

{

  name: 'caching',

  options: {

   behavior: 'NO_STORE'

  }

}

 

// from the CLI

papi.behavior.caching {

  behavior: 'NO_STORE'

}

Now you can easily render the template back into JSON.

jsonnet -J . -co build/jsonnet-rocks.json jsonnet-rocks.jsonnet

 

# or if you are not using go-jsonnet

 

mkdir build; jsonnet -J . jsonnet-rocks.jsonnet > build/jsonnet-rocks.json

The resulting JSON file is correct and can be pushed back to the platform using direct API calls, the Akamai Property Manager CLI, the Akamai Terraform Provider, the Akamai PowerShell Integration, and a few other methods. 

Using the CLI:

akamai pm property-update --property jsonnet-rocks --file build/jsonnet-rocks.json

Watch the Demo

Want to Learn More?

About the Author

author photo

Anthony Hogg is a Senior Enterprise Architect based in Paris, France. Before joining Akamai in 2016, Anthony spent 10 years building, delivering, and maintaining applications for web agencies and SaaS vendors as a full-stack developer. He now puts his deep knowledge of the web platform at the service of Akamai's customers, helping them deliver fast experiences to a global audience, the DevOps way. He is currently focused on automation and bringing Akamai's DevOps renaissance to customers by writing and delivering training content and talks, integrating and building tools, and engaging in technical consulting.