duple 0.0.1 → 0.0.2

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/CHANGES.md ADDED
@@ -0,0 +1,13 @@
1
+ ## 0.0.2 (October 21, 2012) - Fix local db config
2
+
3
+ * Load db connection details from the config file.
4
+ * Added some minimal class-level documentation.
5
+
6
+ ## 0.0.1 (October 19, 2012) - Initial release
7
+
8
+ * Implemented refresh from local to heroku and from heroku to local
9
+ * Implemented heroku to heroku refresh
10
+ * Implemented structure command
11
+ * Implemented config tasks
12
+ * Implemented copy command
13
+ * Added dry-run flag
data/README.md CHANGED
@@ -26,31 +26,31 @@ figure the application. Read and modify it, or clear it out and write your own.
26
26
 
27
27
  # Resets the stage database
28
28
  # Loads the latest production snapshot into the stage database
29
- $ duple refresh production stage
29
+ $ duple refresh -s production -t stage
30
30
 
31
31
  # Downloads the latest full snapshot from production
32
32
  # Resets the development database
33
33
  # Loads the snapshot into the development database
34
- $ duple refresh production development
34
+ $ duple refresh -s production -t development
35
35
 
36
36
  # Captures a new production database snapshot
37
37
  # Downloads the latest full snapshot from production
38
38
  # Resets the development database
39
39
  # Loads the snapshot into the development database
40
- $ duple refresh production development --capture
40
+ $ duple refresh -s production -t development --capture
41
41
 
42
42
  # Downloads the schema and a subset of data from stage
43
43
  # Resets the backstage database
44
44
  # Loads the structure and the subset into the backstage database
45
- $ duple refresh stage backstage --group minimal
45
+ $ duple refresh -s stage -t backstage --group minimal
46
46
 
47
47
  # Downloads the data from the specified tables from the stage database
48
48
  # Loads the data into the backstage database
49
- $ duple copy stage backstage --tables products categories
49
+ $ duple copy -s stage -t backstage --tables products categories
50
50
 
51
51
  ## Future
52
52
 
53
- * Everything.
53
+ * Pre- and post- refresh hooks
54
54
  * Support for skipping pre- and post- refresh steps.
55
55
  * Support for running pre- and post- refresh steps by themselves.
56
56
  * Support for other data stores.
@@ -1,5 +1,25 @@
1
1
  module Duple
2
2
  module CLI
3
+
4
+ # Usage:
5
+ # duple config [COMMAND]
6
+ #
7
+ # Options:
8
+ # -c, [--config=CONFIG] # The location of the config file.
9
+ #
10
+ # Manage your configuration.
11
+ #
12
+ # Tasks:
13
+ # duple config all # Prints the current configuration.
14
+ # duple config environments # Prints the environment configurations.
15
+ # duple config groups # Prints the group configurations.
16
+ # duple config help [COMMAND] # Describe subcommands or one specific subcommand
17
+ # duple config other # Prints other options.
18
+ # duple config post-refresh # Prints the post-refresh tasks.
19
+ # duple config pre-refresh # Prints the pre-refresh tasks.
20
+
21
+ # Options:
22
+ # -c, [--config=CONFIG] # The location of the config file.
3
23
  class Config < Thor
4
24
  include Duple::CLI::Helpers
5
25
 
@@ -1,5 +1,19 @@
1
1
  module Duple
2
2
  module CLI
3
+
4
+ # Usage:
5
+ # duple copy
6
+ #
7
+ # Options:
8
+ # -c, [--config=CONFIG] # The location of the config file.
9
+ # -s, [--source=SOURCE] # The name of the source environment.
10
+ # -t, [--target=TARGET] # The name of the target environment.
11
+ # -g, [--group=GROUP] # The group configuration to use when dumping source data.
12
+ # [--capture] # Capture a new source snapshot before refreshing.
13
+ # --dry-run, [--dry-run] # Perform a dry run of the command. No data will be moved.
14
+ # -t, [--tables=one two three] # A list of tables to include when dumping source data.
15
+ #
16
+ # Copies data from a source to a target database.
3
17
  class Copy < Thor::Group
4
18
  include Duple::CLI::Helpers
