justonedb 1.0.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.
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: