gta 0.2.0 → 0.3.0

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: 6282ea52c54d66479c7bb48a0429f8e4e4113ef7
4
- data.tar.gz: c1e27e7e8a29454847b5081109aeb2a6ae4444e0
3
+ metadata.gz: c4ffc18a8b88a8e3ffa8c99a042a8862ae4ce43c
4
+ data.tar.gz: 85a82924459a0def9f5430961d4be27d4d2ebc83
5
5
  SHA512:
6
- metadata.gz: 817bcf144b42c8e96c56f0ec3709d1fcc8c1f217a1bbb15c3f7bce9428645569fe4817fa2279c0f179b75ac65f6a21619769694c82b3a77c62f817fc788e4765
7
- data.tar.gz: 16cfc6bc1416af0080822bf1fef98a585421e59fc58c9684789080dc6e47a4567f19c456a18287f685f8e524e85ddee58b975f61d04f83d6b5f937a6f2ca62c1
6
+ metadata.gz: 421748d39efd34d5b21c3fa5a24b42ed4857cf5c93e3f126bc0bda394ab89a3216fc7921b77eeec4992a0aef7c1aa9bee881fdb962fa6085d01c06e2fcb1956c
7
+ data.tar.gz: 156c066293e496a062830acee3ed9bf828ea59c0d18ad497f5dcd38de8653c7e23a2c2675af3dec20199e2b45d3377aa14332bba35d3178103082fe47c47b4dc
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ tmp
18
18
  .idea
19
19
  .DS_Store
20
20
  **/.DS_Store
