Etcha 101

Level set understanding of Etcha and how it works.

Etcha is a tool that runs Patterns: imperative build and runtime configurations and scripts, written in Jsonnet.

Patterns and Commands

A Pattern looks something like this:

{
  "build": [
    {
      "always": true,
      "change": "make myapp",
      "id": "build myapp"
    }
  ],
  "run": [
    {
      "change": "curl -L https://s3.example.com/myapp_v2 -o /myapp",
      "check": "myapp --version | grep v2",
      "id": "copy myapp v2",
      "onChange": [
        "restart myapp"
      ]
    },
    {
      "change": "systemctl restart myapp",
      "id": "restart myapp"
    }
  ]
}

In this example, the Pattern contains build and run Commands. Commands are an imperative list of change, check, and remove scripts:

  • check will always run, and is used to check if a command needs to be changed.
  • change will only run under certain conditions, like if check fails.
  • remove will only run if the command is removed in future Patterns or the change value is modified.

In the example above, the build configuration will always run the change, make myapp. The run configuration will run the first Command’s check, and if that doesn’t exit 0, it will run the change. If the change is run, it will also cause the command restart myapp to run its change, too, since it triggered it via onChange.

Jsonnet

Writing JSON is terrible, so instead we write Patterns using Jsonnet:

local app_name = 'myapp';
local restart = function(name)
  {
    change: 'systemctl restart %s' % name,
    id: 'restart %s' % name,
  };

{
  build: [
    {
      always: true,
      change: 'make %s' % app_name,
      id: 'build %s' % app_name,
    },
  ],
  run: [
    {
      change: 'curl -L https://s3.example.com/myapp_v2 -o /myapp',
      check: "myapp --version | grep v2",
      id: "copy %s v2' app_name,
      onChange: [
        'restart myapp',
      ],
    },
    restart(app_name),
  ]
}

With Jsonnet, we can use real programming language features like functions, variables, and imports to describe Patterns and Commands succinctly. Additionally, we can do neat things like lint our Patterns, Commands, and other libraries.

Building JWTs

Etcha builds Patterns into cryptographically signed files called JSON Web Tokens (JWTs). These files contain the raw Pattern Jsonnet files, allowing us to render the Patterns on each node.

Building a JWT will cause Etcha to run the build configurations as part of the build process. In the above example, running etcha build example.jsonnet will also run make myapp.

Sources

Once we have built our JWT, which contains our Patterns, we configure our Etcha instances to Push or Pull the JWT. This is done through Sources in the remote Etcha’s configuration file.

  • If a source is configured to allow pushes, the local Etcha instance can connect to a remote Etcha instance and pushes the JWT.

  • If a source is configured to pull, the remote Etcha instance pulls down a JWT from its local disk, or a web server/object storage location.

In either mode, Etcha will verify the JWT signature and ensure its valid, render the Pattern, and execute the run configuration.

Events and Webhooks

Sources can be configured to listen for Events and Webhooks:

  • Events are fired by Patterns using the onChange property. Etcha also uses Events to extract data from your Patterns.

  • Webhooks are paths configured by a Source. Etcha will run a Source’s Pattern for each Webhook request, and set environment variables containing details about the request.

Putting It All Together

This diagram should make sense now:

flowchart TD
  apps[Apps]
  commands[Commands]
  events[Events]
  jsonnet[Jsonnet]
  jwt[JWT]
  pattern[Pattern]
  source[Source]
  user[User]
  webhooks[Webhooks]

  user -- Writes --> jsonnet
  jsonnet -- Builds --> jwt
  jwt -- Pulls/Pushes --> source
  source -- Imports --> pattern
  pattern -- Executes --> commands

  commands -- Create --> events
  events -- Trigger --> pattern

  apps -- Request --> webhooks
  webhooks -- Trigger --> pattern