pvcglue 0.9.4 → 0.9.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -4
  3. data/bin/pvc +2 -2
  4. data/lib/pvcglue.rb +3 -2
  5. data/lib/pvcglue/cli.rb +37 -2
  6. data/lib/pvcglue/cloud.rb +15 -1
  7. data/lib/pvcglue/cloud_providers.rb +4 -2
  8. data/lib/pvcglue/cloud_providers/digital_ocean.rb +66 -10
  9. data/lib/pvcglue/cloud_providers/linode.rb +6 -3
  10. data/lib/pvcglue/connection.rb +2 -2
  11. data/lib/pvcglue/custom_hashie.rb +5 -0
  12. data/lib/pvcglue/docs.rb +4 -1
  13. data/lib/pvcglue/env.rb +24 -14
  14. data/lib/pvcglue/manager.rb +4 -6
  15. data/lib/pvcglue/minion.rb +65 -6
  16. data/lib/pvcglue/packages.rb +36 -4
  17. data/lib/pvcglue/packages/apt.rb +4 -0
  18. data/lib/pvcglue/packages/apt_repos.rb +15 -0
  19. data/lib/pvcglue/packages/authorized_keys.rb +46 -19
  20. data/lib/pvcglue/packages/firewall.rb +6 -2
  21. data/lib/pvcglue/packages/manager.rb +5 -2
  22. data/lib/pvcglue/packages/postgresql.rb +5 -2
  23. data/lib/pvcglue/packages/redis.rb +59 -0
  24. data/lib/pvcglue/packages/rvm.rb +1 -1
  25. data/lib/pvcglue/packages/secrets.rb +7 -1
  26. data/lib/pvcglue/packages/sidekiq_workers.rb +89 -0
  27. data/lib/pvcglue/packages/slack.rb +39 -0
  28. data/lib/pvcglue/packages/web.rb +2 -1
  29. data/lib/pvcglue/packages/worker.rb +25 -0
  30. data/lib/pvcglue/templates/capfile.erb +3 -1
  31. data/lib/pvcglue/templates/deploy.rb.erb +10 -95
  32. data/lib/pvcglue/templates/redis.conf.erb +807 -171
  33. data/lib/pvcglue/templates/sidekiq.service.erb +33 -0
  34. data/lib/pvcglue/templates/slacktee.erb +7 -0
  35. data/lib/pvcglue/templates/sshd_config.erb +7 -7
  36. data/lib/pvcglue/templates/stage-deploy.rb.erb +66 -8
  37. data/lib/pvcglue/templates/sysctl.conf.erb +62 -0
  38. data/lib/pvcglue/version.rb +1 -1
  39. data/pvcglue.gemspec +2 -1
  40. metadata +25 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e8640111473b3ed136163c0bb0fb2a7af3aca750
4
- data.tar.gz: c509475f64ca8e936b661d238840be07b1ba435c
3
+ metadata.gz: caa789ef881164fe12fd0afa7ea98479ab5a70e4
4
+ data.tar.gz: 1cb97f3b5a5d13984e2b135652a722e149bf63bb
5
5
  SHA512:
6
- metadata.gz: 6880a84309e9d85cb6cd8e4965620f35672e91e55a7ce614e79b97dbba5041070ce6b53bc19f7f9e6a7842d4af0e6bd7d76e119a07e8b90906bdc0e2be3c8955
7
- data.tar.gz: 47f8cded36079c27e0db96acca25db1ebfaecb273a95bff1ee42c0e8ba5c61e79b7bef22cee4685c94dd538008059d7604d45ffda41298c6a20c4ec36d5eb404
6
+ metadata.gz: f22dc9c678ca9edf6ffb7b88183e7e047334f60e9d83f8084c08f55350b70e3424350f05de2d86c0193c3b17eeb3a816b5fc0a88db3df46459a8290fe6403ff9
7
+ data.tar.gz: 32480c58b7d164a511b6c5727d23d22f2a6b0c7059d0f6a125baf9f59acfcce278a67922a654fc8d9c253291f6363839a14eed13b5c95f709f6e2cd7eb50a2b3
data/README.md CHANGED
@@ -4,7 +4,9 @@ The "glue" that creates a tightly integrated (and very small) virtual cloud for
4
4
 
