clc-fork-chef-metal 0.11.beta.6 → 0.11.2.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -16
- data/README.md +127 -91
- data/lib/chef/provider/machine.rb +23 -16
- data/lib/chef/provider/machine_batch.rb +34 -24
- data/lib/chef/resource/machine_batch.rb +28 -0
- data/lib/chef_metal.rb +34 -13
- data/lib/chef_metal/chef_run_data.rb +29 -14
- data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +1 -0
- data/lib/chef_metal/recipe_dsl.rb +2 -2
- data/lib/chef_metal/transport/ssh.rb +31 -29
- data/lib/chef_metal/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4aeb6b366bba61cd66941ffbf1159d6a9c8f5e35
|
4
|
+
data.tar.gz: ace414410903b2f164c3699bfb938cf4a9e76292
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db58b28f165074f0100c5a8db05180e1f28f22fce8868551f295b7f3bef49cc5c44b6c7ec6b3d175517a44b9f5bd6750a0bf7695768e5d12a88baccad6e3ee11
|
7
|
+
data.tar.gz: 3fee03a117fb4ee67076be5c234213d8701d85eccbe5ce615b1c0f6486f316b7edae8d600af427da1e0daa1615c2c5f50a778aef2a67e231643159168bb7851a
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
# Chef Metal Changelog
|
2
2
|
|
3
|
-
## 0.11.
|
3
|
+
## 0.11.2 (6/4/2014)
|
4
4
|
|
5
|
-
- Fix
|
6
|
-
|
5
|
+
- Fix issue where machines with different drivers could get default options from the global current driver
|
6
|
+
|
7
|
+
## 0.11.1 (6/4/2014)
|
8
|
+
|
9
|
+
- fix local mode port forwarding on IPv6 hosts
|
10
|
+
|
11
|
+
## 0.11 (6/4/2014)
|
12
|
+
|
13
|
+
- New Driver interface (see docs/ and blogs/ directories for documentation)
|
14
|
+
- New configuration (see docs/ and blogs/)
|
15
|
+
- get rid of annoying SSL warning (note: this turns off SSL verification, which was the default anyway)
|
16
|
+
- fix machine_batch error report to be less verbose
|
17
|
+
- fail when machine is being moved from driver to driver
|
18
|
+
- @marcusn disconnect from SSH when there is a problem
|
19
|
+
- fix SSH gateway code to honor any options given (@marcusn)
|
20
|
+
- Make machine_batch auto batching smarter (only batch things that have the same actions)
|
21
|
+
- Allow auto batching to be turned off with `auto_batch_machines = false` in recipes or config
|
7
22
|
- Allow this:
|
8
23
|
```ruby
|
9
24
|
machine_batch do
|
@@ -17,20 +32,8 @@
|
|
17
32
|
machines 'a', 'b', 'c'
|
18
33
|
action :destroy
|
19
34
|
end
|
20
|
-
- fix SSH gateway code to honor any options given (@marcusn)
|
21
|
-
|
22
|
-
## 0.11.beta.5 (5/28/2014)
|
23
|
-
|
24
35
|
- fix issue setting Hosted Chef ACLs on nodes
|
25
|
-
- fix
|
26
|
-
|
27
|
-
## 0.11.beta.2 (5/27/2014)
|
28
|
-
|
29
|
-
- Bring in cheffish-0.5.beta.2
|
30
|
-
|
31
|
-
## 0.11.beta (5/23/2014)
|
32
|
-
|
33
|
-
- New Driver interface (see docs/ and blogs/ directories for documentation)
|
36
|
+
- fix local mode forwarding in mixed IPv4/IPv6 environments
|
34
37
|
|
35
38
|
## 0.10.2 (5/2/2014)
|
36
39
|
|
data/README.md
CHANGED
@@ -3,31 +3,72 @@ Chef Metal
|
|
3
3
|
|
4
4
|
This library solves the problem of repeatably creating machines and infrastructures in Chef. It has a plugin model that lets you write bootstrappers for your favorite infrastructures, including VirtualBox, EC2, LXC, bare metal, and many more!
|
5
5
|
|
6
|
-
|
6
|
+
[This video](https://www.youtube.com/watch?v=Yb8QdL30WgM) explains the basics of chef-metal (though provisioners are now called drivers). Slides (more up to date) are [here](http://slides.com/jkeiser/chef-metal).
|
7
|
+
|
8
|
+
Date | Blog
|
9
|
+
-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
10
|
+
6/3/2014 | [machine_batch and parallelization](https://github.com/opscode/chef-metal/blob/master/docs/blogs/2012-05-28-machine_batch.html.markdown#chef-metal-parallelization)
|
11
|
+
6/3/2014 | [Chef Metal, Configuration and Drivers](https://github.com/opscode/chef-metal/blob/master/docs/blogs/2012-05-22-new-driver-interface.html.markdown#chef-metal-configuration-and-drivers)
|
12
|
+
3/4/2014 | [Chef Metal 0.2: Overview](http://www.getchef.com/blog/2014/03/04/chef-metal-0-2-release/) - this is a pretty good overview (though dated).
|
13
|
+
12/20/2014 | [Chef Metal Alpha](http://www.getchef.com/blog/2013/12/20/chef-metal-alpha/)
|
7
14
|
|
8
15
|
Try It Out
|
9
16
|
----------
|
10
17
|
|
11
|
-
|
18
|
+
You can try out Metal in many different flavors.
|
19
|
+
|
20
|
+
HINT: chef-metal looks prettiest with chef 11.14 alpha. `gem install chef --pre` to get it.
|
12
21
|
|
13
|
-
|
14
|
-
cd cheffish
|
15
|
-
rake install
|
16
|
-
cd ..
|
22
|
+
### Vagrant
|
17
23
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
To give it a spin, install Vagrant and VirtualBox and try this from the `chef-metal/docs/examples` directory:
|
25
|
+
|
26
|
+
```
|
27
|
+
gem install chef-metal
|
28
|
+
export CHEF_DRIVER=vagrant
|
29
|
+
chef-client -z vagrant_linux.rb simple.rb
|
30
|
+
```
|
24
31
|
|
25
32
|
This will create two vagrant precise64 linux boxes, "mario" and "luigi1", in `~/machinetest`, bootstrapped to an empty runlist. For Windows, you can replace `myapp::linux` with `myapp::windows`, but you'll need your own Windows vagrant box to do that (licensing!).
|
26
33
|
|
34
|
+
### AWS
|
35
|
+
|
36
|
+
If you have an AWS account, you can spin up a machine there like this:
|
37
|
+
|
38
|
+
```
|
39
|
+
gem install chef-metal
|
40
|
+
export CHEF_DRIVER=fog:AWS
|
41
|
+
chef-client -z simple.rb
|
42
|
+
```
|
43
|
+
|
44
|
+
This will create two linux boxes in the AWS account referenced by your default profile in `~/.aws/config` (or your environment variables).
|
45
|
+
|
46
|
+
### DigitalOcean
|
47
|
+
|
48
|
+
If you are on DigitalOcean and using the `tugboat` gem, you can do this:
|
49
|
+
|
50
|
+
```
|
51
|
+
gem install chef-metal
|
52
|
+
export CHEF_DRIVER=fog:DigitalOcean
|
53
|
+
chef-client -z simple.rb
|
54
|
+
```
|
55
|
+
|
56
|
+
If you aren't using the `tugboat` gem, you can put `driver` and `driver_options` into your `.chef/knife.rb` file.
|
57
|
+
|
58
|
+
This will use your tugboat settings to create whatever sort of instance you normally create.
|
59
|
+
|
60
|
+
### Cleaning up
|
61
|
+
|
62
|
+
When you are done with the examples, run this to clean up:
|
63
|
+
|
64
|
+
```
|
65
|
+
chef-client -z destroy_all.rb
|
66
|
+
```
|
67
|
+
|
27
68
|
What Is Chef Metal?
|
28
69
|
-------------------
|
29
70
|
|
30
|
-
Chef Metal has two major abstractions: the machine resource,
|
71
|
+
Chef Metal has two major abstractions: the machine resource, anf drivers.
|
31
72
|
|
32
73
|
### The `machine` resource
|
33
74
|
|
@@ -54,148 +95,143 @@ end
|
|
54
95
|
|
55
96
|
You will notice the dynamic nature of the number of web servers. It's all code, your imagination is the limit :)
|
56
97
|
|
57
|
-
|
58
|
-
-------
|
59
|
-
|
60
|
-
Chef Metal also works with Test Kitchen, allowing you to test entire clusters, not just machines! The repository for the kitchen-metal gem is https://github.com/doubt72/kitchen-metal.
|
61
|
-
|
62
|
-
Provisioners
|
63
|
-
------------
|
98
|
+
### Drivers
|
64
99
|
|
65
|
-
|
100
|
+
Drivers handle the real work of getting those abstract definitions into real, physical form. They handle the following tasks, idempotently (you can run the resource again and again and it will only create the machine once--though it may notice things are wrong and fix them!):
|
66
101
|
|
67
102
|
* Acquiring machines from the cloud, creating containers or VMs, or grabbing bare metal
|
68
103
|
* Connecting to those machines via ssh, winrm, or other transports
|
69
104
|
* Bootstrapping chef onto the machines and converging the recipes you suggested
|
70
105
|
|
71
|
-
The
|
106
|
+
The driver API is separated out so that new drivers can be made with minimal effort (without having to rewrite ssh, tunneling, bootstrapping, and OS support). But to the user, they appear as a single thing, so that the machine acquisition can use its smarts to autodetect the other bits (transports, OS's, etc.).
|
72
107
|
|
73
|
-
|
108
|
+
Drivers save their data in the Chef node itself, so that they will be accessible to everyone who is using the Chef server to manage the nodes.
|
74
109
|
|
75
|
-
|
110
|
+
Drivers each have their own repository. Current drivers:
|
76
111
|
|
77
112
|
**Cloud:**
|
78
113
|
- [FOG: EC2, DigitalOcean, OpenStack, etc.](https://github.com/opscode/chef-metal-fog)
|
79
114
|
|
80
115
|
**Virtualization:**
|
81
116
|
- [Vagrant: VirtualBox, VMWare Fusion, etc.](https://github.com/opscode/chef-metal-vagrant)
|
117
|
+
- [VSphere](https://github.com/RallySoftware-cookbooks/chef-metal-vsphere) (not yet up to date with 0.11)
|
82
118
|
|
83
119
|
**Containers:**
|
84
|
-
- [LXC](https://github.com/opscode/chef-metal-lxc)
|
85
|
-
- [Docker](https://github.com/opscode/chef-metal-docker)
|
120
|
+
- [LXC](https://github.com/opscode/chef-metal-lxc) (not yet up to date with 0.11)
|
121
|
+
- [Docker](https://github.com/opscode/chef-metal-docker) (not yet up to date with 0.11)
|
86
122
|
|
87
123
|
**Bare Metal:**
|
88
|
-
- [SSH (no PXE)](https://github.com/double-z/chef-metal-ssh)
|
89
|
-
|
90
|
-
### Vagrant
|
124
|
+
- [SSH (no PXE)](https://github.com/double-z/chef-metal-ssh) (not yet up to date with 0.11)
|
91
125
|
|
92
|
-
|
126
|
+
### Anatomy of a Recipe
|
93
127
|
|
94
|
-
|
95
|
-
chef-client -z -o myapp::vagrant,myapp::linux,myapp::small
|
96
|
-
```
|
97
|
-
|
98
|
-
The provisioner specification is in myapp::vagrant and myapp::linux [sample recipes](https://github.com/opscode/chef-metal/tree/master/cookbooks/myapp/recipes), copy/pasted here for your convenience:
|
128
|
+
chef-zero comes with a provisioner for Vagrant, an abstraction that covers VirtualBox, VMWare and other Virtual Machine drivers. In docs/examples, you can run this to try it:
|
99
129
|
|
100
130
|
```ruby
|
101
|
-
|
102
|
-
|
103
|
-
directory "#{ENV['HOME']}/machinetest/repo"
|
104
|
-
with_chef_local_server :chef_repo_path => "#{ENV['HOME']}/machinetest/repo"
|
105
|
-
|
106
|
-
vagrant_box 'precise64' do
|
107
|
-
url 'http://files.vagrantup.com/precise32.box'
|
108
|
-
end
|
131
|
+
export CHEF_DRIVER=vagrant
|
132
|
+
chef-client -z vagrant_linux.rb simple.rb
|
109
133
|
```
|
110
134
|
|
111
|
-
|
135
|
+
This is a chef-client run, which runs multiple **recipes.** Chef Metal is nothing but resources you put in recipes.
|
112
136
|
|
113
|
-
|
137
|
+
The driver is specified on the command line. Drivers are URLs. You could use `vagrant:~/vms` or `fog:AWS:default:us-east-1' as driver URLs. More information [here.](https://github.com/opscode/chef-metal/blob/master/docs/configuration.md#setting-the-driver-with-a-driver-url)
|
114
138
|
|
115
|
-
`
|
139
|
+
The `vagrant_linux.rb` recipe handles the physical specification of the machines and Vagrant box:
|
116
140
|
|
117
141
|
```ruby
|
118
|
-
require '
|
142
|
+
require 'chef_metal_vagrant'
|
119
143
|
|
120
|
-
|
121
|
-
|
122
|
-
|
144
|
+
vagrant_box 'precise64' do
|
145
|
+
url 'http://files.vagrantup.com/precise64.box'
|
146
|
+
end
|
147
|
+
|
148
|
+
with_machine_options :vagrant_options => {
|
149
|
+
'vm.box' => 'precise64'
|
123
150
|
}
|
124
151
|
```
|
125
152
|
|
126
|
-
|
153
|
+
`require 'chef_metal_vagrant'` is how we bring in the `vagrant_box` resource.
|
127
154
|
|
128
|
-
|
129
|
-
machine 'mario' do
|
130
|
-
recipe 'postgresql'
|
131
|
-
recipe 'mydb'
|
132
|
-
tag 'mydb_master'
|
133
|
-
end
|
155
|
+
`vagrant_box` makes sure a particular vagrant box exists, and lets you specify `machine_options` for things like port forwarding, OS definitions, and any other vagrant-isms.
|
134
156
|
|
135
|
-
|
157
|
+
Typically, you declare these in separate files from your machine resources. Chef Metal picks up the drivers and machine_options you have declared, and uses them to instantiate the machines you request. The actual machine definitions, in this case, are in `simple.rb`, and are generic--you could use them against Azure or EC2 as well:
|
136
158
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
recipe 'mywebapp'
|
141
|
-
end
|
159
|
+
```ruby
|
160
|
+
machine 'mario' do
|
161
|
+
tag 'itsame'
|
142
162
|
end
|
143
163
|
```
|
144
164
|
|
145
|
-
|
165
|
+
Other directives, like `recipe 'apache'`, help you set run lists and other information about the machine.
|
166
|
+
|
167
|
+
### Fog (EC2, Openstack and friends)
|
146
168
|
|
147
|
-
chef-metal also comes with a [Fog](http://fog.io/) provisioner that handles provisioning to Amazon's EC2 and other cloud drivers.
|
169
|
+
chef-metal also comes with a [Fog](http://fog.io/) provisioner that handles provisioning to Openstack, Rackspace, Amazon's EC2 and other cloud drivers. Before you begin, you will need to put your AWS credentials in ~/.aws/config in the format [mentioned in Option 1 here](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#d0e726). There are other ways to specify your credentials, but this is the standard one for the Amazon CLI.
|
148
170
|
|
149
171
|
Once your credentials are in, basic usage looks like this:
|
150
172
|
|
151
173
|
```
|
152
|
-
|
174
|
+
export CHEF_DRIVER=fog:AWS
|
175
|
+
chef-client -z simple.rb
|
153
176
|
```
|
154
177
|
|
155
|
-
|
178
|
+
Other valid URLs include `fog:AWS:myprofilename` and `fog:AWS:profilename:us-west-2`.
|
156
179
|
|
157
|
-
|
158
|
-
ec2testdir = File.expand_path('~/ec2test')
|
180
|
+
Most Chef Metal drivers try hard to provide reasonable defaults so you can get started easily. Once you have specified your credentials, AMIs and other things are chosen for you.
|
159
181
|
|
160
|
-
|
182
|
+
You will usually want to create or input a custom key pair for bootstrap. To customize, specify keys and AMI and other options, you can make recipes like this:
|
161
183
|
|
162
|
-
|
184
|
+
```ruby
|
185
|
+
require 'chef_metal_fog'
|
163
186
|
|
164
|
-
|
187
|
+
fog_key_pair 'my_bootstrap_key'
|
165
188
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
189
|
+
with_machine_options :bootstrap_options => {
|
190
|
+
:key_name => 'my_bootstrap_key',
|
191
|
+
:image_id => 'ami-59a4a230',
|
192
|
+
:flavor_id => 't1.micro'
|
193
|
+
}
|
170
194
|
```
|
171
195
|
|
172
|
-
`
|
196
|
+
`fog_key_pair` creates a new key pair (if the files do not already exist) and uploads it to AWS (it will toss an error if the key pair already exists and does not match). By default, `fog_key_pair` will look for matching key files in .chef/keys, ~/.chef/keys and ~/.ssh. If it does not find one, it will place the key in `.chef/keys`. You can override this path in fog_key_pair, but if you do, you will want to modify `private_key_paths` in your configuration to match.
|
197
|
+
|
198
|
+
`with_machine_options` specifies machine_options that will be applied to any `machine` resources chef-client encounters.
|
199
|
+
|
200
|
+
You will notice that we are still using `simple.rb` here. Machine definitions are generally driver-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.
|
173
201
|
|
174
|
-
|
202
|
+
### Pointing Boxes at Chef Servers
|
175
203
|
|
176
|
-
|
204
|
+
By default, Chef Metal will put your boxes on the same Chef server you started chef-client with (in the case of -z, that's a local chef-zero server). Sometimes you want to put your boxes on different servers. There are a couple of ways to do that:
|
177
205
|
|
178
206
|
```ruby
|
179
|
-
|
207
|
+
with_chef_local_server :chef_repo_path => '~/repo'
|
180
208
|
```
|
181
209
|
|
182
|
-
|
210
|
+
`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.
|
211
|
+
|
212
|
+
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:
|
183
213
|
|
184
214
|
```ruby
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
'image_id' => 'ami-59a4a230',
|
189
|
-
'flavor_id' => 't1.micro'
|
190
|
-
}
|
191
|
-
end
|
215
|
+
with_chef_server "https://chef-server.example.org",
|
216
|
+
:client_name => Chef::Config[:node_name],
|
217
|
+
:signing_key_filename => Chef::Config[:client_key]
|
192
218
|
```
|
193
219
|
|
194
|
-
|
220
|
+
Kitchen
|
221
|
+
-------
|
222
|
+
|
223
|
+
Chef Metal also works with Test Kitchen, allowing you to test entire clusters, not just machines! The repository for the kitchen-metal gem is https://github.com/doubt72/kitchen-metal.
|
224
|
+
|
225
|
+
Documentation
|
226
|
+
-------------
|
227
|
+
* [Configuration](https://github.com/opscode/chef-metal/blob/master/docs/configuration.md#configuring-and-using-metal-drivers)
|
228
|
+
* [Writing Drivers](https://github.com/opscode/chef-metal/blob/master/docs/building_drivers.md#writing-drivers)
|
229
|
+
* [Embedding](https://github.com/opscode/chef-metal/blob/master/docs/embedding.md)
|
195
230
|
|
196
231
|
Bugs and The Plan
|
197
232
|
-----------------
|
198
233
|
|
199
|
-
|
234
|
+
Please submit bugs, gripes and feature requests at [https://github.com/opscode/chef-metal/issues](https://twitter.com/jkeiser2), contact jkeiser on Twitter at @jkeiser2, email at [jkeiser@getchef.com](mailto:jkeiser@getchef.com)
|
200
235
|
|
201
|
-
|
236
|
+
To contribute, just make a PR in the appropriate repo--also, make sure you've [signed the Chef Contributor License Agreement](https://secure.echosign.com/public/hostedForm?formid=PJIF5694K6L) (quick couple of minutes online), since this is going into core Chef eventually. It takes some time to process, so if you've just done it, let me know in the PR :) If you already signed this for a Chef contribution, you don't need to do so again--if you're not sure, you can check for your name [here](https://wiki.opscode.com/display/chef/Approved+Contributors)!
|
237
|
+
Â
|
@@ -17,13 +17,16 @@ class Chef::Provider::Machine < Chef::Provider::LWRPBase
|
|
17
17
|
end
|
18
18
|
|
19
19
|
action :allocate do
|
20
|
-
|
20
|
+
if current_driver && current_driver.driver_url != new_driver.driver_url
|
21
|
+
raise "Cannot move '#{machine_spec.name}' from #{current_driver.driver_url} to #{new_driver.driver_url}: machine moving is not supported. Destroy and recreate."
|
22
|
+
end
|
23
|
+
new_driver.allocate_machine(action_handler, machine_spec, new_machine_options)
|
21
24
|
machine_spec.save(action_handler)
|
22
25
|
end
|
23
26
|
|
24
27
|
action :ready do
|
25
28
|
action_allocate
|
26
|
-
machine = current_driver.ready_machine(action_handler, machine_spec,
|
29
|
+
machine = current_driver.ready_machine(action_handler, machine_spec, current_machine_options)
|
27
30
|
machine_spec.save(action_handler)
|
28
31
|
machine
|
29
32
|
end
|
@@ -56,7 +59,7 @@ class Chef::Provider::Machine < Chef::Provider::LWRPBase
|
|
56
59
|
end
|
57
60
|
|
58
61
|
action :converge_only do
|
59
|
-
machine = run_context.chef_metal.connect_to_machine(machine_spec,
|
62
|
+
machine = run_context.chef_metal.connect_to_machine(machine_spec, current_machine_options)
|
60
63
|
begin
|
61
64
|
machine.converge(action_handler)
|
62
65
|
ensure
|
@@ -66,35 +69,39 @@ class Chef::Provider::Machine < Chef::Provider::LWRPBase
|
|
66
69
|
|
67
70
|
action :stop do
|
68
71
|
if current_driver
|
69
|
-
current_driver.stop_machine(action_handler, machine_spec,
|
72
|
+
current_driver.stop_machine(action_handler, machine_spec, current_machine_options)
|
70
73
|
end
|
71
74
|
end
|
72
75
|
|
73
76
|
action :destroy do
|
74
77
|
if current_driver
|
75
|
-
current_driver.destroy_machine(action_handler, machine_spec,
|
78
|
+
current_driver.destroy_machine(action_handler, machine_spec, current_machine_options)
|
76
79
|
end
|
77
80
|
end
|
78
81
|
|
82
|
+
attr_reader :machine_spec
|
83
|
+
|
79
84
|
def new_driver
|
80
85
|
run_context.chef_metal.driver_for(new_resource.driver)
|
81
86
|
end
|
82
87
|
|
83
|
-
def
|
84
|
-
|
88
|
+
def current_driver
|
89
|
+
if machine_spec.driver_url
|
90
|
+
run_context.chef_metal.driver_for(machine_spec.driver_url)
|
91
|
+
end
|
85
92
|
end
|
86
93
|
|
87
|
-
def
|
88
|
-
|
89
|
-
if machine_spec.driver_url
|
90
|
-
run_context.chef_metal.driver_for_url(machine_spec.driver_url)
|
91
|
-
end
|
92
|
-
)
|
94
|
+
def new_machine_options
|
95
|
+
machine_options(new_driver)
|
93
96
|
end
|
94
97
|
|
95
|
-
|
98
|
+
def current_machine_options
|
99
|
+
if current_driver
|
100
|
+
machine_options(current_driver)
|
101
|
+
end
|
102
|
+
end
|
96
103
|
|
97
|
-
def machine_options
|
104
|
+
def machine_options(driver)
|
98
105
|
configs = []
|
99
106
|
configs << {
|
100
107
|
:convergence_options =>
|
@@ -111,7 +118,7 @@ class Chef::Provider::Machine < Chef::Provider::LWRPBase
|
|
111
118
|
end
|
112
119
|
}
|
113
120
|
configs << new_resource.machine_options if new_resource.machine_options
|
114
|
-
configs <<
|
121
|
+
configs << driver.config[:machine_options] if driver.config[:machine_options]
|
115
122
|
Cheffish::MergedConfig.new(*configs)
|
116
123
|
end
|
117
124
|
|
@@ -25,7 +25,8 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
25
25
|
action :allocate do
|
26
26
|
by_new_driver.each do |driver, specs_and_options|
|
27
27
|
driver.allocate_machines(action_handler, specs_and_options, parallelizer) do |machine_spec|
|
28
|
-
|
28
|
+
prefixed_handler = ChefMetal::AddPrefixActionHandler.new(action_handler, "[#{machine_spec.name}] ")
|
29
|
+
machine_spec.save(prefixed_handler)
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -38,7 +39,7 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
38
39
|
with_ready_machines do |m|
|
39
40
|
prefixed_handler = ChefMetal::AddPrefixActionHandler.new(action_handler, "[#{m[:spec].name}] ")
|
40
41
|
machine[:machine].setup_convergence(prefixed_handler)
|
41
|
-
m[:spec].save(
|
42
|
+
m[:spec].save(prefixed_handler)
|
42
43
|
Chef::Provider::Machine.upload_files(prefixed_handler, m[:machine], m[:files])
|
43
44
|
end
|
44
45
|
end
|
@@ -49,15 +50,17 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
49
50
|
m[:machine].setup_convergence(prefixed_handler)
|
50
51
|
m[:spec].save(action_handler)
|
51
52
|
Chef::Provider::Machine.upload_files(prefixed_handler, m[:machine], m[:files])
|
53
|
+
# TODO only converge if machine was modified
|
52
54
|
m[:machine].converge(prefixed_handler)
|
53
|
-
m[:spec].save(
|
55
|
+
m[:spec].save(prefixed_handler)
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
59
|
action :converge_only do
|
58
60
|
parallel_do(@machines) do |m|
|
61
|
+
prefixed_handler = ChefMetal::AddPrefixActionHandler.new(action_handler, "[#{m[:spec].name}] ")
|
59
62
|
machine = run_context.chef_metal.connect_to_machine(m[:spec])
|
60
|
-
machine.converge(
|
63
|
+
machine.converge(prefixed_handler)
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
@@ -101,11 +104,22 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
101
104
|
|
102
105
|
def by_new_driver
|
103
106
|
result = {}
|
107
|
+
drivers = {}
|
104
108
|
@machines.each do |m|
|
105
109
|
if m[:desired_driver]
|
106
|
-
|
110
|
+
drivers[m[:desired_driver]] ||= run_context.chef_metal.driver_for(m[:desired_driver])
|
111
|
+
driver = drivers[m[:desired_driver]]
|
112
|
+
# Check whether the current driver is same or different; we disallow
|
113
|
+
# moving a machine from one place to another.
|
114
|
+
if m[:spec].driver_url
|
115
|
+
drivers[m[:spec].driver_url] ||= run_context.chef_metal.driver_for(m[:spec].driver_url)
|
116
|
+
current_driver = drivers[m[:spec].driver_url]
|
117
|
+
if driver.driver_url != current_driver.driver_url
|
118
|
+
raise "Cannot move '#{m[:spec].name}' from #{current_driver.driver_url} to #{driver.driver_url}: machine moving is not supported. Destroy and recreate."
|
119
|
+
end
|
120
|
+
end
|
107
121
|
result[driver] ||= {}
|
108
|
-
result[driver][m[:spec]] = m[:
|
122
|
+
result[driver][m[:spec]] = m[:machine_options].call(driver)
|
109
123
|
end
|
110
124
|
end
|
111
125
|
result
|
@@ -113,11 +127,13 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
113
127
|
|
114
128
|
def by_current_driver
|
115
129
|
result = {}
|
130
|
+
drivers = {}
|
116
131
|
@machines.each do |m|
|
117
132
|
if m[:spec].driver_url
|
118
|
-
|
133
|
+
drivers[m[:spec].driver_url] ||= run_context.chef_metal.driver_for(m[:spec].driver_url)
|
134
|
+
driver = drivers[m[:spec].driver_url]
|
119
135
|
result[driver] ||= {}
|
120
|
-
result[driver][m[:spec]] = m[:
|
136
|
+
result[driver][m[:spec]] = m[:machine_options].call(driver)
|
121
137
|
end
|
122
138
|
end
|
123
139
|
result
|
@@ -134,7 +150,7 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
134
150
|
:spec => provider.machine_spec,
|
135
151
|
:desired_driver => machine_resource.driver,
|
136
152
|
:files => machine_resource.files,
|
137
|
-
:
|
153
|
+
:machine_options => proc { |driver| provider.machine_options(driver) }
|
138
154
|
}
|
139
155
|
elsif machine.is_a?(ChefMetal::MachineSpec)
|
140
156
|
machine_spec = machine
|
@@ -142,7 +158,7 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
142
158
|
:spec => machine_spec,
|
143
159
|
:desired_driver => new_resource.driver,
|
144
160
|
:files => new_resource.files,
|
145
|
-
:
|
161
|
+
:machine_options => proc { |driver| machine_options(driver) }
|
146
162
|
}
|
147
163
|
else
|
148
164
|
name = machine
|
@@ -152,23 +168,17 @@ class Chef::Provider::MachineBatch < Chef::Provider::LWRPBase
|
|
152
168
|
:spec => machine_spec,
|
153
169
|
:desired_driver => new_resource.driver,
|
154
170
|
:files => new_resource.files,
|
155
|
-
:
|
171
|
+
:machine_options => proc { |driver| machine_options(driver) }
|
156
172
|
}
|
157
173
|
end
|
158
|
-
end.
|
174
|
+
end.to_a
|
159
175
|
end
|
160
176
|
|
161
|
-
def
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def new_config
|
171
|
-
@new_config ||= run_context.chef_metal.driver_config_for(new_resource.driver)
|
177
|
+
def machine_options(driver)
|
178
|
+
result = { :convergence_options => { :chef_server => new_resource.chef_server } }
|
179
|
+
result = Chef::Mixin::DeepMerge.hash_only_merge(result, run_context.chef_metal.config[:machine_options]) if run_context.chef_metal.config[:machine_options]
|
180
|
+
result = Chef::Mixin::DeepMerge.hash_only_merge(result, driver.config[:machine_options]) if driver.config && driver.config[:machine_options]
|
181
|
+
result = Chef::Mixin::DeepMerge.hash_only_merge(result, new_resource.machine_options)
|
182
|
+
result
|
172
183
|
end
|
173
|
-
|
174
184
|
end
|
@@ -41,4 +41,32 @@ class Chef::Resource::MachineBatch < Chef::Resource::LWRPBase
|
|
41
41
|
def add_machine_options(options)
|
42
42
|
@machine_options = Chef::Mixin::DeepMerge.hash_only_merge(@machine_options, options)
|
43
43
|
end
|
44
|
+
|
45
|
+
# We override this because we want to hide @from_recipe and shorten @machines
|
46
|
+
# in error output.
|
47
|
+
def to_text
|
48
|
+
ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS - [ :@from_recipe, :@machines ]
|
49
|
+
text = "# Declared in #{@source_line}\n\n"
|
50
|
+
text << self.class.dsl_name + "(\"#{name}\") do\n"
|
51
|
+
ivars.each do |ivar|
|
52
|
+
if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
|
53
|
+
value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect
|
54
|
+
text << " #{ivar.to_s.sub(/^@/,'')} #{value_string}\n"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
machine_names = @machines.map do |m|
|
58
|
+
if m.is_a?(ChefMetal::MachineSpec)
|
59
|
+
m.name
|
60
|
+
elsif m.is_a?(Chef::Resource::Machine)
|
61
|
+
m.name
|
62
|
+
else
|
63
|
+
m
|
64
|
+
end
|
65
|
+
end
|
66
|
+
text << " machines #{machine_names.inspect}\n"
|
67
|
+
[@not_if, @only_if].flatten.each do |conditional|
|
68
|
+
text << " #{conditional.to_text}\n"
|
69
|
+
end
|
70
|
+
text << "end\n"
|
71
|
+
end
|
44
72
|
end
|
data/lib/chef_metal.rb
CHANGED
@@ -30,23 +30,44 @@ module ChefMetal
|
|
30
30
|
@@registered_driver_classes[name] = driver
|
31
31
|
end
|
32
32
|
|
33
|
-
def self.config_for_url(driver_url, config = Cheffish.profiled_config)
|
34
|
-
if config && config[:drivers] && config[:drivers][driver_url]
|
35
|
-
config = Cheffish::MergedConfig.new(config[:drivers][driver_url], config)
|
36
|
-
end
|
37
|
-
config || {}
|
38
|
-
end
|
39
|
-
|
40
33
|
def self.default_driver(config = Cheffish.profiled_config)
|
41
34
|
driver_for_url(config[:driver], config)
|
42
35
|
end
|
43
36
|
|
44
|
-
def self.driver_for_url(driver_url, config = Cheffish.profiled_config)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
37
|
+
def self.driver_for_url(driver_url, config = Cheffish.profiled_config, allow_different_config = false)
|
38
|
+
#
|
39
|
+
# Create and cache the driver
|
40
|
+
#
|
41
|
+
#
|
42
|
+
# Figure out the driver class
|
43
|
+
#
|
44
|
+
scheme = driver_url.split(':', 2)[0]
|
45
|
+
require "chef_metal/driver_init/#{scheme}"
|
46
|
+
driver_class = @@registered_driver_classes[scheme]
|
47
|
+
|
48
|
+
#
|
49
|
+
# Merge in any driver-specific config
|
50
|
+
#
|
51
|
+
if config[:drivers] && config[:drivers][driver_url]
|
52
|
+
config = Cheffish::MergedConfig.new(config[:drivers][driver_url], config)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Canonicalize the URL
|
57
|
+
#
|
58
|
+
canonicalized_url, canonicalized_config = driver_class.canonicalize_url(driver_url, config)
|
59
|
+
config = canonicalized_config if canonicalized_config
|
60
|
+
|
61
|
+
#
|
62
|
+
# Merge in config from the canonicalized URL if it is different
|
63
|
+
#
|
64
|
+
if canonicalized_url != driver_url
|
65
|
+
if config[:drivers] && config[:drivers][canonicalized_url]
|
66
|
+
config = Cheffish::MergedConfig.new(config[:drivers][canonicalized_url], config)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
driver_class.from_url(canonicalized_url, config)
|
50
71
|
end
|
51
72
|
|
52
73
|
def self.connect_to_machine(machine_spec, config = Cheffish.profiled_config)
|
@@ -13,10 +13,18 @@ module ChefMetal
|
|
13
13
|
|
14
14
|
attr_reader :config
|
15
15
|
attr_reader :drivers
|
16
|
+
attr_reader :current_driver
|
16
17
|
|
17
|
-
with :driver
|
18
18
|
with :machine_options
|
19
19
|
|
20
|
+
def with_driver(driver, options = nil, &block)
|
21
|
+
if drivers[driver] && options
|
22
|
+
raise "Driver #{driver} has already been created, options #{options} would be ignored!"
|
23
|
+
end
|
24
|
+
@current_driver = driver
|
25
|
+
@current_driver_options = options
|
26
|
+
end
|
27
|
+
|
20
28
|
def auto_batch_machines
|
21
29
|
if !@auto_batch_machines.nil?
|
22
30
|
@auto_batch_machines
|
@@ -37,7 +45,7 @@ module ChefMetal
|
|
37
45
|
if @current_machine_options
|
38
46
|
@current_machine_options
|
39
47
|
else
|
40
|
-
|
48
|
+
{}
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
@@ -49,15 +57,31 @@ module ChefMetal
|
|
49
57
|
driver.is_a?(String) ? driver_for_url(driver) : driver
|
50
58
|
end
|
51
59
|
|
52
|
-
def
|
53
|
-
|
60
|
+
def connect_to_machine(name, chef_server = nil)
|
61
|
+
if name.is_a?(MachineSpec)
|
62
|
+
machine_spec = name
|
63
|
+
else
|
64
|
+
machine_spec = ChefMetal::ChefMachineSpec.get(name, chef_server)
|
65
|
+
end
|
66
|
+
ChefMetal.connect_to_machine(machine_spec, config)
|
54
67
|
end
|
55
68
|
|
69
|
+
private
|
70
|
+
|
56
71
|
def driver_for_url(driver_url)
|
57
72
|
drivers[driver_url] ||= begin
|
58
|
-
|
73
|
+
if driver_url == @current_driver && @current_driver_options
|
74
|
+
# Use the driver options if available
|
75
|
+
merged_config = Cheffish::MergedConfig.new({ :driver_options => @current_driver_options }, config)
|
76
|
+
driver = ChefMetal.driver_for_url(driver_url, merged_config)
|
77
|
+
else
|
78
|
+
driver = ChefMetal.driver_for_url(driver_url, config)
|
79
|
+
end
|
59
80
|
# Check the canonicalized driver_url from the driver
|
60
81
|
if driver.driver_url != driver_url
|
82
|
+
if drivers[driver.driver_url] && @current_driver_options
|
83
|
+
raise "Canonical driver #{driver.driver_url} for #{driver_url} has already been created! Current options #{@current_driver_options} would be ignored."
|
84
|
+
end
|
61
85
|
drivers[driver.driver_url] ||= driver
|
62
86
|
else
|
63
87
|
driver
|
@@ -65,15 +89,6 @@ module ChefMetal
|
|
65
89
|
end
|
66
90
|
end
|
67
91
|
|
68
|
-
def connect_to_machine(name, chef_server = nil)
|
69
|
-
if name.is_a?(MachineSpec)
|
70
|
-
machine_spec = name
|
71
|
-
else
|
72
|
-
machine_spec = ChefMetal::ChefMachineSpec.get(name, chef_server)
|
73
|
-
end
|
74
|
-
ChefMetal.connect_to_machine(machine_spec, config)
|
75
|
-
end
|
76
|
-
|
77
92
|
def keys
|
78
93
|
result = (config.keys || {}).dup
|
79
94
|
Array(config.key_path) do |key_path|
|
@@ -13,8 +13,8 @@ require 'chef/provider/machine_execute'
|
|
13
13
|
class Chef
|
14
14
|
module DSL
|
15
15
|
module Recipe
|
16
|
-
def with_driver(driver, &block)
|
17
|
-
run_context.chef_metal.with_driver(driver, &block)
|
16
|
+
def with_driver(driver, options = nil, &block)
|
17
|
+
run_context.chef_metal.with_driver(driver, options, &block)
|
18
18
|
end
|
19
19
|
|
20
20
|
def with_machine_options(machine_options, &block)
|
@@ -109,11 +109,10 @@ module ChefMetal
|
|
109
109
|
def make_url_available_to_remote(local_url)
|
110
110
|
uri = URI(local_url)
|
111
111
|
host = Socket.getaddrinfo(uri.host, uri.scheme, nil, :STREAM)[0][3]
|
112
|
-
if host == '127.0.0.1' || host == '
|
113
|
-
unless session.forward.active_remotes.any? { |port, bind| port == uri.port && bind ==
|
114
|
-
#
|
115
|
-
|
116
|
-
session.forward.remote(uri.port, '127.0.0.1', uri.port)
|
112
|
+
if host == '127.0.0.1' || host == '::1'
|
113
|
+
unless session.forward.active_remotes.any? { |port, bind| port == uri.port && bind == uri.host }
|
114
|
+
Chef::Log.debug("Forwarding local server #{uri.host}:#{uri.port} to port #{uri.port} on #{username}@#{self.host}")
|
115
|
+
session.forward.remote(uri.port, uri.host, uri.port)
|
117
116
|
end
|
118
117
|
end
|
119
118
|
local_url
|
@@ -125,8 +124,9 @@ module ChefMetal
|
|
125
124
|
Chef::Log.debug("Closing SSH session on #{username}@#{host}")
|
126
125
|
@session.close
|
127
126
|
rescue
|
127
|
+
ensure
|
128
|
+
@session = nil
|
128
129
|
end
|
129
|
-
@session = nil
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
@@ -136,37 +136,16 @@ module ChefMetal
|
|
136
136
|
true
|
137
137
|
rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET, Net::SSH::Disconnect
|
138
138
|
Chef::Log.debug("#{username}@#{host} unavailable: network connection failed or broke: #{$!.inspect}")
|
139
|
+
disconnect
|
139
140
|
false
|
140
141
|
rescue Net::SSH::AuthenticationFailed, Net::SSH::HostKeyMismatch
|
141
142
|
Chef::Log.debug("#{username}@#{host} unavailable: SSH authentication error: #{$!.inspect} ")
|
143
|
+
disconnect
|
142
144
|
false
|
143
145
|
end
|
144
146
|
|
145
147
|
protected
|
146
148
|
|
147
|
-
def gateway?
|
148
|
-
options.key?(:ssh_gateway) and ! options[:ssh_gateway].nil?
|
149
|
-
end
|
150
|
-
|
151
|
-
def gateway
|
152
|
-
@gateway ||= begin
|
153
|
-
gw_host, gw_user = options[:ssh_gateway].split('@').reverse
|
154
|
-
gw_host, gw_port = gw_host.split(':')
|
155
|
-
gw_user = ssh_options[:ssh_username] unless gw_user
|
156
|
-
|
157
|
-
ssh_start_opts = { timeout:10 }.merge(ssh_options)
|
158
|
-
ssh_start_opts[:port] = gw_port || 22
|
159
|
-
|
160
|
-
Chef::Log.debug("Opening SSH gateway to #{gw_user}@#{gw_host} with options #{ssh_start_opts.inspect}")
|
161
|
-
begin
|
162
|
-
Net::SSH::Gateway.new(gw_host, gw_user, ssh_start_opts)
|
163
|
-
rescue Errno::ETIMEDOUT
|
164
|
-
Chef::Log.debug("Timed out connecting to gateway: #{$!}")
|
165
|
-
raise InitialConnectTimeout.new($!)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
149
|
def session
|
171
150
|
@session ||= begin
|
172
151
|
ssh_start_opts = { timeout:10 }.merge(ssh_options)
|
@@ -230,6 +209,29 @@ module ChefMetal
|
|
230
209
|
|
231
210
|
attr_reader :original_error
|
232
211
|
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def gateway?
|
216
|
+
options.key?(:ssh_gateway) and ! options[:ssh_gateway].nil?
|
217
|
+
end
|
218
|
+
|
219
|
+
def gateway
|
220
|
+
gw_host, gw_user = options[:ssh_gateway].split('@').reverse
|
221
|
+
gw_host, gw_port = gw_host.split(':')
|
222
|
+
gw_user = ssh_options[:ssh_username] unless gw_user
|
223
|
+
|
224
|
+
ssh_start_opts = { timeout:10 }.merge(ssh_options)
|
225
|
+
ssh_start_opts[:port] = gw_port || 22
|
226
|
+
|
227
|
+
Chef::Log.debug("Opening SSH gateway to #{gw_user}@#{gw_host} with options #{ssh_start_opts.inspect}")
|
228
|
+
begin
|
229
|
+
Net::SSH::Gateway.new(gw_host, gw_user, ssh_start_opts)
|
230
|
+
rescue Errno::ETIMEDOUT
|
231
|
+
Chef::Log.debug("Timed out connecting to gateway: #{$!}")
|
232
|
+
raise InitialConnectTimeout.new($!)
|
233
|
+
end
|
234
|
+
end
|
233
235
|
end
|
234
236
|
end
|
235
237
|
end
|
data/lib/chef_metal/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clc-fork-chef-metal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.2.alpha.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Wrock
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef
|
@@ -86,42 +86,42 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - '='
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.5
|
89
|
+
version: '0.5'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - '='
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.5
|
96
|
+
version: '0.5'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: chef-metal-fog
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - '='
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.5
|
103
|
+
version: '0.5'
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - '='
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.5
|
110
|
+
version: '0.5'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: chef-metal-vagrant
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - '='
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.4
|
117
|
+
version: '0.4'
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - '='
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.4
|
124
|
+
version: '0.4'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: rspec
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|