Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use hatchet.json for config #3

Merged
merged 1 commit into from Mar 25, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
@@ -1 +1,3 @@
.DS_Store .DS_Store
test/fixtures/repos/*

38 changes: 38 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,38 @@
PATH
remote: .
specs:
hatchet (0.0.1)
activesupport
anvil-cli
excon
heroku-api
rake
thor

GEM
remote: http://rubygems.org/
specs:
activesupport (3.2.13)
i18n (= 0.6.1)
multi_json (~> 1.0)
anvil-cli (0.15.0)
progress (~> 2.4.0)
rest-client (~> 1.6.7)
thor (~> 0.15.2)
excon (0.16.10)
heroku-api (0.3.8)
excon (~> 0.16.10)
i18n (0.6.1)
mime-types (1.21)
multi_json (1.7.1)
progress (2.4.0)
rake (10.0.3)
rest-client (1.6.7)
mime-types (>= 1.16)
thor (0.15.4)

PLATFORMS
ruby

DEPENDENCIES
hatchet!
74 changes: 74 additions & 0 deletions README.md
Expand Up @@ -84,6 +84,80 @@ You can specify buildpack to deploy with like so:


Hatchet::App.new("repos/rails3/codetriage", buildpack: "https://github.com/schneems/heroku-buildpack-ruby.git").deploy do |app| Hatchet::App.new("repos/rails3/codetriage", buildpack: "https://github.com/schneems/heroku-buildpack-ruby.git").deploy do |app|


## Hatchet Config

Hatchet is designed to test buildpacks, and requires full repositories
to deploy to Heroku. Web application repos, especially Rails repos, aren't known for
being small, if you're testing a custom buildpack and have
`BUILDPACK_URL` set in your app config, it needs to be cloned each time
you deploy your app. If you've `git add`-ed a bunch of repos then this
clone would be pretty slow, we're not going to do this. Do not commit
your repos to git.

Instead we will keep a structured file called
inventively `hatchet.json` at the root of your project. This file will
describe the structure of your repos, have the name of the repo, and a
git url. We will use it to sync remote git repos with your local
project. It might look something like this

{
"hatchet": {},
"rails3": ["git@github.com:codetriage/codetriage.git"],
"rails2": ["git@github.com:heroku/rails2blog.git"]
}

the 'hatchet' object accessor is reserved for hatchet settings.
. To copy each repo in your `hatchet.json`
run the command:

$ hatchet install

The above `hatchet.json` will produce a directory structure like this:

repos/
rails3/
codetriage/
#...
rails2/
rails2blog/
# ...

While you are running your tests if you reference a repo that isn't
synced locally Hatchet will raise an error. Since you're using a
standard file for your repos, you can now reference the name of the git
repo, provided you don't have conflicting names:

Hatchet::App.new("codetriage").deploy do |app|

If you do have conflicting names, use full paths.

A word of warning on including rails/ruby repos inside of your test
directory, if you're using a runner that looks for patterns such as
`*_test.rb` to run your hatchet tests, it may incorrectly think you want
to run the tests inside of the rails repositories. To get rid of this
problem move your repos direcory out of `test/` or be more specific
with your tests such as moving them to a `test/hatchet` directory and
changing your pattern if you are using `Rake::TestTask` it might look like this:

t.pattern = 'test/hatchet/**/*_test.rb'

A note on external repos: since you're basing tests on these repos, it
is in your best interest to not change them or your tests may
spontaneously fail. In the future we may create a hatchet.lockfile or
something to declare the commit

## Hatchet CLI

Hatchet has a CLI for installing and maintaining external repos you're
using to test against. If you have Hatchet installed as a gem run

$ hatchet --help

For more info on commands. If you're using the source code you can run
the command by going to the source code directory and running:

$ ./bin/hatchet --help





## The Future ## The Future
Expand Down
3 changes: 3 additions & 0 deletions Rakefile
@@ -1,2 +1,5 @@
# encoding: UTF-8 # encoding: UTF-8
require 'bundler/gem_tasks' require 'bundler/gem_tasks'

require 'hatchet/tasks'

74 changes: 74 additions & 0 deletions bin/hatchet
@@ -0,0 +1,74 @@
#!/usr/bin/env ruby

unless File.respond_to? :realpath
class File #:nodoc:
def self.realpath path
return realpath(File.readlink(path)) if symlink?(path)
path
end
end
end
$: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')

