Skip to content

Commit

Permalink
💥 starboard is here
Browse files Browse the repository at this point in the history
  • Loading branch information
ys committed Aug 5, 2014
0 parents commit 7c3aca0
Show file tree
Hide file tree
Showing 144 changed files with 7,524 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
GITHUB_TOKEN=APPLICATION_TOKEN_ACCESSING_THE_REPO
GITHUB_REPO=heroku/starboard-docs-template
HOOK_TOKEN=8b1acb48e21eec7780ed04fd92fe8cadc334b18911b0a6ebb63522791f2e386993ad9af4eb93bd25d63c6a91a695e92522a13489358edac0240e746f5eca6b62
TRELLO_ORGANIZATION=heroku
SESSION_SECRET=cdff8f37e9b22d2f73498c413299295076f34b6a34bea741c1516a8268ad707ab689a3e99e01f7f7acdd787944e48cd8eaa58fc94fabd4a94d7b28d123a3bd6c
HEROKU_BOUNCER_SECRET=5126ae8fb74da32f4791e70100015b8ce79f1c5b8a43fc664a6c390612b22d162ec29ab3dc44926c48d6fdf4c1d5cf5f28e3e7dd9e8564f8c61a4c84600a1827
HEROKU_OAUTH_ID=123
HEROKU_OAUTH_SECRET=123
TRELLO_KEY=abc
8 changes: 8 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
GITHUB_TOKEN=github_token
GITHUB_REPO=ys/markdown
HOOK_TOKEN=123
SESSION_SECRET=cdff8f37e9b22d2f73498c413299295076f34b6a34bea741c1516a8268ad707ab689a3e99e01f7f7acdd787944e48cd8eaa58fc94fabd4a94d7b28d123a3bd6c
HEROKU_BOUNCER_SECRET=5126ae8fb74da32f4791e70100015b8ce79f1c5b8a43fc664a6c390612b22d162ec29ab3dc44926c48d6fdf4c1d5cf5f28e3e7dd9e8564f8c61a4c84600a1827
HEROKU_OAUTH_ID=123
HEROKU_OAUTH_SECRET=123
TRELLO_KEY=abc
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
www.sass-cache.env
Expand Down
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
lannguage: ruby

rvm:
- 2.1.2

bundler_args: --without=development

script: 'script/ci'
23 changes: 23 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
source "https://rubygems.org"
ruby "2.1.2"

gem "sinatra"
gem "sinatra-contrib"
gem "puma"
gem "dalli"
gem "rubyzip"
gem "excon"
gem "sinatra-asset-pipeline"
gem "scrolls"
gem "heroku-bouncer"

gem "rack-ssl"

group "development", "test" do
gem "dotenv"
end

group "test" do
gem "rack-test"
gem "webmock"
end
112 changes: 112 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.3.6)
backports (3.6.0)
coffee-script (2.3.0)
coffee-script-source
execjs
coffee-script-source (1.7.1)
crack (0.4.2)
safe_yaml (~> 1.0.0)
dalli (2.7.2)
dotenv (0.11.1)
dotenv-deployment (~> 0.0.2)
dotenv-deployment (0.0.2)
excon (0.39.2)
execjs (2.2.1)
faraday (0.9.0)
multipart-post (>= 1.2, < 3)
hashie (3.2.0)
heroku-bouncer (0.4.3)
faraday (~> 0.8)
omniauth-heroku (>= 0.1.0)
rack (~> 1.0)
sinatra (~> 1.0)
hike (1.2.3)
jwt (1.0.0)
multi_json (1.10.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
omniauth-heroku (0.1.1)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0)
omniauth-oauth2 (1.2.0)
faraday (>= 0.8, < 0.10)
multi_json (~> 1.3)
oauth2 (~> 1.0)
omniauth (~> 1.2)
puma (2.9.0)
rack (>= 1.1, < 2.0)
rack (1.5.2)
rack-protection (1.5.3)
rack
rack-ssl (1.4.1)
rack
rack-test (0.6.2)
rack (>= 1.0)
rake (10.3.2)
rubyzip (1.1.6)
safe_yaml (1.0.3)
sass (3.3.14)
scrolls (0.3.8)
sinatra (1.4.5)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
sinatra-asset-pipeline (0.4.0)
coffee-script
rake
sass
sinatra
sprockets
sprockets-helpers
sprockets-sass
sinatra-contrib (1.4.2)
backports (>= 2.0)
multi_json
rack-protection
rack-test
sinatra (~> 1.4.0)
tilt (~> 1.3)
sprockets (2.12.1)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-helpers (1.1.0)
sprockets (~> 2.0)
sprockets-sass (1.2.0)
sprockets (~> 2.0)
tilt (~> 1.1)
tilt (1.4.1)
webmock (1.18.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)

