justonedb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 JustOneDB, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,46 @@
1
+ = JustOneDB Command Line Interface
2
+
3
+ The JustOneDB CLI is used to handle data migrations for JustOneDB Cloud Services accessed via Heroku
4
+
5
+ For more information, see <http://www.justonedb.com>.
6
+
7
+ For platform specific details, go to the following
8
+
9
+ * Heroku, see <http://www.justonedb.com/heroku>.
10
+
11
+ == Pre-Requisites
12
+
13
+ The PostgreSQL client program 'pg_dump' must be installed in order to use the migrate function.
14
+
15
+ == Installation
16
+
17
+ $ gem install justonedb
18
+
19
+ == Commands
20
+
21
+ * Migrate JustOneDB database from current to 'NEW' on Heroku
22
+
23
+ $ justonedb heroku migrate
24
+
25
+ Extracting data from JUSTONEDB_DBI_URL using pg_dump
26
+ Importing data into JUSTONEDB_NEW_DBI_URL using psql
27
+ Data imported successfully, please fully check your application before running 'justonedb heroku promote'
28
+
29
+ * Promote 'NEW' variables to current
30
+
31
+ $ justonedb heroku promote
32
+ Promoting JustOneDB 'NEW' vars to current, and saving current to 'OLD'
33
+ Variables modified, please check that your application works correctly, before running 'justonedb heroku purge-old'
34
+
35
+ * Purge 'OLD' variables
36
+
37
+ $ justonedb heroku purge-old
38
+ Purging JustOneDB 'OLD' configuration variables
39
+
40
+ == License
41
+
42
+ Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
43
+
44
+ == Credits
45
+
46
+ Created by Daryl Waycott
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'justonedb/command'
7
+
8
+ args = ARGV.dup
9
+ ARGV.clear
10
+
11
+ JustOneDB::Command.load
12
+
13
+ begin
14
+ platform = args.shift.strip
15
+ command = args.shift.strip
16
+
17
+ rescue
18
+ JustOneDB::Command.help
19
+ exit
20
+ end
21
+
22
+ JustOneDB::Command.run(platform, command, args)
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "justonedb"
5
+ spec.version = "1.0.0"
6
+
7
+ spec.authors = ["JustOneDB"]
8
+ spec.date = "2012-01-18"
9
+ spec.description = "Command-line tool for JustOneDB to assist in deployments on Heroku"
10
+ spec.email = "support@justonedb.com"
11
+ spec.executables = ["justonedb"]
12
+ spec.files = `svn ls -R`.split("\n")
13
+ spec.homepage = "http://www.justonedb.com/"
14
+ spec.require_paths = ['lib']
15
+ spec.rubygems_version = "1.8.10"
16
+ spec.summary = "Command-line tool for JustOneDB database deployed on Heroku, or AppHarbor."
17
+
18
+ spec.required_rubygems_version = Gem::Requirement.new(">= 0") if spec.respond_to? :required_rubygems_version=
19
+ spec.add_dependency 'heroku', '~> 2.15'
20
+ spec.add_dependency 'require_all', '~> 1.2.1'
21
+ end
@@ -0,0 +1,118 @@
1
+ require 'optparse'
2
+ require 'require_all'
3
+ require 'pp'
4
+
5
+ # Some parts adapted from the heroku gem http://rubygems.org/gems/heroku
6
+ module JustOneDB
7
+ module Command
8
+ class InvalidPlatform < RuntimeError; end
9
+ class InvalidCommand < RuntimeError; end
10
+
11
+ class << self
12
+ def load
13
+ require_all File.join(File.dirname(__FILE__), "command", '*.rb')
14
+
15
+ JustOneDB::Command::Base.load
16
+ end
17
+
18
+ def commands
19
+ @commands ||= JustOneDB::Command::Base.commands
20
+ end
21
+
22
+ def platforms
23
+ @platforms ||= JustOneDB::Command::Base.platforms
24
+ end
25
+
26
+ def main_options
27
+ @main_options ||= [
28
+ {
29
+ :option_id => "app",
30
+ :short => '-a',
31
+ :long => '--app APP',
32
+ :description => 'Heroku application'
33
+ },
34
+ {
35
+ :option_id => "help",
36
+ :short => '-h',
37
+ :long => '--help',
38
+ :description => 'Help'
39
+ }
40
+ ]
41
+ end
42
+
43
+ def help
44
+ prefix = "Usage: " + File.basename($PROGRAM_NAME)
45
+ puts "#{prefix} <platform> <command>"
46
+
47
+ main_options.each do |option|
48
+ puts " " * prefix.length + "[ #{option[:short]} #{option[:long]} (#{option[:description]}) ]"
49
+ end
50
+
51
+ # Show all platforms and commands
52
+ puts "\nPlatforms and Commands:"
53
+
54
+ commands.each do |platform, command_hash|
55
+ puts " " * prefix.length + "#{platform}"
56
+ indent = prefix.length + platform.length
57
+
58
+ max_length = 0
59
+ command_hash.each_key { |command| max_length = command.length if command.length > max_length }
60
+
61
+ command_hash.each do |command, cmd_details|
62
+ printf("%*s%-*s - %s\n", indent, " ", max_length, command, cmd_details[:summary])
63
+ #puts " " * indent + "#{command}"
64
+ end
65
+ end
66
+ end
67
+
68
+ def run(platform, command, args)
69
+ # Check the platform
70
+ raise InvalidPlatform unless platforms.include?(platform)
71
+
72
+ # See if the command exists
73
+ raise InvalidCommand unless commands[platform] and commands[platform][command]
74
+
75
+ cmd_details = commands[platform][command]
76
+ options = {}
77
+
78
+ # Check any options available
79
+ optparse = OptionParser.new do |parser|
80
+ main_options.each do |option|
81
+ parser.on("#{option[:short]}", "#{option[:long]}", option[:description]) do |value|
82
+ options[option[:option_id].to_sym] = value
83
+ end
84
+ end
85
+
86
+ if cmd_details[:options]
87
+ cmd_details[:options].each do |option|
88
+ parser.on("#{option[:short]}", "#{option[:long]}", option[:description]) do |value|
89
+ options[option[:option_id].to_sym] = value
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ begin
96
+ optparse.parse!(args)
97
+
98
+ rescue OptionParser::ParseError
99
+ prefix = "Usage: #{$PROGRAM_NAME} "
100
+ puts "#{prefix} <platform> <command>"
101
+
102
+ main_options.each do |option|
103
+ puts " " * prefix.length + "[ #{option[:short]} #{option[:long]} (#{option[:description]}) ]"
104
+ end
105
+
106
+ cmd_details[:options].each do |option|
107
+ puts " " * prefix.length + "[ #{option[:short]} #{option[:long]} (#{option[:description]}) ]"
108
+ end
109
+ end
110
+
111
+ cmd = cmd_details[:cmd_class].new
112
+ bound = cmd_details[:method].bind(cmd)
113
+ bound.call(args, options)
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,106 @@
1
+ require 'require_all'
2
+ require 'open3'
3
+ require 'tmpdir'
4
+
5
+ class JustOneDB::Command::Base
6
+ class MigrationFailed < RuntimeError; end
7
+
8
+ class << self
9
+ def load
10
+ # Now ask each sub-class to load it's commands
11
+ @@commands = {}
12
+ @@platforms = []
13
+ sub_classes.each do |curr_class|
14
+ if curr_class.respond_to?("get_commands")
15
+ commands = curr_class.get_commands
16
+
17
+ commands.each do |command|
18
+ @@commands[command[:platform]] = {} unless @@commands[command[:platform]]
19
+ @@commands[command[:platform]][command[:command]] = command
20
+ @@platforms.push command[:platform] unless @@platforms.include?(command[:platform])
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def commands
27
+ @@commands ||= {}
28
+ end
29
+
30
+ def platforms
31
+ @@platforms ||= []
32
+ end
33
+
34
+ protected
35
+ def inherited(new_class)
36
+ # Store all sub-classes
37
+ sub_classes.push new_class
38
+ end
39
+
40
+ def sub_classes
41
+ @@sub_classes ||= []
42
+ end
43
+
44
+ end
45
+
46
+ def migrate(from, to, tmpdir)
47
+ # Extract details from URI
48
+ dbi = URI.parse(from)
49
+ username, password = dbi.userinfo.split(':')
50
+ path = dbi.path.sub('/', '')
51
+
52
+ tmpfile = File.join(tmpdir, "pg_dump_#{$$}.dmp")
53
+
54
+ # Construct pg_dump command
55
+ pg_dump = "pg_dump -Fp --no-privileges --no-owner --no-tablespaces " +
56
+ "-h #{dbi.host} -p #{dbi.port} -U #{username} #{path} 2>&1 > #{tmpfile}"
57
+
58
+ puts "Extracting data from JUSTONEDB_DBI_URL using pg_dump"
59
+ dumpin, dumpout, wait_thr = Open3.popen2({ "PGPASSWORD" => password }, pg_dump)
60
+ dumpin.close
61
+ dump_stdout = dumpout.read
62
+ dumpout.close
63
+
64
+ begin
65
+ if wait_thr.value.exitstatus > 0
66
+ puts dump_stdout
67
+ raise MigrationFailed, "Database dump failed"
68
+ end
69
+
70
+ # Get a psql process read to read from us (pipe it's input, so that we can remove anything that we don't like
71
+ new_dbi = URI.parse(to)
72
+ username, password = dbi.userinfo.split(':')
73
+ path = new_dbi.path.sub('/', '')
74
+ psqlcmd = "psql --set=ON_ERROR_STOP=on -h #{new_dbi.host} -p #{new_dbi.port} -U #{username} -d #{path}"
75
+ psqlin, psqlout, psqlerr, wait_thr = Open3.popen3({ "PGPASSWORD" => password }, psqlcmd)
76
+
77
+ infd = open(tmpfile, "r")
78
+
79
+ # Ignore creation of plpgsql language, as the user will not have permission to replace the one there
80
+ puts "Importing data into JUSTONEDB_NEW_DBI_URL using psql"
81
+
82
+ # Send everything to psql
83
+ infd.each { |line| psqlin.write line unless line.match(/^create( or replace)? procedural language plpgsql;$/i) }
84
+
85
+ infd.close
86
+ psqlin.close
87
+
88
+ psqlerr.each { |line| puts line }
89
+
90
+ psqlout.close
91
+ psqlerr.close
92
+
93
+ if wait_thr.value.exitstatus > 0
94
+ raise MigrationFailed, "Database restore failed"
95
+ end
96
+
97
+ puts "Data imported successfully, please fully check your application before running 'justonedb heroku promote'"
98
+
99
+ rescue
100
+ File.unlink(tmpfile) if File.exists?(tmpfile)
101
+ raise
102
+ end
103
+ end
104
+ end
105
+
106
+ require_all File.join(File.dirname(__FILE__), "**", '*.rb')
@@ -0,0 +1,51 @@
1
+ require 'justonedb/command/base'
2
+ require 'heroku/auth'
3
+ require 'heroku/command'
4
+ require 'heroku/command/base'
5
+ require 'open3'
6
+
7
+ module JustOneDB::Command
8
+ class InvalidAppConfig < RuntimeError; end
9
+
10
+ class Heroku < JustOneDB::Command::Base
11
+ # Define available commands
12
+
13
+ class << self
14
+ def get_commands
15
+ [
16
+ {
17
+ :cmd_class => self,
18
+ :method => self.instance_method(:migrate),
19
+ :platform => "heroku",
20
+ :command => "migrate",
21
+ :summary => "Migrate Data from current to 'NEW' database",
22
+ :options => [
23
+ {
24
+ :option_id => "directory",
25
+ :short => '-d',
26
+ :long => '--directory TMP_DIR',
27
+ :description => 'Write temporary data to TMP_DIR (default ".")'
28
+ }
29
+ ],
30
+ },
31
+ {
32
+ :cmd_class => self,
33
+ :method => self.instance_method(:promote),
34
+ :platform => "heroku",
35
+ :command => "promote",
36
+ :summary => "Promote JustOneDB 'NEW' database to live",
37
+ :options => NIL
38
+ },
39
+ {
40
+ :cmd_class => self,
41
+ :method => self.instance_method(:purge_old),
42
+ :platform => "heroku",
43
+ :command => "purge-old",
44
+ :summary => "Purge 'OLD' JustOneDB variables",
45
+ :options => NIL
46
+ },
47
+ ]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ require 'justonedb/command/base'
2
+ require 'heroku/auth'
3
+ require 'heroku/command'
4
+ require 'heroku/command/base'
5
+ require 'open3'
6
+ require 'tmpdir'
7
+
8
+ module JustOneDB::Command
9
+ class Heroku < JustOneDB::Command::Base
10
+ def migrate(args, options)
11
+ # Migrate data from JUSTONEDB_DBI_URL to JUSTONEDB_NEW_DBI_URL
12
+ # -d, --directory # Specify temporary dump directory
13
+ directory = options[:directory]
14
+ directory = Dir::tmpdir unless directory
15
+
16
+ herokuapi = ::Heroku::Command::BaseWithApp.new(args, options)
17
+
18
+ vars = herokuapi.heroku.config_vars(herokuapi.app)
19
+
20
+ # Check that we have an old and a new DBI entry
21
+ raise InvalidAppConfig, "JUSTONEDB_DBI_URL undefined" unless vars["JUSTONEDB_DBI_URL"]
22
+ raise InvalidAppConfig, "JUSTONEDB_NEW_DBI_URL undefined" unless vars["JUSTONEDB_NEW_DBI_URL"]
23
+
24
+ super(vars["JUSTONEDB_DBI_URL"], vars["JUSTONEDB_NEW_DBI_URL"], directory)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ require 'justonedb/command/base'
2
+ require 'heroku/auth'
3
+ require 'heroku/command'
4
+ require 'heroku/command/base'
5
+
6
+ module JustOneDB::Command
7
+ class Heroku < JustOneDB::Command::Base
8
+
9
+ def promote(args, options)
10
+ herokuapi = ::Heroku::Command::BaseWithApp.new(args, options)
11
+
12
+ vars = herokuapi.heroku.config_vars(herokuapi.app)
13
+
14
+ # Check that we have an old and a new DBI entry
15
+ raise InvalidAppConfig, "JUSTONEDB_NEW_DBI_URL undefined" unless vars["JUSTONEDB_NEW_DBI_URL"]
16
+ raise InvalidAppConfig, "JUSTONEDB_NEW_REST_BASE undefined" unless vars["JUSTONEDB_NEW_REST_BASE"]
17
+ raise InvalidAppConfig, "JUSTONEDB_NEW_REST_DB undefined" unless vars["JUSTONEDB_NEW_REST_DB"]
18
+ raise InvalidAppConfig, "JUSTONEDB_NEW_SUPPORT_ID undefined" unless vars["JUSTONEDB_NEW_SUPPORT_ID"]
19
+
20
+ puts "Promoting JustOneDB 'NEW' vars to current, and saving current to 'OLD'"
21
+
22
+ detected_app = herokuapi.app
23
+
24
+ new_vars = {}
25
+ new_vars["JUSTONEDB_OLD_DBI_URL"] = vars["JUSTONEDB_DBI_URL"]
26
+ new_vars["JUSTONEDB_OLD_REST_BASE"] = vars["JUSTONEDB_REST_BASE"]
27
+ new_vars["JUSTONEDB_OLD_REST_DB"] = vars["JUSTONEDB_REST_DB"]
28
+ new_vars["JUSTONEDB_OLD_SUPPORT_ID"] = vars["JUSTONEDB_SUPPORT_ID"]
29
+ new_vars["JUSTONEDB_DBI_URL"] = vars["JUSTONEDB_NEW_DBI_URL"]
30
+ new_vars["JUSTONEDB_REST_BASE"] = vars["JUSTONEDB_NEW_REST_BASE"]
31
+ new_vars["JUSTONEDB_REST_DB"] = vars["JUSTONEDB_NEW_REST_DB"]
32
+ new_vars["JUSTONEDB_SUPPORT_ID"] = vars["JUSTONEDB_NEW_SUPPORT_ID"]
33
+
34
+ herokuapi.heroku.add_config_vars(detected_app, new_vars)
35
+
36
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_NEW_DBI_URL")
37
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_NEW_REST_BASE")
38
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_NEW_REST_DB")
39
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_NEW_SUPPORT_ID")
40
+
41
+ puts "Variables modified, please check that your application works correctly, before running 'justonedb heroku purge-old'"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ require 'justonedb/command/base'
2
+ require 'heroku/auth'
3
+ require 'heroku/command'
4
+ require 'heroku/command/base'
5
+
6
+ module JustOneDB::Command
7
+ class Heroku < JustOneDB::Command::Base
8
+
9
+ def purge_old(args, options)
10
+ herokuapi = ::Heroku::Command::BaseWithApp.new(args, options)
11
+
12
+ vars = herokuapi.heroku.config_vars(herokuapi.app)
13
+
14
+ # Check that we have the old entries
15
+ raise InvalidAppConfig, "JUSTONEDB_OLD_DBI_URL undefined" unless vars["JUSTONEDB_OLD_DBI_URL"]
16
+ raise InvalidAppConfig, "JUSTONEDB_OLD_REST_BASE undefined" unless vars["JUSTONEDB_OLD_REST_BASE"]
17
+ raise InvalidAppConfig, "JUSTONEDB_OLD_REST_DB undefined" unless vars["JUSTONEDB_OLD_REST_DB"]
18
+ raise InvalidAppConfig, "JUSTONEDB_OLD_SUPPORT_ID undefined" unless vars["JUSTONEDB_OLD_SUPPORT_ID"]
19
+
20
+ puts "Purging JustOneDB 'OLD' configuration variables"
21
+
22
+ detected_app = herokuapi.app
23
+
24
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_OLD_DBI_URL")
25
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_OLD_REST_BASE")
26
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_OLD_REST_DB")
27
+ herokuapi.heroku.remove_config_var(detected_app, "JUSTONEDB_OLD_SUPPORT_ID")
28
+ end
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: justonedb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - JustOneDB
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-18 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: heroku
16
+ requirement: &74934430 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.15'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *74934430
25
+ - !ruby/object:Gem::Dependency
26
+ name: require_all
27
+ requirement: &74934000 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.2.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *74934000
36
+ description: Command-line tool for JustOneDB to assist in deployments on Heroku
37
+ email: support@justonedb.com
38
+ executables:
39
+ - justonedb
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - LICENSE
44
+ - README.rdoc
45
+ - bin/justonedb
46
+ - justonedb-1.0.0.gemspec
47
+ - lib/justonedb/command/base.rb
48
+ - lib/justonedb/command/heroku/base.rb
49
+ - lib/justonedb/command/heroku/migrate.rb
50
+ - lib/justonedb/command/heroku/promote.rb
51
+ - lib/justonedb/command/heroku/purge-old.rb
52
+ - lib/justonedb/command.rb
53
+ homepage: http://www.justonedb.com/
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 1.8.10
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Command-line tool for JustOneDB database deployed on Heroku, or AppHarbor.
77
+ test_files: []
78
+ has_rdoc: