ironfan 4.8.7 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ # v4.9.0
2
+ * initial vSphere support (thanks @brandonbell) - see lib/ironfan/dsl/vsphere and lib/ironfan/provider/vsphere for more info
3
+
1
4
  # v4.8.7
2
5
  * ec2/machine: fixing stop and start to wait for all valid end-states
3
6
 
data/Gemfile.lock CHANGED
@@ -27,16 +27,16 @@ GEM
27
27
  configliere (0.4.18)
28
28
  highline (>= 1.5.2)
29
29
  multi_json (>= 1.1)
30
- diff-lcs (1.2.1)
30
+ diff-lcs (1.2.2)
31
31
  erubis (2.7.0)
32
- excon (0.19.3)
33
- fog (1.9.0)
32
+ excon (0.20.1)
33
+ fog (1.10.1)
34
34
  builder
35
- excon (~> 0.14)
35
+ excon (~> 0.20)
36
36
  formatador (~> 0.2.0)
37
37
  mime-types
38
38
  multi_json (~> 1.0)
39
- net-scp (~> 1.0.4)
39
+ net-scp (~> 1.1)
40
40
  net-ssh (>= 2.1.3)
41
41
  nokogiri (~> 1.5.0)
42
42
  ruby-hmac
@@ -46,19 +46,19 @@ GEM
46
46
  configliere (>= 0.4.13)
47
47
  json
48
48
  multi_json (>= 1.1)
49
- guard (1.6.2)
49
+ guard (1.7.0)
50
+ formatador (>= 0.2.4)
50
51
  listen (>= 0.6.0)
51
52
  lumberjack (>= 1.0.2)
52
53
  pry (>= 0.9.10)
53
- terminal-table (>= 1.4.3)
54
54
  thor (>= 0.14.6)
55
- guard-rspec (2.4.1)
55
+ guard-rspec (2.5.2)
56
56
  guard (>= 1.1)
57
57
  rspec (~> 2.11)
58
- guard-yard (2.0.1)
58
+ guard-yard (2.1.0)
59
59
  guard (>= 1.1.0)
60
60
  yard (>= 0.7.0)
61
- highline (1.6.15)
61
+ highline (1.6.16)
62
62
  ipaddress (0.8.0)
63
63
  jeweler (1.8.4)
64
64
  bundler (~> 1.0)
@@ -69,26 +69,26 @@ GEM
69
69
  linecache19 (0.5.12)
70
70
  ruby_core_source (>= 0.1.4)
71
71
  listen (0.7.3)
72
- lumberjack (1.0.2)
72
+ lumberjack (1.0.3)
73
73
  method_source (0.8.1)
74
- mime-types (1.21)
74
+ mime-types (1.22)
75
75
  mixlib-authentication (1.3.0)
76
76
  mixlib-log
77
77
  mixlib-cli (1.3.0)
78
78
  mixlib-config (1.1.2)
79
- mixlib-log (1.4.1)
79
+ mixlib-log (1.6.0)
80
80
  mixlib-shellout (1.1.0)
81
81
  moneta (0.6.0)
82
- multi_json (1.6.1)
83
- net-scp (1.0.4)
84
- net-ssh (>= 1.99.1)
85
- net-ssh (2.6.5)
82
+ multi_json (1.7.2)
83
+ net-scp (1.1.0)
84
+ net-ssh (>= 2.6.5)
85
+ net-ssh (2.6.6)
86
86
  net-ssh-gateway (1.2.0)
87
87
  net-ssh (>= 2.6.5)
88
88
  net-ssh-multi (1.1)
89
89
  net-ssh (>= 2.1.4)
90
90
  net-ssh-gateway (>= 0.99.0)
91
- nokogiri (1.5.6)
91
+ nokogiri (1.5.9)
92
92
  ohai (6.16.0)
93
93
  ipaddress
94
94
  mixlib-cli
@@ -97,14 +97,14 @@ GEM
97
97
  mixlib-shellout
98
98
  systemu
99
99
  yajl-ruby
100
- oj (2.0.7)
100
+ oj (2.0.10)
101
101
  polyglot (0.3.3)
102
102
  pry (0.9.12)
103
103
  coderay (~> 1.0.5)
104
104
  method_source (~> 0.8)
105
105
  slop (~> 3.4)
106
- rake (10.0.3)
107
- rdoc (4.0.0)
106
+ rake (10.0.4)
107
+ rdoc (4.0.1)
108
108
  json (~> 1.4)
109
109
  redcarpet (2.2.2)
110
110
  rest-client (1.6.7)
@@ -113,10 +113,10 @@ GEM
113
113
  rspec-core (~> 2.13.0)
114
114
  rspec-expectations (~> 2.13.0)
115
115
  rspec-mocks (~> 2.13.0)
116
- rspec-core (2.13.0)
116
+ rspec-core (2.13.1)
117
117
  rspec-expectations (2.13.0)
118
118
  diff-lcs (>= 1.1.3, < 2.0)
119
- rspec-mocks (2.13.0)
119
+ rspec-mocks (2.13.1)
120
120
  ruby-debug-base19 (0.11.25)
121
121
  columnize (>= 0.3.1)
122
122
  linecache19 (>= 0.5.11)
@@ -133,10 +133,9 @@ GEM
133
133
  multi_json (~> 1.0)
134
134
  simplecov-html (~> 0.7.1)
135
135
  simplecov-html (0.7.1)
136
- slop (3.4.3)
136
+ slop (3.4.4)
137
137
  systemu (2.5.2)
138
- terminal-table (1.4.5)
139
- thor (0.17.0)
138
+ thor (0.18.1)
140
139
  treetop (1.4.12)
141
140
  polyglot
