12. Dynamic Command Configuration

Often, commands will need tokens, passwords, or other sensitive information at runtime in order to do their job; managing your AWS servers, sending a tweet, checking your site availability, and many other tasks would be impossible otherwise (at least, not without resorting to horrible software engineering practices in your command bundles!). Cog provides a way to deal with these important data called dynamic command configuration.

Note

When we talk about “dynamic command configuration”, this should not be confused with the config.yaml file that defines the commands, rules, and permissions present in each command bundle. That configuration is effectively static. The configuration we are concerned with is for the execution of individual commands.

It’s also dynamic in the sense that it can be changed on-the-fly by Cog administrators, with the changes taking effect nearly instantaneously without restarting any applications.

12.1. Core Concepts

As detailed below, there are a few ways to go about specifying and managing the dynamic configuration for a bundle, but the central idea is straightforward. At the core, a dynamic configuration is a series of key-value pairs which are injected as variables into the execution environment of a command.

As a concrete example, let’s look at Cog’s Pingdom bundle. As we can see, the pingdom:check command expects three environment variables to be set: PINGDOM_USER_EMAIL, PINGDOM_USER_PASSWORD, and PINGDOM_APPLICATION_KEY. Each of these credentials are required before we can make a properly authenticated REST API request against Pingdom’s servers.

We can store these credentials in a simple YAML file and make it available to Relay (we’ll talk about exactly how to do that below, but the details aren’t important right now).

Pingdom Dynamic Configuration.

PINGDOM_USER_EMAIL: me@mycompany.com
PINGDOM_USER_PASSWORD: supersecret
PINGDOM_APPLICATION_KEY: abcdefghijklmnopqrstuvwxyz

Relay will inject these values into the execution environment it builds for each command in the pingdom bundle. Commands can then access them as environment variables (e.g. ENV['PINGDOM_USER_EMAIL'] in Ruby, os.environ['PINGDOM_USER_EMAIL'] in Python, etc.)

Warning

Each command in a bundle will receive the same dynamic configuration environment. There is not currently a way to cause one command to receive one set of variables while another receives a different set.

Caution

Any keys starting with the prefixes COG_ or RELAY_ will be logged by Relay and ignored.

12.1.1. Layers

Cog allows you to refine the values of these dynamic configurations based on the room the command is invoked from, the user that invokes the command, or a combination of both. For example, this would allow you to configure the twitter bundle to tweet from a special support account when invoked from your #support Slack channel, but from your main company account when called from your #marketing channel.

All bundles can have a “base” configuration layer, which defines (in the absence of any additional layering) the key-value pairs that will be used for command invocations in general. The YAML file above could define the base layer for the pingdom bundle. If you don’t require any room- or user-specific customizations, this is the only layer you really need to care about; in fact, you can act as though layers don’t even exist.

On top of this base, a “room” layer can be overlaid using a merge strategy. Any keys in common will take their values from the room layer, while any keys only mentioned in the base will take their values from that layer. While there is only one “base” layer, each bundle can have any number of room layers, named for a room in their chat client. In our Twitter example above, we would have a “room/support” layer, and a “room/marketing” layer. Whenever a twitter bundle command was invoked from one of those rooms, the appropriate layer would be put into play.

Finally, the same situation applies for “user” layers. If Alice should only ever tweet from a particular account, the appropriate credentials could be put into a “user/alice” layer (assuming her Cog username is “alice”).

Note

Since different chat clients can have different conventions, Cog normalizes names by lowercasing them. Thus, the room layer for your \”Operations\” room would be \”room/operations\”.

Note

Early in processing a request, Cog resolves a user’s chat handle to that person’s Cog username, and this is what is used to determine the appropriate user configuration layer to apply.

Let’s look at a basic example of how this would work in practice. Let’s say we have a widget:widget command that we want to configure. For it’s base configuration we’ll use this:

base.

WIDGET_FOO: base
WIDGET_BAR: base
WIDGET_BAZ: base

(I leave it to your imagination what exciting things a widget command could do with such configuration values.)

If this command is invoked from our #ops Slack channel, we’ll override a few values:

room/ops.

WIDGET_BAR: ops
WIDGET_BAZ: ops

Finally, if Alice invokes the command, we’ll add one more refinement:

user/alice.

WIDGET_BAZ: alice

Now, if Bob runs this command from the #engineering channel, that invocation will receive just the base configuration values, because we have defined neither a room/engineering layer, nor a user/bob layer.

If Bob runs this command from the #ops channel, however, this is what the command will receive in its environment:

base + room/ops.

WIDGET_FOO: base
WIDGET_BAR: ops
WIDGET_BAZ: ops

As you can see, WIDGET_BAR and WIDGET_BAZ have been overridden, but WIDGET_FOO takes it’s value from the base configuration. Had we added a value for WIDGET_FOO to our room/ops layer, though, that value would have been used here.

Now, when Alice runs this command from #engineering, her invocation will receive this set of values:

base + user/alice.

WIDGET_FOO: base
WIDGET_BAR: base
WIDGET_BAZ: alice

There is no room/engineering layer in place, so we still have the WIDGET_BAR value from our base layer, but the user/alice layer has been overlaid.

If Alice runs the command from #ops, all three layers will be in effect:

base = room/ops + alice.

WIDGET_FOO: base
WIDGET_BAR: ops
WIDGET_BAZ: alice

12.2. How To Manage Dynamic Configuration Values

There are currently two ways to manage dynamic configuration values. The default method involves placing dynamic configuration YAML files on the Relay host (either manually, or via the automation tooling of your choice). The alternative allows Cog to centrally manage the configurations on your behalf.