require 'hatchet'
require 'thor'

class HatchetCLI < Thor
desc "install", "installs repos defined in 'hatchet.json'"
def install
warn_dot_ignore!
puts "Installing repos for hatchet"
dirs.each do |directory, git_repo|
if Dir[directory].present?
puts "== Detected #{git_repo} in #{directory}, pulling\n"
pull(directory, git_repo)
else
puts "== Did not find #{git_repo} in #{directory}, cloning\n"
clone(directory, git_repo)
end
end
end

desc "list", "lists all repos and their destination listed in hatchet.json"
def list
repos.each do |repo, directory|
puts "#{repo}: #{directory}"
end
end

private

def warn_dot_ignore!
gitignore = File.open('.gitignore').read
return if gitignore.include?(config.repo_directory_path)
puts "WARNING: add #{File.join(config.repo_directory_path, '*')} to your .gitignore file \n\n"
end

def config
@config ||= Hatchet::Config.new
end

def repos
config.repos
end

def dirs
config.dirs
end

def pull(path, git_repo)
Dir.chdir(path) do
`git pull --rebase #{git_repo} master`
end
end

def clone(path, git_repo)
path = File.join(path, '..') # up one dir to prevent repos/codetriage/codetriage/#...
FileUtils.mkdir_p(path) # create directory
Dir.chdir(path) do
`git clone #{git_repo}`
end
end
end

HatchetCLI.start(ARGV)
7 changes: 4 additions & 3 deletions hatchet.gemspec
Expand Up @@ -8,9 +8,9 @@ Gem::Specification.new do |gem|
gem.version = Hatchet::VERSION gem.version = Hatchet::VERSION
gem.authors = ["Richard Schneeman"] gem.authors = ["Richard Schneeman"]
gem.email = ["schneems@gmail.com"] gem.email = ["schneems@gmail.com"]
gem.description = %q{The Hatchet is a an integration testing library for developing Heroku buildpacks.} gem.description = %q{Hatchet is a an integration testing library for developing Heroku buildpacks.}
gem.summary = %q{The Hatchet is a an integration testing library for developing Heroku buildpacks.} gem.summary = %q{Hatchet is a an integration testing library for developing Heroku buildpacks.}
gem.homepage = "" gem.homepage = "https://github.com/heroku/hatchet"
gem.license = "MIT" gem.license = "MIT"


gem.files = `git ls-files`.split($/) gem.files = `git ls-files`.split($/)
Expand All @@ -23,4 +23,5 @@ Gem::Specification.new do |gem|
gem.add_dependency "rake" gem.add_dependency "rake"
gem.add_dependency "anvil-cli" gem.add_dependency "anvil-cli"
gem.add_dependency "excon" gem.add_dependency "excon"
gem.add_dependency "thor"
end end
5 changes: 5 additions & 0 deletions hatchet.json
@@ -0,0 +1,5 @@
{
"hatchet": {"directory": "test/fixtures"},
"rails3": ["git@github.com:codetriage/codetriage.git"],
"rails2": ["git@github.com:heroku/rails2blog.git"]
}
5 changes: 5 additions & 0 deletions lib/hatchet.rb
Expand Up @@ -2,6 +2,10 @@
require 'anvil/engine' require 'anvil/engine'
require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/blank'


require 'json'
require 'stringio'
require 'fileutils'

module Hatchet module Hatchet
class App class App
end end
Expand All @@ -13,3 +17,4 @@ class App
require 'hatchet/git_app' require 'hatchet/git_app'
require 'hatchet/stream_exec' require 'hatchet/stream_exec'
require 'hatchet/process_spawn' require 'hatchet/process_spawn'
require 'hatchet/config'
20 changes: 14 additions & 6 deletions lib/hatchet/app.rb
Expand Up @@ -2,14 +2,18 @@ module Hatchet
class App class App
attr_reader :name, :directory attr_reader :name, :directory


def initialize(directory, options = {}) def initialize(repo_name, options = {})
@directory = directory @directory = config.path_for_name(repo_name)
@name = options[:name] || "test-app-#{Time.now.to_f}".gsub('.', '-') @name = options[:name] || "test-app-#{Time.now.to_f}".gsub('.', '-')
@debug = options[:debug]
end end