142
141
  polyglot (>= 0.3.1)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.8.7
1
+ 4.9.0
@@ -148,4 +148,4 @@ rm -r /tmp/knife-bootstrap
148
148
  updatedb
149
149
 
150
150
  echo -e "`date` \n\n**** \n**** Cluster Chef client bootstrap complete\n****\n"
151
- EOF
151
+ EOF
data/ironfan.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ironfan"
8
- s.version = "4.8.7"
8
+ s.version = "4.9.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Infochimps"]
12
- s.date = "2013-03-05"
12
+ s.date = "2013-04-09"
13
13
  s.description = "Ironfan allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks."
14
14
  s.email = "coders@infochimps.com"
15
15
  s.extra_rdoc_files = [
@@ -73,6 +73,7 @@ Gem::Specification.new do |s|
73
73
  "lib/ironfan/dsl/server.rb",
74
74
  "lib/ironfan/dsl/virtualbox.rb",
75
75
  "lib/ironfan/dsl/volume.rb",
76
+ "lib/ironfan/dsl/vsphere.rb",
76
77
  "lib/ironfan/headers.rb",
77
78
  "lib/ironfan/provider.rb",
78
79
  "lib/ironfan/provider/chef.rb",
@@ -90,6 +91,9 @@ Gem::Specification.new do |s|
90
91
  "lib/ironfan/provider/ec2/security_group.rb",
91
92
  "lib/ironfan/provider/virtualbox.rb",
92
93
  "lib/ironfan/provider/virtualbox/machine.rb",
94
+ "lib/ironfan/provider/vsphere.rb",
95
+ "lib/ironfan/provider/vsphere/keypair.rb",
96
+ "lib/ironfan/provider/vsphere/machine.rb",
93
97
  "lib/ironfan/requirements.rb",
94
98
  "notes/Future-development-proposals.md",
95
99
  "notes/Home.md",
@@ -149,7 +153,7 @@ Gem::Specification.new do |s|
149
153
  s.homepage = "http://infochimps.com/labs"
150
154
  s.licenses = ["apachev2"]
151
155
  s.require_paths = ["lib"]
152
- s.rubygems_version = "1.8.24"
156
+ s.rubygems_version = "1.8.23"
153
157
  s.summary = "Infochimps' lightweight cloud orchestration toolkit, built on top of Chef."
154
158
  s.test_files = ["spec/spec_helper/dummy_chef.rb", "spec/integration/spec_helper/launch_cluster.rb", "spec/integration/minimal-chef-repo/roles/systemwide.rb", "spec/integration/minimal-chef-repo/environments/_default.json", "spec/integration/minimal-chef-repo/chefignore", "spec/integration/minimal-chef-repo/knife/knife.rb", "spec/integration/minimal-chef-repo/knife/credentials/knife-org.rb", "spec/integration/spec/simple_cluster_spec.rb", "spec/integration/spec/elb_build_spec.rb", "spec/integration/spec_helper.rb", "spec/ironfan/cluster_spec.rb", "spec/ironfan/ec2/cloud_provider_spec.rb", "spec/ironfan/ec2/elb_spec.rb", "spec/ironfan/ec2/security_group_spec.rb", "spec/chef/cluster_launch_spec.rb", "spec/chef/cluster_bootstrap_spec.rb", "spec/fixtures/gunbai_slice.json", "spec/fixtures/ec2/elb/snakeoil.key", "spec/fixtures/ec2/elb/snakeoil.crt", "spec/fixtures/gunbai.rb", "spec/spec_helper.rb", "spec/test_config.rb"]
155
159
 
@@ -148,4 +148,4 @@ rm -r /tmp/knife-bootstrap
148
148
  updatedb
149
149
 
150
150
  echo -e "`date` \n\n**** \n**** Cluster Chef client bootstrap complete\n****\n"
151
- EOF
151
+ EOF
@@ -9,6 +9,7 @@ module Ironfan
9
9
  require 'chef/node'
10
10
  require 'chef/api_client'
11
11
  require 'fog'
12
+ require 'rbvmomi'
12
13
  end
13
14
 
14
15
  def load_ironfan
@@ -12,6 +12,7 @@ module Ironfan
12
12
  case obj[:name]
13
13
  when :ec2 then Ec2
14
14
  when :virtualbox then VirtualBox
15
+ when :vsphere then Vsphere
15
16
  else raise "Unsupported cloud #{obj[:name]}"
16
17
  end
17
18
  end
@@ -0,0 +1,89 @@
1
+ module Ironfan
2
+ class Dsl
3
+
4
+ class Compute < Ironfan::Dsl
5
+ def vsphere(*attrs,&block)
6
+ cloud(:vsphere, *attrs,&block)
7
+ end
8
+ end
9
+
10
+ class Vsphere < Cloud
11
+ magic :backing, String, :default => ''
12
+ magic :bits, Integer, :default => "64"
13
+ magic :bootstrap_distro, String, :default => 'ubuntu12.04-gems'
14
+ magic :chef_client_script, String
15
+ magic :cluster, String
16
+ magic :cpus, String, :default => "1"
17
+ magic :datacenter, String, :default => ->{ default_datacenter }
18
+ magic :datastore, String
19
+ magic :dns_servers, Array
20
+ magic :domain, String
21
+ magic :default_datacenter, String, :default => ->{ vsphere_datacenters.first }
22
+ magic :gateway, Array
23
+ magic :image_name, String
24
+ magic :ip, String
25
+ magic :memory, String, :default => "4" # Gigabytes
26
+ magic :provider, Whatever, :default => Ironfan::Provider::Vsphere
27
+ magic :ssh_identity_dir, String, :default => ->{ Chef::Config.vsphere_key_dir }
28
+ magic :ssh_user, String, :default => "root"
29
+ magic :subnet, String
30
+ magic :template, String
31
+ magic :validation_key, String, :default => ->{ IO.read(Chef::Config.validation_key) rescue '' }
32
+ magic :virtual_disks, Array, :default => []
33
+ magic :vsphere_datacenters, Array, :default => ['New Datacenter']
34
+ magic :network, String, :default => "VM Network"
35
+
36
+ def image_info
37
+ bit_str = "#{self.bits.to_i}-bit" # correct for legacy image info.
38
+ keys = [datacenter, bit_str, image_name]
39
+ # info = Chef::Config[:vsphere_image_info][ keys ]
40
+ info = nil
41
+ ui.warn("Can't find image for #{[datacenter, bit_str, image_name].inspect}") if info.blank?
42
+ return info || {}
43
+ end
44
+
45
+ def implied_volumes
46
+ result = []
47
+ # FIXME : This is really making assumptions
48
+ result << Ironfan::Dsl::Volume.new(:name => 'root') do
49
+ device '/dev/sda1'
50
+ fstype 'ext4'
51
+ keep false
52
+ mount_point '/'
53
+ end
54
+ return result unless virtual_disks.length > 0
55
+
56
+ virtual_disks.each_with_index do |vd, idx|
57
+ dev, mnt = ["/dev/sd%s" %[(66 + idx).chr.downcase], idx == 0 ? "/mnt" : "/mnt#{idx}"] # WHAAAaaa 0 o ???
58
+ virtualdisk = Ironfan::Dsl::Volume.new(:name => "virtualdisk#{idx}") do
59
+ attachable "VirtualDisk"
60
+ fstype vd[:fs]
61
+ device dev
62
+ mount_point vd[:mount_point] || mnt
63
+ formattable true
64
+ create_at_launch true
65
+ mount_options 'defaults,noatime'
66
+ tags vd[:tags]
67
+ end
68
+ result << virtualdisk
69
+ end
70
+ result
71
+ end
72
+
73
+ def ssh_identity_file(computer)
74
+ cluster = computer.server.cluster_name
75
+ "%s/%s.pem" %[ssh_identity_dir, cluster]
76
+ end
77
+
78
+ def to_display(style,values={})
79
+ return values if style == :minimal
80
+
81
+ values["Datacenter"] = datacenter
82
+ return values if style == :default
83
+
84
+ values
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -30,6 +30,7 @@ module Ironfan
30
30
  class IamServerCertificate < Ironfan::Dsl; end
31
31
  end
32
32
  class VirtualBox < Cloud; end
33
+ class Vsphere < Cloud; end
33
34
  end
34
35
 
35
36
  class Provider < Builder
@@ -57,6 +58,11 @@ module Ironfan
57
58
  class VirtualBox < Ironfan::IaasProvider
58
59
  class Machine < Ironfan::IaasProvider::Machine; end
59
60
  end
61
+ class Vsphere < Ironfan::IaasProvider
62
+ class Machine < Ironfan::IaasProvider::Machine; end
63
+ class Keypair < Ironfan::Provider::Resource; end
64
+ end
65
+
60
66
  end
61
67
 
62
68
  end
@@ -0,0 +1,74 @@
1
+ module Ironfan
2
+ class Provider
3
+ class Vsphere
4
+
5
+ class Keypair < Ironfan::Provider::Resource
6
+ delegate :connection, :connection=, :name, :private_key,
7
+ :to => :adaptee
8
+ # delegate :_dump, :collection, :collection=, :connection,
9
+ # :connection=, :destroy, :fingerprint, :fingerprint=, :identity,
10
+ # :identity=, :name, :name=, :new_record?, :public_key,
11
+ # :public_key=, :reload, :requires, :requires_one, :save,
12
+ # :symbolize_keys, :wait_for, :writable?, :write,
13
+ # :to => :adaptee
14
+
15
+ field :key_filename, String, :default => ->{ "#{Keypair.key_dir}/#{name}.pem" }
16
+
17
+ def self.shared? ; true ; end
18
+ def self.multiple? ; false ; end
19
+ def self.resource_type ; :keypair ; end
20
+
21
+ def self.expected_ids(computer)
22
+ [computer.server.cluster_name]
23
+ end
24
+
25
+ def private_key
26
+ puts "pkey"
27
+ File.open(key_filename, "rb").read
28
+ end
29
+
30
+ def self.public_key(computer)
31
+ key_filename = "%s/%s.pem" %[key_dir, computer.server.cluster_name]
32
+ key = OpenSSL::PKey::RSA.new(File.open(key_filename, "rb").read)
33
+ data = [ key.to_blob ].pack('m0')
34
+ "#{key.ssh_type} #{data}"
35
+ end
36
+
37
+ def self.create_private_key(key, name)
38
+ key_filename = "%s/%s.pem" %[key_dir, name]
39
+ File.open(key_filename, "w", 0600){|f| f.print( key.to_s ) }
40
+ end
41
+
42
+ def to_s
43
+ "<%-15s %-12s>" % [self.class.handle, name]
44
+ end
45
+
46
+ #
47
+ # Manipulation
48
+ #
49
+
50
+ def self.prepare!(computers)
51
+ return if computers.empty?
52
+ name = computers.values[0].server.cluster_name
53
+ return if recall? name
54
+ return if File.exists?("%s/%s.pem" %[key_dir, name])
55
+ Ironfan.step(name, "creating key pair for #{name}", :blue)
56
+ Dir.mkdir(key_dir) if !FileTest::directory?(key_dir)
57
+ create_private_key(OpenSSL::PKey::RSA.new(2048), name)
58
+ end
59
+
60
+ #
61
+ # Utility
62
+ #
63
+
64
+ def self.key_dir
65
+ return Chef::Config.vsphere_key_dir if Chef::Config.vsphere_key_dir
66
+ dir = "#{ENV['HOME']}/.chef/credentials/vshere_keys"
67
+ warn "Please set 'vsphere_key_dir' in your knife.rb. Will use #{dir} as a default"
68
+ dir
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,343 @@
1
+ module Ironfan
2
+ class Provider
3
+ class Vsphere
4
+
5
+ class Machine < Ironfan::IaasProvider::Machine
6
+ delegate :config, :connection, :connection=, :disks, :Destroy_Task, :guest, :PowerOffVM_Task,
7
+ :PowerOnVM_Task, :powerState, :ReconfigVM_Task, :runtime,
8
+ :to => :adaptee
9
+
10
+ def self.shared?() false; end
11
+ def self.multiple?() false; end
12
+ def self.resource_type() :machine; end
13
+ def self.expected_ids(computer) [computer.server.full_name]; end
14
+
15
+ def name
16
+ return adaptee.config.name
17
+ end
18
+
19
+ def keypair
20
+ end
21
+
22
+ def vpc_id
23
+ return true
24
+ end
25
+
26
+ def dns_name
27
+ host = adaptee.guest.hostName
28
+ domain = adaptee.guest.domainName
29
+ return host unless domain
30
+ return "%s.%s" %[host, domain]
31
+ end
32
+
33
+ def public_ip_address
34
+ # FIXME
35
+ return adaptee.guest.ipAddress
36
+ end
37
+
38
+ def private_ip_address
39
+ return adaptee.guest.ipAddress
40
+ end
41
+
42
+ def public_hostname
43
+ # FIXME
44
+ return nil
45
+ end
46
+
47
+ def destroy
48
+ adaptee.PowerOffVM_Task.wait_for_completion unless adaptee.runtime.powerState == "poweredOff"
49
+ adaptee.Destroy_Task.wait_for_completion
50
+ state = "destroyed"
51
+ end
52
+
53
+ def created?
54
+ ["poweredOn", "poweredOff"].include? adaptee.runtime.powerState
55
+ end
56
+
57
+ def wait_for
58
+ return true
59
+ end
60
+
61
+ def running?
62
+ adaptee.runtime.powerState == "poweredOn"
63
+ state = "poweredOn"
64
+ end
65
+
66
+ def stopped?
67
+ adaptee.runtime.powerState == "poweredOff"
68
+ state = "poweredOff"
69
+ end
70
+
71
+ def start
72
+ adaptee.PowerOnVM_Task.wait_for_completion
73
+ end
74
+
75
+ def stop
76
+ # TODO: Gracefully shutdown guest using adatee.ShutdownGuest ?
77
+ # There is no "wait_for_completion" with this however...
78
+ adaptee.PowerOffVM_Task.wait_for_completion
79
+ end
80
+
81
+ def self.ip_settings(settings, hostname)
82
+ # FIXME: A lot of this is required if using a static IP
83
+ # Heavily referenced Serengeti's FOG here
84
+
85
+ ip_settings = RbVmomi::VIM::CustomizationIPSettings.new(:ip =>
86
+ RbVmomi::VIM::CustomizationFixedIp(:ipAddress => settings[:ip]), :gateway => settings[:gateway], :subnetMask => settings[:subnet]) if settings[:ip]
87
+ ip_settings ||= RbVmomi::VIM::CustomizationIPSettings.new("ip" => RbVmomi::VIM::CustomizationDhcpIpGenerator.new())
88
+ ip_settings.dnsDomain = settings[:domain]
89
+ global_ip_settings = RbVmomi::VIM.CustomizationGlobalIPSettings
90
+ global_ip_settings.dnsServerList = settings[:dnsServers]
91
+ global_ip_settings.dnsSuffixList = [settings[:domain]]
92
+ hostname = RbVmomi::VIM::CustomizationFixedName.new(:name => hostname)
93
+ linux_prep = RbVmomi::VIM::CustomizationLinuxPrep.new( :domain => settings[:domain], :hostName => hostname )
94
+ adapter_mapping = [RbVmomi::VIM::CustomizationAdapterMapping.new("adapter" => ip_settings)]
95
+ spec = RbVmomi::VIM::CustomizationSpec.new( :identity => linux_prep,
96
+ :globalIPSettings => global_ip_settings,
97
+ :nicSettingMap => adapter_mapping )
98
+
99
+ return spec
100
+
101
+ end
102
+
103
+ def self.generate_clone_spec(src_config, dc, cpus, memory, datastore, virtual_disks, network, cluster)
104
+ # TODO : A lot of this should be moved to utilities in providers/vsphere.rb
105
+ rspec = Vsphere.get_rspec(dc, cluster)
106
+ rspec.datastore = datastore
107
+
108
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec, :template => false, :powerOn => true)
109
+ clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new, :extraConfig => nil)
110
+
111
+ network = Vsphere.find_network(network, dc)
112
+ card = src_config.hardware.device.find { |d| d.deviceInfo.label == "Network adapter 1" }
113
+ begin
114
+ switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(
115
+ :switchUuid => network.config.distributedVirtualSwitch.uuid,
116
+ :portgroupKey => network.key
117
+ )
118
+ card.backing.port = switch_port
119
+ rescue
120
+ card.backing.deviceName = network.name
121
+ end
122
+
123
+ network_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
124
+ clone_spec.config.deviceChange.push network_spec
125
+
126
+ virtual_disks.each_with_index do |vd, idx|
127
+ size = vd[:size].to_i
128
+ label = vd[:label].to_i
129
+ key = 2001 + idx # key 2000 -> SCSI0, 2001 -> SCSI1...
130
+ filename = vd[:datastore] || datastore.name
131
+
132
+ disk = RbVmomi::VIM.VirtualDisk(
133
+ :key => key,
134
+ :capacityInKB => size * 1024 * 1024,
135
+ :controllerKey => 1000, # SCSI controller
136
+ :unitNumber => idx + 1,
137
+ :backing => RbVmomi::VIM.VirtualDiskFlatVer2BackingInfo(
138
+ :fileName => "[#{filename}]",
139
+ :diskMode => :persistent,
140
+ :thinProvisioned => true,
141
+ :datastore => Vsphere.find_ds(dc, vd[:datastore]) || datastore
142
+ ),
143
+ :deviceInfo => RbVmomi::VIM.Description(
144
+ :label => label,
145
+ :summary => "%sGB" %[size]
146
+ ),
147
+ )
148
+
149
+ disk_spec = {:operation => :add, :fileOperation => :create, :device => disk }
150
+ clone_spec.config.deviceChange.push disk_spec
151
+ end
152
+
153
+ clone_spec.config.numCPUs = Integer(cpus)
154
+ clone_spec.config.memoryMB = Integer(memory) * 1024
155
+
156
+ clone_spec
157
+ end
158
+
159
+ def self.create!(computer)
160
+ return if computer.machine? and computer.machine.created?
161
+ Ironfan.step(computer.name,"creating cloud machine", :green)
162
+
163
+ errors = lint(computer)
164
+ if errors.present? then raise ArgumentError, "Failed validation: #{errors.inspect}" ; end
165
+
166
+ # TODO: Pass launch_desc to a single function and let it do the rest... like fog does
167
+ launch_desc = launch_description(computer)
168
+ cpus = launch_desc[:cpus]
169
+ datacenter = launch_desc[:datacenter]
170
+ memory = launch_desc[:memory]
171
+ template = launch_desc[:template]
172
+ user_data = launch_desc[:user_data]
173
+ virtual_disks = launch_desc[:virtual_disks]
174
+ network = launch_desc[:network]
175
+ ip_settings = launch_desc[:ip_settings]
176
+
177
+ datacenter = Vsphere.find_dc(launch_desc[:datacenter])
178
+ raise "Couldn't find #{launch_desc[:datacenter]} datacenter" unless datacenter
179
+ cluster = Vsphere.find_in_folder(datacenter.hostFolder, RbVmomi::VIM::ClusterComputeResource, launch_desc[:cluster])
180
+ raise "Couldn't find #{launch_desc[:cluster]} cluster in #{datacenter}" unless cluster
181
+ datastore = Vsphere.find_ds(datacenter, launch_desc[:datastore]) # FIXME: add in round robin choosing?
182
+ raise "Couldn't find #{launch_desc[:datastore]} datastore in #{datacenter}" unless datastore
183
+ src_folder = datacenter.vmFolder
184
+ src_vm = Vsphere.find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, template)
185
+ raise "Couldn't find template #{template} in #{datacenter}:#{cluster}" unless src_vm
186
+
187
+ Ironfan.safely do
188
+ clone_spec = generate_clone_spec(src_vm.config, datacenter, cpus, memory, datastore, virtual_disks, network, cluster)
189
+ clone_spec.customization = ip_settings(ip_settings, computer.name) if ip_settings[:ip]
190
+
191
+ vsphere_server = src_vm.CloneVM_Task(:folder => src_vm.parent, :name => computer.name, :spec => clone_spec)
192
+
193
+ state = vsphere_server.info.state # RbVmomi wait_for_completion stops world...
194
+ while (state != 'error') and (state != 'success')
195
+ sleep 2
196
+ state = vsphere_server.info.state
197
+ end
198
+
199
+ # Will break if the VM isn't finished building.
200
+ new_vm = Vsphere.find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, computer.name)
201
+ machine = Machine.new(:adaptee => new_vm)
202
+ computer.machine = machine
203
+ remember machine, :id => machine.name
204
+
205
+ Ironfan.step(computer.name,"pushing keypair", :green)
206
+ public_key = Vsphere::Keypair.public_key(computer)
207
+ # TODO - This probably belongs somewhere else. This is how we'll enject the pubkey as well as any user information we may want
208
+ # This data is not persistent across reboots...
209
+ extraConfig = [{:key => 'guestinfo.pubkey', :value => public_key}, {:key => "guestinfo.user_data", :value => user_data}]
210
+ machine.ReconfigVM_Task(:spec => RbVmomi::VIM::VirtualMachineConfigSpec(:extraConfig => extraConfig)).wait_for_completion
211
+
212
+ # FIXME: This is extremelty fragile right now
213
+ if ip_settings[:ip]
214
+ Ironfan.step(computer.name,"waiting for ip customizations to complete", :green)
215
+ while machine.guest.ipAddress != ip_settings[:ip]
216
+ sleep 2
217
+ end
218
+ end
219
+
220
+ end
221
+ end
222
+
223
+ #
224
+ # Discovery
225
+ #
226
+ def self.load!(cluster=nil)
227
+ # FIXME: Fix this one day when we have multiple "datacenters".
228
+ cloud = cluster.servers.values[0].cloud(:vsphere)
229
+ vm_folder = Vsphere.find_dc(cloud.datacenter).vmFolder
230
+ Vsphere.find_all_in_folder(vm_folder, RbVmomi::VIM::VirtualMachine).each do |fs|
231
+ machine = new(:adaptee => fs)
232
+ if recall? machine.name
233
+ machine.bogus << :duplicate_machines
234
+ recall(machine.name).bogus << :duplicate_machines
235
+ remember machine, :append_id => "duplicate:#{machine.config.uuid}"
236
+ else # never seen it
237
+ remember machine
238
+ end
239
+ Chef::Log.debug("Loaded #{machine}")
240
+ end
241
+ end
242
+
243
+ def self.destroy!(computer)
244
+ return unless computer.machine?
245
+ forget computer.machine.name
246
+ computer.machine.destroy
247
+ end
248
+
249
+ def self.launch_description(computer)
250
+ cloud = computer.server.cloud(:vsphere)
251
+ user_data_hsh = {
252
+ :chef_server => Chef::Config[:chef_server_url],
253
+ :client_key => computer.private_key,
254
+ :cluster_name => computer.server.cluster_name,
255
+ :facet_index => computer.server.index,
256
+ :facet_name => computer.server.facet_name,
257
+ :node_name => computer.name,
258
+ :organization => Chef::Config[:organization],
259
+ }
260
+
261
+ ip_settings = {
262
+ :dnsServers => cloud.dns_servers,
263
+ :domain => cloud.domain,
264
+ :gateway => cloud.gateway,
265
+ :ip => cloud.ip,
266
+ :subnet => cloud.subnet,
267
+ }
268
+
269
+ description = {
270
+ :cpus => cloud.cpus,
271
+ :cluster => cloud.cluster,
272
+ :datacenter => cloud.datacenter,
273
+ :datastore => cloud.datastore,
274
+ :memory => cloud.memory,
275
+ :network => cloud.network,
276
+ :template => cloud.template,
277
+ :user_data => JSON.pretty_generate(user_data_hsh),
278
+ :virtual_disks => cloud.virtual_disks,
279
+ :ip_settings => ip_settings
280
+ }
281
+ description
282
+ end
283
+
284
+ # @returns [Hash{String, Array}] of 'what you did wrong' => [relevant, info]
285
+ def self.lint(computer)
286
+ cloud = computer.server.cloud(:vsphere)
287
+ info = [computer.name, cloud.inspect]
288
+ errors = {}
289
+ server_errors = computer.server.lint
290
+ errors["Unhappy Server"] = server_errors if server_errors.present?
291
+ errors["Datacenter"] = info if cloud.datacenter.blank?
292
+ errors["Template"] = info if cloud.template.blank?
293
+ errors["Datastore"] = info if cloud.datastore.blank?
294
+ errors['Missing client'] = info unless computer.client?
295
+ errors['Missing private_key'] = computer.client unless computer.private_key
296
+ #
297
+ errors
298
+ end
299
+
300
+ def to_display(style,values={})
301
+ # style == :minimal
302
+ values["State"] = runtime.powerState rescue "Terminated"
303
+ values["MachineID"] = config.uuid rescue ""
304
+ # values["Public IP"] = public_ip_address
305
+ values["Private IP"] = guest.ipAddress rescue ""
306
+ # values["Created On"] = created_at.to_date
307
+ return values if style == :minimal
308
+
309
+ # style == :default
310
+ # values["Flavor"] = flavor_id
311
+ # values["AZ"] = availability_zone
312
+ return values if style == :default
313
+
314
+ # style == :expanded
315
+ # values["Image"] = image_id
316
+ values["Virtual Disks"] = disks.map { |d| d.backing.fileName }.join(', ')
317
+ # values["SSH Key"] = key_name
318
+ values
319
+ end
320
+
321
+ def ssh_key
322
+ keypair = cloud.keypair || computer.server.cluster_name
323
+ end
324
+
325
+ def self.validate_resources!(computers)
326
+ recall.each_value do |machine|
327
+ next unless machine.users.empty? and machine.name
328
+ if machine.name.match("^#{computers.cluster.name}-")
329
+ machine.bogus << :unexpected_machine
330
+ end
331
+ next unless machine.bogus?
332
+ fake = Ironfan::Broker::Computer.new
333
+ fake[:machine] = machine
334
+ fake.name = machine.name
335
+ machine.users << fake
336
+ computers << fake
337
+ end
338
+ end
339
+
340
+ end
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,79 @@
1
+ module Ironfan
2
+ class Provider
3
+
4
+ class Vsphere < Ironfan::IaasProvider
5
+ self.handle = :vsphere
6
+
7
+ def self.resources()
8
+ [ Machine, Keypair]
9
+ end
10
+
11
+ #
12
+ # Utility functions
13
+ #
14
+
15
+ # Some methods cribbed and modified from https://github.com/ezrapagel/knife-vsphere, Ezra Pagel, Jesse Campbell
16
+ def self.connection
17
+ @@connection ||= RbVmomi::VIM.connect(self.vsphere_credentials)
18
+ end
19
+
20
+ def self.find_ds(vsphere_dc, name = nil)
21
+ ds = vsphere_dc.datastore
22
+ return ds.grep(RbVmomi::VIM::Datastore).find { |d| d.name == name } if !name.nil?
23
+ end
24
+
25
+ def self.find_dc(vsphere_dc)
26
+ connection.serviceInstance.find_datacenter(vsphere_dc)
27
+ end
28
+
29
+ def self.find_in_folder(folder, type, name)
30
+ folder.childEntity.grep(type).find { |o| o.name == name }
31
+ end
32
+
33
+ def self.get_rspec(dc, cluster)
34
+ if cluster
35
+ resource_pool = cluster.resourcePool
36
+ else
37
+ hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource).find { |o| o.class == RbVmomi::VIM::ComputeResource }
38
+ resource_pool = hosts.resourcePool || hosts.first.resourcePool
39
+ end
40
+ RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => resource_pool)
41
+ end
42
+
43
+ def self.find_all_in_folder(folder, type)
44
+ if folder.instance_of?(RbVmomi::VIM::ClusterComputeResource)
45
+ folder = folder.resourcePool
46
+ end
47
+
48
+ if folder.instance_of?(RbVmomi::VIM::ResourcePool)
49
+ folder.resourcePool.grep(type)
50
+ elsif folder.instance_of?(RbVmomi::VIM::Folder)
51
+ folder.childEntity.grep(type)
52
+ else
53
+ puts "Unknown type #{folder.class}, not enumerating"
54
+ nil
55
+ end
56
+ end
57
+
58
+ def self.find_network(network, dc)
59
+ baseEntity = dc.network
60
+ baseEntity.find { |f| f.name == network }
61
+ end
62
+
63
+ def self.get_pub_key(cluster)
64
+ puts Keypair.private_key
65
+ end
66
+
67
+ private
68
+ def self.vsphere_credentials
69
+ return {
70
+ :user => Chef::Config[:knife][:vsphere_username],
71
+ :port => Chef::Config[:knife][:vsphere_port] || 443,
72
+ :password => Chef::Config[:knife][:vsphere_password],
73
+ :host => Chef::Config[:knife][:vsphere_server],
74
+ :insecure => Chef::Config[:knife][:vsphere_insecure] || true
75
+ }
76
+ end
77
+ end
78
+ end
79
+ end
@@ -13,6 +13,7 @@ module Ironfan
13
13
  case obj[:name]
