gta 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2caadf93e8f7ea5300cf04c062f347d16a5d5168
4
- data.tar.gz: 704003821efb215a1e3dea713bed103f37c543ac
3
+ metadata.gz: 6282ea52c54d66479c7bb48a0429f8e4e4113ef7
4
+ data.tar.gz: c1e27e7e8a29454847b5081109aeb2a6ae4444e0
5
5
  SHA512:
6
- metadata.gz: b9e92cfb7dcdfd6df8640f5404c51593cff14824152addffa3117266f843d3d22249974e28daede22d6cad42790b18852ce276a30e3eb3070aade62ecca11e3b
7
- data.tar.gz: b9c27b5d6b6dc8303420123fc4428cde6758642928adc562278d5d265991d68a25a263b23e7a731b92920a1682200a35387436ae2e58770c78be2a35304762d0
6
+ metadata.gz: 817bcf144b42c8e96c56f0ec3709d1fcc8c1f217a1bbb15c3f7bce9428645569fe4817fa2279c0f179b75ac65f6a21619769694c82b3a77c62f817fc788e4765
7
+ data.tar.gz: 16cfc6bc1416af0080822bf1fef98a585421e59fc58c9684789080dc6e47a4567f19c456a18287f685f8e524e85ddee58b975f61d04f83d6b5f937a6f2ca62c1
data/.gitignore CHANGED
@@ -15,3 +15,6 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .idea
19
+ .DS_Store
20
+ **/.DS_Store
data/gta.gemspec CHANGED
@@ -6,7 +6,7 @@ require 'gta/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "gta"
8
8
  spec.version = GTA::VERSION
9
- spec.authors = ["socialchorus", "Kane Baccigalupi"]
9
+ spec.authors = ["socialchorus", "Kane Baccigalupi", "Ian Cooper"]
10
10
  spec.email = ["developers@socialchorus.com"]
11
11
  spec.description = %q{GTA: the Git Transit Authority - A git based deploy tool for moving code from stage to stage.}
12
12
  spec.summary = %q{GTA: the Git Transit Authority - A git based deploy tool for moving code from stage to stage.}
data/lib/gta.rb CHANGED
@@ -4,3 +4,8 @@ require "gta/version"
4
4
  require "gta/sh"
5
5
  require "gta/manager"
6
6
  require "gta/stage"
