gta 0.1.2 → 0.2.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: 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
-