14
14
  when :chef then Chef
15
15
  when :ec2 then Ec2
16
+ when :vsphere then Vsphere
16
17
  when :virtualbox then VirtualBox
17
18
  else raise "Unsupported provider #{obj[:name]}"
18
19
  end
@@ -19,6 +19,7 @@ require 'ironfan/dsl/volume'
19
19
 
20
20
  require 'ironfan/dsl/cloud'
21
21
  require 'ironfan/dsl/ec2'
22
+ require 'ironfan/dsl/vsphere'
22
23
 
23
24
 
24
25
  # Providers for specific resources
@@ -42,6 +43,10 @@ require 'ironfan/provider/ec2/iam_server_certificate'
42
43
  require 'ironfan/provider/virtualbox'
43
44
  require 'ironfan/provider/virtualbox/machine'
44
45
 
46
+ require 'ironfan/provider/vsphere'
47
+ require 'ironfan/provider/vsphere/machine'
48
+ require 'ironfan/provider/vsphere/keypair'
49
+
45
50
 
46
51
  # Broker classes to coordinate DSL expectations and provider resources
47
52
  require 'ironfan/broker'
metadata CHANGED
@@ -1,148 +1,201 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ironfan
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 4.9.0
4
5
  prerelease:
5
- version: 4.8.7
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Infochimps
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2013-03-05 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2013-04-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
16
15
  name: chef
