formatron 0.1.14 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/formatron.gemspec +2 -1
  3. data/lib/formatron.rb +23 -10
  4. data/lib/formatron/aws.rb +26 -13
  5. data/lib/formatron/chef.rb +53 -9
  6. data/lib/formatron/chef/berkshelf.rb +15 -11
  7. data/lib/formatron/chef/keys.rb +5 -9
  8. data/lib/formatron/chef/knife.rb +39 -30
  9. data/lib/formatron/chef/ssh.rb +4 -1
  10. data/lib/formatron/chef/winrm.rb +37 -0
  11. data/lib/formatron/chef_clients.rb +6 -4
  12. data/lib/formatron/cli/generators/credentials.rb +1 -1
  13. data/lib/formatron/cloud_formation/resources/ec2.rb +58 -57
  14. data/lib/formatron/cloud_formation/scripts.rb +75 -3
  15. data/lib/formatron/cloud_formation/template.rb +6 -0
  16. data/lib/formatron/cloud_formation/template/vpc.rb +8 -0
  17. data/lib/formatron/cloud_formation/template/vpc/subnet.rb +8 -0
  18. data/lib/formatron/cloud_formation/template/vpc/subnet/bastion.rb +12 -0
  19. data/lib/formatron/cloud_formation/template/vpc/subnet/chef_server.rb +12 -0
  20. data/lib/formatron/cloud_formation/template/vpc/subnet/instance.rb +13 -2
  21. data/lib/formatron/cloud_formation/template/vpc/subnet/instance/security_group.rb +47 -2
  22. data/lib/formatron/cloud_formation/template/vpc/subnet/instance/setup.rb +92 -27
  23. data/lib/formatron/cloud_formation/template/vpc/subnet/nat.rb +12 -0
  24. data/lib/formatron/dsl/formatron/global.rb +2 -0
  25. data/lib/formatron/dsl/formatron/global/windows.rb +17 -0
  26. data/lib/formatron/dsl/formatron/vpc/subnet/instance.rb +1 -0
  27. data/lib/formatron/external/dsl.rb +19 -0
  28. data/lib/formatron/util/winrm.rb +40 -0
  29. data/lib/formatron/version.rb +1 -1
  30. metadata +33 -16
@@ -5,19 +5,18 @@ class Formatron
5
5
  # Download the Chef Server keys
6
6
  class Keys
7
7
  # rubocop:disable Metrics/ParameterLists
8
- def initialize(aws:, bucket:, name:, target:, guid:, ec2_key:)
8
+ def initialize(directory:, aws:, bucket:, name:, target:, guid:, ec2_key:)
9
9
  @aws = aws
10
10
  @bucket = bucket
11
11
  @name = name
12
12
  @target = target
13
13
  @guid = guid
14
14
  @ec2_key = ec2_key
15
+ @directory = directory
15
16
  end
16
17
  # rubocop:enable Metrics/ParameterLists
17
18
 
18
- # rubocop:disable Metrics/MethodLength
19
19
  def init
