dbcp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +66 -0
- data/Rakefile +7 -0
- data/bin/dbcp +8 -0
- data/dbcp.gemspec +29 -0
- data/lib/dbcp/cli.rb +53 -0
- data/lib/dbcp/database.rb +32 -0
- data/lib/dbcp/database_snapshot_file.rb +40 -0
- data/lib/dbcp/databases/mysql_database.rb +21 -0
- data/lib/dbcp/databases/postgres_database.rb +11 -0
- data/lib/dbcp/environment.rb +39 -0
- data/lib/dbcp/environment_providers/database_yaml_environment_provider.rb +37 -0
- data/lib/dbcp/execution_host.rb +21 -0
- data/lib/dbcp/execution_hosts/local_execution_host.rb +17 -0
- data/lib/dbcp/execution_hosts/ssh_execution_host.rb +80 -0
- data/lib/dbcp/version.rb +3 -0
- data/lib/dbcp.rb +18 -0
- data/spec/fixtures/config/database.yml +16 -0
- data/spec/lib/dbcp/cli_spec.rb +49 -0
- data/spec/lib/dbcp/database_snapshot_file_spec.rb +73 -0
- data/spec/lib/dbcp/database_spec.rb +14 -0
- data/spec/lib/dbcp/databases/mysql_database_spec.rb +22 -0
- data/spec/lib/dbcp/databases/postgres_database_spec.rb +22 -0
- data/spec/lib/dbcp/environment_providers/database_yaml_environment_provider_spec.rb +42 -0
- data/spec/lib/dbcp/environment_spec.rb +45 -0
- data/spec/lib/dbcp/execution_host_spec.rb +14 -0
- data/spec/lib/dbcp/execution_hosts/local_execution_host_spec.rb +8 -0
- data/spec/lib/dbcp/execution_hosts/ssh_execution_host_spec.rb +45 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/.keep +0 -0
- metadata +203 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dae551f41d7c976a088cafc8ebe340418b9e26ec
|
4
|
+
data.tar.gz: 1210fe47e8461e62afac77a58c238c346020e313
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 88e7b3c91a04ecf258f16cf848aff23a3925653b175cd35ccc0ff1fca586a6ed2b50a1325c463249c3468c0d1f2dc48a0d17638ce67e85742272cbd17dba82cd
|
7
|
+
data.tar.gz: d6adbc1c6773aaf9a6a1748e94c3683ba4f91772022f66172bba177df6d8135959a5406c9f420d7e595dfd79216fe145f95f11129cea6d610f0cb459edd28055
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Gabe Martin-Dempesy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# dbcp
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/gabetax/dbcp.svg?branch=master)](https://travis-ci.org/gabetax/dbcp)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/gabetax/dbcp.png)](https://codeclimate.com/github/gabetax/dbcp)
|
5
|
+
|
6
|
+
Copy Postgres or MySQL databases between application environments.
|
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.
|
9
|
+
|
10
|
+
## A word of caution
|
11
|
+
|
12
|
+
Depending on your application, your production database may contain sensitive personal information like financial or health data. Give careful consideration to the risks of using production data on staging and development environments, and whether it's acceptable to use a tool like this in your application's workflow.
|
13
|
+
|
14
|
+
Treat your production database like a loaded gun. Consider employing some of these safe guards:
|
15
|
+
|
16
|
+
- Use a separate account that only has read access.
|
17
|
+
- Access a replication "follower" instead of the master.
|
18
|
+
- Access a completely separate database that is periodically populated from production, but is updated to use fake values for sensitive information.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
`dbcp` is a stand-alone utility. To install, just run:
|
23
|
+
|
24
|
+
$ gem install dbcp
|
25
|
+
|
26
|
+
You should __not__ need to include dbcp in your `Gemfile`.
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
To copy the production database to the development environment, simply run:
|
31
|
+
|
32
|
+
$ dbcp production development
|
33
|
+
|
34
|
+
Environment credentials can be defined in the following providers:
|
35
|
+
|
36
|
+
### config/database.yml
|
37
|
+
|
38
|
+
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
|
+
|
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.
|
41
|
+
|
42
|
+
Example config/database.yml:
|
43
|
+
|
44
|
+
```yaml
|
45
|
+
staging:
|
46
|
+
adapter: postgresql
|
47
|
+
database: staging_database
|
48
|
+
username: staging_username
|
49
|
+
password: staging_password
|
50
|
+
ssh_uri: ssh://deploy@staging.example.com/www/staging.example.com/current
|
51
|
+
```
|
52
|
+
|
53
|
+
## Roadmap
|
54
|
+
|
55
|
+
The following features are pending:
|
56
|
+
|
57
|
+
Providers:
|
58
|
+
|
59
|
+
- URL passed in as environment
|
60
|
+
- capistrano task
|
61
|
+
- heroku, inferred from git remotes
|
62
|
+
|
63
|
+
Features:
|
64
|
+
|
65
|
+
- Reading configuration from a remote config/database.yml
|
66
|
+
- Definable per-tool specific options, e.g. to allow pg_dump to provide a table exclusion list
|
data/Rakefile
ADDED
data/bin/dbcp
ADDED
data/dbcp.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dbcp/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dbcp"
|
8
|
+
spec.version = Dbcp::VERSION
|
9
|
+
spec.authors = ["Gabe Martin-Dempesy"]
|
10
|
+
spec.email = ["gabetax@gmail.com"]
|
11
|
+
spec.description = %q{Copy SQL databases between application environments}
|
12
|
+
spec.summary = %q{}
|
13
|
+
spec.homepage = "https://github.com/gabetax/dbcp"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "virtus", "~> 1.0.2"
|
22
|
+
spec.add_dependency "net-ssh", "~> 2.8.0"
|
23
|
+
spec.add_dependency "net-sftp", "~> 2.1.2"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "pry"
|
29
|
+
end
|
data/lib/dbcp/cli.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class Cli
|
3
|
+
DEFAULT_DESTINATION = 'development'
|
4
|
+
|
5
|
+
def initialize(stdout = $stdout)
|
6
|
+
@logger = Logger.new stdout
|
7
|
+
@logger.formatter = Proc.new do |severity, datetime, progname, msg|
|
8
|
+
"#{datetime}: #{msg}\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def start(argv)
|
13
|
+
if argv.length < 1
|
14
|
+
usage
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
source = Environment.find(argv.shift)
|
20
|
+
destination = Environment.find(argv.shift || DEFAULT_DESTINATION)
|
21
|
+
|
22
|
+
if source == destination
|
23
|
+
@logger.fatal "source and destination environments are the same"
|
24
|
+
exit 3
|
25
|
+
end
|
26
|
+
|
27
|
+
if source.database.adapter != destination.database.adapter
|
28
|
+
@logger.fatal "source (#{source.database.adapter}) and destination (#{destination.database.adapter}) environments must be the same database type"
|
29
|
+
exit 4
|
30
|
+
end
|
31
|
+
rescue EnvironmentNotFound => e
|
32
|
+
@logger.fatal e.to_s
|
33
|
+
exit 2
|
34
|
+
end
|
35
|
+
|
36
|
+
@logger.info "exporting #{source.environment_name}..."
|
37
|
+
source_snapshot_file = source.export
|
38
|
+
|
39
|
+
@logger.info "transferring data..."
|
40
|
+
destination_snapshot_file = source_snapshot_file.transfer_to(destination)
|
41
|
+
|
42
|
+
@logger.info "importing #{destination_snapshot_file.path} to #{destination.environment_name}..."
|
43
|
+
destination.import destination_snapshot_file
|
44
|
+
|
45
|
+
source_snapshot_file.delete
|
46
|
+
destination_snapshot_file.delete if source_snapshot_file != destination_snapshot_file
|
47
|
+
end
|
48
|
+
|
49
|
+
def usage
|
50
|
+
@logger.fatal "Usage: #{$0} source_environment [destination_environment || #{DEFAULT_DESTINATION}]"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class Database
|
3
|
+
class UnsupportedDatabaseAdapter < StandardError; end
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def build(args)
|
7
|
+
klass_for_adapter(args['adapter']).new args
|
8
|
+
end
|
9
|
+
|
10
|
+
def klass_for_adapter(adapter)
|
11
|
+
klass = case adapter
|
12
|
+
when /mysql/
|
13
|
+
MysqlDatabase
|
14
|
+
when /postgres/
|
15
|
+
PostgresDatabase
|
16
|
+
else
|
17
|
+
raise UnsupportedDatabaseAdapter.new("Unsupported database adapter: #{adapter}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
include Virtus.value_object
|
23
|
+
values do
|
24
|
+
attribute :adapter
|
25
|
+
attribute :database
|
26
|
+
attribute :host, String, default: 'localhost'
|
27
|
+
attribute :socket
|
28
|
+
attribute :username
|
29
|
+
attribute :password
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class DatabaseSnapshotFile
|
3
|
+
attr_reader :path
|
4
|
+
attr_reader :environment
|
5
|
+
|
6
|
+
def initialize(environment)
|
7
|
+
@environment = environment
|
8
|
+
@path = "/tmp/dbcp_#{Time.now.to_f}"
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [DatabaseSnapshotFile]
|
12
|
+
def transfer_to(destination_environment)
|
13
|
+
source_host = environment.execution_host
|
14
|
+
destination_host = destination_environment.execution_host
|
15
|
+
|
16
|
+
return self if source_host.local? && destination_host.local?
|
17
|
+
return self if source_host == destination_host
|
18
|
+
|
19
|
+
destination_snapshot_file = DatabaseSnapshotFile.new(destination_environment)
|
20
|
+
|
21
|
+
if source_host.local? && destination_host.remote?
|
22
|
+
destination_host.upload path, destination_snapshot_file.path
|
23
|
+
|
24
|
+
elsif source_host.remote? && destination_host.local?
|
25
|
+
source_host.download path, destination_snapshot_file.path
|
26
|
+
|
27
|
+
else
|
28
|
+
# both remote
|
29
|
+
source_host.download path, path
|
30
|
+
destination_host.upload path, destination_snapshot_file.path
|
31
|
+
end
|
32
|
+
|
33
|
+
destination_snapshot_file
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
environment.execution_host.execute %W(rm #{path}).shelljoin
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class MysqlDatabase < Database
|
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
|
5
|
+
end
|
6
|
+
|
7
|
+
def import_command(snapshot_file)
|
8
|
+
%W[mysql #{socket_or_host} --user=#{username} --password=#{password} #{database}].shelljoin + ' < ' + snapshot_file.path.shellescape
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def socket_or_host
|
14
|
+
if socket
|
15
|
+
"--socket=#{socket}"
|
16
|
+
else
|
17
|
+
"--host=#{host}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class PostgresDatabase < Database
|
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
|
5
|
+
end
|
6
|
+
|
7
|
+
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
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class EnvironmentNotFound < StandardError; end
|
3
|
+
class ExecutionError < StandardError; end
|
4
|
+
|
5
|
+
class Environment
|
6
|
+
ENVIRONMENT_PROVIDERS = [
|
7
|
+
DatabaseYamlEnvironmentProvider.new('config/database.yml')
|
8
|
+
]
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def find(environment_name)
|
12
|
+
ENVIRONMENT_PROVIDERS.each do |provider|
|
13
|
+
environment = provider.find environment_name
|
14
|
+
return environment if environment
|
15
|
+
end
|
16
|
+
|
17
|
+
raise EnvironmentNotFound.new "Could not locate '#{environment_name}' environment"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# coersion causes issues when assigning rspec doubles
|
22
|
+
include Virtus.value_object(coerce: false)
|
23
|
+
values do
|
24
|
+
attribute :environment_name, String
|
25
|
+
attribute :database, Database
|
26
|
+
attribute :execution_host, ExecutionHost
|
27
|
+
end
|
28
|
+
|
29
|
+
def export
|
30
|
+
DatabaseSnapshotFile.new(self).tap do |snapshot_file|
|
31
|
+
execution_host.execute database.export_command(snapshot_file)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def import(snapshot_file)
|
36
|
+
execution_host.execute database.import_command(snapshot_file)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Dbcp
|
4
|
+
class DatabaseYamlEnvironmentProvider
|
5
|
+
def initialize(database_yaml_path)
|
6
|
+
@database_yaml_path = database_yaml_path
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [Environment, nil]
|
10
|
+
def find(environment_name)
|
11
|
+
begin
|
12
|
+
environment_hash = read_file[environment_name]
|
13
|
+
if environment_hash
|
14
|
+
build_environment environment_name, environment_hash
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
rescue Errno::ENOENT
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def read_file
|
26
|
+
YAML.load_file @database_yaml_path
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_environment(environment_name, environment_hash)
|
30
|
+
Environment.new({
|
31
|
+
environment_name: environment_name,
|
32
|
+
database: Database.build(environment_hash),
|
33
|
+
execution_host: ExecutionHost.build(environment_hash)
|
34
|
+
})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class ExecutionHost
|
3
|
+
class << self
|
4
|
+
def build(args)
|
5
|
+
if args['ssh_uri']
|
6
|
+
SshExecutionHost.new_from_uri args['ssh_uri']
|
7
|
+
else
|
8
|
+
LocalExecutionHost.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# coersion causes issues when assigning rspec doubles
|
14
|
+
include Virtus.value_object(coerce: false)
|
15
|
+
|
16
|
+
def local?
|
17
|
+
!remote?
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Dbcp
|
2
|
+
class LocalExecutionHost < ExecutionHost
|
3
|
+
|
4
|
+
# Cheap way for == to evaluate to `true` between `LocalExecutionHost` objects
|
5
|
+
values do
|
6
|
+
end
|
7
|
+
|
8
|
+
def remote?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(command)
|
13
|
+
Kernel.system command
|
14
|
+
raise ExecutionError.new "Execution failed with exit code #{$?.exitstatus}. Command was: #{command}" unless $?.success?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/sftp'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Dbcp
|
6
|
+
class SshExecutionHost < ExecutionHost
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def new_from_uri(uri_string)
|
10
|
+
uri = URI.parse uri_string
|
11
|
+
raise URI::InvalidURIError.new "SSH URI must be in form 'ssh://username@example.com/path/to_application_root'. We received: '#{uri_string}'." unless uri.user && uri.host
|
12
|
+
new({
|
13
|
+
host: uri.host,
|
14
|
+
port: uri.port,
|
15
|
+
username: uri.user,
|
16
|
+
path: uri.path
|
17
|
+
})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
values do
|
22
|
+
attribute :host
|
23
|
+
attribute :port, Fixnum
|
24
|
+
attribute :username
|
25
|
+
attribute :path
|
26
|
+
end
|
27
|
+
|
28
|
+
def remote?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(command)
|
33
|
+
# http://stackoverflow.com/questions/3386233/how-to-get-exit-status-with-rubys-netssh-library
|
34
|
+
stdout_data = ""
|
35
|
+
stderr_data = ""
|
36
|
+
exitstatus = nil
|
37
|
+
exit_signal = nil
|
38
|
+
|
39
|
+
Net::SSH.start host, username do |ssh|
|
40
|
+
ssh.open_channel do |channel|
|
41
|
+
channel.exec command do |ch, success|
|
42
|
+
unless success
|
43
|
+
raise ExecutionError.new "Exection over SSH to failed for: (ssh.channel.exec)"
|
44
|
+
end
|
45
|
+
channel.on_data do |ch,data|
|
46
|
+
stdout_data+=data
|
47
|
+
end
|
48
|
+
|
49
|
+
channel.on_extended_data do |ch,type,data|
|
50
|
+
stderr_data+=data
|
51
|
+
end
|
52
|
+
|
53
|
+
channel.on_request("exit-status") do |ch,data|
|
54
|
+
exitstatus = data.read_long
|
55
|
+
end
|
56
|
+
|
57
|
+
channel.on_request("exit-signal") do |ch, data|
|
58
|
+
exit_signal = data.read_long
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
ssh.loop
|
63
|
+
end
|
64
|
+
|
65
|
+
raise ExecutionError.new "Execution failed with exit code #{$?.exitstatus}. Command was: #{command}" if exitstatus > 0
|
66
|
+
end
|
67
|
+
|
68
|
+
def download(source_path, destination_path)
|
69
|
+
Net::SFTP.start host, username do |ssh|
|
70
|
+
return ssh.download! source_path, destination_path
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def upload(source_path, destination_path)
|
75
|
+
Net::SFTP.start host, username do |ssh|
|
76
|
+
return ssh.upload! source_path, destination_path
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/dbcp/version.rb
ADDED
data/lib/dbcp.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'virtus'
|
5
|
+
require 'dbcp/cli'
|
6
|
+
require 'dbcp/database'
|
7
|
+
require 'dbcp/databases/mysql_database'
|
8
|
+
require 'dbcp/databases/postgres_database'
|
9
|
+
require 'dbcp/environment_providers/database_yaml_environment_provider'
|
10
|
+
require 'dbcp/execution_host'
|
11
|
+
require 'dbcp/execution_hosts/local_execution_host'
|
12
|
+
require 'dbcp/execution_hosts/ssh_execution_host'
|
13
|
+
require 'dbcp/environment'
|
14
|
+
require 'dbcp/database_snapshot_file'
|
15
|
+
require 'dbcp/version'
|
16
|
+
|
17
|
+
module Dbcp
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
development:
|
2
|
+
adapter: postgresql
|
3
|
+
encoding: unicode
|
4
|
+
database: dev_database
|
5
|
+
pool: 5
|
6
|
+
username: dev_username
|
7
|
+
password: dev_password
|
8
|
+
|
9
|
+
staging:
|
10
|
+
adapter: postgresql
|
11
|
+
encoding: unicode
|
12
|
+
database: staging_database
|
13
|
+
pool: 5
|
14
|
+
username: staging_username
|
15
|
+
password: staging_password
|
16
|
+
ssh_uri: ssh://deploy@staging.example.com/www/staging.example.com/current
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::Cli do
|
4
|
+
subject { Dbcp::Cli.new silent_stdout }
|
5
|
+
let(:silent_stdout) { '/dev/null' }
|
6
|
+
|
7
|
+
before { Dir.chdir lib = File.expand_path('../../../fixtures', __FILE__) }
|
8
|
+
|
9
|
+
describe "#start" do
|
10
|
+
context "success" do
|
11
|
+
let(:source) { double 'Dbcp::Environment', database: double(adapter: 'postgres'), environment_name: 'staging' }
|
12
|
+
let(:destination) { double 'Dbcp::Environment', database: double(adapter: 'postgres'), environment_name: 'development' }
|
13
|
+
let(:source_snapshot_file) { double 'Dbcp::DatabaseSnapshotFile.new', path: '/tmp/foo', transfer_to: destination_snapshot_file }
|
14
|
+
let(:destination_snapshot_file) { double 'Dbcp::DatabaseSnapshotFile.new', path: '/tmp/bar' }
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(Dbcp::Environment).to receive(:find).with('staging') { source }
|
18
|
+
allow(Dbcp::Environment).to receive(:find).with('development') { destination }
|
19
|
+
end
|
20
|
+
|
21
|
+
it "clones the database" do
|
22
|
+
expect(source).to receive(:export) { source_snapshot_file }
|
23
|
+
expect(destination).to receive(:import).with(destination_snapshot_file)
|
24
|
+
expect(source_snapshot_file).to receive(:delete)
|
25
|
+
expect(destination_snapshot_file).to receive(:delete)
|
26
|
+
|
27
|
+
subject.start ['staging']
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "too few arguments" do
|
32
|
+
it "exits" do
|
33
|
+
expect { subject.start [] }.to raise_error(SystemExit)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "two different database types" do
|
38
|
+
it "exist" do
|
39
|
+
expect { subject.start ['development', 'sqlite'] }.to raise_error(SystemExit)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "environments are the same" do
|
44
|
+
it "exits" do
|
45
|
+
expect { subject.start ['development', 'development'] }.to raise_error(SystemExit)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::DatabaseSnapshotFile do
|
4
|
+
subject { Dbcp::DatabaseSnapshotFile.new environment }
|
5
|
+
let(:environment) { double 'Dbcp::Environment', execution_host: execution_host }
|
6
|
+
let(:execution_host) { double 'Dbcp::ExecutionError' }
|
7
|
+
|
8
|
+
describe "#path" do
|
9
|
+
it "defines a temporary path" do
|
10
|
+
expect(subject.path).to match '/tmp/'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#transfer_to" do
|
15
|
+
let(:environment) { double 'Dbcp::Environment', execution_host: source_host }
|
16
|
+
let(:destination) { double 'Dbcp::Environment', execution_host: dest_host }
|
17
|
+
|
18
|
+
context "both local" do
|
19
|
+
let(:source_host) { Dbcp::LocalExecutionHost.new }
|
20
|
+
let(:dest_host) { Dbcp::LocalExecutionHost.new }
|
21
|
+
it "does not transfer" do
|
22
|
+
expect(subject.transfer_to destination).to eq subject
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "source local, destination remote" do
|
27
|
+
let(:source_host) { Dbcp::LocalExecutionHost.new }
|
28
|
+
let(:dest_host) { Dbcp::SshExecutionHost.new }
|
29
|
+
it "uploads to dest host" do
|
30
|
+
expect(dest_host).to receive(:upload)
|
31
|
+
subject.transfer_to destination
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "source remote, destination local" do
|
36
|
+
let(:source_host) { Dbcp::SshExecutionHost.new }
|
37
|
+
let(:dest_host) { Dbcp::LocalExecutionHost.new }
|
38
|
+
it "downloads from source host" do
|
39
|
+
expect(source_host).to receive(:download)
|
40
|
+
subject.transfer_to destination
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "both remote, SAME" do
|
45
|
+
let(:source_host) { Dbcp::SshExecutionHost.new_from_uri 'ssh://staging_user@staging.example.com:2222/www/staging/current' }
|
46
|
+
let(:dest_host) { Dbcp::SshExecutionHost.new_from_uri 'ssh://staging_user@staging.example.com:2222/www/staging/current' }
|
47
|
+
it "does not transfer" do
|
48
|
+
expect(subject.transfer_to destination).to eq subject
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "both remote, DIFFERENT" do
|
53
|
+
let(:source_host) { Dbcp::SshExecutionHost.new host: 'prd.example.com' }
|
54
|
+
let(:dest_host) { Dbcp::SshExecutionHost.new host: 'stg.example.com' }
|
55
|
+
it "downloads from source host" do
|
56
|
+
expect(source_host).to receive(:download)
|
57
|
+
expect(dest_host).to receive(:upload)
|
58
|
+
subject.transfer_to destination
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#delete" do
|
64
|
+
it "has the host execute rm" do
|
65
|
+
expect(execution_host).to receive(:execute) do |command|
|
66
|
+
expect(command).to match 'rm'
|
67
|
+
end
|
68
|
+
|
69
|
+
subject.delete
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::Database do
|
4
|
+
describe ".build" do
|
5
|
+
context "valid type" do
|
6
|
+
specify { expect(Dbcp::Database.build 'adapter' => 'postgresql').to be_a(Dbcp::PostgresDatabase) }
|
7
|
+
end
|
8
|
+
|
9
|
+
context "invalid type" do
|
10
|
+
specify { expect { Dbcp::Database.build 'adapter' => 'invalid'}.to raise_error(Dbcp::Database::UnsupportedDatabaseAdapter) }
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::MysqlDatabase do
|
4
|
+
subject do
|
5
|
+
Dbcp::MysqlDatabase.new({
|
6
|
+
host: 'local',
|
7
|
+
database: 'my_database',
|
8
|
+
username: 'my_user',
|
9
|
+
password: 'my_password',
|
10
|
+
})
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:snapshot_file) { Dbcp::DatabaseSnapshotFile.new double }
|
14
|
+
|
15
|
+
describe "#export_command" do
|
16
|
+
specify { expect(subject.export_command snapshot_file).to match 'mysqldump' }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#import_command" do
|
20
|
+
specify { expect(subject.import_command snapshot_file).to match 'mysql' }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::PostgresDatabase do
|
4
|
+
subject do
|
5
|
+
Dbcp::PostgresDatabase.new({
|
6
|
+
host: 'local',
|
7
|
+
database: 'my_database',
|
8
|
+
username: 'my_user',
|
9
|
+
password: 'my_password',
|
10
|
+
})
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:snapshot_file) { Dbcp::DatabaseSnapshotFile.new double }
|
14
|
+
|
15
|
+
describe "#export_command" do
|
16
|
+
specify { expect(subject.export_command snapshot_file).to match 'pg_dump' }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#import_command" do
|
20
|
+
specify { expect(subject.import_command snapshot_file).to match 'pg_restore' }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::DatabaseYamlEnvironmentProvider do
|
4
|
+
subject { Dbcp::DatabaseYamlEnvironmentProvider.new path }
|
5
|
+
let(:path) { File.expand_path('../../../../fixtures/config/database.yml', __FILE__) }
|
6
|
+
|
7
|
+
describe "#find" do
|
8
|
+
context "when environment exists" do
|
9
|
+
it "returns an environment" do
|
10
|
+
environment = subject.find 'development'
|
11
|
+
expect(environment).to be_a Dbcp::Environment
|
12
|
+
expect(environment.database).to be_a Dbcp::Database
|
13
|
+
expect(environment.environment_name).to eq 'development'
|
14
|
+
expect(environment.database.adapter).to eq 'postgresql'
|
15
|
+
expect(environment.database.database).to eq 'dev_database'
|
16
|
+
expect(environment.database.username).to eq 'dev_username'
|
17
|
+
expect(environment.database.password).to eq 'dev_password'
|
18
|
+
end
|
19
|
+
|
20
|
+
context "without ssh_uri" do
|
21
|
+
it "executes on localhost" do
|
22
|
+
environment = subject.find 'development'
|
23
|
+
expect(environment.execution_host).to be_a Dbcp::LocalExecutionHost
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with ssh_uri" do
|
28
|
+
it "executes on remote host" do
|
29
|
+
environment = subject.find 'staging'
|
30
|
+
expect(environment.execution_host).to be_a Dbcp::SshExecutionHost
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context "when environment doesn't exist" do
|
35
|
+
specify { expect(subject.find 'does-not-exist').to be_nil }
|
36
|
+
end
|
37
|
+
context "when file doesn't exist" do
|
38
|
+
let(:path) { File.expand_path('../../../../fixtures/config/database-does-not-exist.yml', __FILE__) }
|
39
|
+
specify { expect(subject.find 'development').to be_nil }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::Environment do
|
4
|
+
|
5
|
+
describe ".find" do
|
6
|
+
context "when not found" do
|
7
|
+
specify { expect { Dbcp::Environment.find 'does-not-exist' }.to raise_error(Dbcp::EnvironmentNotFound) }
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when found" do
|
11
|
+
let(:environment) { double }
|
12
|
+
before { allow_any_instance_of(Dbcp::DatabaseYamlEnvironmentProvider).to receive(:find).and_return(environment) }
|
13
|
+
specify { expect(Dbcp::Environment.find 'development').to eq environment }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
describe "import/export" do
|
19
|
+
subject { Dbcp::Environment.new database: database, execution_host: execution_host }
|
20
|
+
let(:database) { double 'Dbcp::Database', export_command: double, import_command: double }
|
21
|
+
let(:execution_host) { double 'Dbcp::ExecutionHost', execute: nil }
|
22
|
+
# before { allow(Kernel).to receive(:system) }
|
23
|
+
|
24
|
+
describe "#export" do
|
25
|
+
it "executes the database's export command" do
|
26
|
+
subject.export
|
27
|
+
expect(subject.execution_host).to have_received(:execute).with(database.export_command)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns the snapshot file" do
|
31
|
+
expect(subject.export).to be_a Dbcp::DatabaseSnapshotFile
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#import" do
|
36
|
+
let(:snapshot_file) { double }
|
37
|
+
it "executes the database's import command" do
|
38
|
+
subject.import snapshot_file
|
39
|
+
expect(subject.execution_host).to have_received(:execute).with(database.import_command)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::ExecutionHost do
|
4
|
+
describe ".build" do
|
5
|
+
context "when ssh information is present" do
|
6
|
+
let(:environment_hash) { { 'ssh_uri' => 'ssh://staging_user@staging.example.com/www/staging/current' } }
|
7
|
+
specify { expect(Dbcp::ExecutionHost.build(environment_hash)).to be_a Dbcp::SshExecutionHost }
|
8
|
+
end
|
9
|
+
|
10
|
+
context "without ssh information" do
|
11
|
+
specify { expect(Dbcp::ExecutionHost.build({})).to be_a Dbcp::LocalExecutionHost }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dbcp::SshExecutionHost do
|
4
|
+
subject { Dbcp::SshExecutionHost.new_from_uri 'ssh://staging_user@staging.example.com:2222/www/staging/current' }
|
5
|
+
describe ".new_from_uri" do
|
6
|
+
context "with valid uri" do
|
7
|
+
specify do
|
8
|
+
host = Dbcp::SshExecutionHost.new_from_uri 'ssh://staging_user@staging.example.com:2222/www/staging/current'
|
9
|
+
expect(host).to be_a Dbcp::SshExecutionHost
|
10
|
+
expect(host.host).to eq 'staging.example.com'
|
11
|
+
expect(host.port).to eq 2222
|
12
|
+
expect(host.username).to eq 'staging_user'
|
13
|
+
expect(host.path).to eq '/www/staging/current'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with invalid uri" do
|
18
|
+
specify do
|
19
|
+
expect { Dbcp::SshExecutionHost.new_from_uri 'staging.example.com' }.to raise_error URI::InvalidURIError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#execute" do
|
25
|
+
# Not sure how to either easily unit test, or securely/portably integration test. Suggested appreciated.
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#download" do
|
29
|
+
let(:path) { '/tmp/foo' }
|
30
|
+
it "uses ssh" do
|
31
|
+
expect(Net::SFTP).to receive(:start).with('staging.example.com', 'staging_user') { true }
|
32
|
+
subject.download path, path
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#upload" do
|
37
|
+
let(:path) { '/tmp/foo' }
|
38
|
+
it "uses SFTP" do
|
39
|
+
expect(Net::SFTP).to receive(:start).with('staging.example.com', 'staging_user') { true }
|
40
|
+
subject.upload path, path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/support/.keep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dbcp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabe Martin-Dempesy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: virtus
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-ssh
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.8.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.8.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: net-sftp
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.1.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.1.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: codeclimate-test-reporter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Copy SQL databases between application environments
|
126
|
+
email:
|
127
|
+
- gabetax@gmail.com
|
128
|
+
executables:
|
129
|
+
- dbcp
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .travis.yml
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE.txt
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- bin/dbcp
|
140
|
+
- dbcp.gemspec
|
141
|
+
- lib/dbcp.rb
|
142
|
+
- lib/dbcp/cli.rb
|
143
|
+
- lib/dbcp/database.rb
|
144
|
+
- lib/dbcp/database_snapshot_file.rb
|
145
|
+
- lib/dbcp/databases/mysql_database.rb
|
146
|
+
- lib/dbcp/databases/postgres_database.rb
|
147
|
+
- lib/dbcp/environment.rb
|
148
|
+
- lib/dbcp/environment_providers/database_yaml_environment_provider.rb
|
149
|
+
- lib/dbcp/execution_host.rb
|
150
|
+
- lib/dbcp/execution_hosts/local_execution_host.rb
|
151
|
+
- lib/dbcp/execution_hosts/ssh_execution_host.rb
|
152
|
+
- lib/dbcp/version.rb
|
153
|
+
- spec/fixtures/config/database.yml
|
154
|
+
- spec/lib/dbcp/cli_spec.rb
|
155
|
+
- spec/lib/dbcp/database_snapshot_file_spec.rb
|
156
|
+
- spec/lib/dbcp/database_spec.rb
|
157
|
+
- spec/lib/dbcp/databases/mysql_database_spec.rb
|
158
|
+
- spec/lib/dbcp/databases/postgres_database_spec.rb
|
159
|
+
- spec/lib/dbcp/environment_providers/database_yaml_environment_provider_spec.rb
|
160
|
+
- spec/lib/dbcp/environment_spec.rb
|
161
|
+
- spec/lib/dbcp/execution_host_spec.rb
|
162
|
+
- spec/lib/dbcp/execution_hosts/local_execution_host_spec.rb
|
163
|
+
- spec/lib/dbcp/execution_hosts/ssh_execution_host_spec.rb
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- spec/support/.keep
|
166
|
+
homepage: https://github.com/gabetax/dbcp
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata: {}
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - '>='
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - '>='
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 2.0.6
|
187
|
+
signing_key:
|
188
|
+
specification_version: 4
|
189
|
+
summary: ''
|
190
|
+
test_files:
|
191
|
+
- spec/fixtures/config/database.yml
|
192
|
+
- spec/lib/dbcp/cli_spec.rb
|
193
|
+
- spec/lib/dbcp/database_snapshot_file_spec.rb
|
194
|
+
- spec/lib/dbcp/database_spec.rb
|
195
|
+
- spec/lib/dbcp/databases/mysql_database_spec.rb
|
196
|
+
- spec/lib/dbcp/databases/postgres_database_spec.rb
|
197
|
+
- spec/lib/dbcp/environment_providers/database_yaml_environment_provider_spec.rb
|
198
|
+
- spec/lib/dbcp/environment_spec.rb
|
199
|
+
- spec/lib/dbcp/execution_host_spec.rb
|
200
|
+
- spec/lib/dbcp/execution_hosts/local_execution_host_spec.rb
|
201
|
+
- spec/lib/dbcp/execution_hosts/ssh_execution_host_spec.rb
|
202
|
+
- spec/spec_helper.rb
|
203
|
+
- spec/support/.keep
|