5
5
  PVC Glue is an cloud application manager for Rails applications using your own (virtual) servers.
6
6
 
7
- PVC Glue was developed as a professional grade replacement for Heroku (and others).
7
+ PVC Glue was developed as a professional grade replacement for Heroku (and others). It is
8
+ designed to be used on small to medium size applications, depending on the application
9
+ requirements and hardware used.
8
10
 
9
11
  ![pvcglue diagram](/../master/images/pvcglue.png?raw=true "PVC Glue Server Diagram")
10
12
 
@@ -12,7 +14,7 @@ Currently supported stack:
12
14
 
13
15
  * SSL support: none, manual and automatic with Let's Encrypt
14
16
  * Ubuntu 16.04 LTS
15
- * Provision servers automatically on Digital Ocean (and Linode*)
17
+ * Provision servers automatically on Digital Ocean and Linode
16
18
  * No need to install anything on servers first (you just need SSH access)
17
19
  * Ruby >= 1.9 (multiple versions supported on same server!)
18
20
  * Rails >= 3.2
@@ -21,11 +23,12 @@ Currently supported stack:
21
23
  * Nginx
22
24
  * Phusion Passenger (>= 5.x)
23
25
  * Memcached*
24
- * Redis*
25
- * Designed to easily support multiple staging environments
26
+ * Redis
27
+ * Designed to easily manage multiple environments (i.e. alpha, beta, preview, production ...)
26
28
 
27
29
  Workers:
28
30
 
31
+ * Sidekiq
29
32
  * Delayed Job*
30
33
  * Rescue*
31
34
 
data/bin/pvc CHANGED
@@ -4,7 +4,6 @@ require 'benchmark'
4
4
 
5
5
  # Allow use of Capistrano style environment syntax and convert to 'standard' syntax
6
6
  # Example: `pvc production bootstrap` ==> `pvc bootstrap --stage=production`
7
- # TODO: refactor to use a list of user specified environments
8
7
 
9
8
  Pvcglue.logger.info('Starting up...')
10
9
  # Pvcglue.logger.info("----- Done #{Benchmark.measure { Pvcglue::CLI.start }}")
@@ -12,7 +11,8 @@ Pvcglue.logger.info('Starting up...')
12
11
  def capistrano_style_environment
13
12
  Pvcglue.logger.debug { ARGV.inspect }
14
13
  if ARGV.count >= 2
15
- if %w[local vmtest test alpha beta gamma delta preview production staging].include?(ARGV[0])
14
+ # TODO: refactor to use a list of user specified environments
15
+ if %w[local vmtest test alpha beta gamma delta preview production staging runner].include?(ARGV[0])
16
16
  ARGV[0], ARGV[1] = ARGV[1], "--stage=#{ARGV[0]}"
17
17
  Pvcglue.logger.debug { ARGV.inspect }
18
18
  Pvcglue::CLI.start
@@ -25,7 +25,7 @@ require 'hashie'
25
25
  require 'pvcglue/custom_hashie'
26
26
  require 'pvcglue/minion'
27
27
  require 'pvcglue/cloud_providers'
28
- require 'droplet_kit'
28
+ # require 'droplet_kit'
29
29
  # require 'pvcglue/digital_ocean'
30
30
  require 'logger'
31
31
  require 'pvcglue/connection'
@@ -215,11 +215,12 @@ module Pvcglue
215
215
  true
216
216
  end
217
217
 
218
- def self.system_get_stdout(cmd)
218
+ def self.system_get_stdout(cmd, raise_error = false)
219
219
  Pvcglue.logger.debug { cmd }
220
220
  result = `#{cmd}`