def git_repo # config is read only, should be threadsafe
"git@heroku.com:#{name}.git" def self.config
@config ||= Config.new
end

def config
self.class.config
end end


# runs a command on heroku similar to `$ heroku run #foo` # runs a command on heroku similar to `$ heroku run #foo`
Expand All @@ -33,6 +37,7 @@ def deployed?
!heroku.get_ps(name).body.detect {|ps| ps["process"].include?("web") }.nil? !heroku.get_ps(name).body.detect {|ps| ps["process"].include?("web") }.nil?
end end


# creates a new heroku app via the API
def setup! def setup!
heroku.post_app(name: name) heroku.post_app(name: name)
@app_is_setup = true @app_is_setup = true
Expand All @@ -46,6 +51,9 @@ def teardown!
heroku.delete_app(name) heroku.delete_app(name)
end end


# creates a new app on heroku, "pushes" via anvil or git
# then yields to self so you can call self.run or
# self.deployed?
def deploy(&block) def deploy(&block)
Dir.chdir(directory) do Dir.chdir(directory) do
self.setup! self.setup!
Expand Down
88 changes: 88 additions & 0 deletions lib/hatchet/config.rb
@@ -0,0 +1,88 @@
module Hatchet
class MissingConfig < Errno::ENOENT
def initialize
super("could not find a 'hatchet.json' file in root directory")
end
end
class ParserError < JSON::ParserError; end
class BadRepoName < StandardError
def initialize(name, paths)
msg = "could not find repo: '#{name}', check for spelling or " <<
"duplicate repos. Run `$ hatchet list` to see all " <<
"repo options. Checked in #{paths.inspect}. \n\n" <<
" make sure repos are installed by running `$ hatchet install`"
super(msg)
end
end

# This class is responsible for parsing hatchet.json into something
# meaninful.
class Config
REPOS_DIR_NAME = "repos" # the top level name of repos folder
REPOS_DIRECTORY_ROOT = '.' # the the root directory where your repos folder will be stored

attr_accessor :repos, :dirs

def repo_directory_path
File.join(@repo_directory_path, REPOS_DIR_NAME)
end

# creates new config object, pass in directory where `heroku.json`
# is located
def initialize(directory = '.')
self.repos = {}
self.dirs = {}
Dir.chdir(directory) do
config_file = File.open('hatchet.json').read
init_config! JSON.parse(config_file)
end
rescue Errno::ENOENT
raise MissingConfig
rescue JSON::ParserError => e
raise ParserError, "Improperly formatted json in 'hatchet.json' \n\n" + e.message
end

# use this method to turn "codetriage" into repos/rails3/codetriage
def path_for_name(name)
possible_paths = [repos[name.to_s], "repos/#{name}", name].compact
path = possible_paths.detect do |path|
Dir[path].present?
end
raise BadRepoName.new(name, possible_paths) if path.blank?
path
end

# 'git@github.com:codetriage/codetriage.git' => 'codetriage'
def name_from_git_repo(repo)
repo.split('/').last.chomp('.git')
end

private

def set_internal_config!(config)
@internal_config = config.delete('hatchet')
@repo_directory_path = @internal_config['directory'] || REPOS_DIRECTORY_ROOT
end

# pulls out config and makes easy to use hashes
# dirs has the repo paths as keys and the git_repos as values
# repos has repo names as keys and the paths as values
def init_config!(config)
set_internal_config!(config)
config.each do |(directory, git_repos)|
git_repos.each do |git_repo|
repo_name = name_from_git_repo(git_repo)
repo_path = File.join(repo_directory_path, directory, repo_name)
if repos.key? repo_name
puts " warning duplicate repo found: #{repo_name.inspect}"
repos[repo_name] = false
else
repos[repo_name] = repo_path
end
dirs[repo_path] = git_repo
end
end
end
end
end

4 changes: 4 additions & 0 deletions lib/hatchet/git_app.rb
Expand Up @@ -13,6 +13,10 @@ def setup!
heroku.put_config_vars(name, 'BUILDPACK_URL' => @buildpack) heroku.put_config_vars(name, 'BUILDPACK_URL' => @buildpack)
end end


def git_repo
"git@heroku.com:#{name}.git"
end

def push! def push!
output = `git push #{git_repo} master` output = `git push #{git_repo} master`
[$?.success?, output] [$?.success?, output]
Expand Down