7
+ require "gta/heroku_db"
8
+ require "gta/local_db"
9
+ require "gta/db"
10
+ require "gta/hotfix"
11
+ require "gta/tag_finder"
data/lib/gta/db.rb ADDED
@@ -0,0 +1,57 @@
1
+ module GTA
2
+ class DB
3
+ attr_reader :gta_config_path, :database_config_path, :local_env
4
+
5
+ def initialize(gta_config_path=nil, database_config_path=nil, local_env=nil)
6
+ @gta_config_path = gta_config_path
7
+ @database_config_path = database_config_path
8
+ @local_env = local_env || 'development'
9
+ end
10
+
11
+ def manager
12
+ @manager ||= Manager.new(gta_config_path)
13
+ end
14
+
15
+ def local_db
16
+ @local_db ||= LocalDB.new(local_env, database_config_path)
17
+ end
18
+
19
+ def fetch(stage_name=nil)
20
+ stage = stage_or_final(stage_name)
21
+ db(stage.name).fetch
22
+ end
23
+
24
+ def load(stage_name=nil)
25
+ stage = stage_or_final(stage_name)
26
+ heroku_db = db(stage.name)
27
+ local_db.load(heroku_db.file_name)
28
+ end
29
+
30
+ def pull(stage_name=nil)
31
+ stage = stage_or_final(stage_name)
32
+ fetch(stage.name)
33
+ self.load(stage.name)
34
+ end
35
+
36
+ def restore(destination_name, source_name=nil)
37
+ source = stage_or_final(source_name)
38
+ destination = manager.stage!(destination_name)
39
+
40
+ raise "cannot restore #{destination.name}" unless destination.restorable?
41
+
42
+ source_db = db(source.name)
43
+ source_db.backup
44
+
45
+ destination_db = db(destination.name)
46
+ destination_db.restore_from(source_db.url)
47
+ end
48
+
49
+ def stage_or_final(stage_name)
50
+ manager.stage(stage_name) || manager.final_stage
51
+ end
52
+
53
+ def db(stage_name)
54
+ HerokuDB.new(manager.app_name, stage_name)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,38 @@
1
+ module GTA
2
+ class HerokuDB
3
+ include GTA::Sh
4
+
5
+ attr_reader :env, :app, :database_yml_path
6
+
7
+ def initialize(app, env)
8
+ @env = env
9
+ @app = app
10
+ end
11
+
12
+ def url
13
+ # using backticks in order to get the bash response
14
+ `heroku pgbackups:url --app #{app_signature}`
15
+ end
16
+
17
+ def backup
18
+ sh("heroku pgbackups:capture --expire --app #{app_signature}")
19
+ end
20
+
21
+ def restore_from(url)
22
+ sh("heroku pgbackups:restore DATABASE_URL \"#{url}\" --app #{app_signature} --confirm #{app_signature}")
23
+ end
24
+
25
+ def fetch
26
+ sh("curl -o #{file_name} \"#{url}\"")
27
+ end
28
+
29
+ def file_name
30
+ "~/Downloads/#{app_signature}.sql"
31
+ end
32
+
33
+ def app_signature
34
+ "#{app}-#{env}"
35
+ end
36
+ end
37
+ end
38
+
data/lib/gta/hotfix.rb ADDED
@@ -0,0 +1,41 @@
1
+ module GTA
2
+ class Hotfix
3
+ attr_reader :gta_config_path
4
+
5
+ def initialize(gta_config_path = nil)
6
+ @gta_config_path = gta_config_path
7
+ end
8
+
9
+ def checkout(stage_name=nil)
10
+ stage = stage_for(stage_name)
11
+ not_hotfixable!(stage_name) unless stage
12
+ stage.checkout
13
+ end
14
+
15
+ def manager
16
+ @manager ||= Manager.new(gta_config_path)
17
+ end
18
+
19
+ def deploy
20
+ stage_name = branch_name
21
+ stage = stage_for(stage_name)
22
+ not_hotfixable!(stage_name) if !stage || !stage_name
23
+ sh "git push #{stage_name} #{stage_name}:master"
24
+ end
25
+
26
+ def not_hotfixable!(stage_name)
27
+ raise "stage #{stage_name} not hotfixable"
28
+ end
29
+
30
+ def stage_for(stage_name)
31
+ manager.hotfixer(stage_name)
32
+ end
33
+
34
+ def branch_name
35
+ # using `` because we need the bash output
36
+ branches = `git branch`
37
+ matches = branches.match(/\*\s+(.*)/)
38
+ matches[1].strip if matches
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ module GTA
2
+ class LocalDB
3
+ include Sh
4
+
5
+ attr_reader :database_config_path, :env
6
+
7
+ def initialize(env, database_config_path=nil)
8
+ @env = env
9
+ @database_config_path = database_config_path || self.class.default_database_config_path
10
+ end
11
+
12
+ def load(backup_path)
13
+ sh "pg_restore --verbose --clean --no-acl --no-owner -h localhost#{username}#{database} #{backup_path}"
14
+ end
15
+
16
+ def config
17
+ @config ||= YAML.load(File.read(database_config_path))[env]
18
+ end
19
+
20
+ def username
21
+ config['username'] ? " -U #{config['username']}" : ''
22
+ end
23
+
24
+ def database
25
+ config['database'] ? " -d #{config['database']}" : ''
26
+ end
27
+
28
+ def self.default_database_config_path
29
+ "#{Dir.pwd}/config/database.yml"
30
+ end
31
+
32
+ def self.env_config
33
+ ENV['GTA_DATABASE_CONFIG_PATH']
34
+ end
35
+
36
+ def self.local_database_env
37
+ ENV['RAILS_ENV'] || ENV['GTA_LOCAL_ENV']
38
+ end
39
+ end
40
+ end
data/lib/gta/manager.rb CHANGED
@@ -16,6 +16,10 @@ module GTA
16
16
  end
17
17
  end
18
18
 
19
+ def app_name
20
+ @app_name || config && @app_name
21
+ end
22
+
19
23
  def checkout(name)
20
24
  stage!(name).checkout
21
25
  end
@@ -31,7 +35,10 @@ module GTA
31
35
  end
32
36
 
33
37
  def config
34
- @config ||= YAML.load(File.read(config_path))
38
+ return @config if @config
39
+ parsed = YAML.load(File.read(config_path))
40
+ @app_name = parsed.keys.first
41
+ @config = parsed.values.first
35
42
  end
36
43
 
37
44
  def stages
@@ -42,6 +49,16 @@ module GTA
42
49
  stages.detect{|s| s.name == name.to_s}
43
50
  end
44
51
 
52
+ def final_stage
53
+ stages.detect{|s| s.final? } || stages.last
54
+ end
55
+
56
+ def hotfixer(stage_name=nil)
57
+ hotfixers = stages.select{|s| s.hotfixable?}
58
+ default = stage_name == nil ? hotfixers.first : nil
59
+ hotfixers.detect{|s| s.name == stage_name} || default
60
+ end
61
+
45
62
  def stage!(name)
