duple 0.0.3 → 0.0.4

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.
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