20
- @directory = Dir.mktmpdir 'formatron-chef-server-keys-'
21
20
  S3::ChefServerKeys.get(
22
21
  aws: @aws,
23
22
  bucket: @bucket,
@@ -29,23 +28,20 @@ class Formatron
29
28
  File.write ec2_key, @ec2_key
30
29
  File.chmod 0600, ec2_key
31
30
  end
32
- # rubocop:enable Metrics/MethodLength
33
31
 
34
32
  def user_key
35
33
  S3::ChefServerKeys.user_pem_path directory: @directory
36
34
  end
37
35
 
38
36
  def organization_key
39
- S3::ChefServerKeys.organization_pem_path directory: @directory
37
+ S3::ChefServerKeys.organization_pem_path(
38
+ directory: @directory
39
+ )
40
40
  end
41
41
 
42
42
  def ec2_key
43
43
  File.join @directory, 'ec2_key'
44
44
  end
45
-
46
- def unlink
47
- FileUtils.rm_rf @directory unless @directory.nil?
48
- end
49
45
  end
50
46
  end
51
47
  end
@@ -7,10 +7,18 @@ class Formatron
7
7
  # Wrapper for the knife cli
8
8
  # rubocop:disable Metrics/ClassLength
9
9
  class Knife
10
+ CONFIG_FILE = 'knife.rb'
11
+ DATABAG_SECRET_FILE = 'databag_secret'
12
+ DATABAG_DIRECTORY = 'databag'
13
+ DATABAG_ITEM_SUFFIX = '.json'
14
+
10
15
  # rubocop:disable Metrics/MethodLength
11
16
  # rubocop:disable Metrics/ParameterLists
12
17
  def initialize(
18
+ directory:,
13
19
  keys:,
20
+ administrator_name:,
21
+ administrator_password:,
14
22
  chef_server_url:,
15
23
  username:,
16
24
  organization:,
@@ -18,7 +26,12 @@ class Formatron
18
26
  databag_secret:,
19
27
  configuration:
20
28
  )
29
+ @knife_file = File.join directory, CONFIG_FILE
30
+ @databag_secret_file = File.join directory, DATABAG_SECRET_FILE
31
+ @databag_directory = File.join directory, DATABAG_DIRECTORY
21
32
  @keys = keys
33
+ @administrator_name = administrator_name
34
+ @administrator_password = administrator_password
22
35
  @chef_server_url = chef_server_url
23
36
  @username = username
24
37
  @organization = organization
@@ -31,8 +44,7 @@ class Formatron
31
44
 
32
45
  # rubocop:disable Metrics/MethodLength
33
46
  def init
34
- @knife_file = Tempfile.new 'formatron-knife-'
35
- @knife_file.write <<-EOH.gsub(/^ {10}/, '')
47
+ File.write @knife_file, <<-EOH.gsub(/^ {10}/, '')
36
48
  chef_server_url '#{@chef_server_url}'
37
49
  validation_client_name '#{@organization}-validator'
38
50
  validation_key '#{@keys.organization_key}'
@@ -41,28 +53,25 @@ class Formatron
41
53
  verify_api_cert #{@ssl_verify}
42
54
  ssl_verify_mode #{@ssl_verify ? ':verify_peer' : ':verify_none'}
43
55
  EOH
44
- @knife_file.close
45
- @databag_secret_file = Tempfile.new 'formatron-databag-secret-'
46
- @databag_secret_file.write @databag_secret
47
- @databag_secret_file.close
56
+ File.write @databag_secret_file, @databag_secret
57
+ FileUtils.mkdir_p @databag_directory
48
58
  end
49
59
  # rubocop:enable Metrics/MethodLength
50
60
 
51
61
  def deploy_databag(name:)
52
- databag_file = Tempfile.new ['formatron-databag-', '.json']
53
- databag_file.write @configuration.merge(id: name).to_json
54
- databag_file.close
62
+ databag_file = File.join(
63
+ @databag_directory, "#{name}#{DATABAG_ITEM_SUFFIX}"
64
+ )
65
+ File.write databag_file, @configuration.merge(id: name).to_json
55
66
  _attempt_to_create_databag unless _databag_exists
56
67
  _attempt_to_create_databag_item(
57
68
  name: name,
58
69
  databag_file: databag_file
59
70
  )
60
- ensure
61
- databag_file.unlink unless databag_file.nil?
62
71
  end
63
72
 
64
73
  def _databag_exists
65
- Util::Shell.exec "knife data bag show formatron -c #{@knife_file.path}"
74
+ Util::Shell.exec "knife data bag show formatron -c #{@knife_file}"
66
75
  end
67
76
 
68
77
  def _attempt_to_create_databag
@@ -70,9 +79,7 @@ class Formatron
70
79
  end
71
80
 
72
81
  def _create_databag
73
- # rubocop:disable Metrics/LineLength
74
- Util::Shell.exec "knife data bag create formatron -c #{@knife_file.path}"
75
- # rubocop:enable Metrics/LineLength
82
+ Util::Shell.exec "knife data bag create formatron -c #{@knife_file}"
76
83
  end
77
84
 
78
85
  def _attempt_to_create_databag_item(name:, databag_file:)
@@ -83,7 +90,7 @@ class Formatron
83
90
 
84
91
  def _create_databag_item(databag_file:)
85
92
  # rubocop:disable Metrics/LineLength
86
- Util::Shell.exec "knife data bag from file formatron #{databag_file.path} --secret-file #{@databag_secret_file.path} -c #{@knife_file.path}"
93
+ Util::Shell.exec "knife data bag from file formatron #{databag_file} --secret-file #{@databag_secret_file} -c #{@knife_file}"
87
94
  # rubocop:enable Metrics/LineLength
88
95
  end
89
96
 
@@ -95,7 +102,7 @@ class Formatron
95
102
 
96
103
  def _environment_exists(environment)
97
104
  # rubocop:disable Metrics/LineLength
98
- Util::Shell.exec "knife environment show #{environment} -c #{@knife_file.path}"
105
+ Util::Shell.exec "knife environment show #{environment} -c #{@knife_file}"
99
106
  # rubocop:enable Metrics/LineLength
100
107
  end
101
108
 
@@ -107,59 +114,61 @@ class Formatron
107
114
 
108
115
  def _create_environment(environment)
109
116
  # rubocop:disable Metrics/LineLength
110
- Util::Shell.exec "knife environment create #{environment} -c #{@knife_file.path} -d '#{environment} environment created by formatron'"
117
+ Util::Shell.exec "knife environment create #{environment} -c #{@knife_file} -d '#{environment} environment created by formatron'"
111
118
  # rubocop:enable Metrics/LineLength
112
119
  end
113
120
 
121
+ # rubocop:disable Metrics/MethodLength
114
122
  def bootstrap(
123
+ os:,
115
124
  guid:,
116
125
  bastion_hostname:,
117
126
  cookbook:,
118
127
  hostname:
119
128
  )
120
129
  # rubocop:disable Metrics/LineLength
121
- command = "knife bootstrap #{hostname} --sudo -x ubuntu -i #{@keys.ec2_key} -E #{guid} -r #{cookbook} -N #{guid} -c #{@knife_file.path}#{@ssl_verify ? '' : ' --node-ssl-verify-mode none'} --secret-file #{@databag_secret_file.path}"
122
- command = "#{command} -G ubuntu@#{bastion_hostname}" unless bastion_hostname.eql? hostname
130
+ if os.eql? 'windows'
131
+ command = "knife bootstrap windows winrm #{hostname} -x #{@administrator_name} -P '#{@administrator_password}' -E #{guid} -r #{cookbook} -N #{guid} -c #{@knife_file} --secret-file #{@databag_secret_file}"
132
+ else
133
+ command = "knife bootstrap #{hostname} --sudo -x ubuntu -i #{@keys.ec2_key} -E #{guid} -r #{cookbook} -N #{guid} -c #{@knife_file}#{@ssl_verify ? '' : ' --node-ssl-verify-mode none'} --secret-file #{@databag_secret_file}"
134
+ command = "#{command} -G ubuntu@#{bastion_hostname}" unless bastion_hostname.eql? hostname
135
+ end
123
136
  fail "failed to bootstrap instance: #{guid}" unless Util::Shell.exec command
124
137
  # rubocop:enable Metrics/LineLength
125
138
  end
139
+ # rubocop:enable Metrics/MethodLength
126
140
 
127
141
  def delete_databag(name:)
128
142
  # rubocop:disable Metrics/LineLength
129
- command = "knife data bag delete formatron #{name} -y -c #{@knife_file.path}"
143
+ command = "knife data bag delete formatron #{name} -y -c #{@knife_file}"
130
144
  fail "failed to delete data bag item: #{name}" unless Util::Shell.exec command
131
145
  # rubocop:enable Metrics/LineLength
132
146
  end
133
147
 
134
148
  def delete_node(node:)
135
- command = "knife node delete #{node} -y -c #{@knife_file.path}"
149
+ command = "knife node delete #{node} -y -c #{@knife_file}"
136
150
  fail "failed to delete node: #{node}" unless Util::Shell.exec command
137
151
  end
138
152
 
139
153
  def delete_client(client:)
140
154
  # rubocop:disable Metrics/LineLength
141
- command = "knife client delete #{client} -y -c #{@knife_file.path}"
155
+ command = "knife client delete #{client} -y -c #{@knife_file}"
142
156
  fail "failed to delete client: #{client}" unless Util::Shell.exec command
143
157
  # rubocop:enable Metrics/LineLength
144
158
  end
145
159
 
146
160
  def delete_environment(environment:)
147
161
  # rubocop:disable Metrics/LineLength
148
- command = "knife environment delete #{environment} -y -c #{@knife_file.path}"
162
+ command = "knife environment delete #{environment} -y -c #{@knife_file}"
149
163
  fail "failed to delete environment: #{environment}" unless Util::Shell.exec command
150
164
  # rubocop:enable Metrics/LineLength
151
165
  end
152
166
 
153
167
  def node_exists?(guid:)
154
- command = "knife node show #{guid} -c #{@knife_file.path}"
168
+ command = "knife node show #{guid} -c #{@knife_file}"
155
169
  Util::Shell.exec command
156
170
  end
157
171
 
158
- def unlink
159
- @knife_file.unlink unless @knife_file.nil?
160
- @databag_secret_file.unlink unless @databag_secret_file.nil?
161
- end
162
-
163
172
  private(
164
173
  :_create_databag,
165
174
  :_create_databag_item,
@@ -11,12 +11,15 @@ class Formatron
11
11
  end
12
12
 
13
13
  def run_chef_client(hostname:, bastion_hostname:)
14
+ # use the first-boot.json to ensure the runlist is correct
15
+ # if the node fails to converge the first time (in which case
16
+ # the server will show an empty run list for the node)
14
17
  Formatron::Util::SSH.exec(
15
18
  hostname: hostname,
16
19
  bastion_hostname: bastion_hostname,
17
20
  user: SSH_USER,
18
21
  key: @keys.ec2_key,
19
- command: 'sudo chef-client'
22
+ command: 'sudo chef-client -j /etc/chef/first-boot.json'
20
23
  )
21
24
  end
22
25
 
@@ -0,0 +1,37 @@
1
+ require 'formatron/util/winrm'
2
+
3
+ class Formatron
4
+ class Chef
5
+ # Perform commands on chef nodes over WinRM
6
+ class WinRM
7
+ def initialize(administrator_name:, administrator_password:)
8
+ @administrator_name = administrator_name
9
+ @administrator_password = administrator_password
10
+ end
11
+
12
+ def run_chef_client(hostname:)
13
+ # use the first-boot.json to ensure the runlist is correct
14
+ # if the node fails to converge the first time (in which case
15
+ # the server will show an empty run list for the node)
16
+ Formatron::Util::WinRM.exec(
17
+ hostname: hostname,
18
+ administrator_name: @administrator_name,
19
+ administrator_password: @administrator_password,
20
+ command: 'chef-client -j C:\chef\first-boot.json'
21
+ )
22
+ end
23
+
24
+ def bootstrapped?(hostname:)
25
+ Formatron::Util::WinRM.exec(
26
+ hostname: hostname,
27
+ administrator_name: @administrator_name,
28
+ administrator_password: @administrator_password,
29
+ command: 'if (-not (Test-Path C:\chef\client.pem)) { exit 1 }'
30
+ )
31
+ true
32
+ rescue
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
@@ -5,11 +5,14 @@ class Formatron
5
5
  # rubocop:disable Metrics/AbcSize
6
6
  # rubocop:disable Metrics/MethodLength
7
7
  def initialize(
8
+ directory:,
8
9
  aws:,
9
10
  bucket:,
10
11
  name:,
11
12
  target:,
12
13
  ec2_key:,
14
+ administrator_name:,
15
+ administrator_password:,
13
16
  hosted_zone_name:,
14
17
  vpc:,
15
18
  external:,
@@ -27,6 +30,7 @@ class Formatron
27
30
  bastions = Hash[bastions.map { |k, v| [k, v.sub_domain] }]
28
31
  chef_servers.each do |key, chef_server|
29
32
  @chef_clients[key] = Chef.new(
33
+ directory: directory,
30
34
  aws: aws,
31
35
  bucket: bucket,
32
36
  name: name,
@@ -36,6 +40,8 @@ class Formatron
36
40
  ssl_verify: chef_server.ssl_verify,
37
41
  chef_sub_domain: chef_server.sub_domain,
38
42
  ec2_key: ec2_key,
43
+ administrator_name: administrator_name,
44
+ administrator_password: administrator_password,
39
45
  bastions: bastions,
40
46
  hosted_zone_name: hosted_zone_name,
41
47
  server_stack: chef_server.stack || name,
@@ -57,9 +63,5 @@ class Formatron
57
63
  def init
58
64
  @chef_clients.values.each(&:init)
59
65
  end
60
-
61
- def unlink
62
- @chef_clients.values.each(&:unlink)
63
- end
64
66
  end
65
67
  end
@@ -7,7 +7,7 @@ class Formatron
7
7
  # CLI command for credentials generator
8
8
  module Credentials
9
9
  def self.dot_credentials
10
- File.join '.formatron', 'credentials.json'
10
+ File.join Formatron::WORKING_DIRECTORY, 'credentials.json'
11
11
  end
12
12
 
13
13
  def self.global_credentials
@@ -1,4 +1,5 @@
1
1
  require_relative '../template'
2
+ require_relative '../scripts'
2
3
 
3
4
  class Formatron
4
5
  module CloudFormation
@@ -274,49 +275,72 @@ class Formatron
274
275
  # rubocop:disable Metrics/ParameterLists
275
276
  # rubocop:disable Metrics/AbcSize
276
277
  def self.instance(
277
- scripts: nil,
278
- script_variables: nil,
279
- files: nil,
280
278
  instance_profile:,
281
279
  availability_zone:,
282
280
  instance_type:,
283
281
  key_name:,
282
+ administrator_name:,
283
+ administrator_password:,
284
284
  subnet:,
285
285
  name:,
286
286
  wait_condition_handle:,
287
287
  security_group:,
288
288
  logical_id:,
289
- source_dest_check:
289
+ source_dest_check:,
290
+ os:
290
291
  )
291
- files ||= {}
292
- scripts.each_index do |index|
293
- files["/tmp/formatron/script-#{index}.sh"] = {
294
- content: scripts[index],
295
- mode: '000755',
296
- owner: 'root',
297
- group: 'root'
298
- }
299
- end unless scripts.nil?
300
- script_variables_content =
301
- script_variables.reduce([]) do |content, (key, value)|
302
- content.concat(["#{key}=", value, "\n"])
303
- end unless script_variables.nil?
304
- files['/tmp/formatron/script-variables'] = {
305
- content: Template.join(*script_variables_content),
306
- mode: '000644',
307
- owner: 'root',
308
- group: 'root'
309
- } unless script_variables_content.nil?
292
+ if os.eql? 'windows'
293
+ user_data = Template.base_64(
294
+ Template.join(
295
+ # rubocop:disable Metrics/LineLength
296
+ "<powershell>\n",
297
+ "try\n",
298
+ "{\n",
299
+ Scripts.windows_administrator(
300
+ name: administrator_name,
301
+ password: administrator_password
302
+ ),
303
+ 'winrm quickconfig -q', "\n",
304
+ "winrm set winrm/config/winrs '@{MaxMemoryPerShellMB=\"1024\"}'", "\n",
305
+ "winrm set winrm/config '@{MaxTimeoutms=\"1800000\"}'", "\n",
306
+ "winrm set winrm/config/service '@{AllowUnencrypted=\"true\"}'", "\n",
307
+ "winrm set winrm/config/service/auth '@{Basic=\"true\"}'", "\n",
308
+ 'netsh advfirewall firewall add rule name="WinRM 5985" protocol=TCP dir=in localport=5985 action=allow', "\n",
309
+ 'netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow', "\n",
310
+ 'Stop-Service winrm', "\n",
311
+ 'Set-Service winrm -startuptype "automatic"', "\n",
312
+ 'Start-Service winrm', "\n",
313
+ 'cfn-init.exe -v -s ', Template.ref('AWS::StackName'),
314
+ " -r #{logical_id}",
315
+ ' --region ', Template.ref('AWS::Region'), "\n",
316
+ "}\n",
317
+ "catch\n",
318
+ "{\n",
319
+ 'cfn-signal.exe -e 1 ',
320
+ Template.base_64(Template.ref(wait_condition_handle)), "\n",
321
+ "}\n",
322
+ '</powershell>'
323
+ # rubocop:enable Metrics/LineLength
324
+ )
325
+ )
326
+ else
327
+ user_data = Template.base_64(
328
+ Template.join(
329
+ # rubocop:disable Metrics/LineLength
330
+ "#!/bin/bash -v\n",
331
+ "apt-get -y update\n",
332
+ "apt-get -y install python-setuptools\n",
333
+ "easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz\n",
334
+ "export PATH=$PATH:/opt/aws/bin\n",
335
+ 'cfn-init --region ', Template.ref('AWS::Region'),
336
+ ' -v -s ', Template.ref('AWS::StackName'), " -r #{logical_id}\n",
337
+ "cfn-signal -e $? -r 'Formatron instance configuration complete' '", Template.ref(wait_condition_handle), "'\n"
338
+ # rubocop:enable Metrics/LineLength
339
+ )
340
+ )
341
+ end
310
342
  {
311
343
  Type: 'AWS::EC2::Instance',
312
- Metadata: {
313
- Comment1: 'Create setup scripts',
314
- 'AWS::CloudFormation::Init' => {
315
- config: {
316
- files: files
317
- }
318
- }
319
- },
320
344
  Properties: {
321
345
  IamInstanceProfile: Template.ref(instance_profile),
322
346
  AvailabilityZone: Template.join(
@@ -326,7 +350,7 @@ class Formatron
326
350
  ImageId: Template.find_in_map(
327
351
  Template::REGION_MAP,
328
352
  Template.ref('AWS::Region'),
329
- 'ami'
353
+ os
330
354
  ),
331
355
  SourceDestCheck: source_dest_check,
332
356
  InstanceType: instance_type,
@@ -337,36 +361,13 @@ class Formatron
337
361
  Key: 'Name',
338
362
  Value: name
339
363
  }],
340
- UserData: Template.base_64(
341
- Template.join(
342
- # rubocop:disable Metrics/LineLength
343
- "#!/bin/bash -v\n",
344
- "function error_exit\n",
345
- "{\n",
346
- " cfn-signal -e 1 -r \"$1\" '", Template.ref(wait_condition_handle), "'\n",
347
- " exit 1\n",
348
- "}\n",
349
- "apt-get -y update\n",
350
- "apt-get -y install python-setuptools\n",
351
- "easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz\n",
352
- "export PATH=$PATH:/opt/aws/bin\n",
353
- 'cfn-init --region ', Template.ref('AWS::Region'),
354
- ' -v -s ', Template.ref('AWS::StackName'), " -r #{logical_id} ",
355
- " || error_exit 'Failed to run cfn-init'\n",
356
- "for file in /tmp/formatron/script-*.sh; do\n",
357
- " $file || error_exit \"failed to run Formatron setup script: $file\"\n",
358
- "done\n",
359
- "# If all went well, signal success\n",
360
- "cfn-signal -e $? -r 'Formatron instance configuration complete' '", Template.ref(wait_condition_handle), "'\n"
361
- # rubocop:enable Metrics/LineLength
362
- )
363
- )
364
+ UserData: user_data
364
365
  }
365
366
  }
366
367
  end
367
- # rubocop:enable Metrics/AbcSize
368
368
  # rubocop:enable Metrics/ParameterLists
369
369
  # rubocop:enable Metrics/MethodLength
370
+ # rubocop:enable Metrics/AbcSize
370
371
  end
371
372
  # rubocop:enable Metrics/ModuleLength
372
373
  end