5
19
 
@@ -18,11 +32,11 @@ module Duple
18
32
  end
19
33
 
20
34
  def dump_data
21
- postgres.pg_dump(dump_flags, data_file_path, source_credentials)
35
+ postgres.pg_dump(dump_flags, data_file_path, source_db_config)
22
36
  end
23
37
 
24
38
  def restore_data
25
- postgres.pg_restore('-e -v --no-acl -O -a', data_file_path, target_credentials)
39
+ postgres.pg_restore('-e -v --no-acl -O -a', data_file_path, target_db_config)
26
40
  end
27
41
  end
28
42
  end
@@ -38,7 +38,7 @@ module Duple
38
38
 
39
39
  def group_option
40
40
  class_option :group,
41
- desc: 'Name of the group configuration to use when dumping source data.',
41
+ desc: 'The group configuration to use when dumping source data.',
42
42
  type: :string,
43
43
  aliases: '-g'
44
44
  end
@@ -119,16 +119,40 @@ module Duple
119
119
  @structure_file_path ||= File.join(dump_dir_path, filename)
120
120
  end
121
121
 
122
- def fetch_heroku_credentials(appname)
122
+ 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
128
+ end
129
+
130
+ 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.
123
141
  config_vars = heroku.capture(appname, "config")
124
142
 
125
- return config.dry_run_credentials(appname) if config.dry_run?
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
126
149
 
150
+ def parse_heroku_config(config_vars)
127
151
  db_url = config_vars.split("\n").detect { |l| l =~ /DATABASE_URL/ }
128
152
  raise ArgumentError.new("Missing DATABASE_URL variable for #{appname}") if db_url.nil?
129
153
 
130
154
  db_url.match(
131
- /postgres:\/\/(?<user>.*):(?<password>.*)@(?<host>.*):(?<port>\d*)\/(?<db>.*)/
155
+ /postgres:\/\/(?<username>.*):(?<password>.*)@(?<host>.*):(?<port>\d*)\/(?<database>.*)/
132
156
  )
133
157
  end
134
158
 
@@ -159,22 +183,6 @@ module Duple
159
183
  flags = [ '-Fc -a', include_flags, exclude_flags ].flatten.compact.join(' ')
160
184
  end
161
185
 
162
- def source_credentials
163
- @source_credentials ||= if config.heroku_source?
164
- fetch_heroku_credentials(source_appname)
165
- else
166
- config.local_credentials
167
- end
168
- end
169
-
170
- def target_credentials
171
- @target_credentials ||= if config.heroku_target?
172
- fetch_heroku_credentials(target_appname)
173
- else
174
- config.local_credentials
175
- end
176
- end
177
-
178
186
  def config
179
187
  @config ||= parse_config
180
188
  end
@@ -1,5 +1,13 @@
1
1
  module Duple
2
2
  module CLI
3
+
4
+ # Usage:
5
+ # duple init
6
+ #
7
+ # Options:
8
+ # -c, [--config=CONFIG] # The location of the config file.
9
+ #
10
+ # Generates a sample configuration file.
3
11
  class Init < Thor::Group
4
12
  include Duple::CLI::Helpers
5
13
 
@@ -1,5 +1,18 @@
1
1
  module Duple
2
2
  module CLI
3
+
4
+ # Usage:
5
+ # duple refresh
6
+ #
7
+ # Options:
8
+ # -c, [--config=CONFIG] # The location of the config file.
9
+ # -s, [--source=SOURCE] # The name of the source environment.
10
+ # -t, [--target=TARGET] # The name of the target environment.
11
+ # -g, [--group=GROUP] # The group configuration to use when dumping source data.
12
+ # [--capture] # Capture a new source snapshot before refreshing.
13
+ # -t, [--tables=one two three] # A list of tables to include when dumping source data.
14
+ #
15
+ # Resets and copies schema and data from a source to a target database
3
16
  class Refresh < Thor::Group
4
17
  include Duple::CLI::Helpers
5
18
 
@@ -54,7 +67,7 @@ module Duple
54
67
  def dump_data
55
68
  return unless dump_data?
56
69
 