17
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
18
17
  none: false
19
- requirements:
18
+ requirements:
20
19
  - - ~>
21
- - !ruby/object:Gem::Version
22
- version: "10.16"
20
+ - !ruby/object:Gem::Version
21
+ version: '10.16'
23
22
  type: :runtime
24
23
  prerelease: false
25
- version_requirements: *id001
26
- - !ruby/object:Gem::Dependency
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '10.16'
30
+ - !ruby/object:Gem::Dependency
27
31
  name: fog
28
- requirement: &id002 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
29
33
  none: false
30
- requirements:
34
+ requirements:
31
35
  - - ~>
32
- - !ruby/object:Gem::Version
33
- version: "1.2"
36
+ - !ruby/object:Gem::Version
37
+ version: '1.2'
34
38
  type: :runtime
35
39
  prerelease: false
36
- version_requirements: *id002
37
- - !ruby/object:Gem::Dependency
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.2'
46
+ - !ruby/object:Gem::Dependency
38
47
  name: formatador
39
- requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
40
49
  none: false
41
- requirements:
50
+ requirements:
42
51
  - - ~>
43
- - !ruby/object:Gem::Version
44
- version: "0.2"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.2'
45
54
  type: :runtime
46
55
  prerelease: false
47
- version_requirements: *id003
48
- - !ruby/object:Gem::Dependency
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ - !ruby/object:Gem::Dependency
49
63
  name: gorillib
