pvcglue 0.9.4 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -4
- data/bin/pvc +2 -2
- data/lib/pvcglue.rb +3 -2
- data/lib/pvcglue/cli.rb +37 -2
- data/lib/pvcglue/cloud.rb +15 -1
- data/lib/pvcglue/cloud_providers.rb +4 -2
- data/lib/pvcglue/cloud_providers/digital_ocean.rb +66 -10
- data/lib/pvcglue/cloud_providers/linode.rb +6 -3
- data/lib/pvcglue/connection.rb +2 -2
- data/lib/pvcglue/custom_hashie.rb +5 -0
- data/lib/pvcglue/docs.rb +4 -1
- data/lib/pvcglue/env.rb +24 -14
- data/lib/pvcglue/manager.rb +4 -6
- data/lib/pvcglue/minion.rb +65 -6
- data/lib/pvcglue/packages.rb +36 -4
- data/lib/pvcglue/packages/apt.rb +4 -0
- data/lib/pvcglue/packages/apt_repos.rb +15 -0
- data/lib/pvcglue/packages/authorized_keys.rb +46 -19
- data/lib/pvcglue/packages/firewall.rb +6 -2
- data/lib/pvcglue/packages/manager.rb +5 -2
- data/lib/pvcglue/packages/postgresql.rb +5 -2
- data/lib/pvcglue/packages/redis.rb +59 -0
- data/lib/pvcglue/packages/rvm.rb +1 -1
- data/lib/pvcglue/packages/secrets.rb +7 -1
- data/lib/pvcglue/packages/sidekiq_workers.rb +89 -0
- data/lib/pvcglue/packages/slack.rb +39 -0
- data/lib/pvcglue/packages/web.rb +2 -1
- data/lib/pvcglue/packages/worker.rb +25 -0
- data/lib/pvcglue/templates/capfile.erb +3 -1
- data/lib/pvcglue/templates/deploy.rb.erb +10 -95
- data/lib/pvcglue/templates/redis.conf.erb +807 -171
- data/lib/pvcglue/templates/sidekiq.service.erb +33 -0
- data/lib/pvcglue/templates/slacktee.erb +7 -0
- data/lib/pvcglue/templates/sshd_config.erb +7 -7
- data/lib/pvcglue/templates/stage-deploy.rb.erb +66 -8
- data/lib/pvcglue/templates/sysctl.conf.erb +62 -0
- data/lib/pvcglue/version.rb +1 -1
- data/pvcglue.gemspec +2 -1
- metadata +25 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: caa789ef881164fe12fd0afa7ea98479ab5a70e4
|
4
|
+
data.tar.gz: 1cb97f3b5a5d13984e2b135652a722e149bf63bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
-
|
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
|
data/lib/pvcglue.rb
CHANGED
@@ -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
|
|
data/lib/pvcglue/cli.rb
CHANGED
@@ -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(
|
68
|
-
data = Pvcglue.cloud.minions_filtered(
|
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
|
data/lib/pvcglue/cloud.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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} "
|
data/lib/pvcglue/connection.rb
CHANGED
@@ -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
|
data/lib/pvcglue/docs.rb
CHANGED
data/lib/pvcglue/env.rb
CHANGED
@@ -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
|
-
|
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'] =
|
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
|
-
|
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
|
-
|
109
|
-
|
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
|
117
|
-
Pvcglue.cloud.minions_filtered('pg').values.first
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
data/lib/pvcglue/manager.rb
CHANGED
@@ -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
|
-
|
52
|
-
|
53
|
-
|
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"
|