57
- postgres.pg_dump(dump_flags, data_file_path, source_credentials)
70
+ postgres.pg_dump(dump_flags, data_file_path, source_db_config)
58
71
  end
59
72
 
60
73
  def reset_target
@@ -63,9 +76,9 @@ module Duple
63
76
 
64
77
  def restore_database
65
78
  if download_snapshot?
66
- postgres.pg_restore('-e -v --no-acl -O -a', @snapshot_path, target_credentials)
79
+ postgres.pg_restore('-e -v --no-acl -O -a', @snapshot_path, target_db_config)
67
80
  elsif dump_data?
68
- postgres.pg_restore('-e -v --no-acl -O -a', data_file_path, target_credentials)
81
+ postgres.pg_restore('-e -v --no-acl -O -a', data_file_path, target_db_config)
69
82
  else
70
83
  heroku.run(target_appname, "pgbackups:restore DATABASE #{@source_snapshot_url}")
71
84
  end
@@ -12,6 +12,8 @@ require 'duple/heroku_runner'
12
12
 
13
13
  module Duple
14
14
  module CLI
15
+
16
+ # Configures the high-level structure of the CLI sub-commands.
15
17
  class Root < Thor
16
18
  include Duple::CLI::Helpers
17
19
 
@@ -1,5 +1,15 @@
1
1
  module Duple
2
2
  module CLI
3
+
4
+ # Usage:
5
+ # duple structure
6
+ #
7
+ # Options:
8
+ # -c, [--config=CONFIG] # The location of the config file.
9
+ # -s, [--source=SOURCE] # The name of the source environment.
10
+ # -t, [--target=TARGET] # The name of the target environment.
11
+ #
12
+ # Copies structure from a source to a target database.
3
13
  class Structure < Thor::Group
4
14
  include Duple::CLI::Helpers
5
15
 
@@ -8,7 +18,7 @@ module Duple
8
18
  target_option
9
19
 
10
20
  def dump_structure
11
- postgres.pg_dump('-Fc --no-acl -O -s', structure_file_path, source_credentials)
21
+ postgres.pg_dump('-Fc --no-acl -O -s', structure_file_path, source_db_config)
12
22
  end
13
23
 
14
24
  def reset_target
@@ -16,7 +26,7 @@ module Duple
16
26
  end
17
27
 
18
28
  def load_structure
19
- postgres.pg_restore('-v --no-acl -O -s', structure_file_path, target_credentials)
29
+ postgres.pg_restore('-v --no-acl -O -s', structure_file_path, target_db_config)
20
30
  end
21
31
  end
22
32
  end
@@ -125,24 +125,38 @@ module Duple
125
125
  tables
126
126
  end
127
127
 
128
- def dry_run_credentials(envname)
128
+ def db_config(appname, options = nil)
129
+ options ||= {}
130
+ options = {dry_run: false}.merge(options)
131
+
132
+ if options[:dry_run]
133
+ db_config_for_dry_run(appname)
134
+ else
135
+ db_config_for_app(appname)
136
+ end
137
+ end
138
+
139
+ def db_config_for_dry_run(appname)
129
140
  {
130
- user: "[#{envname}.USER]",
141
+ username: "[#{envname}.USER]",
131
142
  password: "[#{envname}.PASS]",
132
143
  host: "[#{envname}.HOST]",
133
144
  port: "[#{envname}.PORT]",
134
- db: "[#{envname}.DB]"
145
+ database: "[#{envname}.DB]"
135
146
  }
136
147
  end
137
148
 
138
- def local_credentials
139
- # TODO: Override these defaults from the config
149
+ def db_config_for_app(appname)
150
+ env = environments[appname]
151
+ if env['database'].nil?
152
+ raise ArgumentError.new('Invalid config: "database" is required for a local environment.')
153
+ end
140
154
  {
141
- user: 'postgres',
142
- password: '',
143
- host: 'localhost',
144
- port: '5432',
145
- db: 'duple_development'
155
+ username: env['username'] || 'postgres',
156
+ password: env['password'] || '',
157
+ host: env['host'] || 'localhost',
158
+ port: env['port'] || '5432',
159
+ database: env['database']
146
160
  }