50
- requirement: &id004 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
51
65
  none: false
52
- requirements:
66
+ requirements:
53
67
  - - ~>
54
- - !ruby/object:Gem::Version
68
+ - !ruby/object:Gem::Version
55
69
  version: 0.4.2
56
70
  type: :runtime
57
71
  prerelease: false
58
- version_requirements: *id004
59
- - !ruby/object:Gem::Dependency
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.4.2
78
+ - !ruby/object:Gem::Dependency
60
79
  name: json
61
- requirement: &id005 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
62
81
  none: false
63
- requirements:
64
- - - "="
65
- - !ruby/object:Gem::Version
82
+ requirements:
83
+ - - '='
84
+ - !ruby/object:Gem::Version
66
85
  version: 1.5.4
67
86
  type: :runtime
68
87
  prerelease: false
69
- version_requirements: *id005
70
- - !ruby/object:Gem::Dependency
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - '='
92
+ - !ruby/object:Gem::Version
93
+ version: 1.5.4
94
+ - !ruby/object:Gem::Dependency
71
95
  name: bundler
72
- requirement: &id006 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
73
97
  none: false
74
- requirements:
98
+ requirements:
75
99
  - - ~>
76
- - !ruby/object:Gem::Version
77
- version: "1.0"
100
+ - !ruby/object:Gem::Version
101
+ version: '1.0'
78
102
  type: :development