221
221
  Pvcglue.verbose? { result }
222
222
  Pvcglue.logger.debug { "exit_code=#{$?.to_i}" }
223
+ raise($?.inspect) if raise_error && $?.to_i != 0
223
224
  result
224
225
  end
225
226
 
@@ -61,11 +61,46 @@ module Pvcglue
61
61
  Pvcglue::Stack.build(Pvcglue.cloud.minions, roles)
62
62
  end
63
63
 
64
+ desc "show configuration", "Show configuration details for the stage"
65
+ method_option :stage, :required => true, :aliases => "-s"
66
+
67
+ def config(roles = 'all')
68
+ if roles == 'all'
69
+ ap Pvcglue.cloud.data
70
+ else
71
+ minions = Pvcglue.cloud.minions_filtered(roles)
72
+ minions.each do |minion_name, minion|
73
+ filtered = '[filtered]'
74
+ # filtered = nil
75
+ project = minion.merge({
76
+ stages: filtered,
77
+ all_data: filtered,
78
+ cloud: filtered,
79
+ project: filtered,
80
+ stage: filtered,
81
+ default_cloud_provider: filtered,
82
+ connection: filtered
83
+ })
84
+ # project = project.select { |_, value| !value.nil? }
85
+ data = minion.merge({
86
+ cloud: filtered,
87
+ all_data: filtered,
88
+ connection: filtered,
89
+ project: project
90
+ })
91
+ # ap data.select { |_, value| !value.nil? }
92
+ ap(data, indent: 2)
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
64
99
  desc "console", "open rails console"
65
100
  method_option :stage, :required => true, :aliases => "-s"
66
101
 
67
- def console(server='web')
68
- data = Pvcglue.cloud.minions_filtered(server)
102
+ def console(role='web')
103
+ data = Pvcglue.cloud.minions_filtered(role)
69
104
  minion_name = data.keys.first
70
105
  minion = data.values.first
71
106
  working_dir = Pvcglue.cloud.deploy_to_app_current_dir
@@ -365,6 +365,10 @@ module Pvcglue
365
365
  end
366
366
  end
367
367
 
368
+ def generated_file_warning
369
+ 'This is a generated file. Do not modify...or else! :)'
370
+ end
371
+
368
372
  def db_rebuild
369
373
  !!stage[:db_rebuild]
370
374
  end
@@ -446,6 +450,13 @@ module Pvcglue
446
450
  '/var/www/letsencrypt_root'
447
451
  end
448
452
 
453
+ def service_directory
454
+ '/lib/systemd/system/'
455
+ end
456
+
457
+ def service_extension
458
+ '.service'
459
+ end
449
460
 
450
461
  # ==============================================================================================
451
462
 
@@ -495,7 +506,8 @@ module Pvcglue
495
506
 
496
507
 
497
508
  def minion_user_name_base
498
- project[:user_name_base] || 'deploy'
509
+ # project[:user_name_base] || 'deploy'
510
+ project[:user_name_base] || raise('user_name_base is required')
499
511
  end
500
512
 
501
513
  def minion_user_name
@@ -543,6 +555,7 @@ module Pvcglue
543
555
  find_or_raise(data.machines, name)
544
556
  end
545
557
 
558
+
546
559
  def get_minions
547
560
  minions = ::SafeMash.new
548
561
  stage.stack.each do |item|
@@ -564,6 +577,7 @@ module Pvcglue
564
577
  minion.cloud_id = machine.cloud_id
565
578
  minion.remote_user_name = minion_user_name
566
579
  minion.machine_options = machine
580
+ minion.stage_options = item
567
581
 
568
582
  minion.all_data = data
569
583
  minion.project = project
@@ -1,8 +1,10 @@
1
+ # TODO: Refactor this, it's kinda messy :(
1
2
  module Pvcglue
2
3
  class CloudProviders
3
4
  # REQUIRED_OPTIONS = []
4
5
 
5
6
  def self.init(provider_options)