147
161
  end
148
162
 
@@ -1,4 +1,7 @@
1
1
  module Duple
2
+
3
+ # Decorates a Duple::Runner instance with helper methods for executing
4
+ # Heroku commands.
2
5
  class HerokuRunner
3
6
  def initialize(runner)
4
7
  @runner = runner
@@ -1,25 +1,28 @@
1
1
  module Duple
2
+
3
+ # Decorates a Duple::Runner instance with helper methods for executing
4
+ # PostgreSQL commands.
2
5
  class PGRunner
3
6
  def initialize(runner)
4
7
  @runner = runner
5
8
  end
6
9
 
7
- def pg_dump(flags, dump_file, credentials)
8
- pg_command('pg_dump', flags, credentials, nil, "> #{dump_file}")
10
+ def pg_dump(flags, dump_file, db_config)
11
+ pg_command('pg_dump', flags, db_config, nil, "> #{dump_file}")
9
12
  end
10
13
 
11
- def pg_restore(flags, dump_file, credentials)
12
- pg_command('pg_restore', flags, credentials, '-d', "< #{dump_file}")
14
+ def pg_restore(flags, dump_file, db_config)
15
+ pg_command('pg_restore', flags, db_config, '-d', "< #{dump_file}")
13
16
  end
14
17
 
15
- def pg_command(pg_command, flags, credentials, db_flag, tail = nil)
18
+ def pg_command(pg_command, flags, db_config, db_flag, tail = nil)
16
19
  command = []
17
- command << %{PGPASSWORD="#{credentials[:password]}"}
20
+ command << %{PGPASSWORD="#{db_config[:password]}"}
18
21
  command << pg_command
19
22
  command << flags