79
103
  prerelease: false
80
- version_requirements: *id006
81
- - !ruby/object:Gem::Dependency
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '1.0'
110
+ - !ruby/object:Gem::Dependency
82
111
  name: rake
83
- requirement: &id007 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
84
113
  none: false
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: "0"
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
89
118
  type: :development
90
119
  prerelease: false
91
- version_requirements: *id007
92
- - !ruby/object:Gem::Dependency
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
93
127
  name: rspec
94
- requirement: &id008 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
95
129
  none: false
96
- requirements:
130
+ requirements:
97
131
  - - ~>
98
- - !ruby/object:Gem::Version
99
- version: "2.8"
132
+ - !ruby/object:Gem::Version
133
+ version: '2.8'
100
134
  type: :development
101
135
  prerelease: false
102
- version_requirements: *id008
103
- - !ruby/object:Gem::Dependency
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '2.8'
142
+ - !ruby/object:Gem::Dependency
104
143
  name: yard
105
- requirement: &id009 !ruby/object:Gem::Requirement
144
+ requirement: !ruby/object:Gem::Requirement
106
145
  none: false
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: "0.7"
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0.7'
111
150
  type: :development
112
151
  prerelease: false
113
- version_requirements: *id009
114
- - !ruby/object:Gem::Dependency
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0.7'
158
+ - !ruby/object:Gem::Dependency
115
159
  name: redcarpet