12.2.1. Manual Management of Dynamic Configuration

Under manual management, a Relay will look in a directory tree to find YAML files containing layered dynamic configuration values. The layers will be merged as described above (base, then room, then user) and injected into the execution environment. As the files are consulted on each command invocation (rather than cached), any changes to the files will take effect on the next invocation of a command. This is a tiny bit slower compared to caching the contents but ensures commands are always run with the latest configuration.

To enable this mode, Relay must be told where your configuration files will reside by setting the RELAY_DYNAMIC_CONFIG_ROOT configuration. If you are changing this value, you will need to restart Relay for it to take effect.

Within the RELAY_DYNAMIC_CONFIG_ROOT directory, there should be a directory for each bundle that needs dynamic configuration. Each of these bundle directories will contain one or more YAML files (with either a *.yaml or *.yml extension), with each file corresponding to an individual layer. The naming conventions are as follows:

  • base configuration layer: config.yaml, always.
  • room layers: room_${LOWERCASE_ROOM_NAME}.yaml. If desired, 1-on-1 interactions with Cog can be configured with a room_direct.yaml file.
  • user layers: user_${LOWERCASE_COG_USERNAME}.yaml

In the example directory tree below (which assumes a RELAY_DYNAMIC_CONFIG_ROOT of /relay-config), we have the heroku bundle with a single base configuration, the pingdom bundle with a base layer, an “ops” room layer, a 1-on-1 direct chat room layer, and a user layer for “chris”. Finally, the twitter bundle has a single base configuration layer.

relay-config
├── heroku
│   └── config.yaml
├── pingdom
│   ├── config.yaml
│   ├── room_ops.yaml
│   ├── room_direct.yaml
│   └── user_chris.yaml
└── twitter
    └── config.yaml

12.2.2. Cog-managed Dynamic Configuration

While manually-managed dynamic configuration is simple, it can be cumbersome if you run multiple Relays, or do not have filesystem access to your Relay (as is the case with Hosted Cog). In this case, you can submit your dynamic configuration layer files to Cog and it will distribute the values to your Relays as appropriate.

By default your Relay(s) already supports managed dynamic config, but you can always disable it by setting RELAY_MANAGED_DYNAMIC_CONFIG to false. Managed Relays check in with their Cog server periodically (every 5 seconds by default; see RELAY_MANAGED_DYNAMIC_CONFIG_INTERVAL ) to refresh their configuration data.

Note

Currently, managed configuration mode requires each individual Relay to be configured as such; it is not a centrally-enabled option. Future versions of Cog and Relay may change this.

The easiest way submit configuration layers to Cog is by using cogctl, which in turn uses Cog’s REST API.

Warning

These commands and the API they are built on only work for the Cog-managed configuration. They will not have access to manually-managed configuration files on Relay hosts. The manual process is, well, manual.

12.2.2.1. Adding a base layer of dynamic configuration

$ cogctl bundle config create pingdom ~/path/to/config.yaml --layer=base
Created base layer for 'pingdom' bundle

Here, the --layer option is not required; if not specified, “base” is always the default.

Adding other layers is similar:

$ cogctl bundle config create pingdom ~/path/to/room_ops.yaml --layer=room/ops
Created room/ops layer for 'pingdom' bundle
$ cogctl dynamic-config create pingdom ~/path/to/user_chris.yaml --layer=user/chris
Created user/chris layer for 'pingdom' bundle
$ cogctl dynamic-config create pingdom ~/path/to/room_direct.yaml --layer=room/direct
Created room/direct layer for 'pingdom' bundle

12.2.2.2. Showing the layers that exist

You can list all layers that are currently in place for a given bundle.

$ cogctl bundle config layers pingdom
base
room/direct
room/ops
user/chris

For any given layer, you can see the configuration that will be used.

$ cogctl bundle config info pingdom base
PINGDOM_USER_PASSWORD: "secret_dont_tell"
PINGDOM_USER_EMAIL: "cog@operable.io"
PINGDOM_APPLICATION_KEY: "blahblahblah"

Again, if you do not specify a layer, “base” is assumed. That is, cogctl bundle config info pingdom is equivalent to the above command.

You can also see other layers:

$ cogctl bundle config info pingdom room/ops
PINGDOM_USER_PASSWORD: "ops4life"
PINGDOM_USER_EMAIL: "cog_ops@operable.io"
PINGDOM_APPLICATION_KEY: "opsblahblahblah"

Note

The cogctl bundle config info subcommand returns the contents of only the specified layer; it does not show you the effective configuration that might be injected into a command’s execution environment. You are shown exactly what was uploaded when you ran

cogctl bundle config create $BUNDLE $PATH_TO_CONFIGURATION_FILE –layer=$LAYER

not the result of overlaying multiple layers on top of each other.

12.2.2.3. Deleting Configuration Layers

Configuration layers can be deleted individually

$ cogctl bundle config delete pingdom
Deleted 'base' layer for bundle 'pingdom'
$ cogctl bundle config delete pingdom room/ops
Deleted 'room/ops' layer for bundle 'pingdom'

(As before, not specifying a layer defaults to operating on the base layer.)

Note that by deleting the “base” layer only deletes the base layer; any room or user layers will still be applied. If you wish to remove all dynamic configuration, you must remove each layer individually. The following pipelines may be useful:

# Remove ALL layers
cogctl bundle config layers pingdom | xargs -n1 cogctl bundle config delete pingdom

# Remove only room layers
cogctl bundle config layers pingdom | grep "room/" | xargs -n1 cogctl bundle config delete pingdom

# Remove only user layers
cogctl bundle config layers pingdom | grep "user/" | xargs -n1 cogctl bundle config delete pingdom