46
63
  stage(name) || (raise ArgumentError.new("Stage #{name} not found"))
47
64
  end
@@ -0,0 +1,7 @@
1
+ module GTA
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ require File.dirname(__FILE__) + "/tasks.rb"
5
+ end
6
+ end
7
+ end
data/lib/gta/stage.rb CHANGED
@@ -2,7 +2,8 @@ module GTA
2
2
  class Stage
3
3
  include Sh
4
4
 
5
- attr_reader :name, :repository, :source_name, :branch, :tag, :manager
5
+ attr_reader :name, :repository, :source_name, :branch, :tag, :manager,
6
+ :final, :hotfixable, :restorable
6
7
 
7
8
  def initialize(name, manager, opts)
8
9
  @name = name
@@ -11,6 +12,9 @@ module GTA
11
12
  @source_name = opts['source']
12
13
  @branch = opts['branch'] || 'master'
13
14
  @tag = opts['tag']
15
+ @final = opts['final']
16
+ @hotfixable = opts['hotfixable']
17
+ @restorable = opts['restorable']
14
18
  end
15
19
 
16
20
  def stages
@@ -45,11 +49,24 @@ module GTA
45
49
  end
46
50
 
47
51
  def ==(other)
48
- name == other.name &&
52
+ other.is_a?(self.class) &&
53
+ name == other.name &&
49
54
  branch == other.branch &&
50
55
  tag == other.tag
51
56
  end
52
57
 
58
+ def final?
59
+ !!final
60
+ end
61
+
62
+ def restorable?
63
+ !!restorable
64
+ end
65
+
66
+ def hotfixable?
67
+ !!hotfixable
68
+ end
69
+
53
70
  # -----------
54
71
 
55
72
  def source_from(s)
@@ -57,7 +74,7 @@ module GTA
57
74
  end
58
75
 
59
76
  def source_ref
60
- tag || "#{name}/#{branch}"
77
+ TagFinder.new(tag).newest || "#{name}"
61
78
  end
62
79
 
63
80
  def push_command(source_ref, forced=nil)
@@ -0,0 +1,22 @@
1
+ module GTA
2
+ class TagFinder
3
+ attr_reader :tag
4
+
5
+ def initialize(tag)
6
+ @tag = tag
7
+ end
8
+
9
+ def newest
10
+ return unless tag
11
+ tags.last
12
+ end
13
+
14
+ def tag_list
15
+ `git tag -l #{tag}`
16
+ end
17
+
18
+ def tags
19
+ tag_list.split(/\s/)
20
+ end
21
+ end
22
+ end
data/lib/gta/tasks.rb CHANGED
@@ -1,2 +1,6 @@
1
+ require 'gta'
2
+
1
3
  load "#{File.dirname(__FILE__)}/tasks/deploy.rake"
2
4
  load "#{File.dirname(__FILE__)}/tasks/gta.rake"
5
+ load "#{File.dirname(__FILE__)}/tasks/heroku_db.rake"
6
+ load "#{File.dirname(__FILE__)}/tasks/hotfix.rake"
@@ -1,44 +1,47 @@
1
- require 'gta'
2
-
3
- namespace :deploy do
4
- desc 'task that will be run before a deploy'
5
- task :before
6
-
7
- desc 'task that will be run after a deploy'
8
- task :after
9
-
10
- # the meat of a deploy, a git push from source to destination
11
- task :gta_push, :stage_name do |t, args|
12
- raise GTA::Manager.stage_name_error unless stage_name = args[:stage_name]
13
- manager = GTA::Manager.new(GTA::Manager.env_config)
14
- manager.push_to(stage_name)
15
- end
16
-
17
- # a forced version of the meat of the matter
18
- task :gta_force_push, :stage_name do |t, args|
19
- raise GTA::Manager.stage_name_error unless stage_name = args[:stage_name]
20
- manager = GTA::Manager.new(GTA::Manager.env_config)
21
- manager.push_to(stage_name, :force)
1
+ namespace :gta do
2
+ namespace :deploy do
3
+ desc 'task that will be run before a deploy'
4
+ task :before, :stage_name
5
+
6
+ desc 'task that will be run after a deploy'
7
+ task :after, :stage_name
8
+
9
+ def gta_manager(args={})
10
+ return @manager if @manager
11
+ raise GTA::Manager.stage_name_error unless stage_name = args[:stage_name]
12
+ @manager = GTA::Manager.new(GTA::Manager.env_config)
13
+ end
14
+
15
+ # the meat of a deploy, a git push from source to destination
16
+ task :gta_push, :stage_name do |t, args|
17
+ stage_name = args[:stage_name]
18
+ gta_manager(args).push_to(stage_name)
19
+ end
20
+
21
+ # a forced version of the meat of the matter
22
+ task :gta_force_push, :stage_name do |t, args|
23
+ stage_name = args[:stage_name]
24
+ gta_manager(args).push_to(stage_name, :force)
25
+ end
26
+
27
+ task :deploy, :stage_name do |t, args|
28
+ stage_name = args[:stage_name]
29
+ Rake::Task["gta:deploy:before"].invoke(stage_name)
30
+ Rake::Task["gta:deploy:gta_push"].invoke(stage_name)
31
+ Rake::Task["gta:deploy:after"].invoke(stage_name)
32
+ end
33
+
34
+ desc 'force push deploy, running before and after tasks'
35
+ task :force, :stage_name do |t, args|
36
+ stage_name = args[:stage_name]
37
+ Rake::Task["gta:deploy:before"].invoke(stage_name)
38
+ Rake::Task["gta:deploy:gta_force_push"].invoke(stage_name)
39
+ Rake::Task["gta:deploy:after"].invoke(stage_name)
40
+ end
22
41
  end