PLATFORMS
ruby

DEPENDENCIES
dalli
dotenv
excon
heroku-bouncer
puma
rack-ssl
rack-test
rubyzip
scrolls
sinatra
sinatra-asset-pipeline
sinatra-contrib
webmock
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: bundle exec puma
9 changes: 9 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'sinatra/asset_pipeline/task'
require 'rake/testtask'
require './app'

Sinatra::AssetPipeline::Task.define! App

Rake::TestTask.new do |t|
t.pattern = 'test/*_test.rb'
end
73 changes: 73 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Starboard [![Build Status](https://magnum.travis-ci.com/heroku/starboard.svg?token=sCaMa7HeG4812XfRVVH2&branch=master)](https://magnum.travis-ci.com/heroku/starboard)

**WARNING: This code is an early-access release; we wrote it for ourselves and it still harbors a lot of Herokuisms. You will probably have to adapt it to your needs if you want to use it.**

Starboard is a tool which creates Trello boards for tracking the various tasks necessary when onboarding, offboarding, or crossboarding employees. We use it extensively within Heroku.

The tasks themselves are authored as markdown files. When you run starboard, it resolves which tasks are relevant for the given employee and target team, and creates a new Trello board from the relevant markdown files.

Because the files are markdown (and stored in git), they're accessible to everyone in the company. Improvements to onboarding can come from anyone in the form of a pull request to the relevant guide.

### How do I use it to create a Trello board?

Go to your app, log through Trello, fill the form and wait to be redirected to the newly created board.

### Where is it getting the data from?

Starboard depends on a specially-structured GitHub repository which contains the markdown guides. See the [template repository][1] for an example and more documentation.

## Starboard on Heroku

First, you'll need to create a GitHub repository containing the guides themselves. More information about the format of this repository is available as part of the [template repository][1].

### Security

Starboard limits access by means of checking that the user has access to a specific Trello organization.

**Warning**: users who don't have access to the Trello org cannot create boards, but can still access the starboard frontend and nothing prevents them from reverse-engineering the requests to fetch the raw markdown guides. **If this is a problem for you, limit access to the webapp by other means.**

### Deploy the app

You can use the old way and
- Clone the code
- Create an app
- Add a memcached add-on
- Push the code

You will also have to set a few configuration variables.

### Config variables

- `TRELLO_ORGANIZATION` The trello organisation where the boards are created.
- `TRELLO_KEY` The trello API key that can be found at [https://trello.com/1/appKey/generate](https://trello.com/1/appKey/generate)
- `GITHUB_TOKEN` A GitHub access token to get the guides out of your repository. You can create an OAuth app and generate a token or use a personal access token. Please check [https://help.GitHub.com/articles/creating-an-access-token-for-command-line-use](https://help.GitHub.com/articles/creating-an-access-token-for-command-line-use) and use a private repo if you want to keep your content private.
- `GITHUB_REPO` An `orgname/reponame` identifier of the GitHub repository containing your guides.

### Register a Webhook on GitHub

In order for starboard to be aware of updates to your guides, you will need to create a webhook.

Follow the [GitHub guide about webhooks creation][2] and add a hook for `https://<YOUR_HEROKU_APP>.herokuapp.com/guides?t=<YOUR_SECRET_HOOK_TOKEN>`

### First deploy

When you first deploy the app, you'll need to bootstrap the cache. There are two ways to accomplish this:

1. Update your guides after installing the app and setting up the webhook. The update will cause Starboard to cache your guides.

2. Trigger the same mechanism, but manually: `curl -X POST https://<YOUR_HEROKU_APP>.herokuapp.com/guides?t=<YOUR_SECRET_HOOK_TOKEN>`

### Ready to create boards

Hooray! Head over to `https://<YOUR_APP>.herokuapp.com` and go to town.

### GitHub token renewal if you created an OAuth application.

You can regenerate an access token via this curl command.

```
curl -vvv -X POST -H "Content-type: application/json" -u <USERNAME> -H "X-GitHub-OTP: <2FACODE IF YOU 2FA>" -d '{"scopes":["repo"],"note":"starboard access","client_id":"<APP_CLIENT_ID>","client_secret":"<APP_SECRET>"}' https://api.GitHub.com/authorizations
```

[1]: https://GitHub.com/heroku/starboard-docs-template
[2]: https://developer.GitHub.com/webhooks/creating/
77 changes: 77 additions & 0 deletions app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
require "rubygems"
require "bundler"

Bundler.require
require "sinatra/asset_pipeline"

require "securerandom"
require_relative "app/guides"

class App < Sinatra::Base
register Sinatra::AssetPipeline

set :ssl, lambda { !(development? || test?) }
use Rack::SSL, exclude: ->(env){ !ssl? }

configure :development do
require "dotenv"
Dotenv.load
end

configure :production do
ENV["MEMCACHE_SERVERS"] = ENV["MEMCACHIER_SERVERS"] if ENV["MEMCACHIER_SERVERS"]
ENV["MEMCACHE_USERNAME"] = ENV["MEMCACHIER_USERNAME"] if ENV["MEMCACHIER_USERNAME"]
ENV["MEMCACHE_PASSWORD"] = ENV["MEMCACHIER_PASSWORD"] if ENV["MEMCACHIER_PASSWORD"]
end

set :cache, Dalli::Client.new
set :assets_precompile, %w(app.js screen.css)
enable :sessions
set :session_secret, ENV["SESSION_SECRET"]

configure :production, :development do
use ::Heroku::Bouncer,
oauth: { id: ENV["HEROKU_OAUTH_ID"], secret: ENV["HEROKU_OAUTH_SECRET"] },
secret: ENV["HEROKU_BOUNCER_SECRET"],
herokai_only: true,
skip: ->(env) { ENV["HEROKAI_ONLY"] != "true"}
end

Excon.defaults[:middlewares] << Excon::Middleware::RedirectFollower

post "/guides" do
error 401 unless [ENV["HOOK_TOKEN"], token].include? params[:t]
json refresh_guides
end

get "/" do
erb :index, locals: { organization: organization, token: token }
end

get "/guides/*" do
guides.get(params[:splat].first) || error(404)
end

get "/setup" do
erb :setup
end

def refresh_guides
guides.refresh
end

def guides
@guides ||= Guides.new(settings.cache)
end

def organization
ENV["TRELLO_ORGANIZATION"] ||
raise("config var for TRELLO_ORGANIZATION is missing")
end

def token
settings.cache.get("frontend_hook_token") || SecureRandom.hex.tap do |token|
settings.cache.set("frontend_hook_token", token, 60)
end
end
end
51 changes: 51 additions & 0 deletions app/guides.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
require "zip"
require "excon"
require "scrolls"

class Guides

attr_accessor :guides_file
attr_accessor :http_client
attr_reader :cache

def initialize(cache)
@http_client = Excon
@cache = cache
end

def get(name)
cache.get(name)
end

def refresh
Scrolls.log(app: "starboard", action: "refresh") do
refreshed_guides = { guides: [] }
Zip::File.open(guides_file.path) do |io|
io.each do |entry|
filename = entry.name.split('/')[1..-1].join('/')
next if entry.directory?
refreshed_guides[:guides] << filename
cache.set(filename, entry.get_input_stream.read)
end
end
refreshed_guides
end
end

private

def guides_file
unless @guides_file
f = Tempfile.new('zip')
f << zip_response.body
f.flush
f.close
@guides_file = f
end
@guides_file
end

def zip_response
http_client.get("https://:#{ENV['GITHUB_TOKEN']}@api.github.com/repos/#{ENV['GITHUB_REPO']}/zipball/master")
end
end
Loading

0 comments on commit 7c3aca0

Please sign in to comment.