7
+
6
8
  @options = provider_options
7
9
  @name = provider_options.name
8
10
  if provider_options.name == 'digital-ocean'
@@ -24,9 +26,9 @@ module Pvcglue
24
26
  @options
25
27
  end
26
28
 
27
- def validate_options!(options)
29
+ def validate_options!(options, required)
28
30
  errors = []
29
- REQUIRED_OPTIONS.each { |option_name| errors << "#{option_name} required" unless options[option_name] }
31
+ required.each { |option_name| errors << "#{option_name} required" unless options[option_name] }
30
32
  raise("Errors: #{errors.join(', ')}.") if errors.any?
31
33
  end
32
34
 
@@ -1,7 +1,5 @@
1
1
  module Pvcglue
2
2
  class CloudProviders
3
- REQUIRED_OPTIONS = %w(name region capacity image)
4
-
5
3
  class DigitalOcean < Pvcglue::CloudProviders
6
4
 
7
5
  def initialize(provider_options)
@@ -9,17 +7,44 @@ module Pvcglue
9
7
  end
10
8
 
11
9
  def create(options)
12
- validate_options!(options)
13
-
14
- opts = options.to_h
15
- opts[:size] = opts.delete('capacity')
16
-
17
- droplet_options = DropletKit::Droplet.new(opts)
18
- droplet = client.droplets.create(droplet_options)
10
+ validate_options!(options, %w(name region capacity image))
11
+ #doctl compute droplet create test --size 512mb --image ubuntu-16-04-x64 --region sfo2 --ssh-keys d1:fe:e8:53:d4:fb:eb:f1:db:fc:ef:18:f1:cf:1e:5d --enable-backups --enable-private-networking
12
+ cmd = "doctl compute droplet create #{options.name} "
13
+ cmd += "--region #{options.region} "
14
+ cmd += "--size #{options.capacity} "
15
+ cmd += "--image '#{options.image}' "
16
+ cmd += '--enable-private-networking '
17
+ cmd += '--enable-monitoring '
18
+ cmd += '--enable-backups ' if options.backups
19
+ # cmd += "--tags '#{options.group}' " if options.group
20
+ cmd += "--ssh-keys #{options.ssh_keys.join(',')} "
21
+ cmd += '--output json'
22
+
23
+ result = Pvcglue.system_get_stdout(cmd, true)
24
+ array_data = JSON.parse(result)
25
+ array_data.each { |h| h['size_data'] = h.delete('size') }
26
+ data = []
27
+ array_data.each do |machine|
28
+ data << ::SafeMash.new(machine)
29
+ end
30
+ droplet = data.first
31
+ Pvcglue.verbose? { data.inspect }
19
32
  Pvcglue.logger.debug("Created Digital Ocean droplet, ID: #{droplet.id}")
20
33
  droplet
21
34
  end
22
35
 
36
+ # def create(options)
37
+ # validate_options!(options, %w(name region capacity image))
38
+ # byebug
39
+ # opts = options.to_h
40
+ # opts[:size] = opts.delete('capacity')
41
+ #
42
+ # droplet_options = DropletKit::Droplet.new(opts)
43
+ # droplet = client.droplets.create(droplet_options)
44
+ # Pvcglue.logger.debug("Created Digital Ocean droplet, ID: #{droplet.id}")
45
+ # droplet
46
+ # end
47
+ #
23
48
  def ready?(minion)
24
49
  droplet = find_by_name(minion.machine_name)
25
50
 
@@ -53,9 +78,40 @@ module Pvcglue
53
78
  get_ip_addresses(droplet).public
54
79
  end
55
80
 