23
42
 
24
- task :wrap, :stage_name do |t, args|
25
- stage_name = args[:stage_name]
26
- Rake::Task["deploy:before"].invoke(stage_name)
27
- Rake::Task["deploy:gta_push"].invoke(stage_name)
28
- Rake::Task["deploy:before"].invoke(stage_name)
29
- end
30
-
31
- desc 'force push deploy, running before and after tasks'
32
- task :force, :stage_name do |t, args|
33
- stage_name = args[:stage_name]
34
- Rake::Task["deploy:before"].invoke(stage_name)
35
- Rake::Task["deploy:gta_force_push"].invoke(stage_name)
36
- Rake::Task["deploy:before"].invoke(stage_name)
43
+ desc "push deploy, running before and after tasks"
44
+ task :deploy, :stage_name do |t, args|
45
+ Rake::Task["gta:deploy:deploy"].invoke(args[:stage_name])
37
46
  end
38
47
  end
39
-
40
- desc "push deploy, running before and after tasks"
41
- task :deploy, :stage_name do |t, args|
42
- Rake::Task["deploy:wrap"].invoke(args[:stage_name])
43
- end
44
-
@@ -1,5 +1,3 @@
1
- require 'gta'
2
-
3
1
  namespace :gta do
4
2
  desc 'add remote repositories for each of the configured environments'
5
3
  task :setup do