116
- requirement: &id010 !ruby/object:Gem::Requirement
160
+ requirement: !ruby/object:Gem::Requirement
117
161
  none: false
118
- requirements:
119
- - - ">="
120
- - !ruby/object:Gem::Version
121
- version: "2.1"
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '2.1'
122
166
  type: :development
123
167
  prerelease: false
124
- version_requirements: *id010
125
- - !ruby/object:Gem::Dependency
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '2.1'
174
+ - !ruby/object:Gem::Dependency
126
175
  name: oj
127
- requirement: &id011 !ruby/object:Gem::Requirement
176
+ requirement: !ruby/object:Gem::Requirement
128
177
  none: false
129
- requirements:
130
- - - ">="
131
- - !ruby/object:Gem::Version
132
- version: "1.2"
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '1.2'
133
182
  type: :development
134
183
  prerelease: false
135
- version_requirements: *id011
136
- description: Ironfan allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks.
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '1.2'
190
+ description: Ironfan allows you to orchestrate not just systems but clusters of machines.
191
+ It includes a powerful layer on top of knife and a collection of cloud cookbooks.
137
192
  email: coders@infochimps.com
138
193
  executables: []
139
-
140
194
  extensions: []
141
-
142
- extra_rdoc_files:
195
+ extra_rdoc_files:
143
196
  - LICENSE.md
