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 +5 -1
- data/Rakefile +2 -1
- data/lib/duple.rb +3 -0
- data/lib/duple/cli/config.rb +6 -3
- data/lib/duple/cli/helpers.rb +36 -57
- data/lib/duple/cli/refresh.rb +20 -9
- data/lib/duple/config/command.rb +46 -0
- data/lib/duple/config/task.rb +14 -0
- data/lib/duple/configuration.rb +11 -14
- data/lib/duple/endpoint.rb +92 -0
- data/lib/duple/runner.rb +0 -4
- data/lib/duple/source.rb +14 -0
- data/lib/duple/target.rb +14 -0
- data/lib/duple/version.rb +1 -1
- data/spec/config/groups.yml +0 -2
- data/spec/config/kitchensink.yml +6 -12
- data/spec/config/simple.yml +0 -2
- data/spec/config/tasks.yml +0 -2
- data/spec/duple/cli/config_spec.rb +7 -1
- data/spec/duple/cli/refresh_spec.rb +9 -7
- data/spec/duple/configuration_spec.rb +0 -1
- data/spec/duple/runner_spec.rb +9 -0
- data/spec/support/cli_spec_helpers.rb +12 -0
- data/templates/config/duple.yml +43 -50
- metadata +7 -2
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
|
-
*
|
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' =>
|
16
|
+
cane.gte = {'coverage/covered_percent' => 98}
|
16
17
|
end
|
17
18
|
rescue LoadError
|
18
19
|
warn "cane not available, quality task not provided."
|
data/lib/duple.rb
CHANGED
data/lib/duple/cli/config.rb
CHANGED
@@ -33,12 +33,15 @@ module Duple
|
|
33
33
|
say
|
34
34
|
end
|
35
35
|
|
36
|
-
def print_tasks(header,
|
36
|
+
def print_tasks(header, tasks)
|
37
37
|
say header
|
38
38
|
say '-' * 80
|
39
|
-
|
39
|
+
tasks.each do |task|
|
40
40
|
say
|
41
|
-
say ' ' +
|
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
|
data/lib/duple/cli/helpers.rb
CHANGED
@@ -69,8 +69,16 @@ module Duple
|
|
69
69
|
end
|
70
70
|
|
71
71
|
module InstanceMethods
|
72
|
-
def
|
73
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
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
|
-
|
132
|
-
|
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
|
data/lib/duple/cli/refresh.rb
CHANGED
@@ -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
|
45
|
-
|
49
|
+
def run_prerefresh_tasks
|
50
|
+
run_tasks(config.pre_refresh_tasks)
|
51
|
+
end
|
46
52
|
|
47
|
-
|
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 =
|
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 =
|
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
|
data/lib/duple/configuration.rb
CHANGED
@@ -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
|
data/lib/duple/runner.rb
CHANGED
data/lib/duple/source.rb
ADDED
data/lib/duple/target.rb
ADDED
data/lib/duple/version.rb
CHANGED
data/spec/config/groups.yml
CHANGED
data/spec/config/kitchensink.yml
CHANGED
@@ -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:
|
46
|
-
command: db:migrate
|
42
|
+
command_type: shell
|
43
|
+
command: rake db:migrate
|
47
44
|
scrub_data:
|
48
45
|
- subject: target
|
49
|
-
command_type:
|
50
|
-
command: snapshot:scrub
|
46
|
+
command_type: shell
|
47
|
+
command: rake snapshot:scrub
|
51
48
|
test_data:
|
52
49
|
- subject: target
|
53
|
-
command_type:
|
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
|
data/spec/config/simple.yml
CHANGED
data/spec/config/tasks.yml
CHANGED
@@ -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(
|
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:
|
376
|
+
runner.should_receive(:run).once.ordered.with('heroku maintenance:off -a duple-stage')
|
375
377
|
|
376
|
-
invoke_refresh(
|
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:
|
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)
|
data/spec/duple/runner_spec.rb
CHANGED
@@ -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)
|
data/templates/config/duple.yml
CHANGED
@@ -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
|
-
|
25
|
-
- subject:
|
26
|
-
command_type:
|
27
|
-
command:
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
34
|
-
- subject:
|
35
|
-
command_type:
|
36
|
-
command:
|
37
|
-
|
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:
|
42
|
-
command:
|
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 #
|
49
|
+
all: # Defines a group called "all"
|
50
|
+
include_all: true # Include all tables
|
59
51
|
|
60
|
-
|
61
|
-
|
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: #
|
57
|
+
no_comments: # Defines a group called "no_comments"
|
58
|
+
exclude_tables: # Include all but the listed tables
|
70
59
|
- comments
|
71
|
-
|
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.
|
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-
|
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
|