@@ -0,0 +1,24 @@
1
+ namespace :gta do
2
+ namespace :heroku do
3
+ namespace :db do
4
+ def gta_db
5
+ @gta_db ||= GTA::DB.new(GTA::Manager.env_config, GTA::LocalDB.env_config, GTA::LocalDB.local_database_env)
6
+ end
7
+
8
+ desc 'download the database from the specified stage or from the last stage'
9
+ task :fetch, :stage_name do |t, args|
10
+ gta_db.fetch(args[:stage_name])
11
+ end
12
+
13
+ desc 'load local database with downloaded backup from stage'
14
+ task :load, :stage_name do |t, args|
15
+ gta_db.load(args[:stage_name])
16
+ end
17
+
18
+ desc 'restore remote database from another stage'
19
+ task :restore, :stage_name do |t, args|
20
+ gta_db.restore(args[:stage_name], ENV['source'])
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ namespace :gta do
2
+ namespace :hotfix do
3
+ desc "Checkout a stage for hotfix change and future deploy"
4
+ task :checkout, :stage_name do |t, args|
5
+ hotfix = GTA::Hotfix.new(GTA::Manager.env_config)
6
+ hotfix.checkout(args[:stage_name])
7
+ end
8
+
9
+ desc "deploy checked out branch to that remote"
10
+ task :deploy do
11
+ hotfix = GTA::Hotfix.new(GTA::Manager.env_config)
12
+ hotfix.deploy
13
+ end
14
+ end
15
+ end
data/lib/gta/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module GTA
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
data/spec/db_spec.rb ADDED
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+
3
+ describe GTA::DB do
4
+ let(:config_path) { File.dirname(__FILE__) + "/fixtures/config" }
5
+ let(:gta_config_path) { "#{config_path}/gta.yml" }
6
+ let(:database_config_path) { "#{config_path}/database.yml" }
7
+ let(:db) { GTA::DB.new(gta_config_path, database_config_path) }
8
+ let(:heroku_db) { double(fetch: true) }
9
+
10
+ before do
11
+ GTA::HerokuDB.stub(:new).and_return(heroku_db)
12
+ end
13
+
14
+ it "has a manager" do
15
+ db.manager.should be_a(GTA::Manager)
16
+ end
17
+
18
+ describe "#fetch" do
19
+ context "when a stage is provided" do
20
+ it "will construct a heroku db object with the right stage information" do
21
+ GTA::HerokuDB.should_receive(:new)
22
+ .with('activator', 'staging')
23
+ .and_return(heroku_db)
24
+ db.fetch('staging')
25
+ end
26
+
27
+ it "will call #fetch on the heroku db object" do
28
+ heroku_db.should_receive(:fetch)
29
+ db.fetch('staging')
30
+ end
31
+ end
32
+
33
+ context "when a stage is not provided" do
34
+ it "will use the default stage" do
35
+ GTA::HerokuDB.should_receive(:new)
36
+ .with('activator', 'qa')
37
+ .and_return(heroku_db)
38
+ db.fetch
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#load' do
44
+ let(:local_db) { double('local_db') }
45
+
46
+ before do
47
+ db.stub(:local_db).and_return(local_db)
48
+ end
49
+
50
+ context "when no source is given" do
51
+ it "loads the last stage's download into the local database" do
52
+ GTA::HerokuDB.should_receive(:new)
53
+ .with('activator', 'qa')
54
+ .and_return(heroku_db)
55
+ heroku_db.stub(:file_name).and_return("~/Downloads/activator-qa.sql")
56
+ local_db.should_receive(:load).with("~/Downloads/activator-qa.sql")
57
+ db.load
58
+ end
59
+ end
60
+
61
+ context "when a source is passed" do
62
+ it "loads the source download into the local database" do
63
+ GTA::HerokuDB.should_receive(:new)
64
+ .with('activator', 'production')
65
+ .and_return(heroku_db)
66
+ heroku_db.stub(:file_name).and_return("~/Downloads/activator-production.sql")
67
+ local_db.should_receive(:load).with("~/Downloads/activator-production.sql")
68
+ db.load('production')
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#pull' do
74
+ it "calls #fetch with the source (default or other)" do
75
+ db.should_receive(:fetch).with('qa')
76
+ db.should_receive(:load).with('qa')
77
+ db.pull
78
+ end
79
+
80
+ it "calls #load with the source (default or other)" do
81
+ db.should_receive(:fetch).with('production')
82
+ db.should_receive(:load).with('production')
83
+ db.pull('production')
84
+ end
85
+ end
86
+
87
+ describe '#restore' do
88
+ let(:manager) { db.manager }
89
+ let(:source_heroku_db) { double('source db', backup: true, url: 'source-url') }
90
+ let(:destination_heroku_db) { double('distination db', restore_from: true) }
91
+
92
+ context 'when the destination stage is not restorable' do
93
+ it "raises an error" do
94
+ expect {
95
+ db.restore('production')
96
+ }.to raise_error
97
+ end
98
+ end
99
+
100
+ context "when the stage is restorable, and no source stage information is given" do
101
+ before do
102
+ GTA::HerokuDB.stub(:new) do |app_name, stage_name|
103
+ if stage_name == 'qa'
104
+ source_heroku_db
105
+ elsif stage_name == 'staging'
106
+ destination_heroku_db
107
+ end
108
+ end
109
+ end
110
+
111
+ it "makes a backup of the final stage" do
112
+ source_heroku_db.should_receive(:backup)
113
+ db.restore('staging')
114
+ end
115
+
116
+ it "gets the url for the final stage's database" do
117
+ source_heroku_db.should_receive(:url).and_return('source-url')
118
+ db.restore('staging')
119
+ end
120
+
121
+ it "calls restore on the destination stage with the database url" do
122
+ destination_heroku_db.should_receive(:restore_from).with('source-url')
123
+ db.restore('staging')
124
+ end
125
+ end
126
+
127
+ context "when a second 'source' stage is specified" do
128
+ before do
129
+ GTA::HerokuDB.stub(:new) do |app_name, stage_name|
130
+ if stage_name == 'production'
131
+ source_heroku_db
132
+ elsif stage_name == 'qa'
133
+ destination_heroku_db
134
+ end
135
+ end
136
+ end
137
+
138
+ it "makes a backup of the source stage" do
139
+ source_heroku_db.should_receive(:backup)
140
+ db.restore('qa', 'production')
141
+ end
142
+
143
+ it "gets the url for the source's database" do
144
+ source_heroku_db.should_receive(:url).and_return('source-url')
145
+ db.restore('qa', 'production')
146
+ end
147
+
148
+ it "calls restore on the destination stage with the database url" do
149
+ destination_heroku_db.should_receive(:restore_from).with('source-url')
150
+ db.restore('qa', 'production')
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,3 @@
1
+ development:
2
+ database: activator_dev
3
+ username: socialchorus
@@ -1,19 +1,25 @@
1
- origin:
2
- repository: git@github.com:socialchorus/activator.git
1
+ activator:
2
+ origin:
3
+ repository: git@github.com:socialchorus/activator.git
3
4
 
