pvcglue 0.9.3 → 0.9.4
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.
- checksums.yaml +4 -4
- data/bin/pvc +4 -0
- data/lib/pvcglue.rb +55 -15
- data/lib/pvcglue/cli.rb +14 -2
- data/lib/pvcglue/cloud.rb +42 -26
- data/lib/pvcglue/cloud_providers.rb +34 -0
- data/lib/pvcglue/cloud_providers/digital_ocean.rb +79 -0
- data/lib/pvcglue/cloud_providers/linode.rb +129 -0
- data/lib/pvcglue/configuration.rb +5 -0
- data/lib/pvcglue/connection.rb +2 -0
- data/lib/pvcglue/docs.rb +97 -0
- data/lib/pvcglue/manager.rb +0 -1
- data/lib/pvcglue/minion.rb +30 -22
- data/lib/pvcglue/packages.rb +4 -0
- data/lib/pvcglue/packages/apt_repos.rb +33 -4
- data/lib/pvcglue/packages/authorized_keys.rb +24 -8
- data/lib/pvcglue/packages/firewall.rb +1 -0
- data/lib/pvcglue/packages/load_balancer.rb +4 -1
- data/lib/pvcglue/packages/manager.rb +24 -3
- data/lib/pvcglue/packages/ssh_key_check.rb +7 -1
- data/lib/pvcglue/packages/ssl.rb +13 -3
- data/lib/pvcglue/pvcify.rb +1 -1
- data/lib/pvcglue/stack.rb +38 -42
- data/lib/pvcglue/templates/pvc_manager.toml.erb +182 -0
- data/lib/pvcglue/version.rb +1 -1
- data/pvcglue.gemspec +4 -2
- metadata +22 -25
- data/lib/pvcglue/all_the_things.rb +0 -7
- data/lib/pvcglue/digital_ocean.rb +0 -21
@@ -60,6 +60,7 @@ module Pvcglue
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def configure_manager
|
63
|
+
byebug
|
63
64
|
manager = ask('What is the IP address or host name of the manager?')
|
64
65
|
default = !no?('Will this be the default manager? (Y/n)')
|
65
66
|
file_name = default ? user_file_name : project_file_name
|
@@ -145,6 +146,10 @@ module Pvcglue
|
|
145
146
|
File.join(tmp_dir, 'pvcglue')
|
146
147
|
end
|
147
148
|
|
149
|
+
def template_override_dir
|
150
|
+
File.join(application_dir, 'config', 'pvcglue', 'templates')
|
151
|
+
end
|
152
|
+
|
148
153
|
def cloud_cache_file_name
|
149
154
|
# Just in case the Rails project hasn't yet been run, make sure the tmp
|
150
155
|
# dir exists.
|
data/lib/pvcglue/connection.rb
CHANGED
@@ -75,6 +75,7 @@ module Pvcglue
|
|
75
75
|
|
76
76
|
def system_command?(cmd)
|
77
77
|
Pvcglue.logger.debug { cmd }
|
78
|
+
Pvcglue.docs.log_cmd(cmd)
|
78
79
|
system(cmd)
|
79
80
|
Pvcglue.logger.debug { "exit_code=#{$?.exitstatus}" }
|
80
81
|
$?
|
@@ -170,6 +171,7 @@ module Pvcglue
|
|
170
171
|
end
|
171
172
|
|
172
173
|
def write_to_file(user, data, file, owner = nil, group = nil, permissions = nil)
|
174
|
+
Pvcglue.docs.log_file_write(user: user, style: :shell, data: data, file: file, owner: owner, group: group, permissions: permissions)
|
173
175
|
tmp_file = Tempfile.new('pvc')
|
174
176
|
begin
|
175
177
|
tmp_file.write(data)
|
data/lib/pvcglue/docs.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
module Pvcglue
|
2
|
+
class Docs
|
3
|
+
ROLLS = {
|
4
|
+
lb: 'Load Balancer'
|
5
|
+
}
|
6
|
+
|
7
|
+
def initialize(enabled)
|
8
|
+
@enabled = enabled
|
9
|
+
@data = []
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :enabled, :data, :current_minion, :collecting
|
13
|
+
|
14
|
+
def add(s)
|
15
|
+
return unless collecting
|
16
|
+
data << s
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_paragraph(text)
|
20
|
+
add('')
|
21
|
+
add(text)
|
22
|
+
add('')
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_annotation(text)
|
26
|
+
add('')
|
27
|
+
add("> #{text}")
|
28
|
+
add('')
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def level_1_roles(minion)
|
33
|
+
return unless enabled
|
34
|
+
self.current_minion = minion
|
35
|
+
if minion
|
36
|
+
self.collecting = true
|
37
|
+
level_1(minion.roles.map { |role| ROLLS[role.to_sym] || role }.join(', '))
|
38
|
+
else
|
39
|
+
self.collecting = false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def level_1(heading)
|
44
|
+
return unless enabled
|
45
|
+
add_header(1, heading)
|
46
|
+
end
|
47
|
+
|
48
|
+
def level_2(heading)
|
49
|
+
return unless enabled
|
50
|
+
add_header(2, heading)
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_header(level, heading)
|
54
|
+
return unless enabled
|
55
|
+
add('')
|
56
|
+
add("#{'#'*level} #{heading}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_item(options)
|
60
|
+
return unless enabled
|
61
|
+
options = ::SafeMash.new(options)
|
62
|
+
add_header(3, options.heading)
|
63
|
+
add('----')
|
64
|
+
yield
|
65
|
+
add_paragraph(options.body) if options.body && options.body.present?
|
66
|
+
if options.reference && options.reference.present?
|
67
|
+
add('')
|
68
|
+
add("See also: [#{options.reference}](#{options.reference})")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def log_file_write(options)
|
73
|
+
return unless enabled
|
74
|
+
options = ::SafeMash.new(options)
|
75
|
+
add_annotation("Write data to `#{options.file}`")
|
76
|
+
add_block(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def log_cmd(cmd)
|
80
|
+
return unless enabled
|
81
|
+
# add_block(data: cmd, style: 'shell')
|
82
|
+
add("> `> #{cmd}`<br />")
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_block(options)
|
86
|
+
options = ::SafeMash.new(options)
|
87
|
+
add("```#{options.style}")
|
88
|
+
add(options.data)
|
89
|
+
add('```')
|
90
|
+
end
|
91
|
+
|
92
|
+
def done
|
93
|
+
return unless enabled
|
94
|
+
File.write('/home/andrew/projects/slate-pvc/source/includes/_logs.md', data.join("\n"))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/pvcglue/manager.rb
CHANGED
data/lib/pvcglue/minion.rb
CHANGED
@@ -3,10 +3,11 @@ module Pvcglue
|
|
3
3
|
include Hashie::Extensions::Mash::SafeAssignment
|
4
4
|
|
5
5
|
def build!
|
6
|
-
#
|
6
|
+
minion = self # for readability
|
7
|
+
|
7
8
|
Pvcglue.logger.info('BUILD') { "Building #{machine_name}" }
|
9
|
+
Pvcglue.docs.level_1_roles(minion)
|
8
10
|
|
9
|
-
minion = self # for readability
|
10
11
|
Pvcglue::Packages::SshKeyCheck.apply(minion)
|
11
12
|
Pvcglue::Packages::AptRepos.apply(minion)
|
12
13
|
Pvcglue::Packages::AptUpdate.apply(minion)
|
@@ -37,40 +38,47 @@ module Pvcglue
|
|
37
38
|
public_ip.present?
|
38
39
|
end
|
39
40
|
|
41
|
+
def pvc_cloud_provider
|
42
|
+
byebug unless machine_options.cloud_provider || default_cloud_provider
|
43
|
+
@pvc_cloud_provider ||= Pvcglue::CloudProviders.init(machine_options.cloud_provider || default_cloud_provider)
|
44
|
+
end
|
45
|
+
|
40
46
|
def create!
|
41
|
-
|
42
|
-
Pvcglue.logger.warn("Provisioning a machine for #{machine_name} on #{cloud_provider.name}...")
|
47
|
+
Pvcglue.logger.warn("Provisioning a machine for #{machine_name} on #{pvc_cloud_provider.name}...")
|
43
48
|
|
44
49
|
# TODO: Tags. production, staging, load-balancer, web, worker, database, postgress, cache, memcache...
|
50
|
+
|
45
51
|
name = machine_name
|
46
|
-
|
47
|
-
image =
|
48
|
-
region =
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
capacity = pvc_cloud_provider.options.capacity
|
53
|
+
image = pvc_cloud_provider.options.image
|
54
|
+
region = pvc_cloud_provider.options.region
|
55
|
+
if pvc_cloud_provider.options.initial_users
|
56
|
+
ssh_keys = pvc_cloud_provider.options.initial_users.map { |description, ssh_key| ssh_key }
|
57
|
+
else
|
58
|
+
ssh_keys = []
|
59
|
+
end
|
60
|
+
# backups = cloud_provider.backups.nil? ? true : cloud_provider.backups # default to true -- safety first!
|
61
|
+
backups = pvc_cloud_provider.options.backups.nil? ? true : machine_options.cloud_provider.backups # default to true -- safety first!
|
62
|
+
tags = pvc_cloud_provider.options.tags
|
63
|
+
group = pvc_cloud_provider.options.group
|
64
|
+
|
65
|
+
options = ::SafeMash.new(
|
59
66
|
name: name,
|
60
67
|
region: region,
|
61
68
|
image: image,
|
62
|
-
|
69
|
+
capacity: capacity,
|
63
70
|
ssh_keys: ssh_keys,
|
64
71
|
backups: backups,
|
65
72
|
ipv6: false,
|
66
73
|
private_networking: true,
|
67
74
|
user_data: '',
|
68
75
|
monitoring: true,
|
69
|
-
tags: tags
|
76
|
+
tags: tags,
|
77
|
+
group: group,
|
70
78
|
)
|
71
|
-
|
72
|
-
|
73
|
-
|
79
|
+
|
80
|
+
machine = pvc_cloud_provider.create(options)
|
81
|
+
self.machine = machine
|
74
82
|
true
|
75
83
|
end
|
76
84
|
|
data/lib/pvcglue/packages.rb
CHANGED
@@ -21,13 +21,41 @@ module Pvcglue
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def install!
|
24
|
+
docs.level_2('Repositories')
|
25
|
+
|
26
|
+
# TODO: Make this a package that checks for the existence of software-properties-common
|
27
|
+
#echo 'Acquire::ForceIPv4 "true";' | tee /etc/apt/apt.conf.d/99force-ipv4
|
28
|
+
docs.set_item(
|
29
|
+
heading: 'Force use of IPv4',
|
30
|
+
body: 'Needed for Linode as there were intermittent problems connecting to their repositories over IPv6 (February 2017)'
|
31
|
+
) do
|
32
|
+
connection.write_to_file(:root, 'Acquire::ForceIPv4 "true";', '/etc/apt/apt.conf.d/99force-ipv4')
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
docs.set_item(
|
37
|
+
heading: 'Update Repositories'
|
38
|
+
) do
|
39
|
+
connection.run!(:root, '', 'apt update -y')
|
40
|
+
end
|
41
|
+
docs.set_item(
|
42
|
+
heading: 'Install Requirements',
|
43
|
+
body: 'Install the requirements for adding more repositories'
|
44
|
+
) do
|
45
|
+
connection.run!(:root, '', 'apt install -y software-properties-common python-software-properties')
|
46
|
+
end
|
24
47
|
# These could be refactored into packages. :)
|
25
48
|
|
26
49
|
if nginx_needed?
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
50
|
+
docs.set_item(
|
51
|
+
heading: 'Nginx',
|
52
|
+
body: 'Install the latest version using the Phusion repos.',
|
53
|
+
reference: 'https://www.phusionpassenger.com/library/install/nginx/install/oss/xenial/'
|
54
|
+
) do
|
55
|
+
connection.run!(:root, '', 'apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7')
|
56
|
+
connection.run!(:root, '', 'apt-get install -y apt-transport-https ca-certificates')
|
57
|
+
connection.write_to_file(:root, PASSENGER_SOURCES_LIST_DATA, PASSENGER_SOURCES_LIST_FILENAME)
|
58
|
+
end
|
31
59
|
end
|
32
60
|
|
33
61
|
if node_js_needed?
|
@@ -40,6 +68,7 @@ module Pvcglue
|
|
40
68
|
connection.run!(:root, '', 'add-apt-repository "deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main"')
|
41
69
|
connection.run!(:root, '', 'wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -')
|
42
70
|
end
|
71
|
+
# byebug # docs resume here
|
43
72
|
|
44
73
|
set_minion_state(:apt_repos_updated_at, Time.now.utc)
|
45
74
|
end
|
@@ -11,22 +11,38 @@ module Pvcglue
|
|
11
11
|
|
12
12
|
def install!
|
13
13
|
# TODO: Safety check to see if user is locking himself out. :)
|
14
|
-
|
15
|
-
if data.count == 0
|
16
|
-
raise('No authorized keys found for root users!')
|
14
|
+
if manager_first_bootstrap?
|
17
15
|
# TODO: work out system for pvc-manager access
|
18
16
|
data = [`cat ~/.ssh/id_rsa.pub`.strip]
|
17
|
+
else
|
18
|
+
data = minion.get_root_authorized_keys_data
|
19
|
+
if data.count == 0
|
20
|
+
raise('No authorized keys found for root users!')
|
21
|
+
end
|
19
22
|
end
|
20
23
|
connection.write_to_file(:root, data.join("\n"), '/root/.ssh/authorized_keys')
|
21
24
|
|
22
25
|
connection.mkdir_p(:root, "/home/#{user_name}/.ssh", user_name, user_name, '0700')
|
23
|
-
|
24
|
-
if
|
25
|
-
raise('No authorized keys found for users!')
|
26
|
-
# TODO: work out system for pvc-manager access
|
26
|
+
|
27
|
+
if manager_first_bootstrap?
|
27
28
|
data = [`cat ~/.ssh/id_rsa.pub`.strip]
|
29
|
+
else
|
30
|
+
data = minion.get_users_authorized_keys_data
|
31
|
+
if data.count == 0
|
32
|
+
raise('No authorized keys found for users!')
|
33
|
+
# TODO: work out system for pvc-manager access
|
34
|
+
end
|
28
35
|
end
|
29
|
-
connection.write_to_file(:root, data.join("\n"),
|
36
|
+
connection.write_to_file(:root, data.join("\n"), user_authorized_keys_file_name, user_name, user_name, '0644')
|
37
|
+
end
|
38
|
+
|
39
|
+
def user_authorized_keys_file_name
|
40
|
+
"/home/#{user_name}/.ssh/authorized_keys"
|
41
|
+
end
|
42
|
+
|
43
|
+
def manager_first_bootstrap?
|
44
|
+
return false unless has_role?(:manager)
|
45
|
+
@manager_first_bootstrap ||= !Pvcglue::Packages::Manager.configuration_exists?
|
30
46
|
end
|
31
47
|
end
|
32
48
|
end
|
@@ -21,6 +21,7 @@ module Pvcglue
|
|
21
21
|
unless has_role?(:manager)
|
22
22
|
minion.cloud.minions.each do |other_minion_name, other_minion|
|
23
23
|
next if other_minion_name == minion.machine_name
|
24
|
+
next unless other_minion.provisioned?
|
24
25
|
connection.run!(:root, '', "ufw allow from #{other_minion.private_ip}")
|
25
26
|
end
|
26
27
|
end
|
@@ -6,6 +6,8 @@ module Pvcglue
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def install!
|
9
|
+
sync_maintenance_files
|
10
|
+
|
9
11
|
if Pvcglue.cloud.ssl_mode == :acme && !connection.file_exists?(:root, Pvcglue.cloud.nginx_ssl_crt_file_name)
|
10
12
|
# Don't include the SSL stuff in the Nginx config until we have a cert,
|
11
13
|
# but Nginx has to be configured to get the cert from Let's Encrypt
|
@@ -24,7 +26,6 @@ module Pvcglue
|
|
24
26
|
nginx_restart!
|
25
27
|
end
|
26
28
|
|
27
|
-
sync_maintenance_files
|
28
29
|
end
|
29
30
|
|
30
31
|
def nginx_restart!
|
@@ -56,10 +57,12 @@ module Pvcglue
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def sync_maintenance_files
|
60
|
+
Pvcglue.logger.debug { 'Synchronizing maintenance mode files' }
|
59
61
|
source_dir = Pvcglue.configuration.app_maintenance_files_dir
|
60
62
|
dest_dir = Pvcglue.cloud.maintenance_files_dir
|
61
63
|
maintenance_file_name = File.join(source_dir, 'maintenance.html')
|
62
64
|
unless File.exists?(maintenance_file_name)
|
65
|
+
Pvcglue.logger.debug { 'Creating default maintenance mode files' }
|
63
66
|
# TODO: Make this use a template
|
64
67
|
`mkdir -p #{source_dir}`
|
65
68
|
File.write(maintenance_file_name, '-Maintenance Mode-')
|
@@ -48,6 +48,14 @@ module Pvcglue
|
|
48
48
|
new.get_configuration
|
49
49
|
end
|
50
50
|
|
51
|
+
def self.configuration_exists?
|
52
|
+
new.configuration_exists?
|
53
|
+
end
|
54
|
+
|
55
|
+
def configuration_exists?
|
56
|
+
connection.file_exists?(user_name, ::Pvcglue::Manager.manager_file_name)
|
57
|
+
end
|
58
|
+
|
51
59
|
def get_configuration
|
52
60
|
# if connection.file_exists?(user_name, ::Pvcglue::Manager.manager_file_name)
|
53
61
|
# data = connection.read_from_file(user_name, ::Pvcglue::Manager.manager_file_name)
|
@@ -56,8 +64,12 @@ module Pvcglue
|
|
56
64
|
# raise("Remote manager file not found: #{::Pvcglue::Manager.manager_file_name}")
|
57
65
|
# end
|
58
66
|
data = '' # to use `data` in block
|
59
|
-
Pvcglue.
|
60
|
-
data =
|
67
|
+
if Pvcglue.command_line_options[:cloud_manager_override]
|
68
|
+
data = File.read(Pvcglue.command_line_options[:cloud_manager_override])
|
69
|
+
else
|
70
|
+
Pvcglue.filter_verbose do
|
71
|
+
data = connection.read_from_file(user_name, ::Pvcglue::Manager.manager_file_name)
|
72
|
+
end
|
61
73
|
end
|
62
74
|
::Pvcglue.cloud.data = TOML.parse(data)
|
63
75
|
end
|
@@ -67,6 +79,13 @@ module Pvcglue
|
|
67
79
|
end
|
68
80
|
|
69
81
|
def push_configuration
|
82
|
+
raise('Not supported for local manager') if Pvcglue.command_line_options[:cloud_manager_override]
|
83
|
+
|
84
|
+
test_data = File.read(::Pvcglue.cloud.local_file_name)
|
85
|
+
TOML.parse(test_data) # At least make sure it's valid TOML
|
86
|
+
|
87
|
+
# TODO: More in-depth validations
|
88
|
+
|
70
89
|
connection.upload_file(user_name, ::Pvcglue.cloud.local_file_name, ::Pvcglue::Manager.manager_file_name, nil, nil, '600')
|
71
90
|
git_commit!
|
72
91
|
raise('Error saving configuration') unless working_directory_clean?
|
@@ -92,8 +111,10 @@ module Pvcglue
|
|
92
111
|
if connection.file_exists?(user_name, ::Pvcglue::Manager.manager_file_name)
|
93
112
|
data = connection.read_from_file(user_name, ::Pvcglue::Manager.manager_file_name)
|
94
113
|
else
|
95
|
-
|
114
|
+
template = Tilt.new(Pvcglue.template_file_name('pvc_manager.toml.erb'))
|
115
|
+
data = template.render(self, minion: minion)
|
96
116
|
end
|
117
|
+
|
97
118
|
file_name = ::Pvcglue.cloud.local_file_name
|
98
119
|
if File.exist?(file_name)
|
99
120
|
backup_file_name = ::Pvcglue.configuration.versioned_filename(file_name)
|
@@ -2,8 +2,14 @@ module Pvcglue
|
|
2
2
|
class Packages
|
3
3
|
class SshKeyCheck < Pvcglue::Packages
|
4
4
|
def installed?
|
5
|
+
docs.level_2('Status')
|
5
6
|
# This has the side effect of adding the server to known_hosts file, to prevent needing interactive prompt
|
6
|
-
|
7
|
+
docs.set_item(
|
8
|
+
heading: 'Verify Connection',
|
9
|
+
body: 'Connect to the machine and wait until it''s ready (with automatic retry). Also add the machine to the `known_hosts` file, to prevent an interactive prompt'
|
10
|
+
) do
|
11
|
+
connection.ssh_retry_wait(:root, '-o strictHostKeyChecking=no', 'echo', 30, 1)
|
12
|
+
end
|
7
13
|
true
|
8
14
|
end
|
9
15
|
end
|
data/lib/pvcglue/packages/ssl.rb
CHANGED
@@ -21,9 +21,19 @@ module Pvcglue
|
|
21
21
|
force_option = Pvcglue.command_line_options[:force_cert] ? '--force ' : ''
|
22
22
|
debug_option = Pvcglue.logger.level == 0 ? '--debug ' : ''
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
begin
|
25
|
+
|
26
|
+
# Test with http://www.example.com/.well-known/acme-challenge/test.html
|
27
|
+
base_name = "test-#{SecureRandom.hex}.html"
|
28
|
+
verification_file_name = File.join(Pvcglue.cloud.letsencrypt_full, base_name)
|
29
|
+
connection.write_to_file(:root, "Everything's shiny, Cap'n. Not to fret.", verification_file_name, 'root', 'www-data', '660')
|
30
|
+
|
31
|
+
unless Net::HTTP.get(first_domain, "/.well-known/acme-challenge/#{base_name}") =~ /shiny/
|
32
|
+
Pvcglue.logger.error("Unable to connect to #{first_domain} at #{minion.public_ip}")
|
33
|
+
raise(Thor::Error, 'Please fix and then restart.')
|
34
|
+
end
|
35
|
+
ensure
|
36
|
+
# TODO: Delete verification file
|
27
37
|
end
|
28
38
|
|
29
39
|
result = connection.ssh?(:root, '', "/root/.acme.sh/acme.sh #{debug_option}#{staging_option}#{force_option}--issue #{domain_options} -w #{Pvcglue.cloud.letsencrypt_root}")
|