20
- command << %{-h #{credentials[:host]} -U #{credentials[:user]} -p #{credentials[:port]}}
23
+ command << %{-h #{db_config[:host]} -U #{db_config[:username]} -p #{db_config[:port]}}
21
24
  command << db_flag if db_flag
22
- command << credentials[:db]
25
+ command << db_config[:database]
23
26
  command << tail if tail
24
27
  @runner.run(command.join(' '))
25
28
  end
data/lib/duple/runner.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'fileutils'
2
2
 
3
3
  module Duple
4
+
5
+ # Helper class for executing shell commands.
4
6
  class Runner
5
7
 
6
8
  def initialize(options = nil)
data/lib/duple/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Duple
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -16,7 +16,7 @@ describe Duple::CLI::Copy do
16
16
  }
17
17
 
18
18
  context 'with neither tables nor group option' do
19
- it 'fetches the source credentials' do
19
+ it 'fetches the source db config' do
20
20
  expect {
21
21
  invoke_copy(tables: nil)
22
22
  }.to raise_error(ArgumentError, 'One of --group or --tables options is required.')
@@ -27,7 +27,7 @@ describe Duple::CLI::Copy do
27
27
  let(:source) { 'stage' }
28
28
  let(:target) { 'development' }
29
29
 
30
- it 'fetches the source credentials' do
30
+ it 'fetches the source db config' do
31
31
  runner.should_receive(:capture).with("heroku config -a duple-stage")
32
32
  .and_return(heroku_config_response)
33
33
 
@@ -53,14 +53,14 @@ describe Duple::CLI::Copy do
53
53
  let(:source) { 'production' }
54
54
  let(:target) { 'stage' }
55
55
 
56
- it 'fetches the source credentials' do
56
+ it 'fetches the source db config' do
57
57
  runner.should_receive(:capture).with("heroku config -a duple-production")
58
58
  .and_return(heroku_config_response)
59
59
 
60
60
  invoke_copy
61
61
  end
62
62
 
63
- it 'fetches the target credentials' do
63
+ it 'fetches the target db config' do
64
64
  runner.should_receive(:capture).with("heroku config -a duple-stage")
65
65
  .and_return(heroku_config_response)
66
66
 
@@ -86,7 +86,7 @@ describe Duple::CLI::Copy do
86
86
  let(:source) { 'development' }
87
87
  let(:target) { 'stage' }
88
88
 
89
- it 'fetches the target credentials' do
89
+ it 'fetches the target db config' do
90
90
  runner.should_receive(:capture).with("heroku config -a duple-stage")
91
91
  .and_return(heroku_config_response)
92
92
 
@@ -18,7 +18,7 @@ describe Duple::CLI::Structure do
18
18
  let(:source) { 'stage' }
19
19
  let(:target) { 'development' }
20
20
 
21
- it 'fetches the source credentials' do
21
+ it 'fetches the source db config' do
22
22
  runner.should_receive(:capture).with("heroku config -a duple-stage")
23
23
  .and_return(heroku_config_response)
24
24
 
@@ -57,14 +57,14 @@ describe Duple::CLI::Structure do
57
57
  let(:source) { 'production' }
58
58
  let(:target) { 'stage' }
59
59
 
60
- it 'fetches the source credentials' do
60
+ it 'fetches the source db config' do
61
61
  runner.should_receive(:capture).with("heroku config -a duple-production")
62
62
  .and_return(heroku_config_response)
63
63
 
64
64
  invoke_structure
65
65
  end
66
66
 
67
- it 'fetches the target credentials' do
67
+ it 'fetches the target db config' do
68
68
  runner.should_receive(:capture).with("heroku config -a duple-stage")
69
69
  .and_return(heroku_config_response)
70
70
 
@@ -103,7 +103,7 @@ describe Duple::CLI::Structure do
103
103
  let(:source) { 'development' }
104
104
  let(:target) { 'stage' }
105
105
 
106
- it 'fetches the target credentials' do
106
+ it 'fetches the target db config' do
107
107
  runner.should_receive(:capture).with("heroku config -a duple-stage")
108
108
  .and_return(heroku_config_response)
109
109
 
@@ -2,6 +2,54 @@ require 'spec_helper'
2
2
 
3
3
  describe Duple::Configuration do
4
4
 
5
+
6
+ describe '#db_config' do
7
+ let(:config_hash) { YAML.load(File.read('spec/config/simple.yml'))}
8
+
9
+ it 'supplies default database values for a local environment' do
10
+ config = Duple::Configuration.new(config_hash, {})
11
+ c = config.db_config('development')
12
+ c[:username].should == 'postgres'
13
+ c[:password].should == ''
14
+ c[:host].should == 'localhost'
15
+ c[:port].should == '5432'
16
+ end
17
+
18
+ context 'with all db config parameters' do
19
+ let(:db_config_hash) {
20
+ db_hash = config_hash['environments']['development']
21
+ db_hash['username'] = 'config_username'
22
+ db_hash['password'] = 'config_password'
23
+ db_hash['host'] = 'config_host'
24
+ db_hash['port'] = '6022'
25
+ db_hash['database'] = 'config_database'
26
+ config_hash
27
+ }
28
+ let(:config) { Duple::Configuration.new(db_config_hash, {}) }
29
+ subject(:db_config) { config.db_config('development') }
30
+
31
+ it 'loads the options from the config' do
32
+ db_config[:username].should == 'config_username'
33
+ db_config[:password].should == 'config_password'
34
+ db_config[:host].should == 'config_host'
35
+ db_config[:port].should == '6022'
36
+ db_config[:database].should == 'config_database'
37
+ end
38
+ end
39
+
40
+ it 'fails if the database name is missing' do
41
+ config_hash['environments']['development'].delete('database')
42
+ config = Duple::Configuration.new(config_hash, {})
43
+
44
+ expect {
45
+ config.db_config('development')
46
+ }.to raise_error(
47
+ ArgumentError,
48
+ 'Invalid config: "database" is required for a local environment.'
49
+ )
50
+ end
51
+ end
52
+
5
53
  describe '#excluded_tables' do
6
54
  let(:config_hash) { YAML.load(File.read('spec/config/groups.yml'))}
7
55
 
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.1
4
+ version: 0.0.2
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-19 00:00:00.000000000 Z
12
+ date: 2012-10-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -119,6 +119,7 @@ files:
119
119
  - .gitignore
120
120
  - .rspec
121
121
  - .simplecov
122
+ - CHANGES.md
122
123
  - Gemfile
123
124
  - LICENSE.txt
124
125
  - README.md