4
- ci:
5
- source: origin
6
- repository: git@github.com:socialchorus/activator.git
5
+ ci:
6
+ source: origin
7
+ tag: ci/*
8
+ repository: git@github.com:socialchorus/activator.git
7
9
 
8
- staging:
9
- source: ci
10
- tag: staging/*
11
- repository: git@heroku.com:activator-staging.git
10
+ staging:
11
+ source: ci
12
+ repository: git@heroku.com:activator-staging.git
13
+ restorable: true
14
+ hotfixable: true
12
15
 
13
- qa:
14
- source: staging
15
- repository: git@heroku.com:activator-qa.git
16
+ qa:
17
+ source: staging
18
+ repository: git@heroku.com:activator-qa.git
19
+ restorable: true
20
+ final: true
21
+ hotfixable: true
16
22
 
17
- production:
18
- source: qa
19
- repository: git@heroku.com:activator-production.git
23
+ production:
24
+ source: qa
25
+ repository: git@heroku.com:activator-production.git
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe GTA::HerokuDB do
4
+ let(:database_yml_path) { File.dirname(__FILE__) + "/fixtures/config/database.yml" }
5
+ let(:heroku_db) { GTA::HerokuDB.new('activator', 'staging') }
6
+
7
+ describe '#url' do
8
+ it "gets the temporary database url from heroku" do
9
+ heroku_db.should_receive(:`)
10
+ .with("heroku pgbackups:url --app activator-staging")
11
+ .and_return('backup url')
12
+ heroku_db.url.should == 'backup url'
13
+ end
14
+ end
15
+
16
+ describe '#backup' do
17
+ it "sends a heroku command to backup the databse, with the expire flag" do
18
+ heroku_db.should_receive(:sh)
19
+ .with("heroku pgbackups:capture --expire --app activator-staging")
20
+ heroku_db.backup
21
+ end
22
+ end
23
+
24
+ describe '#restore_from(url)' do
25
+ it "sends a heroku command to restore the database from the given url" do
26
+ heroku_db.should_receive(:sh)
27
+ .with('heroku pgbackups:restore DATABASE_URL "http://my-database-url.com" --app activator-staging --confirm activator-staging')
28
+ heroku_db.restore_from("http://my-database-url.com")
29
+ end
30
+ end
31
+
32
+ describe '#fetch' do
33
+ it "downloads the latest database backup to an appropriately named file in downloads" do
34
+ heroku_db.should_receive(:url).and_return('http://heroku-backup-url.com')
35
+ heroku_db.should_receive(:sh)
36
+ .with('curl -o ~/Downloads/activator-staging.sql "http://heroku-backup-url.com"')
37
+ heroku_db.fetch
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe GTA::Hotfix do
4
+ let(:gta_config_path) { File.dirname(__FILE__) + "/fixtures/config/gta.yml" }
5
+ let(:hotfix) { GTA::Hotfix.new(gta_config_path) }
6
+ let(:manager) { hotfix.manager }
7
+ let(:stage) { double(checkout: true) }
8
+
9
+ describe '#checkout' do
10
+ context "no stage provided" do
11
+ it "gets the first hotfixable stage" do
12
+ manager.should_receive(:hotfixer)
13
+ .and_return(stage)
14
+ hotfix.checkout
15
+ end
16
+
17
+ it "finds and checks out the first hotfixable stage" do
18
+ manager.should_receive(:hotfixer).with(nil).and_return(stage)
19
+ stage.should_receive(:checkout)
20
+ hotfix.checkout
21
+ end
22
+ end
23
+
24
+ context "stage is not hotfixable" do
25
+ it "raises an error" do
26
+ expect {
27
+ hotfix.checkout('production')
28
+ }.to raise_error
29
+ end
30
+ end
31
+
32
+ context "stage is hotfixable" do
33
+ it "checks out that stage" do
34
+ manager.should_receive(:hotfixer)
35
+ .with('staging')
36
+ .and_return(stage)
37
+ stage.should_receive(:checkout)
38
+ hotfix.checkout('staging')
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#deploy' do
44
+ context "when on a branch that maps to a stage" do
45
+ before do
46
+ hotfix.stub(:`).and_return(branch_output)
47
+ hotfix.stub(:sh)
48
+ end
49
+
50
+ context "if it is hotfixable" do
51
+ let(:branch_output) {
52
+ " bunny\n ftc_language\n* qa\n production"
53
+ }
54
+
55
+ it "should call #sh with the right deploy command" do
56
+ hotfix.should_receive(:sh)
57
+ .with("git push qa qa:master")
58
+ .and_return("deploying")
59
+ hotfix.deploy.should == "deploying"
60
+ end
61
+ end
62
+
63
+ context "if it is not hotfixable" do
64
+ let(:branch_output) {
65
+ " bunny\n ftc_language\n* master\n production"
66
+ }
67
+
68
+ it "raises an error" do
69
+ expect {
70
+ hotfix.deploy
71
+ }.to raise_error
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe GTA::LocalDB do
4
+ let(:env) { 'development' }
5
+ let(:database_config_path) { File.dirname(__FILE__) + "/fixtures/config/database.yml" }
6
+ let(:local_db) { GTA::LocalDB.new(env, database_config_path) }
7
+
8
+ describe '#load' do
9
+ it "does a postgres load of the right database based on the config" do
10
+ local_db.should_receive(:sh).with(
11
+ "pg_restore --verbose --clean --no-acl --no-owner -h localhost -U socialchorus -d activator_dev ~/Downloads/activator-staging.sql"
12
+ )
13
+ local_db.load("~/Downloads/activator-staging.sql")
14
+ end
15
+ end
16
+ end
data/spec/manager_spec.rb CHANGED
@@ -9,6 +9,10 @@ describe GTA::Manager do
9
9
  manager.stages.map(&:class).uniq.should == [GTA::Stage]
10
10
  end
11
11
 
12
+ it "has an app name" do
13
+ manager.app_name.should == 'activator'
14
+ end
15
+
12
16
  describe '#fetch' do
13
17
  it "loops through each stage and calls fetch" do
14
18
  manager.stages.each do |stage|
@@ -37,6 +41,56 @@ describe GTA::Manager do
37
41
  end
38
42
  end
39
43
 
44
+ describe '#final_stage' do
45
+ context "when there are more than one final stages" do
46
+ before do
47
+ manager.stages.first.stub(:final?).and_return(true)
48
+ end
49
+
50
+ it "returns the first stage that responds to #final? with true" do
51
+ manager.final_stage.should == manager.stages.first
52
+ end
53
+ end
54
+
55
+ context "when there is only one final stage" do
56
+ it "returns the only one defined" do
57
+ manager.final_stage.name.should == 'qa'
58
+ end
59
+ end
60
+
61
+ context "when there are no final stages" do
62
+ before do
63
+ manager.stages.each{|s| s.stub(:final?).and_return(false) }
64
+ end
65
+
66
+ it "chooses the last stage in the configuration" do
67
+ manager.final_stage.name.should == 'production'
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#hotfixer' do
73
+ context 'when not passed a stage name' do
74
+ it "returns the first stage that is hotfixable" do
75
+ manager.hotfixer.name.should == 'staging'
76
+ end
77
+ end
78
+
79
+ context "when passed a stage name" do
80
+ context "when the stage name matches a hotfixable stage" do
81
+ it "returns the stage" do
82
+ manager.hotfixer('qa').name.should == 'qa'
83
+ end
84
+ end
85
+
86
+ context "when the stage name matches as non hotfixbale stage" do
87
+ it "returns nil" do
88
+ manager.hotfixer('production').should == nil
89
+ end
90
+ end
91
+ end
92
+ end
93
+
40
94
  describe '#push_to' do
41
95
  before do
42
96
  manager.stub(:fetch)
data/spec/stage_spec.rb CHANGED
@@ -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: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/sendit:master")
110
+ stage.should_receive(:sh).with("git push staging origin:master")
111
111
  stage.push(origin)
112
112
  end
113
113
  end
@@ -123,8 +123,10 @@ describe GTA::Stage do
123
123
 
124
124
  context "when the source has a tag" do
125
125
  let(:tag) { 'my-tag' }
126
+ let(:tag_finder) { double(newest: tag) }
126
127
 
127
128
  it "uses the tag as the source reference" do
129
+ GTA::TagFinder.should_receive(:new).with(tag).and_return(tag_finder)
128
130
  stage.should_receive(:sh).with("git push staging my-tag:master")
129
131
  stage.push
130
132
  end
@@ -132,7 +134,7 @@ describe GTA::Stage do
132
134
 
133
135
  context "force push" do
134
136
  it "adds the -f flag to the git command" do
135
- stage.should_receive(:sh).with("git push -f staging ci/master:master")
137
+ stage.should_receive(:sh).with("git push -f staging ci:master")
136
138
  stage.force_push
137
139
  end
138
140
  end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe GTA::TagFinder do
4
+ let(:finder) { GTA::TagFinder.new(tag)}
5
+
6
+ describe '#newest' do
7
+ context 'it receives a tag' do
8
+ let(:tag) { 'ci/*' }
9
+ before do
10
+ finder.stub(:`).and_return(git_response)
11
+ end
12
+
13
+ context 'we get a list from git' do
14
+ let(:git_response) {
15
+ "ci/12310\nci/12313\nci/29374"
16
+ }
17
+
18
+ it 'should return the most recent one' do
19
+ finder.newest.should == "ci/29374"
20
+ end
21
+ end
22
+
23
+ context 'we get a single line from git' do
24
+ let(:git_response) {
25
+ "ci/29374"
26
+ }
27
+
28
+ it 'should return it' do
29
+ finder.newest.should == "ci/29374"
30
+ end
31
+ end
32
+
33
+ context 'we get an empty string from git' do
34
+ let(:git_response) { "" }
35
+
36
+ it 'should be nil' do
37
+ finder.newest.should == nil
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'it receives nothing' do
43
+ let(:tag) { nil }
44
+
45
+ it "returns nothing" do
46
+ finder.newest.should == nil
47
+ end
48
+ end
49
+ end
50
+ end
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - socialchorus
8
8
  - Kane Baccigalupi
9
+ - Ian Cooper
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2013-07-29 00:00:00.000000000 Z
13
+ date: 2013-07-31 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: ansi
@@ -83,18 +84,31 @@ files:
83
84
  - Rakefile
84
85
  - gta.gemspec
85
86
  - lib/gta.rb
87
+ - lib/gta/db.rb
88
+ - lib/gta/heroku_db.rb
89
+ - lib/gta/hotfix.rb
90
+ - lib/gta/local_db.rb
86
91
  - lib/gta/manager.rb
87
- - lib/gta/remote_db.rb
92
+ - lib/gta/railtie.rb
88
93
  - lib/gta/sh.rb
89
94
  - lib/gta/stage.rb
95
+ - lib/gta/tag_finder.rb
90
96
  - lib/gta/tasks.rb
91
97
  - lib/gta/tasks/deploy.rake
92
98
  - lib/gta/tasks/gta.rake
99
+ - lib/gta/tasks/heroku_db.rake
100
+ - lib/gta/tasks/hotfix.rake
93
101
  - lib/gta/version.rb
102
+ - spec/db_spec.rb
103
+ - spec/fixtures/config/database.yml
94
104
  - spec/fixtures/config/gta.yml
105
+ - spec/heroku_db_spec.rb
106
+ - spec/hotfix_spec.rb
107
+ - spec/local_db_spec.rb
95
108
  - spec/manager_spec.rb
96
109
  - spec/spec_helper.rb
97
110
  - spec/stage_spec.rb
111
+ - spec/tag_finder_spec.rb
98
112
  homepage: http://github.com/socialchorus/gta
99
113
  licenses:
100
114
  - MIT
@@ -121,7 +135,13 @@ specification_version: 4
121
135
  summary: 'GTA: the Git Transit Authority - A git based deploy tool for moving code
122
136
  from stage to stage.'
123
137
  test_files:
138
+ - spec/db_spec.rb
139
+ - spec/fixtures/config/database.yml
124
140
  - spec/fixtures/config/gta.yml
141
+ - spec/heroku_db_spec.rb
142
+ - spec/hotfix_spec.rb
143
+ - spec/local_db_spec.rb
125
144
  - spec/manager_spec.rb
126
145
  - spec/spec_helper.rb
127
146
  - spec/stage_spec.rb
147
+ - spec/tag_finder_spec.rb
data/lib/gta/remote_db.rb DELETED
@@ -1,22 +0,0 @@
1
- module Remote
2
- class DB
3
- attr_reader :env, :app
4
-
5
- def initialize(env, app)
6
- raise "Environment not allowed" unless ['qa', 'staging'].include?(env)
7
- @env = env
8
- @app = app
9
- end
10
-
11
- def reset
12
- puts "==> RESETTING the #{app}-#{env} db..."
13
- `heroku pg:reset DATABASE --app #{app}-#{env} --confirm #{app}-#{env}`
14
- end
15
-
16
- def restore
17
- puts "==> RESTORING #{app}-#{env} from latest backup..."
18
- `heroku pgbackups:restore DATABASE_URL "#{DB::Config.db_url(app)}" --app #{app}-#{env} --confirm #{app}-#{env}`
19
- end
20
- end
21
- end
22
-