duple 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -50,7 +50,11 @@ figure the application. Read and modify it, or clear it out and write your own.
50
50
 
51
51
  ## Future
52
52
 
53
- * Pre- and post- refresh hooks
53
+ * Custom error classes.
54
+ * Improve documentation
55
+ * Support non-Heroku remote PostgreSQL servers.
56
+ * Move knowledge of endpoint types out of Thor tasks.
57
+
54
58
  * Support for skipping pre- and post- refresh steps.
55
59
  * Support for running pre- and post- refresh steps by themselves.
56
60
  * Support for other data stores.
data/Rakefile CHANGED
@@ -10,9 +10,10 @@ begin
10
10
 
11
11
  desc "Run cane to check quality metrics"
12
12
  Cane::RakeTask.new(:cane) do |cane|
13
+ cane.abc_max = 15
13
14
  cane.style_measure = 100
14
15
  cane.style_glob = '{lib}/**/*.rb'
15
- cane.gte = {'coverage/covered_percent' => 95}
16
+ cane.gte = {'coverage/covered_percent' => 98}
16
17
  end
17
18
  rescue LoadError
18
19
  warn "cane not available, quality task not provided."
@@ -1,3 +1,6 @@
1
1
  require 'duple/version'
2
2
  require 'duple/configuration'
3
+ require 'duple/endpoint'
4
+ require 'duple/source'
5
+ require 'duple/target'
3
6
  require 'duple/cli/root'
@@ -33,12 +33,15 @@ module Duple
33
33
  say
34
34
  end
35
35
 
36
- def print_tasks(header, task_list)
36
+ def print_tasks(header, tasks)
37
37
  say header
38
38
  say '-' * 80
39
- task_list.each do |task_name, commands|
39
+ tasks.each do |task|
40
40
  say
41
- say ' ' + task_name
41
+ say ' ' + task.name
42
+ commands = task.commands.map do |c|
43
+ { subject: c.subject, command_type: c.type, command: c.command }
44
+ end
42
45
  print_table commands, indent: 4
43
46
  end
44
47
  say
@@ -69,8 +69,16 @@ module Duple
69
69
  end
70
70
 
71
71
  module InstanceMethods
72
- def default_config_path
73
- File.join('config', 'duple.yml')
72
+ def config
73
+ @config ||= parse_config
74
+ end
75
+
76
+ def parse_config
77
+ config_path = File.join(destination_root, app_config_path)
78
+ config_data = File.read(config_path)
79
+ erbed = ERB.new(config_data).result
80
+ config_hash = YAML.load(erbed) || {}
81
+ Duple::Configuration.new(config_hash, options)
74
82
  end
75
83
 
76
84
  def app_config_path(verify_file = true)
@@ -81,89 +89,71 @@ module Duple
81
89
  config_path
82
90
  end
83
91
 
92
+ def default_config_path
93
+ File.join('config', 'duple.yml')
94
+ end
95
+
96
+ def source
97
+ @source ||= Duple::Source.new(config, runner)
98
+ end
99
+
100
+ def target
101
+ @target ||= Duple::Target.new(config, runner)
102
+ end
103
+
84
104
  def runner
85
105
  @runner ||= Duple::Runner.new(dry_run: config.dry_run?)
86
106
  end
87
107
 
88
108
  def postgres
109
+ # TODO: This should go away as the methods are moved into the endpoint classes.
89
110
  @pg_runner ||= Duple::PGRunner.new(runner)
90
111
  end
91
112
 
92
113
  def heroku
114
+ # TODO: This should go away as the methods are moved into the endpoint classes.
93
115
  @heroku ||= Duple::HerokuRunner.new(runner)
94
116
  end
95
117
 
96
- def source_appname
97
- @source_appname ||= config.heroku_name(config.source_environment)
98
- end
99
-
100
118
  def target_appname
101
- @target_appname ||= config.heroku_name(config.target_environment)
119
+ # TODO: This should go away as the methods are moved into the endpoint classes.
120
+ target.appname
102
121
  end
103
122
 
104
123
  def dump_dir_path
124
+ # TODO: This should go on the configuration and be configurable.
105
125
  File.join('tmp', 'duple')
106
126
  end
107
127
 
108
128
  def data_file_path
