dbcp 0.0.1 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dae551f41d7c976a088cafc8ebe340418b9e26ec
4
- data.tar.gz: 1210fe47e8461e62afac77a58c238c346020e313
3
+ metadata.gz: e37c2566e5ac25d166c8fd27df6209e2f83863f1
4
+ data.tar.gz: 0960077bc0488bd979f3b97b751a940b3e6f11c4
5
5
  SHA512:
6
- metadata.gz: 88e7b3c91a04ecf258f16cf848aff23a3925653b175cd35ccc0ff1fca586a6ed2b50a1325c463249c3468c0d1f2dc48a0d17638ce67e85742272cbd17dba82cd
7
- data.tar.gz: d6adbc1c6773aaf9a6a1748e94c3683ba4f91772022f66172bba177df6d8135959a5406c9f420d7e595dfd79216fe145f95f11129cea6d610f0cb459edd28055
6
+ metadata.gz: 0d8b2f092e45700399fbfda1e91cdb0c80205ecf9670b07c5b5996d153ddf9570df48c57278a8427e7ec32350d11148356864f3a0c427d74dc462a45b0747ff0
7
+ data.tar.gz: fdde59cd6bd19e060aefd6a1c5999087ebeab62aa2c08c5f4135fadb320979bffb0e8b514256258fc65767646a990b16ec498bbd686aee5741af50b491d70bec
data/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
 
6
6
  Copy Postgres or MySQL databases between application environments.
7
7
 
