pvcglue 0.1.5

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.
Files changed (65) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +91 -0
  6. data/Rakefile +1 -0
  7. data/bin/pvc +13 -0
  8. data/lib/pvcglue.rb +43 -0
  9. data/lib/pvcglue/all_the_things.rb +7 -0
  10. data/lib/pvcglue/bootstrap.rb +8 -0
  11. data/lib/pvcglue/capistrano.rb +35 -0
  12. data/lib/pvcglue/cli.rb +150 -0
  13. data/lib/pvcglue/cloud.rb +278 -0
  14. data/lib/pvcglue/configuration.rb +157 -0
  15. data/lib/pvcglue/db.rb +145 -0
  16. data/lib/pvcglue/deploy.rb +4 -0
  17. data/lib/pvcglue/env.rb +141 -0
  18. data/lib/pvcglue/manager.rb +137 -0
  19. data/lib/pvcglue/nodes.rb +29 -0
  20. data/lib/pvcglue/packages.rb +47 -0
  21. data/lib/pvcglue/packages/bootstrap.rb +92 -0
  22. data/lib/pvcglue/packages/env.rb +80 -0
  23. data/lib/pvcglue/packages/firewall.rb +48 -0
  24. data/lib/pvcglue/packages/manager.rb +102 -0
  25. data/lib/pvcglue/packages/nginx.rb +10 -0
  26. data/lib/pvcglue/packages/nodejs.rb +17 -0
  27. data/lib/pvcglue/packages/passenger.rb +28 -0
  28. data/lib/pvcglue/packages/postgresql.rb +10 -0
  29. data/lib/pvcglue/packages/role_db.rb +47 -0
  30. data/lib/pvcglue/packages/role_lb.rb +64 -0
  31. data/lib/pvcglue/packages/role_memcached.rb +14 -0
  32. data/lib/pvcglue/packages/role_web.rb +60 -0
  33. data/lib/pvcglue/packages/rvm.rb +75 -0
  34. data/lib/pvcglue/packages/timezone.rb +17 -0
  35. data/lib/pvcglue/packages/ubuntu.rb +100 -0
  36. data/lib/pvcglue/railtie.rb +11 -0
  37. data/lib/pvcglue/ssl.rb +37 -0
  38. data/lib/pvcglue/templates/20auto-upgrades.erb +2 -0
  39. data/lib/pvcglue/templates/authorized_keys.erb +3 -0
  40. data/lib/pvcglue/templates/capfile.erb +20 -0
  41. data/lib/pvcglue/templates/database.yml.erb +57 -0
  42. data/lib/pvcglue/templates/denial_of_service.erb +3 -0
  43. data/lib/pvcglue/templates/deploy.rb.erb +81 -0
  44. data/lib/pvcglue/templates/gemrc.erb +1 -0
  45. data/lib/pvcglue/templates/hosts.erb +9 -0
  46. data/lib/pvcglue/templates/lb.nginx.conf.erb +88 -0
  47. data/lib/pvcglue/templates/lb.sites-enabled.erb +74 -0
  48. data/lib/pvcglue/templates/maintenance_mode.erb +46 -0
  49. data/lib/pvcglue/templates/memcached.conf.erb +55 -0
  50. data/lib/pvcglue/templates/passenger.list.erb +2 -0
  51. data/lib/pvcglue/templates/pg_hba.conf.erb +101 -0
  52. data/lib/pvcglue/templates/postgresql.conf.erb +557 -0
  53. data/lib/pvcglue/templates/sshd_config.erb +91 -0
  54. data/lib/pvcglue/templates/stage-deploy.rb.erb +33 -0
  55. data/lib/pvcglue/templates/timezone.erb +1 -0
  56. data/lib/pvcglue/templates/ufw.rules.erb +42 -0
  57. data/lib/pvcglue/templates/ufw.rules6.erb +25 -0
  58. data/lib/pvcglue/templates/web.bashrc.erb +120 -0
  59. data/lib/pvcglue/templates/web.env.erb +3 -0
  60. data/lib/pvcglue/templates/web.nginx.conf.erb +82 -0
  61. data/lib/pvcglue/templates/web.sites-enabled.erb +8 -0
  62. data/lib/pvcglue/toml_pvc_dumper.rb +53 -0
  63. data/lib/pvcglue/version.rb +3 -0
  64. data/pvcglue.gemspec +33 -0
  65. metadata +296 -0