129
+ # TODO: This should go on the configuration and be configurable.
109
130
  @data_file_path ||= File.join(dump_dir_path, "#{config.source_name}-data.dump")
110
131
  end
111
132
 
112
133
  def structure_file_path
134
+ # TODO: This should go on the configuration and be configurable.
113
135
  filename = "#{config.source_name}-structure.dump"
114
136
  @structure_file_path ||= File.join(dump_dir_path, filename)
115
137
  end
116
138
 
117
139
  def snapshot_file_path(timestamp)
140
+ # TODO: This should go on the configuration and be configurable.
118
141
  filename = "#{config.source_name}-#{timestamp}.dump"
119
142
  @structure_file_path ||= File.join(dump_dir_path, filename)
120
143
  end
121
144
 
122
145
  def source_db_config
123
- @source_db_config ||= if config.heroku_source?
124
- fetch_heroku_db_config(source_appname)
125
- else
126
- config.db_config(config.source_name)
127
- end
146
+ # TODO: This should go away as the methods are moved into the endpoint classes.
147
+ @source_db_config ||= source.db_config
128
148
  end
129
149
 
130
150
  def target_db_config
131
- @target_db_config ||= if config.heroku_target?
132
- fetch_heroku_db_config(target_appname)
133
- else
134
- config.db_config(config.target_name)
135
- end
136
- end
137
-
138
- def fetch_heroku_db_config(appname)
139
- # Run the heroku config command first, even if it's a dry run. So
140
- # that the command to get the config will show up in the dry run log.
141
- config_vars = heroku.capture(appname, "config")
142
-
143
- if config.dry_run?
144
- config.db_config(appname, dry_run: true)
145
- else
146
- parse_heroku_config(config_vars)
147
- end
148
- end
149
-
150
- def parse_heroku_config(config_vars)
151
- db_url = config_vars.split("\n").detect { |l| l =~ /DATABASE_URL/ }
152
- raise ArgumentError.new("Missing DATABASE_URL variable for #{appname}") if db_url.nil?
153
-
154
- db_url.match(
155
- /postgres:\/\/(?<username>.*):(?<password>.*)@(?<host>.*):(?<port>\d*)\/(?<database>.*)/
156
- )
157
- end
158
-
159
- def fetch_latest_snapshot_time(appname)
160
- response = heroku.capture(appname, 'pgbackups')
161
- last_line = response.split("\n").last
162
- timestring = last_line.match(/\w+\s+(?<timestamp>[\d\s\/\:\.]+)\s+.*/)[:timestamp]
163
- DateTime.strptime(timestring, '%Y/%m/%d %H:%M.%S')
151
+ # TODO: This should go away as the methods are moved into the endpoint classes.
152
+ @target_db_config ||= target.db_config
164
153
  end
165
154
 
166
155
  def reset_database(env)
156
+ # TODO: This should be moved to the endpoint classes.
167
157
  if config.heroku?(env)
168
158
  appname = config.heroku_name(env)
169
159
  heroku.run(appname, 'pg:reset')
@@ -174,6 +164,7 @@ module Duple
174
164
  end
175
165
 
176
166
  def dump_flags
167
+ # TODO: This should be moved to the endpoint classes.
177
168
  include_tables = config.included_tables
178
169
  include_flags = include_tables.map { |t| "-t #{t}" }
179
170
 
@@ -182,18 +173,6 @@ module Duple
182
173
 
183
174
  flags = [ '-Fc -a', include_flags, exclude_flags ].flatten.compact.join(' ')
184
175
  end
185
-
186
- def config
187
- @config ||= parse_config
188
- end
189
-
190
- def parse_config
191
- config_path = File.join(destination_root, app_config_path)
192
- config_data = File.read(config_path)
193
- erbed = ERB.new(config_data).result
194
- config_hash = YAML.load(erbed) || {}
195
- Duple::Configuration.new(config_hash, options)
196
- end
197
176
  end
198
177
  end
199
178
  end
@@ -24,10 +24,6 @@ module Duple
24
24
  tables_option
25
25
 
26
26
  no_tasks do
27
- def capture_snapshot?
28
- config.capture? && config.heroku_source?
29
- end
30
-
31
27
  def fetch_snapshot_url?
32
28
  config.heroku_source? && !config.filtered_tables?
33
29
  end
@@ -39,24 +35,35 @@ module Duple
39
35
  def dump_data?
40
36
  config.local_source? || config.filtered_tables?
41
37
  end
38
+
39
+ def run_tasks(tasks)
40
+ tasks.each do |task|
41
+ task.commands.each do |c|
42
+ source.execute(c) if c.source?
43
+ target.execute(c) if c.target?
44
+ end
45
+ end
46
+ end
42
47
  end
43
48
 
44
- def capture_snapshot
45
- return unless capture_snapshot?
49
+ def run_prerefresh_tasks
50
+ run_tasks(config.pre_refresh_tasks)
51
+ end
46
52
 
47
- heroku.run(source_appname, 'pgbackups:capture')
53
+ def capture_snapshot
54
+ source.capture_snapshot if config.capture?
48
55
  end
49
56
 
50
57
  def fetch_snapshot_url
51
58
  return unless fetch_snapshot_url?
52
59
 
53
- @source_snapshot_url = heroku.capture(source_appname, 'pgbackups:url').strip
60
+ @source_snapshot_url = source.snapshot_url
54
61
  end
55
62
 
56
63
  def download_snapshot
57
64
  return unless download_snapshot?
58
65
 
59
- timestamp = fetch_latest_snapshot_time(source_appname)
66
+ timestamp = source.latest_snapshot_time
60
67
 
61
68
  @snapshot_path = snapshot_file_path(timestamp.strftime('%Y-%m-%d-%H-%M-%S'))
62
69
  unless File.exists?(@snapshot_path)
@@ -83,6 +90,10 @@ module Duple
83
90
  heroku.run(target_appname, "pgbackups:restore DATABASE #{@source_snapshot_url}")
84
91
  end
85
92
  end
93
+
94
+ def run_postrefresh_tasks
95
+ run_tasks(config.post_refresh_tasks)
96
+ end
86
97
  end
87
98
  end
88
99
  end
@@ -0,0 +1,46 @@
1
+ module Duple
2
+ module Config
3
+ # Represents a command that can be executed in the source or target environment.
4
+ class Command
5
+ SHELL = 'shell'
6
+ HEROKU = 'heroku'
7
+ VALID_TYPES = [SHELL, HEROKU]
8
+
9
+ SOURCE = 'source'
10
+ TARGET = 'target'
11
+ VALID_SUBJECTS = [SOURCE, TARGET]
12
+
13
+ attr_accessor :subject, :type, :command
14
+
15
+ def initialize(config_hash)
16
+ @command = config_hash['command']
17
+ @type = config_hash['command_type']
18
+ @subject = config_hash['subject']
19
+
20
+ unless VALID_TYPES.include?(@type)
21
+ raise ArgumentError.new("Invalid config: #{@type} is not a valid command type.")
22
+ end
23
+
24
+ unless VALID_SUBJECTS.include?(@subject)
25
+ raise ArgumentError.new("Invalid config: #{@subject} is not a valid command subject.")
26
+ end
27
+ end
28
+
29
+ def source?
30
+ subject == Duple::Config::Command::SOURCE
31
+ end
32
+
33
+ def target?
34
+ subject == Duple::Config::Command::TARGET
35
+ end
36
+
37
+ def heroku?
38
+ type == Duple::Config::Command::HEROKU
39
+ end
40
+
41
+ def shell?
42
+ type == Duple::Config::Command::SHELL
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ require 'duple/config/command'
2
+
3
+ module Duple
4
+ module Config
5
+ # Represents a set of commands that can be executed in the source or target environment.
6
+ class Task
7
+ attr_accessor :name, :commands
8
+ def initialize(name, command_list)
9
+ @name = name
10
+ @commands = command_list.map { |c| Duple::Config::Command.new(c) }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,5 @@
1
+ require 'duple/config/task'
2
+
1
3
  module Duple
2
4
 
3
5
  # Represents the configuration that will be used to perform the data
@@ -181,23 +183,11 @@ module Duple
181
183
  end
182
184
 
183
185
  def pre_refresh_tasks
184
- raw_config['pre_refresh'] || {}
185
- end
186
-
187
- def pre_refresh_task(task_name)
188
- task = pre_refresh_tasks[task_name]
189
- raise ArgumentError.new("Invalid pre_refresh task: #{task_name}") if task.nil?
190
- task
186
+ @pre_refresh_tasks ||= build_tasks(raw_config['pre_refresh'])
191
187
  end
192
188
 
193
189
  def post_refresh_tasks
194
- raw_config['post_refresh'] || {}
195
- end
196
-
197
- def post_refresh_task(task_name)
198
- task = post_refresh_tasks[task_name]
199
- raise ArgumentError.new("Invalid post_refresh task: #{task_name}") if task.nil?
200
- task
190
+ @post_refresh_tasks ||= build_tasks(raw_config['post_refresh'])
201
191
  end
202
192
 
203
193
  def other_options
@@ -206,6 +196,13 @@ module Duple
206
196
 
207
197
  protected
208
198
 
199
+ def build_tasks(task_hash)
200
+ return [] if task_hash.nil?
201
+ task_hash.map do |name, command_list|
202
+ Duple::Config::Task.new(name, command_list)
203
+ end
204
+ end
205
+
209
206
  def env_names_by_flag(flag_name, flag_value, allow_multiple = false)
210
207
  matching_envs = environments.select { |n, c| c[flag_name] == flag_value }
211
208
  if matching_envs.size > 1 && !allow_multiple
@@ -0,0 +1,92 @@
1
+ module Duple
2
+ module Endpoint
3
+ def self.included(base)
4
+ base.send(:attr_reader, :config, :runner)
5
+ end
6
+
7
+ def initialize(config, runner)
8
+ @config = config
9
+ @runner = runner
10
+ if config.heroku?(environment)
11
+ extend Duple::HerokuEndpoint
12
+ else
13
+ extend Duple::PostgreSQLEndpoint
14
+ end
15
+ end
16
+ end
17
+
18
+ module HerokuEndpoint
19
+ def appname
20
+ @appname ||= config.heroku_name(environment)
21
+ end
22
+
23
+ def heroku
24
+ @heroku ||= Duple::HerokuRunner.new(runner)
25
+ end
26
+
27
+ def db_config
28
+ @db_config ||= fetch_db_config
29
+ end
30
+
31
+ def snapshot_url
32
+ @snapshot_url ||= heroku.capture(appname, 'pgbackups:url').strip
33
+ end
34
+
35
+ def latest_snapshot_time
36
+ unless @snapshot_time
37
+ response = heroku.capture(appname, 'pgbackups')
38
+ last_line = response.split("\n").last
39
+ timestring = last_line.match(/\w+\s+(?<timestamp>[\d\s\/\:\.]+)\s+.*/)[:timestamp]
40
+ @snapshot_time = DateTime.strptime(timestring, '%Y/%m/%d %H:%M.%S')
41
+ end
42
+ @snapshot_time
43
+ end
44
+
45
+ def fetch_db_config
46
+ # Run the heroku config command first, even if it's a dry run, so
47
+ # that the command to get the config will show up in the dry run log.
48
+ config_vars = heroku.capture(appname, "config")
49
+
50
+ if config.dry_run?
51
+ config.db_config(name, dry_run: true)
52
+ else
53
+ parse_config(config_vars)
54
+ end
55
+ end
56
+
57
+ def parse_config(config_vars)
58
+ db_url = config_vars.split("\n").detect { |l| l =~ /DATABASE_URL/ }
59
+ raise ArgumentError.new("Missing DATABASE_URL variable for #{appname}") if db_url.nil?
60
+
61
+ db_url.match(
62
+ /postgres:\/\/(?<username>.*):(?<password>.*)@(?<host>.*):(?<port>\d*)\/(?<database>.*)/
63
+ )
64
+ end
65
+
66
+ def capture_snapshot
67
+ heroku.run(appname, 'pgbackups:capture')
68
+ end
69
+
70
+ def execute(cmd)
71
+ if cmd.shell?
72
+ heroku.run(appname, "run \"#{cmd.command}\"")
73
+ elsif cmd.heroku?
74
+ heroku.run(appname, cmd.command)
75
+ end
76
+ end
77
+ end
78
+
79
+ module PostgreSQLEndpoint
80
+ def db_config
81
+ config.db_config(name)
82
+ end
83
+
84
+ def capture_snapshot
85
+ # Do nothing. When we have non-local PostgreSQL endpoints, this will need to be implemented.
86
+ end
87
+
88
+ def execute(cmd)
89
+ runner.run(cmd.command) if cmd.shell?
90
+ end
91
+ end
92
+ end
@@ -53,10 +53,6 @@ module Duple
53
53
  @options[:recorder]
