Cogy

Build Status Gem Version Inline docs

Cogy integrates Cog with Rails in a way that writing & deploying commands from your application is a breeze.

See the API documentation here.

Status

Cogy is still in public alpha.

While we use it in production, it's still under heavy development. This means that there are a few rough edges and some important bits are missing.

However we'd love any feedback, suggestions or ideas.

Why

Creating a ChatOps command that talks with a Rails app typically involves writing a route, maybe a controller, an action and code to handle the command arguments and options.

This is a tedious and repetitive task and involves a lot of boilerplate code each time someone wants to add a new command.

Cogy is an opinionated library that provides a way to get rid of all the repetitive work.

Writing a new command and deploying it is as simple as:

# in cogy/my_commands.rb

on "foo", desc: "Echo a foo bar back at you!" do
  "@#{handle}: foo bar"
end

...and deploying! After a second or so, the command is ready to be used.

How it works

Cogy is essentially three things:

  1. An opinionated way to write, manage & ship commands: All Cogy commands are defined in your Rails app and end up invoking a single executable within the Relay. Cogy also provides bundle versioning and dynamically generates the installable bundle config, which is also served by your Rails application and consumed by the cogy:install command that installs the new Cogy-generated bundle when you deploy your application.
  2. A library that provides the API for defining the commands. This library is integrated in your application via a Rails Engine that routes the incoming requests to their respective handlers. It also creates the /inventory endpoint, which serves the installable bundle configuration in YAML and can be consumed directly by the cogy:install command.
  3. A Cog bundle that contains the executable that all the commands end up invoking. It is placed inside the Relays and performs the requests to your application when a user invokes a command in the chat. It then posts the result back to the user. It also contains the cogy:install command for automating the task of installing the new bundle when a command is added/modified.

Take a look at the relevant diagrams for an illustration of how Cogy works.

Requirements

Install

Add it to your Gemfile:

gem "cogy"

Then run bundle install

Next, run the generator:

$ bin/rails g cogy:install

This will create a sample command, mount the engine and add a sample configuration initializer in your application.

Usage

Defining a new command:

# in cogy/commands.rb

on "foo", desc: "Echo a bar" do
  "bar"
end

This will print "bar" back to the user who calls !foo in Slack, for example.

Inside the block there are the following pre-defined helpers available:

  • #args: an array containing the arguments passed to the command
  • #opts: a hash containing the options passed to the command
  • #handle: the chat handle of the user who called the command
  • #env: a hash containing the Cogy environment, that is, every environment variable starting with 'COGY_' and set in the Relay

For instructions on defining your own helpers, see Helpers.

A more complete example:

# in cogy/commands.rb
on "calc",
  args: [:a, :b],
  opts: { op: { type: "string" } },
  desc: "Performs a calculation between numbers <a> and <b>",
  examples: "myapp:calc sum 1 2" do
  op = opts[:op].to_sym
  result = args.map(&:to_i).inject(&op)
  "Hello @#{user}, the result is: #{result}"
end

For more examples see the test commands.

Configuration

The configuration options provided are the following:

# in config/initializers/cogy.rb

Cogy.configure do |config|
  # Used in the generated bundle config YAML.
  #
  # Default: "cogy"
  config.bundle_name = "myapp"

  # Used in the generated bundle config YAML.
  #
  # Default: "Cogy-generated commands"
  config.bundle_description = "myapp-generated commands from Cogy"

  # Can be either a string or an object that responds to `#call` and returns
  # a string.
  config.bundle_version = "0.0.1"

  # if you used a callable object, it will be evaluated each time the inventory
  # is called. This can be useful if you want the version to change dynamically
  # when it's needed.
  #
  # For example, this will change the version only when a command is
  # added or is modified (uses the 'grit' gem).
  config.bundle_version = -> {
    repo = Grit::Repo.new(Rails.root.to_s)
    repo.log("HEAD", "cogy/", max_count: 1).first.date.strftime("%y%m%d.%H%M%S")
  }

  # The path in the Relay where the cogy command executable is located at.
  config.executable_path = "/cogcmd/cogy"

  # Paths in your application where the files that define the commands live in.
  # For example the default value will search for all `*.rb` files in the `cogy/`
  # directory relative to the root of your application.
  #
  # Default: ["cogy"]
  config.command_load_paths = "cogy"
end

You can use the generator to quickly create a config initializer in your app:

$ bin/rails g cogy:config

Helpers

It is possible to define helpers that can be used throughout commands. They can be defined during configuration and can accept a variable number of arguments, or no arguments at all.

Let's define a helper that fetches a Shops address of the user who called the command:

Cogy.configure do |c|
  c.helper(:shop_address) { Shop.find_by(owner: handle).address }
end

(Note that custom helpers also have access to the default helpers like handle, args etc.)

Then we could have a command like this:

on "shop_address", desc: "Returns the user's Shop address" do
  "@#{handle}: Your shop's address is #{shop_address}"
end

We can also define helpers that accept arguments:

Cogy.configure do |c|
  c.helper(:format) { |answer| answer.titleize }
end

Then in our command we could call it like so:

on "foo", desc: "Nothing special" do
  format "hello there, how are you today?"
end

Rails' URL helpers are also available inside the commands.

Error template

When a command throws an error the default error template is rendered, which is the following:

@<%= @user %>: Command '<%= @cmd %>' returned an error.

```
<%= @exception.class %>:<%= @exception.message %>
```

It can be overriden in the application by creating a view in app/views/cogy/error.text.erb.

Authors

License

Cogy is licensed under MIT. See LICENSE.