chef-metal 0.3.1 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b55bda561c7c8ea4e4ee5634ad368dfb2c42f6ef
4
- data.tar.gz: 03c66248dee33dc38e0c314fef3ea97ec6842212
3
+ metadata.gz: 93940616771e3e091f441bce2587a1383f7859ef
4
+ data.tar.gz: 8e49d7a64afdc1fe06c82b99056accc1b4ae587c
5
5
  SHA512:
6
- metadata.gz: b235456d07ee75d0c30ee8f0b1686d54113d7c936ac057af916238ffc6c9abd4d137dababe6d394a4b017934ad46ceb8d2575f73bf8622ea2a977c7e0304041c
7
- data.tar.gz: 509f41552724c0cdaeb3e8213fa2ae4ac6a7a6b07e308ebe5bf535f4a02ca0003683ec3236d6d98d43fed4243e9c4df7b135a9c5179776f5ef56a77857a4368e
6
+ metadata.gz: 1815d8c7b09c9fdb5fa68bcea2282abe2253bb71e4d3345c12b75774e545dea7532ddb45772f428b4c65b4361fb9006b45e633d12c80cdd780860f89b9b06886
7
+ data.tar.gz: 5bf3f00b97b72d98a9fcbf962c99bcb201afc696ae7ed25f9ef023234442e16cfab6e1a5e0d4256554a4ad3769f881b986be6593ebfb637624361708c2cd77e8
data/README.md CHANGED
@@ -92,7 +92,16 @@ end
92
92
 