81
+ def run(cmd)
82
+ result = Pvcglue.system_get_stdout(cmd, true)
83
+ array_data = JSON.parse(result)
84
+ array_data.each { |h| h['size_data'] = h.delete('size') }
85
+ data = []
86
+ array_data.each do |machine|
87
+ data << ::SafeMash.new(machine)
88
+ end
89
+ Pvcglue.verbose? { data.inspect }
90
+ [data, request_error(data)]
91
+ end
92
+
93
+ def request_error(data)
94
+ # TODO: Better error handling
95
+ return nil # for now
96
+ # return nil unless data.errors
97
+ # raise data.errors.values.join('. ')
98
+ end
99
+
56
100
  def droplets
57
- client.droplets.all
101
+ # client.droplets.all
58
102
  # @droplets ||= client.droplets.all
103
+ # return nil
104
+ cmd = 'doctl compute droplet list --output json'
105
+
106
+ result = Pvcglue.system_get_stdout(cmd, true)
107
+ array_data = JSON.parse(result)
108
+ array_data.each { |h| h['size_data'] = h.delete('size') }
109
+ data = []
110
+ array_data.each do |machine|
111
+ data << ::SafeMash.new(machine)
112
+ end
113
+ Pvcglue.verbose? { data.inspect }
114
+ data
59
115
  end
60
116
 
61
117
  def client
@@ -1,11 +1,14 @@
1
1
  module Pvcglue
2
2
  class CloudProviders
3
- REQUIRED_OPTIONS = %w(name region capacity image)
4
-
5
3
  class Linode < Pvcglue::CloudProviders
6
4
 
5
+ def initialize(provider_options)
6
+ @options = provider_options
7
+ end
8
+
7
9
  def create(options)
8
- validate_options!(options)
10
+ # TODO: Set machine name. Example: "thekraken-alpha@ubuntu:~$" should be "thekraken-alpha@staging-web:~$"
11
+ validate_options!(options, %w(name region capacity image))
9
12
 
10
13
  cmd = "linode create #{options.name} "
11
14
  cmd += "--location #{options.region} "
@@ -162,10 +162,10 @@ module Pvcglue
162
162
 
163
163
  end
164
164
 
165
- def write_to_file_from_template(user, template_file_name, file, owner = nil, group = nil, permissions = nil)
165
+ def write_to_file_from_template(user, template_file_name, file, locals = nil, owner = nil, group = nil, permissions = nil)
166
166
  Pvcglue.logger.debug { "Writing to #{file} from template '#{template_file_name}'" }
167
167
  template = Tilt.new(Pvcglue.template_file_name(template_file_name))
168
- data = template.render(self, minion: minion)
168
+ data = template.render(self, {minion: minion}.merge(locals || {}))
169
169
 
170
170
  write_to_file(user, data, file, owner, group, permissions)
171
171
  end
@@ -1,3 +1,8 @@
1
1
  class SafeMash < Hashie::Mash
2
2
  include Hashie::Extensions::Mash::SafeAssignment
3
3
  end
4
+
5
+ # class OverrideMash < Hashie::Mash
6
+ # disable_warnings
7
+ # include Hashie::Extensions::MethodAccessWithOverride
8
+ # end
@@ -57,7 +57,10 @@ module Pvcglue
57
57
  end
58
58
 
59
59
  def set_item(options)
60
- return unless enabled
60
+ unless enabled
61
+ yield
62
+ return
63
+ end
61
64
  options = ::SafeMash.new(options)
62
65
  add_header(3, options.heading)
63
66
  add('----')
@@ -93,38 +93,48 @@ module Pvcglue
93
93
  defaults['RAILS_SECRET_TOKEN'] = SecureRandom.hex(64) # From rails/railties/lib/rails/tasks/misc.rake
94
94
  defaults['SECRET_KEY_BASE'] = SecureRandom.hex(64) # From rails/railties/lib/rails/tasks/misc.rake
95
95
 
96
- if Pvcglue.cloud.minions_filtered('pg').any?
96
+ # Don't set a default until we have a provisioned server
97
+ if db_host
97
98
  defaults['DB_USER_POSTGRES_HOST'] = db_host
