fourchette 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80792e1bd7068c821be389021a9f5fed508fa191
4
- data.tar.gz: 6339113cc97d55bba92d78f8682d1536e6dab04c
3
+ metadata.gz: 86b3fc5cd9c714d3d471d4c3d652bc7318ddbba3
4
+ data.tar.gz: 40ced7fbd5ec40fef7af008bc3ec741054f6f817
5
5
  SHA512:
6
- metadata.gz: 0461c8fd5f5d17525ac2d53517708debf373abd46e316678448fd33e96b994f1bafcdcd3ab4e85052a926698d7b3832e5782c10005782b86073296d25c48a62b
7
- data.tar.gz: 63c99b34b08815f143d9a0b034067e5aa2d2652cc759b429435c685e7262c5395d17e665352af0029da04f0c0c31d6e503640ce48d9cf7255b4285867f0aaa6b
6
+ metadata.gz: 8ccf7053c47fb02a12ea201ad576b23099c3438326239db955521557504a6d412c3abda99d9bd1ec82b72458255f5393705fac5b88dacec7b047b1d05fc14210
7
+ data.tar.gz: 713b94f1b440f409b3e6a95e203490f27f81187a28d1fd343bea199688ccbf55d19416c1b8d84c0f4491a541383f3a3c301c6e530eebcdbf98cb4a6a88f659bc
data/Gemfile CHANGED
@@ -2,4 +2,4 @@ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'puma'
5
+ gem 'puma'
data/Guardfile CHANGED
@@ -1,5 +1,5 @@
1
1
  guard :rspec, cmd: 'bundle exec rspec' do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