93
93
  `vagrant_box` makes sure a particular vagrant box exists, and lets you specify `provisioner_options` for things like port forwarding, OS definitions, and any other vagrant-isms. A more complicated vagrant box, with provisioner options, can be found in [myapp::windows](https://github.com/opscode/chef-metal/blob/master/cookbooks/myapp/recipes/windows.rb).
94
94
 
95
- `with_chef_local_server` is a generic directive that creates a chef-zero server pointed at the given repository. nodes, clients, data bags, and all data will be stored here on your provisioner machine if you do this. You can use `with_chef_server` instead if you want to point at OSS, Hosted or Enterprise Chef, and if you don't specify a Chef server at all, it will use the one you are running chef-client against.
95
+ `with_chef_local_server` is a generic directive that creates a chef-zero server pointed at the given repository. nodes, clients, data bags, and all data will be stored here on your provisioner machine if you do this. You can use `with_chef_server` instead if you want to point at OSS, Hosted or Enterprise Chef, and if you don't specify a Chef server at all, it will use the one you are running chef-client against. Keep in mind when using `with_chef_server` and running `chef-client -z` on your workstation that you will also need to set the client name and signing key for the chef server. If you've already got knife.rb set up, then something like this will correctly create a client for the chef server on instance using your knife.rb configuration:
96
+
97
+ ```ruby
98
+ require 'chef/config'
99
+
100
+ with_chef_server "https://chef-server.example.org", {
101
+ :client_name => Chef::Config[:node_name],
102
+ :signing_key_filename => Chef::Config[:client_key]
103
+ }
104
+ ```
96
105
 
97
106
  Typically, you declare these in separate files from your machine resources. Chef Metal picks up the provisioners you have declared, and uses them to instantiate the machines you request. The actual machine definitions, in this case, are in `myapp::small`, and are generic--you could use them against EC2 as well:
98
107
 
@@ -150,6 +159,18 @@ To pass options like ami, you can say something like this:
150
159
  with_provisioner_options :image_id => 'ami-5ee70037'
151
160
  ```
152
161
 
162
+ If you need to pass bootstrapping options on a per-machine basis, you can do that as well by doing something like the following:
163
+
164
+ ```ruby
165
+ machine "Ubuntu_64bit" do
166
+ action :create
167
+ provisioner_options 'bootstrap_options' => {
168
+ 'image_id' => 'ami-59a4a230',
169
+ 'flavor_id' => 't1.micro'
170
+ }
171
+ end
172
+ ```
173
+
153
174
  You will notice that we are still using `myapp::small` here. Machine definitions are generally provisioner-independent. This is an important feature that allows you to spin up your clusters in different places to create staging, test or miniature dev environments.
154
175
 
155
176
  Bugs and The Plan
@@ -18,6 +18,8 @@ class Chef::Provider::FogKeyPair < Chef::Provider::LWRPBase
18
18
  case new_resource.provisioner.compute_options[:provider]
19
19
  when 'DigitalOcean'
20
20
  compute.destroy_key_pair(@current_id)
21
+ when 'OpenStack'
22
+ compute.key_pairs.destroy(@current_id)
21
23
  else
22
24
  compute.key_pairs.delete(new_resource.name)
23
25
  end
@@ -45,6 +47,8 @@ class Chef::Provider::FogKeyPair < Chef::Provider::LWRPBase
45
47
  new_fingerprint = case new_resource.provisioner.compute_options[:provider]
46
48
  when 'DigitalOcean'
47
49
  Cheffish::KeyFormatter.encode(desired_key, :format => :openssh)
50
+ when 'OpenStack'
51
+ Cheffish::KeyFormatter.encode(desired_key, :format => :openssh)
48
52
  else
49
53
  Cheffish::KeyFormatter.encode(desired_key, :format => :fingerprint)
50
54
  end
@@ -55,6 +59,8 @@ class Chef::Provider::FogKeyPair < Chef::Provider::LWRPBase
55
59
  case new_resource.provisioner.compute_options[:provider]
56
60
  when 'DigitalOcean'
57
61
  compute.create_ssh_key(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
62
+ when 'OpenStack'
63
+ compute.create_key_pair(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
58
64
  else
59
65
  compute.import_key_pair(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
60
66
  end
@@ -72,6 +78,8 @@ class Chef::Provider::FogKeyPair < Chef::Provider::LWRPBase
72
78
  case new_resource.provisioner.compute_options[:provider]
73
79
  when 'DigitalOcean'
74
80
  compute.create_ssh_key(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
81
+ when 'OpenStack'
82
+ compute.create_key_pair(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
75
83
  else
76
84
  compute.import_key_pair(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
77
85
  end
@@ -131,6 +139,14 @@ class Chef::Provider::FogKeyPair < Chef::Provider::LWRPBase
131
139
  else
132
140
  current_resource.action :delete
133
141
  end
142
+ when 'OpenStack'
143
+ current_key_pair = compute.key_pairs.get(new_resource.name)
144
+ if current_key_pair
145
+ @current_id = current_key_pair.name
146
+ @current_fingerprint = current_key_pair ? compute.key_pairs.get(@current_id).public_key : nil
147
+ else
148
+ current_resource.action :delete
149
+ end
134
150
  else
135
151
  current_key_pair = compute.key_pairs.get(new_resource.name)
136
152
  if current_key_pair
@@ -21,11 +21,14 @@ module ChefMetal
21
21
  require 'inifile'
22
22
  inifile = IniFile.load(File.expand_path(credentials_ini_file))
23
23
  inifile.each_section do |section|
24
- @credentials[section] = {
25
- :access_key_id => inifile[section]['aws_access_key_id'],
26
- :secret_access_key => inifile[section]['aws_secret_access_key'],
27
- :region => inifile[section]['region']
28
- }
24
+ if section =~ /^\s*profile\s+(.+)$/ || section =~ /^\s*(default)\s*/
25
+ profile = $1.strip
26
+ @credentials[profile] = {
27
+ :access_key_id => inifile[section]['aws_access_key_id'],
28
+ :secret_access_key => inifile[section]['aws_secret_access_key'],
29
+ :region => inifile[section]['region']
30
+ }
31
+ end
29
32
  end
30
33
  end
31
34
 
@@ -6,11 +6,15 @@ require 'chef_metal/provisioner/fog_provisioner'
6
6
  class Chef
7
7
  class Recipe
8
8
  def with_fog_provisioner(options = {}, &block)
9
- ChefMetal.with_provisioner(ChefMetal::Provisioner::FogProvisioner.new(options, &block))
9
+ ChefMetal.with_provisioner(ChefMetal::Provisioner::FogProvisioner.new(options), &block)
10
10
  end
11
11
 
12
12
  def with_fog_ec2_provisioner(options = {}, &block)
13
13
  with_fog_provisioner({ :provider => 'AWS' }.merge(options), &block)
14
14
  end
15
+
16
+ def with_fog_openstack_provisioner(options = {}, &block)
17
+ with_fog_provisioner({ :provider => 'OpenStack' }.merge(options), &block)
18
+ end
15
19
  end
16
20
  end
@@ -162,8 +162,8 @@ elif test -f "/etc/debian_version"; then
162
162
  platform="debian"
163
163
  platform_version=`cat /etc/debian_version`
164
164
  elif test -f "/etc/redhat-release"; then
165
- platform=`sed 's/^\(.\+\) release.*/\1/' /etc/redhat-release | tr '[A-Z]' '[a-z]'`
166
- platform_version=`sed 's/^.\+ release \([.0-9]\+\).*/\1/' /etc/redhat-release`
165
+ platform=`sed 's/^\\(.\\+\\) release.*/\\1/' /etc/redhat-release | tr '[A-Z]' '[a-z]'`
166
+ platform_version=`sed 's/^.\\+ release \\([.0-9]\\+\\).*/\\1/' /etc/redhat-release`
167
167
 
168
168
  # If /etc/redhat-release exists, we act like RHEL by default
169
169
  if test "$platform" = "fedora"; then
@@ -172,8 +172,8 @@ platform_version="6.0"
172
172
  fi
173
173
  platform="el"
174
174
  elif test -f "/etc/system-release"; then
175
- platform=`sed 's/^\(.\+\) release.\+/\1/' /etc/system-release | tr '[A-Z]' '[a-z]'`
176
- platform_version=`sed 's/^.\+ release \([.0-9]\+\).*/\1/' /etc/system-release | tr '[A-Z]' '[a-z]'`
175
+ platform=`sed 's/^\\(.\\+\\) release.\\+/\\1/' /etc/system-release | tr '[A-Z]' '[a-z]'`
176
+ platform_version=`sed 's/^.\\+ release \\([.0-9]\\+\\).*/\\1/' /etc/system-release | tr '[A-Z]' '[a-z]'`
177
177
  # amazon is built off of fedora, so act like RHEL
178
178
  if test "$platform" = "amazon linux ami"; then
179
179
  platform="el"
@@ -0,0 +1,44 @@
1
+ module ChefMetal
2
+ # Reads in a credentials file
3
+ class OpenstackCredentials
4
+ def initialize
5
+ @credentials = {}
6
+ end
7
+
8
+ def default
9
+ @credentials['default'] || @credentials.first[1]
10
+ end
11
+
12
+ def keys
13
+ @credentials.keys
14
+ end
15
+
16
+ def [](name)
17
+ @credentials[name]
18
+ end
19
+
20
+ def load_yaml(credentials_yaml_file)
21
+ creds_file = YAML.load_file(File.expand_path(credentials_yaml_file))
22
+ creds_file.each do |section, creds|
23
+ @credentials[section] = {
24
+ :openstack_username => creds_file[section]['openstack_username'],
25
+ :openstack_api_key => creds_file[section]['openstack_api_key'],
26
+ :openstack_tenant => creds_file[section]['openstack_tenant'],
27
+ :openstack_auth_url => creds_file[section]['openstack_auth_url']
28
+ }
29
+ end
30
+ end
31
+
32
+ def load_default
33
+ load_yaml('~/.fog')
34
+ end
35
+
36
+ def self.method_missing(name, *args, &block)
37
+ singleton.send(name, *args, &block)
38
+ end
39
+
40
+ def self.singleton
41
+ @openstack_credentials ||= OpenstackCredentials.new
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,7 @@
1
1
  require 'chef_metal/provisioner'
2
2
  require 'chef_metal/aws_credentials'
3
+ require 'chef_metal/openstack_credentials'
4
+ require 'chef_metal/version'
3
5
 
4
6
  module ChefMetal
5
7
  class Provisioner
@@ -32,7 +34,9 @@ module ChefMetal
32
34
  # - :ssh_timeout - the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
33
35
  def initialize(compute_options)
34
36
  @base_bootstrap_options = compute_options.delete(:base_bootstrap_options) || {}
35
- if compute_options[:provider] == 'AWS'
37
+
38
+ case compute_options[:provider]
39
+ when 'AWS'
36
40
  aws_credentials = compute_options.delete(:aws_credentials)
37
41
  if aws_credentials
38
42
  @aws_credentials = aws_credentials
@@ -42,6 +46,19 @@ module ChefMetal
42
46
  end
43
47
  compute_options[:aws_access_key_id] ||= @aws_credentials.default[:access_key_id]
44
48
  compute_options[:aws_secret_access_key] ||= @aws_credentials.default[:secret_access_key]
49
+ when 'OpenStack'
50
+ openstack_credentials = compute_options.delete(:openstack_credentials)
51
+ if openstack_credentials
52
+ @openstack_credentials = openstack_credentials
53
+ else
54
+ @openstack_credentials = ChefMetal::OpenstackCredentials.new
55
+ @openstack_credentials.load_default
56
+ end
57
+
58
+ compute_options[:openstack_username] ||= @openstack_credentials.default[:openstack_username]
59
+ compute_options[:openstack_api_key] ||= @openstack_credentials.default[:openstack_api_key]
60
+ compute_options[:openstack_auth_url] ||= @openstack_credentials.default[:openstack_auth_url]
61
+ compute_options[:openstack_tenant] ||= @openstack_credentials.default[:openstack_tenant]
45
62
  end
46
63
  @key_pairs = {}
47
64
  @compute_options = compute_options
@@ -50,6 +67,7 @@ module ChefMetal
50
67
 
51
68
  attr_reader :compute_options
52
69
  attr_reader :aws_credentials
70
+ attr_reader :openstack_credentials
53
71
  attr_reader :key_pairs
54
72
 
55
73
  def current_base_bootstrap_options
@@ -89,7 +107,7 @@ module ChefMetal
89
107
  # Example bootstrap_options for ec2:
90
108
  # :image_id =>'ami-311f2b45',
91
109
  # :flavor_id =>'t1.micro',
92
- # :key_name => 'pey-pair-name'
110
+ # :key_name => 'key-pair-name'
93
111
  #
94
112
  # node['normal']['provisioner_output'] will be populated with information
95
113
  # about the created machine. For vagrant, it is a hash with this
@@ -100,13 +118,21 @@ module ChefMetal
100
118
  #
101
119
  def acquire_machine(provider, node)
102
120
  # Set up the modified node data
103
- provisioner_options = node['normal']['provisioner_options'] || {}
104
121
  provisioner_output = node['normal']['provisioner_output'] || {
105
- 'provisioner_url' => provisioner_url
122
+ 'provisioner_url' => provisioner_url,
123
+ 'provisioner_version' => ChefMetal::VERSION,
124
+ 'creator' => aws_login_info[1]
106
125
  }
107
126
 
108
127
  if provisioner_output['provisioner_url'] != provisioner_url
109
- raise "Switching providers for a machine is not currently supported! Use machine :destroy and then re-create the machine on the new provider."
128
+ if (provisioner_output['provisioner_version'].to_f <= 0.3) && provisioner_output['provisioner_url'].start_with?('fog:AWS:') && compute_options[:provider] == 'AWS'
129
+ Chef::Log.warn "The upgrade from chef-metal 0.3 to 0.4 changed the provisioner URL format! Metal will assume you are in fact using the same AWS account, and modify the provisioner URL to match."
130
+ provisioner_output['provisioner_url'] = provisioner_url
131
+ provisioner_output['provisioner_version'] ||= ChefMetal::VERSION
132
+ provisioner_output['creator'] ||= aws_login_info[1]
133
+ else
134
+ raise "Switching providers for a machine is not currently supported! Use machine :destroy and then re-create the machine on the new provider."
135
+ end
110
136
  end
111
137
 
112
138
  node['normal']['provisioner_output'] = provisioner_output
@@ -143,6 +169,7 @@ module ChefMetal
143
169
  if need_to_create
144
170
  # If the server does not exist, create it
145
171
  bootstrap_options = bootstrap_options_for(provider.new_resource, node)
172
+ bootstrap_options.merge(:name => provider.new_resource.name)
146
173
 
147
174
  start_time = Time.now
148
175
  timeout = option_for(node, :create_timeout)
@@ -158,8 +185,22 @@ module ChefMetal
158
185
  end
159
186
 
160
187
  if server
188
+ @@ip_pool_lock = Mutex.new
161
189
  # Re-retrieve the server in a more malleable form and wait for it to be ready
162
190
  server = compute.servers.get(server.id)
191
+ if bootstrap_options[:floating_ip_pool]
192
+ Chef::Log.info 'Attaching IP from pool'
193
+ server.wait_for { ready? }
194
+ provider.converge_by "attach floating IP from #{bootstrap_options[:floating_ip_pool]} pool" do
195
+ attach_ip_from_pool(server, bootstrap_options[:floating_ip_pool])
196
+ end
197
+ elsif bootstrap_options[:floating_ip]
198
+ Chef::Log.info 'Attaching given IP'
199
+ server.wait_for { ready? }
200
+ provider.converge_by "attach floating IP #{bootstrap_options[:floating_ip]}" do
201
+ attach_ip(server, bootstrap_options[:floating_ip])
202
+ end
203
+ end
163
204
  provider.converge_by "machine #{node['name']} created as #{server.id} on #{provisioner_url}" do
164
205
  end
165
206
  # Wait for the machine to come up and for ssh to start listening
@@ -205,6 +246,31 @@ module ChefMetal
205
246
  machine_for(node, server)
206
247
  end
207
248
 
249
+ # Attach IP to machine from IP pool
250
+ # Code taken from kitchen-openstack driver
251
+ # https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb#L196-L207
252
+ def attach_ip_from_pool(server, pool)
253
+ @@ip_pool_lock.synchronize do
254
+ Chef::Log.info "Attaching floating IP from <#{pool}> pool"
255
+ free_addrs = compute.addresses.collect do |i|
256
+ i.ip if i.fixed_ip.nil? and i.instance_id.nil? and i.pool == pool
257
+ end.compact
258
+ if free_addrs.empty?
259
+ raise ActionFailed, "No available IPs in pool <#{pool}>"
260
+ end
261
+ attach_ip(server, free_addrs[0])
262
+ end
263
+ end
264
+
265
+ # Attach given IP to machine
266
+ # Code taken from kitchen-openstack driver
267
+ # https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb#L209-L213
268
+ def attach_ip(server, ip)
269
+ Chef::Log.info "Attaching floating IP <#{ip}>"
270
+ server.associate_address ip
271
+ (server.addresses['public'] ||= []) << { 'version' => 4, 'addr' => ip }
272
+ end
273
+
208
274
  # Connect to machine without acquiring it
209
275
  def connect_to_machine(node)
210
276
  machine_for(node)
@@ -224,7 +290,7 @@ module ChefMetal
224
290
  # If the machine doesn't exist, we silently do nothing
225
291
  if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
226
292
  server = compute.servers.get(node['normal']['provisioner_output']['server_id'])
227
- provider.converge_by "stop machine #{node['name']} (#{server.id} at #{provisioner_url}" do
293
+ provider.converge_by "stop machine #{node['name']} (#{server.id} at #{provisioner_url})" do
228
294
  server.stop
229
295
  end
230
296
  end
@@ -246,9 +312,11 @@ module ChefMetal
246
312
  def provisioner_url
247
313
  provider_identifier = case compute_options[:provider]
248
314
  when 'AWS'
249
- compute_options[:aws_access_key_id]
315
+ aws_login_info[0]
250
316
  when 'DigitalOcean'
251
317
  compute_options[:digitalocean_client_id]
318
+ when 'OpenStack'
319
+ compute_options[:openstack_auth_url]
252
320
  else
253
321
  '???'
254
322
  end
@@ -273,6 +341,32 @@ module ChefMetal
273
341
  end
274
342
  end
275
343
 
344
+ # Returns [ Account ID, User ]
345
+ # Account ID is the 12 digit identifier on your Manage Account page in AWS Console. It is used as part of all ARNs identifying resources.
346
+ # User is an identifier like "root" or "user/username" or "federated-user/username"
347
+ def aws_login_info
348
+ @aws_login_info ||= begin
349
+ iam = Fog::AWS::IAM.new(:aws_access_key_id => compute_options[:aws_access_key_id], :aws_secret_access_key => compute_options[:aws_secret_access_key])
350
+ arn = begin
351
+ # TODO it would be nice if Fog let you do this normally ...
352
+ iam.send(:request, {
353
+ 'Action' => 'GetUser',
354
+ :parser => Fog::Parsers::AWS::IAM::GetUser.new
355
+ }).body['User']['Arn']
356
+ rescue Fog::AWS::IAM::Error
357
+ # TODO Someone tell me there is a better way to find out your current
358
+ # user ID than this! This is what happens when you use an IAM user
359
+ # with default privileges.
360
+ if $!.message =~ /AccessDenied.+(arn:aws:iam::\d+:\S+)/
361
+ arn = $1
362
+ else
363
+ raise
364
+ end
365
+ end
366
+ arn.split(':')[4..5]
367
+ end
368
+ end
369
+
276
370
  def symbolize_keys(options)
277
371
  options.inject({}) { |result,(key,value)| result[key.to_sym] = value; result }
278
372
  end
@@ -392,7 +486,20 @@ module ChefMetal
392
486
  if compute_options[:sudo] || (!compute_options.has_key?(:sudo) && username != 'root')
393
487
  options[:prefix] = 'sudo '
394
488
  end
395
- ChefMetal::Transport::SSH.new(server.public_ip_address, username, ssh_options, options)
489
+
490
+ remote_host = nil
491
+ if compute_options[:use_private_ip_for_ssh]
492
+ remote_host = server.private_ip_address
493
+ elsif !server.public_ip_address
494
+ Chef::Log.warn("Server has no public ip address. Using private ip '#{server.private_ip_address}'. Set provisioner option 'use_private_ip_for_ssh' => true if this will always be the case ...")
495
+ remote_host = server.private_ip_address
496
+ elsif server.public_ip_address
497
+ remote_host = server.public_ip_address
498
+ else
499
+ raise "Server #{server.id} has no private or public IP address!"
500
+ end
501
+
502
+ ChefMetal::Transport::SSH.new(remote_host, username, ssh_options, options)
396
503
  end
397
504
 
398
505
  def wait_until_ready(server, timeout)
@@ -10,4 +10,4 @@ class Chef
10
10
  ChefMetal.with_provisioner_options(provisioner_options, &block)
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module ChefMetal
2
- VERSION = '0.3.1'
2
+ VERSION = '0.4'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-metal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: '0.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Keiser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-18 00:00:00.000000000 Z
11
+ date: 2014-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef
@@ -141,6 +141,7 @@ files:
141
141
  - lib/chef_metal/machine/unix_machine.rb
142
142
  - lib/chef_metal/machine/windows_machine.rb
143
143
  - lib/chef_metal/machine.rb
144
+ - lib/chef_metal/openstack_credentials.rb
144
145
  - lib/chef_metal/provisioner/fog_provisioner.rb
145
146
  - lib/chef_metal/provisioner/vagrant_provisioner.rb
146
147
  - lib/chef_metal/provisioner.rb