144
197
  - README.md
145
- files:
198
+ files:
146
199
  - .gitignore
147
200
  - .rspec
148
201
  - .yardopts
@@ -199,6 +252,7 @@ files:
199
252
  - lib/ironfan/dsl/server.rb
200
253
  - lib/ironfan/dsl/virtualbox.rb
201
254
  - lib/ironfan/dsl/volume.rb
255
+ - lib/ironfan/dsl/vsphere.rb
202
256
  - lib/ironfan/headers.rb
203
257
  - lib/ironfan/provider.rb
204
258
  - lib/ironfan/provider/chef.rb
@@ -216,6 +270,9 @@ files:
216
270
  - lib/ironfan/provider/ec2/security_group.rb
217
271
  - lib/ironfan/provider/virtualbox.rb
218
272
  - lib/ironfan/provider/virtualbox/machine.rb
273
+ - lib/ironfan/provider/vsphere.rb
274
+ - lib/ironfan/provider/vsphere/keypair.rb
275
+ - lib/ironfan/provider/vsphere/machine.rb
219
276
  - lib/ironfan/requirements.rb
220
277
  - notes/Future-development-proposals.md
221
278
  - notes/Home.md
@@ -272,36 +329,34 @@ files:
272
329
  - spec/test_config.rb
273
330
  - tasks/chef_config.rake
274
331
  homepage: http://infochimps.com/labs
275
- licenses:
332
+ licenses:
276
333
  - apachev2
277
334
  post_install_message:
278
335
  rdoc_options: []
279
-
280
- require_paths:
336
+ require_paths:
281
337
  - lib
282
- required_ruby_version: !ruby/object:Gem::Requirement
338
+ required_ruby_version: !ruby/object:Gem::Requirement
283
339
  none: false
284
- requirements:
285
- - - ">="
286
- - !ruby/object:Gem::Version
287
- hash: -3865228418823474445
288
- segments:
340
+ requirements:
341
+ - - ! '>='
342
+ - !ruby/object:Gem::Version
343
+ version: '0'
344
+ segments:
289
345
  - 0
290
- version: "0"
291
- required_rubygems_version: !ruby/object:Gem::Requirement
346
+ hash: 3090386787638060354
347
+ required_rubygems_version: !ruby/object:Gem::Requirement
292
348
  none: false
293
- requirements:
294
- - - ">="
295
- - !ruby/object:Gem::Version
296
- version: "0"
349
+ requirements:
350
+ - - ! '>='
351
+ - !ruby/object:Gem::Version
352
+ version: '0'
297
353
  requirements: []
298
-
299
354
  rubyforge_project:
300
- rubygems_version: 1.8.24
355
+ rubygems_version: 1.8.23
301
356
  signing_key:
302
357
  specification_version: 3
303
358
  summary: Infochimps' lightweight cloud orchestration toolkit, built on top of Chef.
304
- test_files:
359
+ test_files:
305
360
  - spec/spec_helper/dummy_chef.rb
306
361
  - spec/integration/spec_helper/launch_cluster.rb
307
362
  - spec/integration/minimal-chef-repo/roles/systemwide.rb