54
54
  end
55
55
 
56
- def live?
57
- !@options[:dry_run]
58
- end
59
-
60
56
  def dry_run?
61
57
  @options[:dry_run]
62
58
  end
@@ -0,0 +1,14 @@
1
+ module Duple
2
+ # Represents the source of the data/schema transfer.
3
+ class Source
4
+ include Duple::Endpoint
5
+
6
+ def environment
7
+ config.source_environment
8
+ end
9
+
10
+ def name
11
+ @name ||= config.source_name
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Duple
2
+ # Represents the target of the data/schema transfer.
3
+ class Target
4
+ include Duple::Endpoint
5
+
6
+ def environment
7
+ config.target_environment
8
+ end
9
+
10
+ def name
11
+ @name ||= config.target_name
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Duple
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -1,5 +1,3 @@
1
- dump_dir: tmp
2
-
3
1
  environments:
4
2
  development:
5
3
  type: local
@@ -1,6 +1,3 @@
1
- dump_dir: tmp
2
- postgres_user: <%= ENV['POSTGRES_DEV_USER'] || 'postgres' %>
3
-
4
1
  environments:
5
2
  development:
6
3
  type: local
@@ -42,24 +39,21 @@ post_refresh:
42
39
  command: ps:scale worker=1
43
40
  migrate:
44
41
  - subject: target
45
- command_type: rake
46
- command: db:migrate
42
+ command_type: shell
43
+ command: rake db:migrate
47
44
  scrub_data:
48
45
  - subject: target
49
- command_type: rake
50
- command: snapshot:scrub
46
+ command_type: shell
47
+ command: rake snapshot:scrub
51
48
  test_data:
52
49
  - subject: target
53
- command_type: rake
54
- command: snapshot:setup_testers
50
+ command_type: shell
51
+ command: rake snapshot:setup_testers
55
52
 
56
53
  groups:
57
54
  all:
58
55
  include_all: true # Include all tables
59
56
 
60
- structure:
61
- exclude_all: true # Exclude all tables (structure only)
62
-
63
57
  minimal:
64
58
  include_tables: # Include only the listed tables
65
59
  - categories
@@ -1,5 +1,3 @@
1
- dump_dir: tmp
2
-
3
1
  environments:
4
2
  development:
5
3
  type: local
@@ -1,5 +1,3 @@
1
- dump_dir: tmp
2
-
3
1
  environments:
4
2
  development:
5
3
  type: local
@@ -1,9 +1,15 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Duple::CLI::Config do
4
+ let(:script) { Duple::CLI::Config.new }
5
+ it 'raises an error if an invalid config path is supplied' do
6
+ expect {
7
+ script.invoke(:all, [], {config: 'spec/config/nonexistent.yml'})
8
+ }.to raise_error(ArgumentError, 'Missing config file: spec/config/nonexistent.yml')
9
+ end
10
+
4
11
  it 'prints the configuration' do
5
12
  result = capture_stdout do
6
- script = Duple::CLI::Config.new
7
13
  script.invoke(:all, [], {config: 'spec/config/kitchensink.yml'})
8
14
  end
9
15
 
@@ -293,9 +293,8 @@ describe Duple::CLI::Refresh do
293
293
  end
294
294
 
295
295
  context 'with pre-refresh tasks' do
296
- before { pending 'Implement pre-refresh tasks' }
297
-
298
296
  before {
297
+ stub_prerefresh_tasks
299
298
  stub_fetch_url
300
299
  stub_fetch_config
301
300
  stub_fetch_backups
@@ -305,6 +304,7 @@ describe Duple::CLI::Refresh do
305
304
  stub_reset_local
306
305
  stub_restore_url
307
306
  stub_restore_data
307
+ stub_postrefresh_tasks
308
308
  }
309
309
 
310
310
  let(:source) { 'production' }
@@ -315,7 +315,7 @@ describe Duple::CLI::Refresh do
315
315
  runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
316
316
  runner.should_receive(:run).any_number_of_times.ordered.with(/heroku pgbackups/)
317
317
 
318
- invoke_refresh(table_options.merge(capture: true))
318
+ invoke_refresh(task_options.merge(capture: true))
319
319
  end