98
- defaults['DB_USER_POSTGRES_PORT'] = "5432"
99
+ defaults['DB_USER_POSTGRES_PORT'] = '5432'
99
100
  defaults['DB_USER_POSTGRES_USERNAME'] = "#{Pvcglue.cloud.app_name}_#{Pvcglue.cloud.stage_name_validated}"
100
101
  defaults['DB_USER_POSTGRES_DATABASE'] = "#{Pvcglue.cloud.app_name}_#{Pvcglue.cloud.stage_name_validated}"
101
102
  defaults['DB_USER_POSTGRES_PASSWORD'] = new_password
102
103
  end
103
104
 
104
- if Pvcglue.cloud.minions_filtered('memcached').any?
105
+ # Don't set a default until we have a provisioned server
106
+ if memcached_host
105
107
  defaults['MEMCACHE_SERVERS'] = memcached_host
106
108
  end
107
109
 
108
- if Pvcglue.cloud.minions_filtered('redis').any?
109
- defaults['REDIS_SERVER'] = redis_host
110
+ # Don't set a default until we have a provisioned server
111
+ if redis_url
112
+ defaults['REDIS_URL'] = redis_url
110
113
  end
111
114
 
112
115
  defaults
113
116
  end
114
117
 
115
118
  def self.db_host
116
- # Assume 1 pg server
117
- Pvcglue.cloud.minions_filtered('pg').values.first.private_ip
119
+ # Assume first pg server
120
+ minion = Pvcglue.cloud.minions_filtered('pg').values.first
121
+ minion && minion.private_ip
118
122
  end
119
123
 
120
124
  def self.memcached_host
121
- node = Pvcglue.cloud.find_minion_by_name('memcached', false)
122
- node ? "#{node['memcached']['private_ip']}:11211" : ""
123
- end
124
-
125
- def self.redis_host
126
- node = Pvcglue.cloud.find_minion_by_name('redis', false)
127
- node ? "#{node['redis']['private_ip']}:6379" : ""
125
+ # Assume first server
126
+ minion = Pvcglue.cloud.minions_filtered('memcached').values.first
127
+ ip = minion && minion.private_ip
128
+ ip ? "#{ip}:11211" : nil
129
+ end
130
+
131
+ def self.redis_url
132
+ # Assume first server
133
+ # TODO: (low priority) check all other apps/environments to see if
134
+ # the current database number is used, and find an unused one, if it is.
135
+ minion = Pvcglue.cloud.minions_filtered('redis').values.first
136
+ ip = minion && minion.private_ip
137
+ ip ? "redis://#{ip}:6379/0" : nil
128
138
  end
129
139
 
130
140
  def self.new_password
@@ -4,6 +4,7 @@ module Pvcglue
4
4
 
5
5
  class Manager < Thor
6
6
 
7
+ # TODO: Rename to 'build'?
7
8
  desc "bootstrap", "bootstrap"
8
9
 
9
10
  def bootstrap
@@ -48,12 +49,9 @@ module Pvcglue
48
49
  desc "shell", "run shell"
49
50
 
50
51
  def sh # `shell` is a Thor reserved word
51
- working_dir = self.class.manager_dir
52
- cloud_manager = Pvcglue.configuration.cloud_manager
53
- user_name = self.class.user_name
54
- cloud_name = Pvcglue.configuration.cloud_name
55
- puts "Connection to #{cloud_name} cloud on manager at (#{cloud_manager}) as user '#{user_name}'..."
56
- system(%(ssh -p #{Pvcglue.cloud.port_in_context(:manager)} -t #{user_name}@#{cloud_manager} "cd #{working_dir} && bash -i"))
52
+ minion = Pvcglue.cloud.manager_minion
53
+ Pvcglue.logger.warn("Connecting to #{minion.machine_name} (#{minion.public_ip}) as user '#{minion.remote_user_name}'...")
54
+ minion.connection.ssh!(minion.remote_user_name, '', '')
57
55
  end
58
56
 
59
57
  desc "user PATH_TO_FILE", "add or update user's ssh key to allow access to the manager"