pvcglue 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
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"