pvcglue 0.9.3 → 0.9.4

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