320
320
 
321
321
  it 'executes the tasks in order' do
@@ -352,8 +352,9 @@ describe Duple::CLI::Refresh do
352
352
  end
353
353
 
354
354
  context 'with post-refresh tasks' do
355
- before { pending 'Implement post-refresh tasks' }
355
+ # before { pending 'Implement post-refresh tasks' }
356
356
  before {
357
+ stub_prerefresh_tasks
357
358
  stub_fetch_url
358
359
  stub_fetch_config
359
360
  stub_fetch_backups
@@ -363,6 +364,7 @@ describe Duple::CLI::Refresh do
363
364
  stub_reset_local
364
365
  stub_restore_url
365
366
  stub_restore_data
367
+ stub_postrefresh_tasks
366
368
  }
367
369
 
368
370
  let(:source) { 'production' }
@@ -371,9 +373,9 @@ describe Duple::CLI::Refresh do
371
373
 
372
374
  it 'executes the tasks after refreshing' do
373
375
  runner.should_receive(:run).any_number_of_times.ordered.with(/heroku pgbackups/)
374
- runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
376
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:off -a duple-stage')
375
377
 
376
- invoke_refresh(table_options.merge(capture: true))
378
+ invoke_refresh(task_options.merge(capture: true))
377
379
  end
378
380
 
379
381
  it 'executes the tasks in order' do
@@ -401,7 +403,7 @@ describe Duple::CLI::Refresh do
401
403
 
402
404
  it 'executes the tasks in order' do
403
405
  runner.should_receive(:run).once.ordered.with('rake refresh:finish')
404
- runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
406
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:off -a duple-stage')
405
407
  runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:finish" -a duple-stage')
406
408
 
407
409
  invoke_refresh(task_options)
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  describe Duple::Configuration do
4
4
 
5
-
6
5
  describe '#db_config' do
7
6
  let(:config_hash) { YAML.load(File.read('spec/config/simple.yml'))}
8
7
 
@@ -61,5 +61,14 @@ describe Duple::Runner do
61
61
  runner.capture('date')
62
62
  recorder.string.split("\n").should == ['ls tmp', 'date']
63
63
  end
64
+
65
+ context 'that is not I/O-like' do
66
+ let(:recorder) { "" }
67
+ it 'raises an error' do
68
+ expect {
69
+ runner.capture('date')
70
+ }.to raise_error(ArgumentError, 'Invalid :recorder option: ')
71
+ end
72
+ end
64
73
  end
65
74
  end
@@ -25,6 +25,18 @@ module Duple
25
25
  File.read('spec/config/heroku_config.txt')
26
26
  end
27
27
 