21
+ log/*
data/README.md CHANGED
@@ -31,7 +31,10 @@ Or install it yourself as:
31
31
 
32
32
  ## Usage
33
33
 
34
- Rake tasks are in progress; stay tuned.
34
+ The main use case is via rake task. Include the rake tasks via the
35
+ project Rakefile.
36
+
37
+
35
38
 
36
39
  ## Contributing
37
40
 
data/lib/gta.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'open3'
1
2
  require 'ansi/code'
2
3
 
3
4
  require "gta/version"
@@ -9,3 +10,5 @@ require "gta/local_db"
9
10
  require "gta/db"
10
11
  require "gta/hotfix"
11
12
  require "gta/tag_finder"
13
+ require "gta/commander"
14
+ require "gta/file_logger"
@@ -0,0 +1,104 @@
1
+ module GTA
2
+ class Commander
3
+ attr_reader :command, :output, :exit_status
4
+
5
+ LINE_LENGTH = 80
6
+
7
+ def initialize(command=nil)
8
+ @output = []
9
+ @command = command
10
+ end
11
+
12
+ def perform
13
+ _perform(false)
14
+ end
15
+
16
+ def perform!
17
+ _perform(true)
18
+ end
19
+
20
+ def write_output(line)
21
+ message = normalize_output(" #{line}")
22
+ puts "#{ANSI.white_on_black}#{message}"
23
+ output << line
24
+ end
25
+
26
+ def write_failure(message = command)
27
+ message = normalize_output(" Command failed: #{message}")
28
+ puts "#{ANSI.red_on_black}#{message}#{ANSI.ansi}"
29
+ write_to_log(message)
30
+ write_to_log("-"*LINE_LENGTH)
31
+ end
32
+
33
+ # --------------
34
+
35
+ def _perform(should_raise)
36
+ run_command(should_raise)
37
+ puts_reset
38
+
39
+ write_to_log(output.join)
40
+ handle_failure(should_raise) unless exit_status && exit_status.success?
41
+
42
+ output.join
43
+ end
44
+
45
+ def run_command(should_raise)
46
+ write_command
47
+ Open3.popen3(command) do |stdin, stdout, stderr, process_status|
48
+ stderr.sync = true
49
+ stdout.sync = true
50
+
51
+ while (line = stderr.gets)
52
+ write_output line
53
+ end
54
+
55
+ while (line = stdout.gets)
56
+ write_output line
57
+ end
58
+
59
+ @exit_status = process_status.value
60
+ end
61
+ rescue Errno::ENOENT => e
62
+ handle_failure(should_raise, e)
63
+ end
64
+
65
+ def handle_failure(should_raise, e=nil)
66
+ write_failure
67
+ if e
68
+ output << e.message
69
+ write_failure(e.message)
70
+ write_failure(e.backtrace)
71
+ end
72
+ raise CommandFailure, "FAILED! #{command}" if should_raise
73
+ end
74
+
75
+ def normalize_output(output)
76
+ normalized = output.gsub("\n",'')
77
+ normalized += " "*(LINE_LENGTH-normalized.size-1) if normalized.size < LINE_LENGTH
78
+ normalized += ' '
79
+ normalized
80
+ end
81
+
82
+ def write_command
83
+ message = normalize_output(" GTA: #{command}")
84
+ puts "#{ANSI.black_on_white}#{message}#{ANSI.ansi}"
85
+ write_to_log(command)
86
+ write_to_log("-"*LINE_LENGTH)
87
+ end
88
+
89
+ def puts_reset
90
+ puts "#{ANSI.ansi}"
91
+ end
92
+
93
+ def logger
94
+ @logger ||= FileLogger.new
95
+ end
96
+
97
+ def write_to_log(response)
98
+ logger.write(response)
99
+ end
100
+
101
+ class CommandFailure < StandardError
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,25 @@
1
+ module GTA
2
+ class FileLogger
3
+ attr_reader :log_dir
4
+
5
+ def initialize(log_dir=nil)
6
+ @log_dir = log_dir ||= "#{Dir.pwd}/log"
7
+ ensure_log_dir_and_file
8
+ end
9
+
10
+ def ensure_log_dir_and_file
11
+ FileUtils.mkdir_p(log_dir)
12
+ FileUtils.touch(log_file) unless File.exist?(log_file)
13
+ end
14
+
15
+ def log_file
16
+ "#{log_dir}/gta.log"
17
+ end
18
+
19
+ def write(stuff)
20
+ File.open(log_file, 'a') do |f|
21
+ f.write(stuff)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -10,8 +10,7 @@ module GTA
10
10
  end
11
11
 
12
12
  def url
13
- # using backticks in order to get the bash response
14
- `heroku pgbackups:url --app #{app_signature}`
13
+ sh!("heroku pgbackups:url --app #{app_signature}").strip
15
14
  end
16
15
 
17
16
  def backup
@@ -19,11 +18,11 @@ module GTA
19
18
  end
20
19
 
21
20
  def restore_from(url)
22
- sh("heroku pgbackups:restore DATABASE_URL \"#{url}\" --app #{app_signature} --confirm #{app_signature}")
21
+ sh!("heroku pgbackups:restore DATABASE_URL \"#{url}\" --app #{app_signature} --confirm #{app_signature}")
23
22
  end
24
23
 
25
24
  def fetch
26
- sh("curl -o #{file_name} \"#{url}\"")
25
+ sh!("curl -o #{file_name} \"#{url}\"")
27
26
  end
28
27
 
29
28
  def file_name
@@ -1,5 +1,7 @@
1
1
  module GTA
2
2
  class Hotfix
3
+ include Sh
4
+
3
5
  attr_reader :gta_config_path
4
6
 
5
7
  def initialize(gta_config_path = nil)
@@ -20,7 +22,7 @@ module GTA
20
22
  stage_name = branch_name
21
23
  stage = stage_for(stage_name)
22
24
  not_hotfixable!(stage_name) if !stage || !stage_name
23
- sh "git push #{stage_name} #{stage_name}:master"
25
+ sh!("git push #{stage_name} #{stage_name}:master")
24
26
  end
25
27
 
26
28
  def not_hotfixable!(stage_name)
@@ -32,8 +34,7 @@ module GTA
32
34
  end
33
35
 
34
36
  def branch_name
35
- # using `` because we need the bash output
36
- branches = `git branch`
37
+ branches = sh!("git branch").strip
37
38
  matches = branches.match(/\*\s+(.*)/)
38
39
  matches[1].strip if matches
39
40
  end
@@ -10,7 +10,7 @@ module GTA
10
10
  end
11
11
 
12
12
  def load(backup_path)
13
- sh "pg_restore --verbose --clean --no-acl --no-owner -h localhost#{username}#{database} #{backup_path}"
13
+ sh("pg_restore --verbose --clean --no-acl --no-owner -h localhost#{username}#{database} #{backup_path}")
14
14
  end
15
15
 
16
16
  def config
@@ -8,7 +8,7 @@ module GTA
8
8
 
9
9
  def push_to(name, forced = nil)
10
10
  s = stage!(name)
11
- fetch
11
+ fetch!
12
12
  if forced == :force
13
13
  s.force_push
14
14
  else
@@ -30,6 +30,10 @@ module GTA
30
30
  stages.each(&:fetch)
31
31
  end
32
32
 
33
+ def fetch!
34
+ stages.each(&:fetch!)
35
+ end
36
+
33
37
  def setup
34
38
  stages.each(&:add_remote)
35
39
  end
@@ -1,8 +1,15 @@
1
1
  module GTA
2
2
  module Sh
3
3
  def sh(command)
4
- puts "#{ANSI.black_on_white} GTA: #{command} #{ANSI.ansi}"
5
- system(command)
4
+ commander(command).perform
5
+ end
6
+
7
+ def sh!(command)
8
+ commander(command).perform
9
+ end
10
+
11
+ def commander(command=nil)
12
+ Commander.new(command)
6
13
  end
7
14
  end
8
15
  end
@@ -29,23 +29,39 @@ module GTA
29
29
  raise "no name defined for #{self}" unless name
30
30
  raise "no repository defined for #{name}" unless repository
31
31
 
32
- sh "git remote add #{name} #{repository}"
32
+ sh("git remote add #{name} #{repository}")
33
33
  end
34
34
 
35
35
  def checkout
36
- sh "git checkout -b #{name} -t #{name}/#{branch}"
36
+ sh(checkout_command)
37
+ end
38
+
39
+ def checkout!
40
+ sh!(checkout_command)
41
+ end
42
+
43
+ def checkout_command
44
+ "git checkout -b #{name} -t #{name}/#{branch}"
37
45
  end
38
46
 
39
47
  def push(s=source, forced=nil)
40
- sh push_command(source_from(s), forced)
48
+ sh!(push_command(source_from(s), forced))
41
49
  end
42
50
 
43
51
  def force_push(s=source)
44
- sh push_command(source_from(s), :force)
52
+ sh!(push_command(source_from(s), :force))
45
53
  end
46
54
 
47
55
  def fetch
48
- sh "git fetch #{name}"
56
+ sh(fetch_command)
57
+ end
58
+
59
+ def fetch!
60
+ sh!(fetch_command)
61
+ end
62
+
63
+ def fetch_command
64
+ "git fetch #{name}"
49
65
  end
50
66
 
51
67
  def ==(other)
File without changes
@@ -1,3 +1,3 @@
1
1
  module GTA
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe GTA::Commander do
4
+ let(:commander) { GTA::Commander.new(command) }
5
+ let(:log_path) { File.dirname(__FILE__) + "/../log/gta.log" }
6
+ let(:log) { File.read(log_path) }
7
+
8
+ before do
9
+ File.delete(log_path) if File.exist?(log_path)
10
+ end
11
+
12
+ context 'when command is successful' do
13
+ let(:command) { 'echo foo' }
14
+
15
+ it "performs the command and returns the output" do
16
+ capture_stdout do
17
+ commander.perform.should == "foo\n"
18
+ end
19
+ end
20
+
21
+ it "writes to the log" do
22
+ capture_stdout do
23
+ commander.perform
24
+ end
25
+
26
+ log.should include "foo"
27
+ end
28
+ end
29
+
30
+ context 'when the command is not successful' do
31
+ context 'with a bash error, that bubbles up to a Ruby error' do
32
+ let(:command) { 'foo' }
33
+
34
+ context 'when #perform! used' do
35
+ it "raises an error when the #perform! method is called" do
36
+ capture_stdout do
37
+ expect {
38
+ commander.perform!
39
+ }.to raise_error
40
+ end
41
+ end
42
+ end
43
+
44
+ context 'when #perform is used' do
45
+ it "sends outputs the message in red" do
46
+ output = capture_stdout do
47
+ commander.perform
48
+ end
49
+ output.should include(ANSI.red_on_black)
50
+ output.should include("No such file or directory - foo")
51
+ end
52
+
53
+ it "returns the error message when the non-bang method is used" do
54
+ capture_stdout do
55
+ commander.perform.should == "No such file or directory - foo"
56
+ end
57
+ end
58
+
59
+ it "writes to the log" do
60
+ capture_stdout do
61
+ commander.perform
62
+ end
63
+
64
+ log.should include "No such file or directory - foo"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -6,7 +6,7 @@ describe GTA::HerokuDB do
6
6
 
7
7
  describe '#url' do
8
8
  it "gets the temporary database url from heroku" do
9
- heroku_db.should_receive(:`)
9
+ heroku_db.should_receive(:sh!)
10
10
  .with("heroku pgbackups:url --app activator-staging")
11
11
  .and_return('backup url')
12
12
  heroku_db.url.should == 'backup url'
@@ -23,7 +23,7 @@ describe GTA::HerokuDB do
23
23
 
24
24
  describe '#restore_from(url)' do
25
25
  it "sends a heroku command to restore the database from the given url" do
26
- heroku_db.should_receive(:sh)
26
+ heroku_db.should_receive(:sh!)
27
27
  .with('heroku pgbackups:restore DATABASE_URL "http://my-database-url.com" --app activator-staging --confirm activator-staging')
28
28
  heroku_db.restore_from("http://my-database-url.com")
29
29
  end
@@ -32,7 +32,7 @@ describe GTA::HerokuDB do
32
32
  describe '#fetch' do
33
33
  it "downloads the latest database backup to an appropriately named file in downloads" do
34
34
  heroku_db.should_receive(:url).and_return('http://heroku-backup-url.com')
35
- heroku_db.should_receive(:sh)
35
+ heroku_db.should_receive(:sh!)
36
36
  .with('curl -o ~/Downloads/activator-staging.sql "http://heroku-backup-url.com"')
37
37
  heroku_db.fetch
38
38
  end
@@ -43,8 +43,13 @@ describe GTA::Hotfix do
43
43
  describe '#deploy' do
44
44
  context "when on a branch that maps to a stage" do
45
45
  before do
46
- hotfix.stub(:`).and_return(branch_output)
47
- hotfix.stub(:sh)
46
+ hotfix.stub(:sh!) do |command|
47
+ if command == "git branch"
48
+ branch_output
49
+ else
50
+ "deploying"
51
+ end
52
+ end
48
53
  end
49
54
 
50
55
  context "if it is hotfixable" do
@@ -53,7 +58,7 @@ describe GTA::Hotfix do
53
58
  }
54
59
 
55
60
  it "should call #sh with the right deploy command" do
56
- hotfix.should_receive(:sh)
61
+ hotfix.should_receive(:sh!)
57
62
  .with("git push qa qa:master")
58
63
  .and_return("deploying")
59
64
  hotfix.deploy.should == "deploying"
@@ -93,12 +93,12 @@ describe GTA::Manager do
93
93
 
94
94
  describe '#push_to' do
95
95
  before do
96
- manager.stub(:fetch)
96
+ manager.stub(:fetch!)
97
97
  manager.stage(:qa).stub(:push)
98
98
  end
99
99
 
100
100
  it "fetches" do
101
- manager.should_receive(:fetch)
101
+ manager.should_receive(:fetch!)
102
102
  manager.push_to(:qa)
103
103
  end
104
104
 
@@ -98,7 +98,7 @@ describe GTA::Stage do
98
98
 
99
99
  context "when using internally defined source object" do
100
100
  it "sends the right git shell command" do
101
- stage.should_receive(:sh).with("git push staging ci:master")
101
+ stage.should_receive(:sh!).with("git push staging ci:master")
102
102
  stage.push
103
103
  end
104
104
  end
@@ -107,7 +107,7 @@ describe GTA::Stage do
107
107
  let(:origin) { GTA::Stage.new('origin', manager, opts.merge('branch' => 'sendit')) }
108
108
 
109
109
  it "sends the right git shell command" do
110
- stage.should_receive(:sh).with("git push staging origin:master")
110
+ stage.should_receive(:sh!).with("git push staging origin:master")
111
111
  stage.push(origin)
112
112
  end
113
113
  end
@@ -116,7 +116,7 @@ describe GTA::Stage do
116
116
  let(:branch) { 'alt_branch' }
117
117
 
118
118
  it "sends the right git shell command" do
119
- stage.should_receive(:sh).with("git push staging foo:alt_branch")
119
+ stage.should_receive(:sh!).with("git push staging foo:alt_branch")
120
120
  stage.push('foo')
121
121
  end
122
122
  end
@@ -127,14 +127,14 @@ describe GTA::Stage do
127
127
 
128
128
  it "uses the tag as the source reference" do
129
129
  GTA::TagFinder.should_receive(:new).with(tag).and_return(tag_finder)
130
- stage.should_receive(:sh).with("git push staging my-tag:master")
130
+ stage.should_receive(:sh!).with("git push staging my-tag:master")
131
131
  stage.push
132
132
  end
133
133
  end
134
134
 
135
135
  context "force push" do
136
136
  it "adds the -f flag to the git command" do
137
- stage.should_receive(:sh).with("git push -f staging ci:master")
137
+ stage.should_receive(:sh!).with("git push -f staging ci:master")
138
138
  stage.force_push
139
139
  end
140
140
  end
@@ -0,0 +1,21 @@
1
+ def capture_stdout
2
+ old_stdout = $stdout.dup
3
+ rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
4
+ $stdout = wr
5
+ yield
6
+ wr.close
7
+ rd.read
8
+ ensure
9
+ $stdout = old_stdout
10
+ end
11
+
12
+ def capture_stderr
13
+ old_stderr = $stderr.dup
14
+ rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
15
+ $stderr = wr
16
+ yield
17
+ wr.close
18
+ rd.read
19
+ ensure
20
+ $stderr = old_stderr
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - socialchorus
@@ -84,7 +84,9 @@ files:
84
84
  - Rakefile
85
85
  - gta.gemspec
86
86
  - lib/gta.rb
87
+ - lib/gta/commander.rb
87
88
  - lib/gta/db.rb
89
+ - lib/gta/file_logger.rb
88
90
  - lib/gta/heroku_db.rb
89
91
  - lib/gta/hotfix.rb
90
92
  - lib/gta/local_db.rb
@@ -94,11 +96,13 @@ files:
94
96
  - lib/gta/stage.rb
95
97
  - lib/gta/tag_finder.rb
96
98
  - lib/gta/tasks.rb
99
+ - lib/gta/tasks/commander.rb
97
100
  - lib/gta/tasks/deploy.rake
98
101
  - lib/gta/tasks/gta.rake
99
102
  - lib/gta/tasks/heroku_db.rake
100
103
  - lib/gta/tasks/hotfix.rake
101
104
  - lib/gta/version.rb
105
+ - spec/commander_spec.rb
102
106
  - spec/db_spec.rb
103
107
  - spec/fixtures/config/database.yml
104
108
  - spec/fixtures/config/gta.yml
@@ -108,6 +112,7 @@ files:
108
112
  - spec/manager_spec.rb
109
113
  - spec/spec_helper.rb
110
114
  - spec/stage_spec.rb
115
+ - spec/support/capturers.rb
111
116
  - spec/tag_finder_spec.rb
112
117
  homepage: http://github.com/socialchorus/gta
113
118
  licenses:
@@ -135,6 +140,7 @@ specification_version: 4
135
140
  summary: 'GTA: the Git Transit Authority - A git based deploy tool for moving code
136
141
  from stage to stage.'
137
142
  test_files:
143
+ - spec/commander_spec.rb
138
144
  - spec/db_spec.rb
139
145
  - spec/fixtures/config/database.yml
140
146
  - spec/fixtures/config/gta.yml
@@ -144,4 +150,5 @@ test_files:
144
150
  - spec/manager_spec.rb
145
151
  - spec/spec_helper.rb
146
152
  - spec/stage_spec.rb
153
+ - spec/support/capturers.rb
147
154
  - spec/tag_finder_spec.rb