Giter Club home page Giter Club logo

cogy's Introduction

Cogy

Build Status Gem Version Documentation

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:

  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 (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.
  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 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 command
  • handle: the chat handle of the user who called the command
  • env: 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", "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 example http://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.

cogy's People

Contributors

agis avatar apostergiou avatar bill-kolokithas avatar charkost avatar fragoulis avatar gnanou avatar iridakos avatar rpk0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

nikosvlagoidis

cogy's Issues

Get rid of Handler for Context

Handler does not make much sense now, plus we need a class to hold the execution context for a command invocation. That way we could also have an env method, similar to how ActionPack has.

Install generator

It should:

  1. add the route
  2. create a cogy folder and place a README
  3. create the initializer (reuse the existing generator)

Use dynamic configuration for providing Cogy host URLs

This could be done by injecting a cog_url option in all the generated commands. Then the executable will read this option and act accordingly.

If we do this, then we can also get rid of the hardcoded URLs inside the executable and make it totally Rails-app-agnostic.

Blocked by #5.

Inject 'COGY_BACKEND' in the config section

Right now users have to set the COGY_BACKEND environment variable in order to use Cogy (see here). Ideally, this shouldn't be necessary.

When operable/cog#1143 is fixed, we can inject COGY_BACKEND from the Cogy configuration into the inventory so that it passes through to the cogy executable. Even better, we could generate it automatically, or have a sane default.

  • use the new setting also in Capistrano task

Support JSON responses and templates

We should add support for easily returning JSON to Cog and also define templates for commands.

Proposal

JSON responses

We could detect if the result of a command invocation is a Hash, and if it is, then inject the "JSON" line in the response and possibly call render :json instead of render :text.

Templates

Templates are defined in separate files (without an extension), named after their corresponding command. For example, for a command named "foo", Cogy would search in app/cogy/templates/**/* for a file named foo and, if found, it would use it when generating the bundle config.

Defining templates

(TBD)

Meaningful backtraces

When an error occurs inside a command, the backtrace is not useful due to eval. We should fix this.

cli: Test commands from the terminal

Ship an executable from the gem, that can be used to test cogy commands from the terminal.

It should probably share the same code with the Relay executable that we'll ship eventually, so this depends on #5.

Create cogy bundle

This bundle will:

  • ship the cogy executable that should be placed in the Relays (#5)
    • the URL that it hits should be configurable using a dynamic configuration
  • provide the install command that accepts the inventory endpoint as a URL and install the bundle to Cog

Supersedes #5.

Customizable user helpers

We should provide a way for users to define their own helpers, like user or something, that will be available inside commands and will return the Rails User object. It should be configurable like this:

Cogy.configure do |c|
  c.user_lambda = ->(id) { User.find(id) }
end

Then the helper could just call this lambda with the Cogy ENV as the argument.

Blocked on #35.

Implement an authentication scheme

We should somehow only accept requests from the cogy executable. This could be done by generating a token from the host application and send it in the config section of the bundle config YAML.

For this, we need operable/cog#1143 to be fixed first.

Test with Rails 5

This is a matter of testing with a Rails 5 dummy and update the dependency constraints. It should mostly be fine for Rails 5.

Generators

Things we could generate:

  • initializer
  • possibly the route
  • a cogy folder inside the app with a README

Add tests

This should be closed once we add tests that test the expected results of commands.

bundle_version & executable_path shouldn't raise error if unset

The generators added in cd3ac72 are effectively unusable because Cogy will throw an error on boot if bundle_version and executable_path configuration options aren't set (which aren't, if the user haven't done so manually).

We should just warn or do nothing at all if those configs aren't set. We should instead default them to something, like all the other configuration values.

Add diagram illustrating the flow

It should illustrate the typical workflow of writing a new command:

  1. writing a bundle
  2. deploying the app
  3. capistrano task
  4. cog trigger
  5. cogy:install

Also how calling a command works:

  1. user typing in Slack
  2. Cog invoking cogy executable
  3. executable hitting the application etc.

Default options `required` to false

Or require them to be defined explicitly. If we go the "not-required by default" way, then maybe we should also support a default description in commands, so that :desc isn't required.

Also update examples in README and docs.

Implement a web interface

This will list all the available commands and provide the ability to execute them from the browser.

Add API documentation

  • Cogy
  • Command
  • Handler
  • controller & views
  • Integrate yardoc tasks and gem
  • link from README

Make the request a POST

The executable should do a POST instead of a GET.

It's more intuitive that it's a POST that triggers a command that may change the state of the application and we won't clutter the URL with all the environment info we want to share in the future.

Loosen rails deps.

We should probably support everything in the 4.2 series. Also update README accordingly.

Automate bundle version bump

Right now, we provide a way to bump the version automatically when a file changes (using the grit gem). We should instead provide a portable way to do this without having to copy/paste code from our documentation.

Command namespaces

Right now all commands are under a single bundle. This probably won't scale well when different teams start adding team-specific commands.

We need a way to namespace individual commands.

Find a better alternative to `next`

Right now, since all the user-defined commands are eventually turn into Proc objects, we can't use return. So we are forced to use next instead, but it's not intuitive.

deployment: Add capistrano tasks and sample Cog trigger

Document the recommended deployment flow and provide:

  • capistrano tasks for invoking the trigger
  • possibly configuration options for controlling those
  • a sample Cog trigger (using our 'cogutils' bundle)
    • we have to release cogutils first
  • capistrano 2
  • capistrano 3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.