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.
@@ -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.
@@ -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)
@@ -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
@@ -191,7 +191,6 @@ module Pvcglue
191
191
 
192
192
  def self.push_configuration
193
193
  Pvcglue::Packages::Manager.push_configuration
194
- # Pvcglue::Packages.apply('manager-push'.to_sym, :manager, manager_node, 'pvcglue', 'manager')
195
194
  # clear_cloud_data_cache
196
195
  end
197
196
 
@@ -3,10 +3,11 @@ module Pvcglue
3
3
  include Hashie::Extensions::Mash::SafeAssignment
4
4
 
5
5
  def build!
6
- # puts '*'*175
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
- raise(Thor::Error, "#{cloud_provider.name} unknown") unless cloud_provider.name == 'digital-ocean'
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
- size = capacity || cloud_provider.default_capacity
47
- image = cloud_provider.image
48
- region = cloud_provider.region
49
- # ap cloud_provider.initial_users
50
- # ap cloud_provider
51
- ssh_keys = cloud_provider.initial_users.map { |description, ssh_key| ssh_key }
52
- backups = cloud_provider.backups.nil? ? true : cloud_provider.backups # default to true -- safety first!
53
- tags = cloud_provider.tags
54
-
55
- # client.droplets.all.each do |droplet|
56
- # ap droplet
57
- # end
58
- droplet = DropletKit::Droplet.new(
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
- size: size,
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
- droplet = Pvcglue::DigitalOcean.client.droplets.create(droplet)
72
- self.droplet = droplet
73
- Pvcglue.logger.debug("Droplet ID: #{droplet.id}")
79
+
80
+ machine = pvc_cloud_provider.create(options)
81
+ self.machine = machine
74
82
  true
75
83
  end
76
84
 
@@ -17,6 +17,10 @@ module Pvcglue
17
17
  @errors = []
18
18
  end
19
19
 
20
+ def docs
21
+ Pvcglue.docs
22
+ end
23
+
20
24
  def errors?
21
25
  errors.size > 0
22
26
  end
@@ -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
- # Reference: https://www.phusionpassenger.com/library/install/nginx/install/oss/xenial/
28
- connection.run!(:root, '', 'apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7')
29
- connection.run!(:root, '', 'apt-get install -y apt-transport-https ca-certificates')
30
- connection.write_to_file(:root, PASSENGER_SOURCES_LIST_DATA, PASSENGER_SOURCES_LIST_FILENAME)
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
- data = minion.get_root_authorized_keys_data
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
- data = minion.get_users_authorized_keys_data
24
- if data.count == 0
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"), "/home/#{user_name}/.ssh/authorized_keys", user_name, user_name, '0644')
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.filter_verbose do
60
- data = connection.read_from_file(user_name, ::Pvcglue::Manager.manager_file_name)
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
- data = "# Pvcglue manager configuration file\n\n"
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
- connection.ssh_retry_wait(:root, '-o strictHostKeyChecking=no', 'echo', 30, 1)
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
@@ -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
- unless Net::HTTP.get(first_domain, '/.well-known/acme-challenge/test.html') =~ /shiny/
25
- Pvcglue.logger.error("Unable to connect to #{first_domain} at #{minion.public_ip}")
26
- raise(Thor::Error, 'Please fix and then restart.')
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}")