3
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
- watch('spec/spec_helper.rb') { "spec" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
5
  end
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Jean-Philippe Boily
1
+ Copyright (c) 2014-2015 Jean-Philippe Boily
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -44,6 +44,16 @@ We use it a lot at [Rainforest QA](https://www.rainforestqa.com/). If you want t
44
44
 
45
45
  ## Installation
46
46
 
47
+ You have two choices here, the easy path, or the manual path.
48
+
49
+ **Easy**
50
+
51
+ [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https%3A%2F%2Fgithub.com%2Frainforestapp%2Ffourchette-app)
52
+
53
+ **Manual**
54
+
55
+ This will give you more flexibility to create before and after actions, though you could also do it with the easy path and cloning your repo, etc.
56
+
47
57
  1. run `gem install fourchette`
48
58
  2. run `fourchette new my-app-name`. You can replace "my-app-name" by whatever you want it, this is the name of the directory your Fourchette app will be created in.
49
59
  3. run `cd my-app-name` (replace app name, again)
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
- require "fourchette/rake_tasks"
1
+ require 'fourchette/rake_tasks'
2
2
 
3
3
  begin
4
4
  require 'rspec/core/rake_task'
5
5
  # Set default Rake task to spec
6
6
  RSpec::Core::RakeTask.new(:spec)
7
- task :default => :spec
7
+ task default: :spec
8
8
  rescue LoadError => ex
9
9
  # That's ok, it just means we don't have RSpec loaded
10
10
  end
@@ -13,4 +13,4 @@ desc 'Brings up a REPL with the code loaded'
13
13
  task :console do
14
14
  require './lib/fourchette'
15
15
  Pry.start
16
- end
16
+ end
data/bin/fourchette CHANGED
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "thor"
3
+ require 'thor'
4
4
 
5
5
  module Fourchette
6
6
  class CLI < Thor
7
7
  include Thor::Actions
8
8
 
9
- desc "new APP_NAME", "This will create a fourchette app for you under APP_NAME directory."
9
+ desc 'new APP_NAME', 'This will create a fourchette app for you under APP_NAME directory.'
10
10
  def new(name)
11
11
  # Create directory
12
12
  empty_directory(name)
13
13
 
14
14
  # Copy template files
15
- ['Gemfile','config.ru', 'Procfile', 'Rakefile', 'config/puma.rb', 'callbacks.rb'].each do |file_name|
15
+ ['Gemfile', 'config.ru', 'Procfile', 'Rakefile', 'config/puma.rb', 'callbacks.rb'].each do |file_name|
16
16
  copy_file(file_name, "#{name}/#{file_name}")
17
17
  end
18
18
 
19
19
  # Run bundle install
20
20
  run("bundle install --gemfile #{name}/Gemfile")
21
21
  end
22
- end
22
+ end
23
23
  end
24
24
 
25
- Fourchette::CLI.source_root(File.expand_path("../../templates", __FILE__))
25
+ Fourchette::CLI.source_root(File.expand_path('../../templates', __FILE__))
26
26
  Fourchette::CLI.start(ARGV)
data/fourchette.gemspec CHANGED
@@ -4,34 +4,34 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'fourchette/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "fourchette"
7
+ spec.name = 'fourchette'
8
8
  spec.version = Fourchette::VERSION
9
- spec.authors = ["Jean-Philippe Boily"]
10
- spec.email = ["j@jipi.ca"]
11
- spec.summary = %q{Your new best friend for isolated testing environments on Heroku.}
12
- spec.description = %q{Fourchette is your new best friend for having isolated testing environment. It will help you test your GitHub PRs against a fork of one your Heroku apps. You will have one Heroku app per PR now. Isn't that amazing? It will make testing way easier and you won't have the (maybe) broken code from other PRs on staging but only the code that requires testing.}
13
- spec.homepage = "https://github.com/jipiboily/fourchette"
14
- spec.license = "MIT"
9
+ spec.authors = ['Jean-Philippe Boily']
10
+ spec.email = ['j@jipi.ca']
11
+ spec.summary = 'Your new best friend for isolated testing environments on Heroku.'
12
+ spec.description = "Fourchette is your new best friend for having isolated testing environment. It will help you test your GitHub PRs against a fork of one your Heroku apps. You will have one Heroku app per PR now. Isn't that amazing? It will make testing way easier and you won't have the (maybe) broken code from other PRs on staging but only the code that requires testing."
13
+ spec.homepage = 'https://github.com/jipiboily/fourchette'
14
+ spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency "rake"
22
- spec.add_dependency "sinatra"
23
- spec.add_dependency "sinatra-contrib"
24
- spec.add_dependency "octokit"
25
- spec.add_dependency "git"
26
- spec.add_dependency "heroku", "~> 3.9" # Deprecated, but best/easiest solution for pgbackups...
27
- spec.add_dependency "rest-client" # required for phbackups
21
+ spec.add_dependency 'rake'
22
+ spec.add_dependency 'sinatra'
23
+ spec.add_dependency 'sinatra-contrib'
24
+ spec.add_dependency 'octokit'
25
+ spec.add_dependency 'git'
26
+ spec.add_dependency 'heroku', '~> 3.9' # Deprecated, but best/easiest solution for pgbackups...
27
+ spec.add_dependency 'rest-client' # required for phbackups
28
28
  spec.add_dependency 'platform-api', '~> 0.2.0'
29
- spec.add_dependency "sucker_punch"
30
- spec.add_dependency "thor"
29
+ spec.add_dependency 'sucker_punch'
30
+ spec.add_dependency 'thor'
31
31
 
32
32
  spec.add_development_dependency 'foreman'
33
33
  spec.add_development_dependency 'pry-byebug'
34
- spec.add_development_dependency 'rspec', '~> 2.14.1'
34
+ spec.add_development_dependency 'rspec', '~> 3.1.0'
35
35
  spec.add_development_dependency 'guard-rspec'
36
36
  spec.add_development_dependency 'terminal-notifier-guard'
37
37
  spec.add_development_dependency 'coveralls'
data/lib/fourchette.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "fourchette/version"
1
+ require 'fourchette/version'
2
2
  require 'sinatra'
3
3
  require 'json'
4
4
  require 'platform-api'
@@ -7,16 +7,16 @@ require 'git'
7
7
  require 'sucker_punch'
8
8
  require 'rest-client'
9
9
 
10
+ def attempt_pry
11
+ require 'pry'
12
+ rescue LoadError => ex
13
+ puts "Oops: #{ex}"
14
+ end
15
+
10
16
  # TODO: Extract this to development.rb and production.rb
11
17
  if development?
12
- require "sinatra/reloader"
13
-
14
- begin
15
- require "pry"
16
- rescue LoadError => ex
17
- # That's ok, we don't care...it was probably loaded from another project
18
- # and not to hack on Fourchette anyways!
19
- end
18
+ require 'sinatra/reloader'
19
+ attempt_pry
20
20
 
21
21
  FOURCHETTE_CONFIG = {
22
22
  env_name: 'fourchette-dev'
@@ -27,7 +27,6 @@ else
27
27
  }
28
28
  end
29
29
 
30
-
31
30
  module Fourchette
32
31
  DEBUG = ENV['DEBUG'] ? true : false
33
32
 
@@ -42,4 +41,4 @@ require_relative 'fourchette/fork'
42
41
  require_relative 'fourchette/heroku'
43
42
  require_relative 'fourchette/pgbackups'
44
43
  require_relative 'fourchette/callbacks'
45
- require_relative 'fourchette/tarball'
44
+ require_relative 'fourchette/tarball'
@@ -1,7 +1,7 @@
1
1
  class Fourchette::Fork
2
2
  include Fourchette::Logger
3
3
 
4
- def initialize params
4
+ def initialize(params)
5
5
  @params = params
6
6
  @heroku = Fourchette::Heroku.new
7
7
  @github = Fourchette::GitHub.new
@@ -9,43 +9,40 @@ class Fourchette::Fork
9
9
 
10
10
  def update
11
11
  create_unless_exists
12
- tarball = Fourchette::Tarball.new
13
- tarball_url = tarball.url(github_git_url, git_branch_name, ENV['FOURCHETTE_GITHUB_PROJECT'])
14
- options = {
15
- source_blob: {
16
- url: tarball_url
17
- }
18
- }
19
12
 
20
- build = @heroku.client.build.create(fork_name, options)
13
+ build = @heroku.client.build.create(fork_name, tarball_options)
21
14
  monitor_build(build)
22
15
  end
23
16
 
24
- def monitor_build build
25
- logger.info "Start of the build process on Heroku..."
17
+ def monitor_build(build)
18
+ logger.info 'Start of the build process on Heroku...'
26
19
  build_info = @heroku.client.build.info(fork_name, build['id'])
27
20
  # Let's just leave some time to Heroku to download the tarball and start
28
21
  # the process. This is some random timing that seems to make sense at first.
29
22
  sleep 30
30
23
  if build_info['status'] == 'failed'
31
- @github.comment_pr(pr_number, "The build failed on Heroku. See the activity tab on Heroku.")
24
+ @github.comment_pr(
25
+ pr_number, 'The build failed on Heroku. See the activity tab on Heroku.'
26
+ )
32
27
  fail Fourchette::DeployException
33
28
  end
34
29
  end
35
30
 
36
31
  def create
37
- @github.comment_pr(pr_number, "Fourchette is initializing a new fork.") if Fourchette::DEBUG
32
+ @github.comment_pr(
33
+ pr_number, 'Fourchette is initializing a new fork.') if Fourchette::DEBUG
38
34
  create_unless_exists
39
35
  update
40
36
  end
41
37
 
42
38
  def delete
43
39
  @heroku.delete(fork_name)
44
- @github.comment_pr(pr_number, "Test app deleted!")
40
+ @github.comment_pr(pr_number, 'Test app deleted!')
45
41
  end
46
42
 
47
43
  def fork_name
48
- "#{ENV['FOURCHETTE_HEROKU_APP_PREFIX']}-PR-#{pr_number}".downcase # It needs to be lowercase only.
44
+ # It needs to be lowercase only.
45
+ "#{ENV['FOURCHETTE_HEROKU_APP_PREFIX']}-PR-#{pr_number}".downcase
49
46
  end
50
47
 
51
48
  def branch_name
@@ -57,19 +54,42 @@ class Fourchette::Fork
57
54
  end
58
55
 
59
56
  def create_unless_exists
60
- unless @heroku.app_exists?(fork_name)
61
- @heroku.fork(ENV['FOURCHETTE_HEROKU_APP_TO_FORK'] ,fork_name)
57
+ unless app_exists?
58
+ @heroku.fork(ENV['FOURCHETTE_HEROKU_APP_TO_FORK'], fork_name)
62
59
  post_fork_url
63
60
  end
64
61
  end
65
62
 
66
63
  private
67
64
 
65
+ def app_exists?
66
+ @heroku.app_exists?(fork_name)
67
+ end
68
+
69
+ def tarball_options
70
+ {
71
+ source_blob: {
72
+ url: tarball_url
73
+ }
74
+ }
75
+ end
76
+
77
+ def tarball_url
78
+ Fourchette::Tarball.new.url(
79
+ github_git_url,
80
+ git_branch_name,
81
+ ENV['FOURCHETTE_GITHUB_PROJECT']
82
+ )
83
+ end
84
+
68
85
  # Update PR with URL. This is a method so that we can override it and just not
69
86
  # have that, if we don't want. Use case: we have custom domains, so we post
70
87
  # the URLs later on.
71
88
  def post_fork_url
72
- @github.comment_pr(pr_number, "Test URL: #{@heroku.client.app.info(fork_name)['web_url']}")
89
+ @github.comment_pr(
90
+ pr_number,
91
+ "Test URL: #{@heroku.client.app.info(fork_name)['web_url']}"
92
+ )
73
93
  end
74
94
 
75
95
  def git_branch_name
@@ -77,6 +97,11 @@ class Fourchette::Fork
77
97
  end
78
98
 
79
99
  def github_git_url
80
- @params['pull_request']['head']['repo']['clone_url'].gsub("//github.com", "//#{ENV['FOURCHETTE_GITHUB_USERNAME']}:#{ENV['FOURCHETTE_GITHUB_PERSONAL_TOKEN']}@github.com")
100
+ @params['pull_request']['head']['repo']['clone_url']
101
+ .gsub(
102
+ '//github.com',
103
+ "//#{ENV['FOURCHETTE_GITHUB_USERNAME']}:" \
104
+ "#{ENV['FOURCHETTE_GITHUB_PERSONAL_TOKEN']}@github.com"
105
+ )
81
106
  end
82
107
  end
@@ -29,14 +29,25 @@ class Fourchette::GitHub
29
29
  octokit.remove_hook(ENV['FOURCHETTE_GITHUB_PROJECT'], fourchette_hook.id)
30
30
  end
31
31
 
32
- def comment_pr pr_number, comment
33
- comment = "****** FOURCHETTE COMMENT ******\n\n#{comment}\n\n****** END OF FOURCHETTE COMMENT ******" if Fourchette::DEBUG
32
+ def comment_pr(pr_number, comment)
33
+ if Fourchette::DEBUG
34
+ comment = <<-TXT
35
+ ****** FOURCHETTE COMMENT ******\n
36
+ \n#{comment}\n\n
37
+ ****** END OF FOURCHETTE COMMENT ******
38
+ TXT
39
+ end
40
+
34
41
  octokit.add_comment(ENV['FOURCHETTE_GITHUB_PROJECT'], pr_number, comment)
35
42
  end
36
43
 
37
44
  private
45
+
38
46
  def octokit
39
- @octokit_client ||= Octokit::Client.new(login: ENV['FOURCHETTE_GITHUB_USERNAME'], password: ENV['FOURCHETTE_GITHUB_PERSONAL_TOKEN'])
47
+ @octokit_client ||= Octokit::Client.new(
48
+ login: ENV['FOURCHETTE_GITHUB_USERNAME'],
49
+ password: ENV['FOURCHETTE_GITHUB_PERSONAL_TOKEN']
50
+ )
40
51
  end
41
52
 
42
53
  def create_hook
@@ -44,18 +55,20 @@ class Fourchette::GitHub
44
55
  octokit.create_hook(
45
56
  ENV['FOURCHETTE_GITHUB_PROJECT'],
46
57
  'web',
47
- {
48
- url: "#{ENV['FOURCHETTE_APP_URL']}/hooks",
49
- content_type: 'json',
50
- fourchette_env: FOURCHETTE_CONFIG[:env_name]
51
- },
52
- {
53
- :events => ['pull_request'],
54
- :active => true
55
- }
58
+ hook_options,
59
+ events: ['pull_request'],
60
+ active: true
56
61
  )
57
62
  end
58
63
 
64
+ def hook_options
65
+ {
66
+ url: "#{ENV['FOURCHETTE_APP_URL']}/hooks",
67
+ content_type: 'json',
68
+ fourchette_env: FOURCHETTE_CONFIG[:env_name]
69
+ }
70
+ end
71
+
59
72
  def hooks
60
73
  octokit.hooks(ENV['FOURCHETTE_GITHUB_PROJECT'])
61
74
  end
@@ -82,20 +95,14 @@ class Fourchette::GitHub
82
95
  toggle_active_state_to hook, false
83
96
  end
84
97
 
85
- def toggle_active_state_to hook, active_value
98
+ def toggle_active_state_to(hook, active_value)
86
99
  octokit.edit_hook(
87
100
  ENV['FOURCHETTE_GITHUB_PROJECT'],
88
101
  hook.id,
89
102
  'web',
90
- {
91
- url: "#{ENV['FOURCHETTE_APP_URL']}/hooks",
92
- content_type: 'json',
93
- fourchette_env: FOURCHETTE_CONFIG[:env_name]
94
- },
95
- {
96
- :events => ['pull_request'],
97
- :active => active_value
98
- }
103
+ hook_options,
104
+ events: ['pull_request'],
105
+ active: active_value
99
106
  )
100
107
  end
101
108
  end
@@ -1,88 +1,92 @@
1
1
  class Fourchette::Heroku
2
2
  include Fourchette::Logger
3
3
 
4
- def app_exists? name
5
- client.app.list.collect { |app| app if app['name'] == name }.reject(&:nil?).any?
4
+ EXCEPTIONS = [
5
+ Excon::Errors::UnprocessableEntity,
6
+ Excon::Errors::ServiceUnavailable
7
+ ]
8
+
9
+ def app_exists?(name)
10
+ client.app.list.collect do |app|
11
+ app if app['name'] == name
12
+ end.reject(&:nil?).any?
6
13
  end
7
14
 
8
- def fork from, to
15
+ def fork(from, to)
9
16
  create_app(to)
10
17
  copy_config(from, to)
11
18
  copy_add_ons(from, to)
12
19
  copy_pg(from, to)
13
- copy_RACK_AND_RAILS_ENV_again(from, to)
20
+ copy_rack_and_rails_env_again(from, to)
14
21
  end
15
22
 
16
- def delete app_name
23
+ def delete(app_name)
17
24
  logger.info "Deleting #{app_name}"
18
25
  client.app.delete(app_name)
19
26
  end
20
27
 
21
28
  def client
22
- unless @heroku_client
23
- api_key = ENV['FOURCHETTE_HEROKU_API_KEY']
24
- @heroku_client = PlatformAPI.connect(api_key)
25
- end
26
- @heroku_client
29
+ api_key = ENV['FOURCHETTE_HEROKU_API_KEY']
30
+ @heroku_client ||= PlatformAPI.connect(api_key)
27
31
  end
28
32
 
29
- def config_vars app_name
33
+ def config_vars(app_name)
30
34
  client.config_var.info(app_name)
31
35
  end
32
36
 
33
- def git_url app_name
37
+ def git_url(app_name)
34
38
  client.app.info(app_name)['git_url']
35
39
  end
36
40
 
37
- def create_app name
41
+ def create_app(name)
38
42
  logger.info "Creating #{name}"
39
- client.app.create({ name: name })
43
+ client.app.create(name: name)
40
44
  end
41
45
 
42
- def copy_config from, to
46
+ def copy_config(from, to)
43
47
  logger.info "Copying configs from #{from} to #{to}"
44
48
  from_congig_vars = config_vars(from)
45
49
  # WE SHOULD NOT MOVE THE HEROKU_POSTGRES_*_URL or DATABASE_URL...
46
- from_congig_vars.reject! { |k, v| k.start_with?('HEROKU_POSTGRESQL_') && k.end_with?('_URL') }
47
- from_congig_vars.reject! { |k, v| k == ('DATABASE_URL') }
50
+ from_congig_vars.reject! do |k, _v|
51
+ k.start_with?('HEROKU_POSTGRESQL_') && k.end_with?('_URL')
52
+ end
53
+ from_congig_vars.reject! { |k, _v| k == ('DATABASE_URL') }
48
54
  client.config_var.update(to, from_congig_vars)
49
55
  end
50
56
 
51
- def copy_add_ons from, to
57
+ def copy_add_ons(from, to)
52
58
  logger.info "Copying addons from #{from} to #{to}"
53
59
  from_addons = client.addon.list(from)
54
60
  from_addons.each do |addon|
55
61
  name = addon['plan']['name']
56
62
  begin
57
63
  logger.info "Adding #{name} to #{to}"
58
- client.addon.create(to, { plan: name })
59
- rescue Excon::Errors::UnprocessableEntity => e
64
+ client.addon.create(to, plan: name)
65
+ rescue *EXCEPTIONS => e
60
66
  logger.error "Failed to copy addon #{name}"
61
67
  logger.error e
62
68
  end
63
69
  end
64
70
  end
65
71
 
66
- def copy_pg from, to
72
+ def copy_pg(from, to)
67
73
  if pg_enabled?(from)
68
74
  logger.info "Copying Postgres's data from #{from} to #{to}"
69
75
  backup = Fourchette::Pgbackups.new
70
- logger.info backup.copy(from, to)
76
+ backup.copy(from, to)
71
77
  else
72
78
  logger.info "Postgres not enabled on #{from}. Skipping data copy."
73
79
  end
74
80
  end
75
81
 
76
- def copy_RACK_AND_RAILS_ENV_again(from, to)
82
+ def copy_rack_and_rails_env_again(from, to)
77
83
  env_to_update = get_original_env(from)
78
- unless env_to_update.empty?
79
- client.config_var.update(to, env_to_update)
80
- end
84
+ client.config_var.update(to, env_to_update) unless env_to_update.empty?
81
85
  end
82
86
 
83
87
  def get_original_env(from)
84
88
  environments = {}
85
- ['RACK_ENV', 'RAILS_ENV'].each do |var|
89
+ %w(RACK_ENV RAILS_ENV).each do |var|
86
90
  if client.config_var.info(from)[var]
87
91
  environments[var] = client.config_var.info(from)[var]
88
92
  end
@@ -92,7 +96,7 @@ class Fourchette::Heroku
92
96
 
93
97
  def pg_enabled?(app)
94
98
  client.addon.list(app).any? do |addon|
95
- addon['addon_service']['name'] == 'Heroku Postgres'
99
+ addon['addon_service']['name'] =~ /heroku.postgres/i
96
100
  end
97
101
  end
98
102
  end