28
+ def stub_prerefresh_tasks
29
+ runner.stub(:run).with(/heroku run "rake refresh:prepare/)
30
+ runner.stub(:run).with(/rake refresh:prepare/)
31
+ runner.stub(:run).with(/heroku maintenance:on/)
32
+ end
33
+
34
+ def stub_postrefresh_tasks
35
+ runner.stub(:run).with(/heroku run "rake refresh:finish/)
36
+ runner.stub(:run).with(/rake refresh:finish/)
37
+ runner.stub(:run).with(/heroku maintenance:off/)
38
+ end
39
+
28
40
  def stub_fetch_url
29
41
  runner.stub(:capture).with(/heroku pgbackups:url/)
30
42
  .and_return(heroku_pgbackups_url_response)
@@ -1,71 +1,64 @@
1
- dump_dir: tmp
2
- postgres_user: <%= ENV['POSTGRES_DEV_USER'] || 'postgres' %>
3
-
4
1
  environments:
5
2
  development:
6
- type: local
7
- default_target: true
3
+ type: local # The "development" database is hosted on your machine.
4
+ default_target: true # Use "development" as the default data target.
5
+ database: duple_development # The name of the "development" database is "duple_development"
8
6
 
9
7
  backstage:
10
- type: heroku
11
- appname: duple-backstage
8
+ type: heroku # The "backstage" database is hosted on Heroku.
9
+ appname: duple-backstage # The "backstage" app is called "duple-backstage" on Heroku.
12
10
 
13
11
  stage:
14
- type: heroku
15
- appname: duple-stage
16
- default_source: true
12
+ type: heroku # The "stage" database is hosted on Heroku.
13
+ appname: duple-stage # The "stage" app is called "duple-stage" on Heroku.
14
+ default_source: true # Use "stage" as the default data source.
17
15
 
18
16
  production:
19
- type: heroku
20
- appname: duple-production
21
- allow_target: false
17
+ type: heroku # The "production" database is hosted on Heroku.
18
+ appname: duple-production # The "production" app is called "duple-production" on Heroku.
19
+ allow_target: false # The "production" database cannot be used as a data target.
22
20
 
23
21
  pre_refresh:
24
- disable_target:
25
- - subject: target
26
- command_type: heroku
27
- command: maintenance:on
28
- - subject: target
29
- command_type: heroku
30
- command: ps:scale worker=0
22
+ prepare_source: # Defines a set of commands called "prepare_source"
23
+ - subject: source # Execute "rake refresh:prepare" in the source shell.
24
+ command_type: shell
25
+ command: rake refresh:prepare
26
+ prepare_target: # Defines a set of commands called "prepare_target"
27
+ - subject: target # Execute "heroku maintenance:on" on the target.
28
+ command_type: heroku # Note: The command will not be executed in a local
29
+ command: maintenance:on # environment.
30
+ - subject: target # Execute "rake refresh:prepare" on the target.
31
+ command_type: shell
32
+ command: rake refresh:prepare
31
33
 
32
34
  post_refresh:
33
- enable_target:
34
- - subject: target
35
- command_type: heroku
36
- command: restart
37
- - subject: target
35
+ finish_source: # Defines a set of commands called "finish_source"
36
+ - subject: source # Execute "rake refresh:finish" in the source shell.
37
+ command_type: shell
38
+ command: rake refresh:finish
39
+
40
+ finish_target: # Defines a set of commands called "finish_source"
41
+ - subject: target # Execute "heroku maintenance:off" on the target.
38
42
  command_type: heroku
39
43
  command: maintenance:off
40
- - subject: target
41
- command_type: heroku
42
- command: ps:scale worker=1
43
- migrate:
44
- - subject: target
45
- command_type: rake
46
- command: db:migrate
47
- scrub_data:
48
- - subject: target
49
- command_type: rake
50
- command: snapshot:scrub
51
- test_data:
52
- - subject: target
53
- command_type: rake
54
- command: snapshot:setup_testers
44
+ - subject: target # Execute "rake refresh:finish" on the target.
45
+ command_type: shell
46
+ command: rake refresh:finish
55
47
 
56
48
  groups:
57
- all:
58
- include_all: true # Include all tables
49
+ all: # Defines a group called "all"
50
+ include_all: true # Include all tables
59
51
 
60
- structure:
61
- exclude_all: true # Exclude all tables (structure only)
62
-
63
- minimal:
64
- include_tables: # Include only the listed tables
52
+ minimal: # Defines a group called "minimal"
53
+ include_tables: # Include only the listed tables
65
54
  - categories
66
55
  - links
67
56
 
68
- no_comments:
69
- exclude_tables: # Include all but the listed tables
57
+ no_comments: # Defines a group called "no_comments"
58
+ exclude_tables: # Include all but the listed tables
70
59
  - comments
71
- - comments_posts
60
+
61
+ all_but_comments: # Defines a group called "all_but_comments"
62
+ include_all: true # Include all tables...
63
+ exclude_tables: # Except for comments
64
+ - comments # NOTE: This behaves identically to the no_comments group.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: duple
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-22 00:00:00.000000000 Z
12
+ date: 2012-10-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -134,10 +134,15 @@ files:
134
134
  - lib/duple/cli/refresh.rb
135
135
  - lib/duple/cli/root.rb
136
136
  - lib/duple/cli/structure.rb
137
+ - lib/duple/config/command.rb
138
+ - lib/duple/config/task.rb
137
139
  - lib/duple/configuration.rb
140
+ - lib/duple/endpoint.rb
138
141
  - lib/duple/heroku_runner.rb
139
142
  - lib/duple/pg_runner.rb
140
143
  - lib/duple/runner.rb
144
+ - lib/duple/source.rb
145
+ - lib/duple/target.rb
141
146
  - lib/duple/version.rb
142
147
  - spec/config/groups.yml
143
148
  - spec/config/heroku_config.txt