8
- Setting an employee up to work on a web application for the first time is time consuming. Tools like [Vagrant](http://www.vagrantup.com) have made it easy to get your environment setup, but you still need to get your relational database setup. In rails you can load your `db/schema.rb` and hope that `db/seeds.rb` is well curated, but seldom has enough to let a developer hit the ground running. Working with your production database while developing is extremely convenient. The [parity](http://12factor.net/dev-prod-parity) helps preview database performance. It also makes investigating data-specific bugs much easier. The goal of `dbcp` is to make copying databases between development, staging, and production environments as easy as copying a file on your local disk.
8
+ Setting an employee up to work on a web application for the first time is time consuming. Tools like [Vagrant](http://www.vagrantup.com) have made it easy to get your environment setup, but you still need to get your relational database setup. In rails you can load your `db/schema.rb` and hope that `db/seeds.rb` is well curated, but seldom has enough to let a developer hit the ground running. Working with your production database while developing is extremely convenient. The [parity](http://12factor.net/dev-prod-parity) helps preview database performance. It also makes investigating data-specific bugs much easier.
9
+
10
+ The goal of `dbcp` is to make copying databases between development, staging, and production environments as easy as copying a file on your local disk. It's an adapter for platform-specific utilities like `pg_dump` and `mysqldump`, simplifies lookup of credentials using storage mechanisms you're already using, and handles transfer of dump files between hosts.
9
11
 
10
12
  ## A word of caution
11
13
 
@@ -37,30 +39,59 @@ Environment credentials can be defined in the following providers:
37
39
 
38
40
  Rails defines credentials for its database environments in a file at [`config/database.yml`](https://github.com/rails/rails/blob/master/guides/code/getting_started/config/database.yml). By default this file is generated with only development and test environments, but any additional environments added will be leveraged by `dbcp`. Although this is a rails convention, `dbcp` parses this file outside of any framework, so it will work even if you're using this convention in another framework.
39
41
 
40
- The database export or import can be executed on a remote host over ssh and then copied between environment hosts if you specify the remote host via an `ssh_uri` entry in the database.yml. This is helpful if the database host only allows connections from specific servers.
42
+ The database export or import can be executed on a remote host over ssh and then copied between environment hosts if you specify the remote host via an `ssh_uri` entry in the database.yml. This is helpful if the database host only allows connections from specific servers. If your `ssh_uri` optionally includes a path to your application root on the remote server, dbcp will load the database credentials from the remote server's config/database.yml.
41
43
 
42
44
  Example config/database.yml:
43
45
 
44
46
  ```yaml
47
+ # Local database
48
+ development:
49
+ adapter: postgresql
50
+ encoding: unicode
51
+ pool: 5
52
+ database: development_database
53
+ username: development_username
54
+ password: development_password
55
+
56
+ # Remote database, credentials provided locally, executed from remote host over ssh
45
57
  staging:
46
58
  adapter: postgresql
47
59
  database: staging_database
48
60
  username: staging_username
49
61
  password: staging_password
50
- ssh_uri: ssh://deploy@staging.example.com/www/staging.example.com/current
62
+ ssh_uri: ssh://deploy@staging.example.com
63
+
64
+ # Remote database, credentials fetched over ssh, executed from remote host over ssh
65
+ production:
66
+ ssh_uri: ssh://deploy@production.example.com/www/production.example.com/current
51
67
  ```
52
68
 
69
+ $ dbcp staging development
70
+
71
+ ### URI
72
+
73
+ You can use a database URI in place of an environment name as follows:
74
+
75
+ $ dbcp postgres://my_username:my_pass@db.example.com/my_database development
76
+
53
77
  ## Roadmap
54
78
 
55
79
  The following features are pending:
56
80
 
57
81
  Providers:
58
82
 
59
- - URL passed in as environment
60
- - capistrano task
61
- - heroku, inferred from git remotes
83
+ - Capistrano task
84
+ - Heroku, environment name inferred from git remotes
62
85
 
63
86
  Features:
64
87
 
65
- - Reading configuration from a remote config/database.yml
66
88
  - Definable per-tool specific options, e.g. to allow pg_dump to provide a table exclusion list
89
+ - URI Provider: specify an remote ssh execution host, perhaps using '@@' as a URI separator?
90
+
91
+ Refactors:
92
+
93
+ - Handle pg_restore warnings
94
+ - Better logging
95
+ - Better help
96
+
97
+ [Open an issue](https://github.com/gabetax/dbcp/issues) if there's something else you'd like to see supported.
data/lib/dbcp/database.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Dbcp
2
2
  class Database
3
+ class BlankDatabaseDefinition < StandardError; end
3
4
  class UnsupportedDatabaseAdapter < StandardError; end
4
5
 
5
6
  class << self
@@ -13,6 +14,8 @@ module Dbcp
13
14
  MysqlDatabase
14
15
  when /postgres/
15
16
  PostgresDatabase
17
+ when nil, ''
18
+ raise BlankDatabaseDefinition.new("No database adapter was provided.")
16
19
  else
17
20
  raise UnsupportedDatabaseAdapter.new("Unsupported database adapter: #{adapter}")
18
21
  end
@@ -24,6 +27,7 @@ module Dbcp
24
27
  attribute :adapter
25
28
  attribute :database
26
29
  attribute :host, String, default: 'localhost'
30
+ attribute :port, Fixnum
27
31
  attribute :socket
28
32
  attribute :username
29
33
  attribute :password
@@ -1,21 +1,25 @@
1
1
  module Dbcp
2
2
  class MysqlDatabase < Database
3
3
  def export_command(snapshot_file)
4
- %W[mysqldump #{socket_or_host} --user=#{username} --password=#{password} --add-drop-table --extended-insert --result-file=#{snapshot_file.path} #{database}].shelljoin
4
+ %W[mysqldump #{build_socket_or_host} #{build_port} --user=#{username} --password=#{password} --add-drop-table --extended-insert --result-file=#{snapshot_file.path} #{database}].reject(&:empty?).shelljoin
5
5
  end
6
6
 
7
7
  def import_command(snapshot_file)
8
- %W[mysql #{socket_or_host} --user=#{username} --password=#{password} #{database}].shelljoin + ' < ' + snapshot_file.path.shellescape
8
+ %W[mysql #{build_socket_or_host} #{build_port} --user=#{username} --password=#{password} #{database}].reject(&:empty?).shelljoin + ' < ' + snapshot_file.path.shellescape
9
9
  end
10
10
 
11
11
  private
12
12
 
13
- def socket_or_host
13
+ def build_socket_or_host
14
14
  if socket
15
15
  "--socket=#{socket}"
16
16
  else
17
17
  "--host=#{host}"
18
18
  end
19
19
  end
20
+
21
+ def build_port
22
+ "--port=#{port}" if host && port
23
+ end
20
24
  end
21
25
  end
@@ -1,11 +1,38 @@
1
1
  module Dbcp
2
2
  class PostgresDatabase < Database
3
3
  def export_command(snapshot_file)
4
- %W[export PGPASSWORD=#{password}].shelljoin + '; ' + %W[pg_dump --host #{host} --username #{username} --file #{snapshot_file.path} --format c #{database}].shelljoin
4
+ build_password + [
5
+ 'pg_dump',
6
+ '--host', host,
7
+ build_port,
8
+ '--username', username,
9
+ '--file', snapshot_file.path,
10
+ '--format', 'c',
11
+ database
12
+ ].flatten.compact.shelljoin
5
13
  end
6
14
 
7
15
  def import_command(snapshot_file)
8
- %W[export PGPASSWORD=#{password}].shelljoin + '; ' + %W[pg_restore --host #{host} --username #{username} --clean --dbname #{database} #{snapshot_file.path}].shelljoin
16
+ build_password + [
17
+ 'pg_restore',
18
+ '--host', host,
19
+ build_port,
20
+ '--username', username,
21
+ '--dbname', database,
22
+ '--clean',
23
+ snapshot_file.path
24
+ ].flatten.compact.shelljoin
9
25
  end
26
+
27
+ private
28
+
29
+ def build_password
30
+ %W[export PGPASSWORD=#{password}].shelljoin + '; '
31
+ end
32
+
33
+ def build_port
34
+ ['--port', port] if host && port
35
+ end
36
+
10
37
  end
11
38
  end
@@ -4,7 +4,8 @@ module Dbcp
4
4
 
5
5
  class Environment
6
6
  ENVIRONMENT_PROVIDERS = [
7
- DatabaseYamlEnvironmentProvider.new('config/database.yml')
7
+ DatabaseYamlEnvironmentProvider.new('config/database.yml'),
8
+ UriEnvironmentProvider.new
8
9
  ]
9
10
 
10
11
  class << self
@@ -27,11 +27,28 @@ module Dbcp
27
27
  end
28
28
 
29
29
  def build_environment(environment_name, environment_hash)
30
+ execution_host = ExecutionHost.build(environment_hash)
31
+
32
+ begin
33
+ database = Database.build(environment_hash)
34
+ rescue Database::BlankDatabaseDefinition => e
35
+ if execution_host.remote?
36
+ database = Database.build fetch_remote_environment_hash(execution_host)[environment_name]
37
+ else
38
+ raise e
39
+ end
40
+ end
41
+
30
42
  Environment.new({
31
43
  environment_name: environment_name,
32
- database: Database.build(environment_hash),
33
- execution_host: ExecutionHost.build(environment_hash)
44
+ database: database,
45
+ execution_host: execution_host
34
46
  })
35
47
  end
48
+
49
+ def fetch_remote_environment_hash(execution_host)
50
+ YAML.load execution_host.download "#{execution_host.path}/#{@database_yaml_path}"
51
+ end
52
+
36
53
  end
37
54
  end
@@ -0,0 +1,29 @@
1
+ require 'uri'
2
+
3
+ module Dbcp
4
+ class UriEnvironmentProvider
5
+ def find(environment_name)
6
+ uri = URI.parse(environment_name)
7
+ return nil unless uri.scheme
8
+
9
+ build_environment environment_name, uri
10
+ end
11
+
12
+ private
13
+
14
+ def build_environment(environment_name, uri)
15
+ Environment.new({
16
+ environment_name: environment_name,
17
+ database: Database.build({
18
+ 'adapter' => uri.scheme,
19
+ 'username' => uri.user,
20
+ 'password' => uri.password,
21
+ 'host' => uri.host,
22
+ 'port' => uri.port,
23
+ 'database' => uri.path[1..-1] # Trim leading '/'
24
+ }),
25
+ execution_host: LocalExecutionHost.new
26
+ })
27
+ end
28
+ end
29
+ end
@@ -65,7 +65,8 @@ module Dbcp
65
65
  raise ExecutionError.new "Execution failed with exit code #{$?.exitstatus}. Command was: #{command}" if exitstatus > 0
66
66
  end
67
67
 
68
- def download(source_path, destination_path)
68
+ # Omitting destination_path will return file contents as a string
69
+ def download(source_path, destination_path = nil)
69
70
  Net::SFTP.start host, username do |ssh|
70
71
  return ssh.download! source_path, destination_path
71
72
  end
data/lib/dbcp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dbcp
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/dbcp.rb CHANGED
@@ -1,11 +1,11 @@
1
- require 'pry'
2
-
3
1
  require 'logger'
2
+ require 'shellwords'
4
3
  require 'virtus'
5
4
  require 'dbcp/cli'
6
5
  require 'dbcp/database'
7
6
  require 'dbcp/databases/mysql_database'
8
7
  require 'dbcp/databases/postgres_database'
8
+ require 'dbcp/environment_providers/uri_environment_provider'
9
9
  require 'dbcp/environment_providers/database_yaml_environment_provider'
10
10
  require 'dbcp/execution_host'
11
11
  require 'dbcp/execution_hosts/local_execution_host'
@@ -3,6 +3,8 @@ development:
3
3
  encoding: unicode
4
4
  database: dev_database
5
5
  pool: 5
6
+ host: db.example.com
7
+ port: 6543
6
8
  username: dev_username
7
9
  password: dev_password
8
10
 
@@ -13,4 +15,7 @@ staging:
13
15
  pool: 5
14
16
  username: staging_username
15
17
  password: staging_password
18
+ ssh_uri: ssh://deploy@staging.example.com
19
+
20
+ staging_ssh_only:
16
21
  ssh_uri: ssh://deploy@staging.example.com/www/staging.example.com/current
@@ -0,0 +1,7 @@
1
+ staging_ssh_only:
2
+ adapter: postgresql
3
+ encoding: unicode
4
+ database: remote_staging_database
5
+ pool: 5
6
+ username: remote_staging_username
7
+ password: remote_staging_password
@@ -4,7 +4,8 @@ describe Dbcp::Cli do
4
4
  subject { Dbcp::Cli.new silent_stdout }
5
5
  let(:silent_stdout) { '/dev/null' }
6
6
 
7
- before { Dir.chdir lib = File.expand_path('../../../fixtures', __FILE__) }
7
+ extend ExecuteInDirectory
8
+ execute_in_directory(File.expand_path('../../../fixtures', __FILE__))
8
9
 
9
10
  describe "#start" do
10
11
  context "success" do
@@ -2,12 +2,16 @@ require 'spec_helper'
2
2
 
3
3
  describe Dbcp::Database do
4
4
  describe ".build" do
5
- context "valid type" do
5
+ context "supported type" do
6
6
  specify { expect(Dbcp::Database.build 'adapter' => 'postgresql').to be_a(Dbcp::PostgresDatabase) }
7
7
  end
8
8
 
9
- context "invalid type" do
10
- specify { expect { Dbcp::Database.build 'adapter' => 'invalid'}.to raise_error(Dbcp::Database::UnsupportedDatabaseAdapter) }
9
+ context "no type" do
10
+ specify { expect { Dbcp::Database.build 'adapter' => nil}.to raise_error(Dbcp::Database::BlankDatabaseDefinition) }
11
+ end
12
+
13
+ context "unsupported type" do
14
+ specify { expect { Dbcp::Database.build 'adapter' => 'unsupported'}.to raise_error(Dbcp::Database::UnsupportedDatabaseAdapter) }
11
15
  end
12
16
 
13
17
  end
@@ -2,7 +2,10 @@ require 'spec_helper'
2
2
 
3
3
  describe Dbcp::DatabaseYamlEnvironmentProvider do
4
4
  subject { Dbcp::DatabaseYamlEnvironmentProvider.new path }
5
- let(:path) { File.expand_path('../../../../fixtures/config/database.yml', __FILE__) }
5
+ let(:path) { 'config/database.yml' }
6
+
7
+ extend ExecuteInDirectory
8
+ execute_in_directory(File.expand_path('../../../../fixtures', __FILE__))
6
9
 
7
10
  describe "#find" do
8
11
  context "when environment exists" do
@@ -12,6 +15,8 @@ describe Dbcp::DatabaseYamlEnvironmentProvider do
12
15
  expect(environment.database).to be_a Dbcp::Database
13
16
  expect(environment.environment_name).to eq 'development'
14
17
  expect(environment.database.adapter).to eq 'postgresql'
18
+ expect(environment.database.host).to eq 'db.example.com'
19
+ expect(environment.database.port).to eq 6543
15
20
  expect(environment.database.database).to eq 'dev_database'
16
21
  expect(environment.database.username).to eq 'dev_username'
17
22
  expect(environment.database.password).to eq 'dev_password'
@@ -30,10 +35,25 @@ describe Dbcp::DatabaseYamlEnvironmentProvider do
30
35
  expect(environment.execution_host).to be_a Dbcp::SshExecutionHost
31
36
  end
32
37
  end
38
+
39
+ context "without database definition, but with ssh_uri" do
40
+ let(:remote_yaml) { File.read remote_yaml_path }
41
+ let(:remote_yaml_path) { File.expand_path('../../../../fixtures/config/remote_database.yml', __FILE__) }
42
+ it "fetches database definition from database.yml on remote host" do
43
+ allow_any_instance_of(Dbcp::SshExecutionHost).to receive(:download).with('/www/staging.example.com/current/config/database.yml') { remote_yaml }
44
+ environment = subject.find 'staging_ssh_only'
45
+ expect(environment.database).to be_a Dbcp::PostgresDatabase
46
+ expect(environment.database.database).to eq 'remote_staging_database'
47
+ expect(environment.database.username).to eq 'remote_staging_username'
48
+ expect(environment.database.password).to eq 'remote_staging_password'
49
+ end
50
+ end
33
51
  end
52
+
34
53
  context "when environment doesn't exist" do
35
54
  specify { expect(subject.find 'does-not-exist').to be_nil }
36
55
  end
56
+
37
57
  context "when file doesn't exist" do
38
58
  let(:path) { File.expand_path('../../../../fixtures/config/database-does-not-exist.yml', __FILE__) }
39
59
  specify { expect(subject.find 'development').to be_nil }
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dbcp::UriEnvironmentProvider do
4
+ describe "#find" do
5
+ context "when a URI" do
6
+ let(:uri) { 'postgres://my_username:my_password@db.example.com:5432/my_database' }
7
+ it "returns an environment" do
8
+ environment = subject.find(uri)
9
+ expect(environment).to be_a Dbcp::Environment
10
+ expect(environment.environment_name).to eq uri
11
+ expect(environment.database).to be_a Dbcp::PostgresDatabase
12
+ expect(environment.database.username).to eq 'my_username'
13
+ expect(environment.database.password).to eq 'my_password'
14
+ expect(environment.database.host).to eq 'db.example.com'
15
+ expect(environment.database.database).to eq 'my_database'
16
+ end
17
+ end
18
+
19
+ context "when not a valid URI" do
20
+ let(:uri) { 'development'}
21
+ specify { expect(subject.find(uri)).to be_nil }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module ExecuteInDirectory
2
+ def execute_in_directory(path)
3
+ around(:each) do |example|
4
+ old = Dir.pwd
5
+ Dir.chdir path
6
+ example.run
7
+ Dir.chdir old
8
+ end
9
+ end
10
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabe Martin-Dempesy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-07 00:00:00.000000000 Z
11
+ date: 2014-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: virtus
@@ -146,23 +146,27 @@ files:
146
146
  - lib/dbcp/databases/postgres_database.rb
147
147
  - lib/dbcp/environment.rb
148
148
  - lib/dbcp/environment_providers/database_yaml_environment_provider.rb
149
+ - lib/dbcp/environment_providers/uri_environment_provider.rb
149
150
  - lib/dbcp/execution_host.rb
150
151
  - lib/dbcp/execution_hosts/local_execution_host.rb
151
152
  - lib/dbcp/execution_hosts/ssh_execution_host.rb
152
153
  - lib/dbcp/version.rb
153
154
  - spec/fixtures/config/database.yml
155
+ - spec/fixtures/config/remote_database.yml
154
156
  - spec/lib/dbcp/cli_spec.rb
155
157
  - spec/lib/dbcp/database_snapshot_file_spec.rb
156
158
  - spec/lib/dbcp/database_spec.rb
157
159
  - spec/lib/dbcp/databases/mysql_database_spec.rb
158
160
  - spec/lib/dbcp/databases/postgres_database_spec.rb
159
161
  - spec/lib/dbcp/environment_providers/database_yaml_environment_provider_spec.rb
162
+ - spec/lib/dbcp/environment_providers/uri_environment_provider_spec.rb
160
163
  - spec/lib/dbcp/environment_spec.rb
161
164
  - spec/lib/dbcp/execution_host_spec.rb
162
165
  - spec/lib/dbcp/execution_hosts/local_execution_host_spec.rb
163
166
  - spec/lib/dbcp/execution_hosts/ssh_execution_host_spec.rb
164
167
  - spec/spec_helper.rb
165
168
  - spec/support/.keep
169
+ - spec/support/execute_in_directory.rb
166
170
  homepage: https://github.com/gabetax/dbcp
167
171
  licenses:
168
172
  - MIT
@@ -189,15 +193,18 @@ specification_version: 4
189
193
  summary: ''
190
194
  test_files:
191
195
  - spec/fixtures/config/database.yml
196
+ - spec/fixtures/config/remote_database.yml
192
197
  - spec/lib/dbcp/cli_spec.rb
193
198
  - spec/lib/dbcp/database_snapshot_file_spec.rb
194
199
  - spec/lib/dbcp/database_spec.rb
195
200
  - spec/lib/dbcp/databases/mysql_database_spec.rb
196
201
  - spec/lib/dbcp/databases/postgres_database_spec.rb
197
202
  - spec/lib/dbcp/environment_providers/database_yaml_environment_provider_spec.rb
203
+ - spec/lib/dbcp/environment_providers/uri_environment_provider_spec.rb
198
204
  - spec/lib/dbcp/environment_spec.rb
199
205
  - spec/lib/dbcp/execution_host_spec.rb
200
206
  - spec/lib/dbcp/execution_hosts/local_execution_host_spec.rb
201
207
  - spec/lib/dbcp/execution_hosts/ssh_execution_host_spec.rb
202
208
  - spec/spec_helper.rb
203
209
  - spec/support/.keep
210
+ - spec/support/execute_in_directory.rb