@@ -0,0 +1,157 @@
1
+ require 'toml'
2
+ # Inspired by http://robots.thoughtbot.com/mygem-configure-block
3
+ # and https://github.com/thoughtbot/clearance/blob/master/lib/clearance/configuration.rb
4
+
5
+ # Example for '~/.pvcglue.toml':
6
+ # cloud_manager = "nnn.nnn.nnn.nnn"
7
+
8
+ module Pvcglue
9
+ class Configuration < Thor
10
+
11
+ attr_accessor :cloud_manager
12
+ attr_accessor :cloud_name
13
+ attr_accessor :application_name
14
+ attr_accessor :context
15
+
16
+ def self.file_name
17
+ ENV['PVCGLUE_FILE_NAME'] || '.pvcglue.toml'
18
+ end
19
+
20
+ def self.env_prefix
21
+ ENV['PVCGLUE_ENV_PREFIX'] || 'PVCGLUE'
22
+ end
23
+
24
+
25
+ # silence Thor warnings, as these are not Thor commands. (But we still need 'say' and 'ask' and friends.)
26
+ no_commands do
27
+
28
+ def initialize
29
+ #ENV["PVCGLUE_#{'application_name'.upcase}"] = 'override'
30
+ init(:cloud_manager) || configure_manager
31
+ raise(Thor::Error, "The manager has not been configured. :(") if cloud_manager.nil?
32
+ init_except_manager
33
+ end
34
+
35
+ def init_except_manager
36
+ init(:cloud_name, 'cluster_one')
37
+ init(:application_name, find_app_name)
38
+ end
39
+
40
+ def configure_manager
41
+ say('The manager has not been configured.')
42
+ manager = ask('What is the IP address or host name of the manager?')
43
+ default = !no?('Will this be the default manager? (Y/n)')
44
+ file_name = default ? user_file_name : project_file_name
45
+ File.write(file_name, %(cloud_manager = "#{manager}"\n))
46
+ say("Manager written to #{file_name}.")
47
+
48
+ @conf = nil # clear cache
49
+ init(:cloud_manager)
50
+ init_except_manager
51
+ clear_cloud_cache
52
+ end
53
+
54
+ def init(option, default=nil)
55
+ # ENV first, then pvcglue.toml (checking current working directory first, then in user home '~'), then default
56
+ # NOTE: In the context of Rails, a standard initializer can also be used, and will override all settings here, but that should not really apply for 'pvcglue'
57
+ # /config/initializers/pvcglue.rb:
58
+ # Pvcglue.configure do |config|
59
+ # config.cloud_manager = '192.168.0.1'
60
+ # end
61
+ value = ENV["#{self.class.env_prefix}_#{option.upcase}"] || get_conf(option) || default
62
+ #puts "Setting #{option}=#{value}"
63
+ instance_variable_set("@#{option}", value)
64
+ end
65
+
66
+ def merge_into_conf(file_name)
67
+ #puts "*"*80
68
+ #puts file_name
69
+ #puts File.exists?(file_name).inspect
70
+ if File.exists?(file_name)
71
+ data = TOML.load_file(file_name)
72
+ #puts data.inspect
73
+ @conf.merge!(data)
74
+ end
75
+ end
76
+
77
+ def get_conf(option)
78
+ unless @conf
79
+ @conf = {}
80
+ merge_into_conf(user_file_name)
81
+ merge_into_conf(project_file_name)
82
+ end
83
+ @conf[option.to_s]
84
+ end
85
+
86
+ def project_file_name
87
+ File.join(application_dir, self.class.file_name)
88
+ end
89
+
90
+ def user_file_name
91
+ File.join(Dir.home, self.class.file_name)
92
+ end
93
+
94
+ def find_app_name
95
+ # try rack file...anyone know a better way, without loading Rails?
96
+ rack_up = File.join(application_dir, 'config.ru')
97
+ $1.downcase if File.exists?(rack_up) && File.read(rack_up) =~ /^run (.*)::/
98
+ end
99
+
100
+ def options
101
+ Hash[instance_variables.map { |name| [name.to_s[1..-1].to_sym, instance_variable_get(name)] }].reject { |k| k == :conf }
102
+ end
103
+
104
+ def tmp_dir
105
+ File.join(application_dir, 'tmp')
106
+ end
107
+
108
+ def cloud_cache_file_name
109
+ # Just in case the Rails project hasn't yet been run, make sure the tmp
110
+ # dir exists.
111
+ Dir.mkdir(tmp_dir) unless Dir.exist?(tmp_dir)
112
+
113
+ File.join(tmp_dir, "pvcglue_#{cloud_manager}_#{cloud_name}_#{application_name}_cache.toml")
114
+ end
115
+
116
+ def clear_cloud_cache
117
+ File.delete(cloud_cache_file_name) if File.exists?(cloud_cache_file_name)
118
+ end
119
+
120
+ def application_dir
121
+ Dir.pwd
122
+ end
123
+
124
+ def app_maintenance_files_dir
125
+ File.join(application_dir, 'public', 'maintenance')
126
+ end
127
+
128
+ def ruby_version_file_name
129
+ File.join(application_dir,'.ruby-version')
130
+ end
131
+
132
+ def ruby_version
133
+ File.read(ruby_version_file_name).strip
134
+ end
135
+
136
+ def web_app_base_dir
137
+ '/sites'
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ # --------------------------------------------------------------------------------------------------------------------
144
+
145
+ def self.configuration
146
+ @configuration ||= Configuration.new
147
+ end
148
+
149
+ def self.configuration=(config)
150
+ @configuration = config
151
+ end
152
+
153
+ def self.configure
154
+ yield configuration
155
+ end
156
+
157
+ end
data/lib/pvcglue/db.rb ADDED
@@ -0,0 +1,145 @@
1
+ module Pvcglue
2
+ class Db < Thor
3
+
4
+ desc "config", "create/update database.yml"
5
+
6
+ def config
7
+ Pvcglue::Db.configure_database_yml
8
+ end
9
+
10
+ desc "push", "push"
11
+
12
+ def push(file_name = nil)
13
+ raise(Thor::Error, "Stage required.") if Pvcglue.cloud.stage_name.nil?
14
+ pg_restore(self.class.remote, file_name)
15
+ end
16
+
17
+ desc "pull", "pull"
18
+
19
+ def pull(file_name = nil)
20
+ raise(Thor::Error, "Stage required.") if Pvcglue.cloud.stage_name.nil?
21
+ pg_dump(self.class.remote, file_name)
22
+ end
23
+
24
+ desc "dump", "dump"
25
+
26
+ def dump(file_name = nil)
27
+ raise(Thor::Error, "Stage should not be set for this command.") unless Pvcglue.cloud.stage_name.nil?
28
+ pg_dump(self.class.local, file_name)
29
+ end
30
+
31
+ desc "restore", "restore"
32
+
33
+ def restore(file_name = nil)
34
+ raise(Thor::Error, "Stage should not be set for this command.") unless Pvcglue.cloud.stage_name.nil?
35
+ pg_restore(self.class.local, file_name)
36
+ end
37
+
38
+ desc "info", "info"
39
+
40
+ def info
41
+ if Pvcglue.cloud.stage_name
42
+ pp self.class.remote
43
+ else
44
+ pp self.class.local
45
+ end
46
+ end
47
+
48
+ # ------------------------------------------------------------------------------------------------------------------
49
+
50
+ def self.database_yml_file_name
51
+ File.join(Pvcglue::Capistrano.application_config_dir, 'database.yml')
52
+ end
53
+
54
+ class Db_Config < Struct.new(:host, :port, :database, :username, :password)
55
+ end
56
+
57
+ def self.local_info
58
+ @local_info ||= begin
59
+ template = Tilt::ERBTemplate.new('config/database.yml')
60
+ output = template.render
61
+ # puts output.inspect
62
+ info = YAML::load(output)
63
+ # puts info.inspect
64
+ info
65
+ end
66
+ end
67
+
68
+ def self.local
69
+ @local ||= begin
70
+ dev = local_info["development"]
71
+ Db_Config.new(dev["host"], dev["port"], dev["database"], dev["username"], dev["password"])
72
+ end
73
+ end
74
+
75
+ def self.remote
76
+ @remote ||= begin
77
+ Pvcglue::Env.initialize_stage_env
78
+ env = Pvcglue.cloud.stage_env
79
+ Db_Config.new(db_host_public,
80
+ env["DB_USER_POSTGRES_PORT"],
81
+ env["DB_USER_POSTGRES_DATABASE"],
82
+ env["DB_USER_POSTGRES_USERNAME"],
83
+ env["DB_USER_POSTGRES_PASSWORD"])
84
+ end
85
+ end
86
+
87
+ def self.db_host_public
88
+ node = Pvcglue.cloud.find_node('db')
89
+ node['db']['public_ip']
90
+ end
91
+
92
+
93
+ def self.file_helper(file_name)
94
+ # TODO: make it more helpful ;)
95
+ stage = Pvcglue.cloud.stage_name || "dev"
96
+ file_name = "#{Pvcglue.configuration.application_name}_#{stage}_#{Time.now.strftime("%Y-%m-%d")}.dump" unless file_name
97
+ # "#{File.dirname(file_name)}/#{File.basename(file_name, '.*')}.dump"
98
+ file_name
99
+ end
100
+
101
+ def self.configure_database_yml
102
+ Pvcglue.render_template('database.yml.erb', Pvcglue::Db.database_yml_file_name)
103
+ end
104
+
105
+
106
+ # ------------------------------------------------------------------------------------------------------------------
107
+
108
+ # silence Thor warnings, as these are not Thor commands. (But we still need 'say' and 'ask' and friends.)
109
+ no_commands do
110
+
111
+ def destroy_prod?
112
+ say("Are you *REALLY* sure you want to DESTROY the PRODUCTION database?")
113
+ input = ask("Type 'destroy production' if you are:")
114
+ raise(Thor::Error, "Ain't gonna do it.") if input.downcase != "destroy production"
115
+ puts "ok, going through with the it..."
116
+ end
117
+
118
+
119
+ def pg_dump(source, file_name)
120
+ cmd = "pg_dump -Fc --no-acl --no-owner -h #{source.host} -p #{source.port}"
121
+ cmd += " -U #{source.username}" if source.username
122
+ cmd += " #{source.database} -v -f #{self.class.file_helper(file_name)}"
123
+ puts cmd
124
+ unless system({"PGPASSWORD" => source.password}, cmd)
125
+ puts "ERROR:"
126
+ puts $?.inspect
127
+ end
128
+ end
129
+
130
+ def pg_restore(dest, file_name)
131
+ Pvcglue.cloud.stage_name == 'production' && destroy_prod?
132
+ cmd = "pg_restore --verbose --clean --no-acl --no-owner -h #{dest.host} -p #{dest.port}"
133
+ cmd += " -U #{dest.username}" if dest.username
134
+ cmd += " -d #{dest.database} #{self.class.file_helper(file_name)}"
135
+ puts cmd
136
+ unless system({"PGPASSWORD" => dest.password}, cmd)
137
+ puts "ERROR:"
138
+ puts $?.inspect
139
+ end
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,4 @@
1
+ module Pvcglue
2
+ class Deploy
3
+ end
4
+ end
@@ -0,0 +1,141 @@
1
+ require 'pp'
2
+
3
+ module Pvcglue
4
+ class Env < Thor
5
+
6
+ desc "push", "push"
7
+
8
+ def push
9
+ Pvcglue::Packages.apply('env-push'.to_sym, :manager, Pvcglue::Manager.manager_node, 'pvcglue')
10
+ self.class.clear_stage_env_cache
11
+ Pvcglue::Packages.apply('app-env-file'.to_sym, :env, Pvcglue.cloud.nodes_in_stage('web'))
12
+ end
13
+
14
+ desc "pull", "pull"
15
+
16
+ def pull
17
+ Pvcglue::Packages.apply('env-pull'.to_sym, :manager, Pvcglue::Manager.manager_node, 'pvcglue')
18
+ self.class.clear_stage_env_cache
19
+ end
20
+
21
+ desc "list", "list"
22
+
23
+ def list
24
+ self.class.initialize_stage_env
25
+ Pvcglue.cloud.stage_env.each { |key, value| puts "#{key}=#{value}" }
26
+ end
27
+
28
+ desc "default", "reset env to default. Destructive!!!"
29
+
30
+ def default
31
+ if yes?("Are you sure?")
32
+ Pvcglue.cloud.stage_env = Pvcglue::Env.stage_env_defaults
33
+ Pvcglue::Env.save_stage_env
34
+ end
35
+ end
36
+
37
+ desc "set", "set environment variable(s) for the stage XYZ=123 [ZZZ=321]"
38
+
39
+ def set(*args)
40
+ self.class.initialize_stage_env
41
+ options = Hash[args.each.map { |l| l.chomp.split('=') }]
42
+ Pvcglue.cloud.stage_env.merge!(options)
43
+ self.class.save_stage_env
44
+ Pvcglue::Packages.apply('app-env-file'.to_sym, :env, Pvcglue.cloud.nodes_in_stage('web'))
45
+ end
46
+
47
+ desc "unset", "remove environment variable(s) for the stage XYZ [ZZZ]"
48
+
49
+ def unset(*args)
50
+ self.class.initialize_stage_env
51
+ args.each { |arg| puts "WARNING: Key '#{arg}' not found." unless Pvcglue.cloud.stage_env.delete(arg) }
52
+ self.class.save_stage_env
53
+ Pvcglue::Packages.apply('app-env-file'.to_sym, :env, Pvcglue.cloud.nodes_in_stage('web'))
54
+ end
55
+
56
+ desc "rm", "alternative to unset"
57
+
58
+ def rm(*args)
59
+ unset(*args)
60
+ end
61
+
62
+
63
+ # ------------------------------------------------------------------------------------------------------------------
64
+
65
+ def self.initialize_stage_env
66
+ unless read_cached_stage_env
67
+ Pvcglue::Packages.apply('env-get-stage'.to_sym, :manager, Pvcglue::Manager.manager_node, 'pvcglue')
68
+ write_stage_env_cache
69
+ end
70
+ merged = stage_env_defaults.merge(Pvcglue.cloud.stage_env)
71
+ if merged != Pvcglue.cloud.stage_env
72
+ Pvcglue.cloud.stage_env = merged
73
+ save_stage_env
74
+ end
75
+ end
76
+
77
+ def self.save_stage_env
78
+ Pvcglue::Packages.apply('env-set-stage'.to_sym, :manager, Pvcglue::Manager.manager_node, 'pvcglue')
79
+ write_stage_env_cache
80
+ end
81
+
82
+ def self.stage_env_defaults
83
+ {
84
+ 'RAILS_SECRET_TOKEN' => SecureRandom.hex(64),
85
+ 'DB_USER_POSTGRES_HOST' => db_host,
86
+ 'DB_USER_POSTGRES_PORT' => "5432",
87
+ 'DB_USER_POSTGRES_USERNAME' => "#{Pvcglue.cloud.app_name}_#{Pvcglue.cloud.stage_name_validated}",
88
+ 'DB_USER_POSTGRES_DATABASE' => "#{Pvcglue.cloud.app_name}_#{Pvcglue.cloud.stage_name_validated}",
89
+ 'DB_USER_POSTGRES_PASSWORD' => new_password,
90
+ 'MEMCACHE_SERVERS' => memcached_host
91
+ }
92
+ end
93
+
94
+ def self.db_host
95
+ node = Pvcglue.cloud.find_node('db')
96
+ node['db']['private_ip']
97
+ end
98
+
99
+ def self.memcached_host
100
+ node = Pvcglue.cloud.find_node('memcached')
101
+ "#{node['memcached']['private_ip']}:11211"
102
+ end
103
+
104
+ def self.new_password
105
+ "a#{SecureRandom.hex(16)}"
106
+ end
107
+
108
+ def self.write_stage_env_cache
109
+ File.write(stage_env_cache_file_name, TOML.dump(Pvcglue.cloud.stage_env))
110
+ end
111
+
112
+ def self.read_cached_stage_env
113
+ # TODO: Only use cache in development of gem, do not cache by default, use Manager config
114
+ if File.exists?(stage_env_cache_file_name)
115
+ stage_env = File.read(stage_env_cache_file_name)
116
+ Pvcglue.cloud.stage_env = TOML.parse(stage_env)
117
+ return true
118
+ end
119
+ false
120
+ end
121
+
122
+ def self.stage_env_file_name
123
+ File.join(Pvcglue::Manager.manager_dir, stage_env_file_name_base)
124
+ end
125
+
126
+ def self.stage_env_file_name_base
127
+ @stage_env_file_name_base ||= "#{Pvcglue.configuration.cloud_name}_#{Pvcglue.configuration.application_name}_#{Pvcglue.cloud.stage_name_validated}.env.toml"
128
+ end
129
+
130
+ def self.stage_env_cache_file_name
131
+ File.join(Pvcglue.configuration.tmp_dir, "pvcglue_cache_#{stage_env_file_name_base}")
132
+ end
133
+
134
+ def self.clear_stage_env_cache
135
+ File.delete(stage_env_cache_file_name) if File.exists?(stage_env_cache_file_name)
136
+ end
137
+
138
+
139
+ end
140
+
141
+ end