Cogy
Cogy integrates Cog with Rails in a way that writing & deploying commands from your application is a breeze.
See the API documentation here.
Refer to the Changelog to see what's changed between releases.
Features
- Define commands from your Rails app (see Usage)
- Bundle config is generated automatically
- Commands are installed automatically when you deploy (see Deployment)
- Supports JSON responses and Cog Templates (see Returning JSON to COG)
- Customizable error template (see Error template)
...and more on the way!
Why
Creating ChatOps commands that talk 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 boilerplate stuff so you can focus on just the actual commands.
Deploying a new command is as simple as writing:
# in cogy/my_commands.rb
on "foo", desc: "Echo a foo bar back at you!" do
"@#{handle}: foo bar"
end
...and deploying!
How it works
Cogy is essentially three things:
- 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 (see below). 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. - 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 thecogy:install
command. - 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 a detailed illustration.
Requirements
- Cog 1.0.0.beta2 or later
- cogy bundle 0.4.0 or later
- Ruby 2.1 or later
- Rails 4.2 or later
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 things change fast.
However we'd love any feedback, suggestions or ideas.
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/my_commands.rb
on "foo", desc: "Echo a bar" do
"bar"
end
This will print "bar" back to the user who calls !foo
in Slack.
Let's define a command that simply adds the numbers passed as arguments:
# in cogy/calculations.rb
on "add", args: [:a, :b], desc: "Add two numbers" do
a.to_i + b.to_i
end
Inside the block there are the following helpers available:
args
: an array containing the arguments passed to the command- arguments can also be accessed by their names as local variables
opts
: a hash containing the options passed to the commandhandle
: the chat handle of the user who called the commandenv
: a hash containing the Relay environment as available in the cogy bundle
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", required: true } },
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.
Returning JSON to Cog
You can return JSON to Cog by just returning a Hash
:
on "foo", desc: "Just a JSON" do
{ a: 3 }
end
The hash is automatically converted to JSON. The above command would return the following response to Cog:
COG_TEMPLATE: foo
JSON
{"a":3}
To customize the Cog template to be used, use the template
option:
on "foo", desc: "Just a JSON", template: "bar" do
{ a: 3 }
end
Info on how Cog handles JSON can be found in the official documentation.
Templates
Templates are defined in their own files under templates/
inside any of
the command load paths. For example:
$ tree
.
├── README.rdoc
├── <..>
├── cogy
│ ├── some_commands.rb
│ └── templates
│ └── foo # <--- a template named 'foo'
|── <...>
Given the following template:
# in cogy/templates/foo
~ hello world ~
the resulting bundle config would look like this:
---
cog_bundle_version: 4
name: foo
description: The bundle you really need
version: 0.0.1
commands:
<...>
templates:
foo:
body: |-
~ hello world ~
Refer to the Cog book for more on templates.
Configuration
The configuration options provided are the following:
# in config/initializers/cogy.rb
Cogy.configure do |config|
# Configuration related to the generated Cog bundle. Will be used when
# generating the bundle config YAML to be installed.
config.bundle = {
# The bundle name.
#
# Default: "myapp"
name: "myapp",
# The bundle description
#
# Default: "Cog commands generated from Cogy"
description: "myapp-generated commands from Cogy",
# The bundle version.
#
# Can be either a string or an object that responds to `#call` and returns
# a string.
#
# Default: "0.0.1"
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
# automatically.
#
# For example, this will change the version only when a command is
# added or is modified (uses the 'grit' gem).
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.
cogy_executable: "/cogcmd/cogy"
# The endpoint where Cogy is reachable at. This depends on where you've
# mounted the Cogy engine at.
cogy_endpoint: "http://www.example.com/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. This is useful for DRYing repetitive code.
They are defined during configuration and may also accept arguments.
Let's define a helper that fetches the address
of a Shop
record:
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 that makes use of the helper:
on "shop_address", desc: "Returns the user's Shop address" do
"@#{handle}: Your shop's address is #{shop_address}"
end
Helpers may also accept arguments:
Cogy.configure do |c|
c.helper(:format) { |answer| answer.titleize }
end
This helper could be called like so:
on "foo", desc: "Nothing special" do
format "hello there, how are you today?"
end
Rails URL helpers (ie. foo_url
) 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
.
Testing commands
We don't yet provide means to write tests for the commands, but you can easily test them by executing a request to your development server. For example, if you mounted the engine like so:
mount Cogy::Engine, at: "cogy"
you can test a foo
command like this:
$ curl -XPOST --data "COG_ARGV_0=foo" http://localhost:3000/cogy/cmd/foo
In the request body you may pass the complete or any part of the Cog environment you need.
This is essentially what the cogy executable also does.
Deployment
Cogy provides integration with Capistrano 2 & 3.
There is just one task, cogy:notify_cog
, which executes the
installation Trigger.
The task should run
after the application server is restarted, so that the new commands
are picked up and served by the Inventory endpoint. In Capistrano 2 for
example, it should run after the built-in deploy:restart
task.
The following options need to be set:
cogy_release_trigger_url
: This is the URL of the Cog Trigger that will install the newly deployed bundle (ie.!cogy:install
)cogy_endpoint
: Where the Cogy Engine is mounted at. For examplehttp://myapp.com/cogy
.
You can also configure the timeout value for the request to the Trigger by
setting the cogy_trigger_timeout
option (default: 7).
The code of the task can be found here.
Capistrano 2
Add the following in config/deploy.rb
:
# in config/deploy.rb
require "cogy/capistrano"
set :cogy_release_trigger_url, "<TRIGGER-INVOCATION-URL>"
set :cogy_endpoint, "<COGY-MOUNT-POINT>"
after "deploy:restart", "cogy:notify_cog"
Capistrano 3
Add the following in your Capfile:
require "cogy/capistrano"
Then configure the task and hook it:
# in config/deploy.rb
set :cogy_release_trigger_url, "<TRIGGER-INVOCATION-URL>"
set :cogy_endpoint, "<COGY-MOUNT-POINT>"
after "<app-restart-task>", "cogy:notify_cog"
Installation Trigger
In order to automate the process of installing the new bundle versions (eg. after a new command is added), you must create a Cog Trigger that will perform the installation, which will be called when you deploy your app.
The trigger will look this:
$ cogctl triggers
Name ID Enabled Pipeline
ReleaseUrlTrigger d10df83b-a737-4fc4-8d9b-bf9627412d0a true cogy:install --url $body.url > chat://#general
It essentially uses the cogy bundle and installs the bundle config which is served by your application (ie. http://your-app.com/cogy/inventory).
See Deployment on information about how this trigger is invoked.
Development
Running the tests and RuboCop for the latest Rails version:
$ rake
Running just the tests:
$ rake test
Running RuboCop:
$ rake rubocop
Generating documentation:
$ rake yard
Running the tests for all the supported Rails versions:
$ appraisal rake
Or for a specific version:
$ appraisal 4.2 rake test
Authors
License
Cogy is licensed under MIT. See LICENSE.