Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Use hatchet.json for config #3

Merged
merged 1 commit into from

1 participant

@schneems
Owner

The new hatchet.json stores info of where your external rails/ruby projects that you're testing against are located. This goes along with the new $ hatchet CLI.

Declare dependencies in your hatchet.json like this:

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

Then run $ hatchet install which will clone a directory structure like this:

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

Since hatchet.json manages these dependencies, you can now just use the name of the repo such as codetriage instead of the full path to the repo such as repos/rails3/codetriage

After installing repos you should add the directory where your repos live in your .gitignore file so they are not required when a buildpack is required in production on Heroku.

cc @hone, ps you should grab the @hone02 github name if it's not already taken

@schneems schneems Use hatchet.json for config
The new `hatchet.json` stores info of where your external rails/ruby projects that you're testing against are located. This goes along with the new `$ hatchet` CLI.

Declare dependencies in your `hatchet.json` like this:

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

Then run `$ hatchet install` which will clone a directory structure like this:

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

Since `hatchet.json` manages these dependencies, you can now just use the name of the repo such as `codetriage` instead of the full path to the repo such as `repos/rails3/codetriage'

After installing repos you should add the directory where your repos live in your `.gitignore` file so they are not required when a buildpack is required in production on Heroku.
40e9dab
@schneems schneems merged commit 56381ab into master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 25, 2013
  1. @schneems

    Use hatchet.json for config

    schneems authored
    The new `hatchet.json` stores info of where your external rails/ruby projects that you're testing against are located. This goes along with the new `$ hatchet` CLI.
    
    Declare dependencies in your `hatchet.json` like this:
    
        {
          "rails3": ["git@github.com:codetriage/codetriage.git"],
          "rails2": ["git@github.com:heroku/rails2blog.git"]
        }
    
    Then run `$ hatchet install` which will clone a directory structure like this:
    
        repos/
          rails3/
            codetriage/
              #...
          rails2/
            rails2blog/
              # …
    
    Since `hatchet.json` manages these dependencies, you can now just use the name of the repo such as `codetriage` instead of the full path to the repo such as `repos/rails3/codetriage'
    
    After installing repos you should add the directory where your repos live in your `.gitignore` file so they are not required when a buildpack is required in production on Heroku.
This page is out of date. Refresh to see the latest.
View
4 .gitignore
@@ -1 +1,3 @@
-.DS_Store
+.DS_Store
+test/fixtures/repos/*
+
View
38 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!
View
74 README.md
@@ -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 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
View
3  Rakefile
@@ -1,2 +1,5 @@
# encoding: UTF-8
require 'bundler/gem_tasks'
+
+require 'hatchet/tasks'
+
View
74 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)
View
7 hatchet.gemspec
@@ -8,9 +8,9 @@ Gem::Specification.new do |gem|
gem.version = Hatchet::VERSION
gem.authors = ["Richard Schneeman"]
gem.email = ["schneems@gmail.com"]
- gem.description = %q{The 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.homepage = ""
+ gem.description = %q{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 = "https://github.com/heroku/hatchet"
gem.license = "MIT"
gem.files = `git ls-files`.split($/)
@@ -23,4 +23,5 @@ Gem::Specification.new do |gem|
gem.add_dependency "rake"
gem.add_dependency "anvil-cli"
gem.add_dependency "excon"
+ gem.add_dependency "thor"
end
View
5 hatchet.json
@@ -0,0 +1,5 @@
+{
+ "hatchet": {"directory": "test/fixtures"},
+ "rails3": ["git@github.com:codetriage/codetriage.git"],
+ "rails2": ["git@github.com:heroku/rails2blog.git"]
+}
View
5 lib/hatchet.rb
@@ -2,6 +2,10 @@
require 'anvil/engine'
require 'active_support/core_ext/object/blank'
+require 'json'
+require 'stringio'
+require 'fileutils'
+
module Hatchet
class App
end
@@ -13,3 +17,4 @@ class App
require 'hatchet/git_app'
require 'hatchet/stream_exec'
require 'hatchet/process_spawn'
+require 'hatchet/config'
View
20 lib/hatchet/app.rb
@@ -2,14 +2,18 @@ module Hatchet
class App
attr_reader :name, :directory
- def initialize(directory, options = {})
- @directory = directory
- @name = options[:name] || "test-app-#{Time.now.to_f}".gsub('.', '-')
- @debug = options[:debug]
+ def initialize(repo_name, options = {})
+ @directory = config.path_for_name(repo_name)
+ @name = options[:name] || "test-app-#{Time.now.to_f}".gsub('.', '-')
end
- def git_repo
- "git@heroku.com:#{name}.git"
+ # config is read only, should be threadsafe
+ def self.config
+ @config ||= Config.new
+ end
+
+ def config
+ self.class.config
end
# runs a command on heroku similar to `$ heroku run #foo`
@@ -33,6 +37,7 @@ def deployed?
!heroku.get_ps(name).body.detect {|ps| ps["process"].include?("web") }.nil?
end
+ # creates a new heroku app via the API
def setup!
heroku.post_app(name: name)
@app_is_setup = true
@@ -46,6 +51,9 @@ def teardown!
heroku.delete_app(name)
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)
Dir.chdir(directory) do
self.setup!
View
88 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
+
View
4 lib/hatchet/git_app.rb
@@ -13,6 +13,10 @@ def setup!
heroku.put_config_vars(name, 'BUILDPACK_URL' => @buildpack)
end
+ def git_repo
+ "git@heroku.com:#{name}.git"
+ end
+
def push!
output = `git push #{git_repo} master`
[$?.success?, output]
View
4 lib/hatchet/tasks.rb
@@ -1,8 +1,10 @@
+require 'rake'
require 'rake/testtask'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
- t.pattern = 'test/**/*_test.rb'
+ t.pattern = 'test/hatchet/**/*_test.rb'
t.verbose = false
end
+
1  repos/rails3/codetriage
@@ -1 +0,0 @@
-Subproject commit 135482aedbc460a9b8ae48aadb5907ef9db27873
View
23 test/hatchet/config_test.rb
@@ -0,0 +1,23 @@
+require 'test_helper'
+
+class ConfigTest < Test::Unit::TestCase
+ def setup
+ @config = Hatchet::Config.new
+ end
+
+ def test_config_path_for_name
+ assert_equal 'test/fixtures/repos/rails3/codetriage', @config.path_for_name('codetriage')
+ end
+
+ def test_config_dirs
+ expected_dirs = { "test/fixtures/repos/rails3/codetriage" => "git@github.com:codetriage/codetriage.git",
+ "test/fixtures/repos/rails2/rails2blog" => "git@github.com:heroku/rails2blog.git" }
+ assert_equal expected_dirs, @config.dirs
+ end
+
+ def test_config_repos
+ expected_repos = { "codetriage" => "test/fixtures/repos/rails3/codetriage",
+ "rails2blog" => "test/fixtures/repos/rails2/rails2blog" }
+ assert_equal expected_repos, @config.repos
+ end
+end
View
3  test/repos/rails3/triage_test.rb → ...hatchet/integration/rails3/triage_test.rb
@@ -2,7 +2,7 @@
class TriageTest < Test::Unit::TestCase
def test_foo
- Hatchet::App.new("repos/rails3/codetriage").deploy do |app|
+ Hatchet::GitApp.new("codetriage").deploy do |app|
assert true
assert app.deployed?
app.run("bash") do |cmd|
@@ -12,3 +12,4 @@ def test_foo
end
end
end
+
Something went wrong with that request. Please try again.