chef-metal 0.2.1 → 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3002d430afde46610d70663bf72b42f7740b6b3b
4
- data.tar.gz: b8f8311dc26ae7c0fa756704479169f170c6e47d
3
+ metadata.gz: b141298163e702535479301b6c88044960e65cc0
4
+ data.tar.gz: 64383c31616aa7fce1f7785d41134b6516d9b51d
5
5
  SHA512:
6
- metadata.gz: 4f2e703168cdc5f97e35270a432f1ba88544f87cb0e5e4863bde5f81dff0ab5df96c5bfc790ec5f53743c616bb928ea73e7c0f29df48816f66028706089bb8e6
7
- data.tar.gz: 04318115c38fe30f91fa386f0e5cc43791c474a4b442e0ad5e54f29780b338bde865c66f4d17ff0a2842ac65024e2599c197a1fcd2f4a9038d89272cd88c0951
6
+ metadata.gz: b86349e657a31e3594823b7036aac2c6fd2e5cf2e629da6d1db58a4069502a494bade45142d19db91533cfa66b698600c78f910955cf18a9d8ea0d5754c0e812
7
+ data.tar.gz: 2dbcb57bdfac12bea0e67c0da47b05a6f9cdc77415d14169820244f4c1ec3172ddc227e19a91f686ae39cc134394fbe11d82d66db9501a3ddbe5ee2b0b3fcba3
data/README.md CHANGED
@@ -120,10 +120,10 @@ chef-metal also comes with a [Fog](http://fog.io/) provisioner that handles prov
120
120
  Once your credentials are in, basic usage looks like this:
121
121
 
122
122
  ```
123
- chef-client -z -o myapp::vagrant,myapp::small
123
+ chef-client -z -o myapp::ec2,myapp::small
124
124
  ```
125
125
 
126
- The provisioner definition in `myapp::vagrant` looks like this:
126
+ The provisioner definition in `myapp::ec2` looks like this:
127
127
 
128
128
  ```ruby
129
129
  ec2testdir = File.expand_path('~/ec2test')
@@ -0,0 +1,121 @@
1
+ require 'chef_metal/convergence_strategy/precreate_chef_objects'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ require 'digest/md5'
5
+ require 'thread'
6
+
7
+ module ChefMetal
8
+ class ConvergenceStrategy
9
+ class InstallCached < PrecreateChefObjects
10
+ def initialize(options = {})
11
+ @client_rb_path ||= '/etc/chef/client.rb'
12
+ @client_pem_path ||= '/etc/chef/client.pem'
13
+ @chef_version ||= options[:chef_version]
14
+ @prerelease ||= options[:prerelease]
15
+ @package_cache_path ||= options[:package_cache_path] || "#{ENV['HOME']}/.chef/package_cache"
16
+ @package_cache = {}
17
+ @tmp_dir = '/tmp'
18
+ FileUtils.mkdir_p(@package_cache_path)
19
+ @download_lock = Mutex.new
20
+ end
21
+
22
+ def setup_convergence(provider, machine, machine_resource)
23
+ super
24
+
25
+ # Install chef-client. TODO check and update version if not latest / not desired
26
+ if machine.execute_always('chef-client -v').exitstatus != 0
27
+ platform, platform_version, machine_architecture = machine.detect_os(provider)
28
+ package_file = download_package_for_platform(provider, machine, platform, platform_version, machine_architecture)
29
+ remote_package_file = "#{@tmp_dir}/#{File.basename(package_file)}"
30
+ machine.upload_file(provider, package_file, remote_package_file)
31
+ install_package(provider, machine, remote_package_file)
32
+ end
33
+ end
34
+
35
+ def converge(provider, machine)
36
+ machine.execute(provider, 'chef-client')
37
+ end
38
+
39
+ private
40
+
41
+ def download_package_for_platform(provider, machine, platform, platform_version, machine_architecture)
42
+ @package_cache_lock.synchronize do
43
+ @package_cache ||= {}
44
+ @package_cache[platform] ||= {}
45
+ @package_cache[platform][platform_version] ||= {}
46
+ @package_cache[platform][platform_version][machine_architecture] ||= { :lock => Mutex.new }
47
+ end
48
+ @package_cache[platform][platform_version][machine_architecture][:lock].synchronize do
49
+ if !@package_cache[platform][platform_version][machine_architecture][:file]
50
+ #
51
+ # Grab metadata
52
+ #
53
+ metadata = download_metadata_for_platform(machine, platform, platform_version, machine_architecture)
54
+
55
+ # Download actual package desired by metadata
56
+ package_file = "#{@package_cache_path}/#{URI(metadata['url']).path.split('/')[-1]}"
57
+
58
+ ChefMetal.inline_resource(provider) do
59
+ remote_file package_file do
60
+ source metadata['url']
61
+ checksum metadata['sha256']
62
+ end
63
+ end
64
+
65
+ @package_cache[platform][platform_version][machine_architecture][:file] = package_file
66
+ end
67
+ end
68
+ @package_cache[platform][platform_version][machine_architecture][:file]
69
+ end
70
+
71
+ def download_metadata_for_platform(machine, platform, platform_version, machine_architecture)
72
+ #
73
+ # Figure out the URL to the metadata
74
+ #
75
+ metadata_url="https://www.opscode.com/chef/metadata"
76
+ metadata_url << "?v=#{@chef_version}"
77
+ metadata_url << "&prerelease=#{@prerelease ? 'true' : 'false'}"
78
+ metadata_url << "&p=#{platform}"
79
+ metadata_url << "&pv=#{platform_version}"
80
+ metadata_url << "&m=#{machine_architecture}"
81
+
82
+ # solaris 9 lacks openssl, solaris 10 lacks recent enough credentials - your base O/S is completely insecure, please upgrade
83
+ if platform == 'solaris2' && (platform_version == '5.9' || platform_version == '5.10')
84
+ metadata_url.sub(/^https/, 'http')
85
+ end
86
+
87
+ # Download and parse the metadata
88
+ Chef::Log.debug("Getting metadata for machine #{machine.node['name']}: #{metadata_url}")
89
+ metadata_str = Net::HTTP.get(URI(metadata_url))
90
+ metadata = {}
91
+ metadata_str.each_line do |line|
92
+ key, value = line.split("\t", 2)
93
+ metadata[key] = value
94
+ end
95
+ metadata
96
+ end
97
+
98
+ def install_package(provider, machine, remote_package_file)
99
+ extension = File.extname(remote_package_file)
100
+ result = case extension
101
+ when '.rpm'
102
+ machine.execute(provider, "rpm -Uvh --oldpackage --replacepkgs \"#{remote_package_file}\"")
103
+ when '.deb'
104
+ machine.execute(provider, "dpkg -i \"#{remote_package_file}\"")
105
+ when '.solaris'
106
+ machine.write_file(provider, "#{@tmp_dir}/nocheck", <<EOM)
107
+ conflict=nocheck
108
+ action=nocheck
109
+ mail=
110
+ EOM
111
+ machine.execute(provider, "pkgrm -a \"#{@tmp_dir}/nocheck\" -n chef")
112
+ machine.execute(provider, "pkgadd -n -d \"#{remote_package_file}\" -a \"#{@tmp_dir}/nocheck\" chef")
113
+ when '.sh'
114
+ machine.execute(provider, "sh \"#{remote_package_file}\"")
115
+ else
116
+ raise "Unknown package extension '#{extension}' for file #{remote_package_file}"
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -68,7 +68,7 @@ module ChefMetal
68
68
  end
69
69
 
70
70
  else
71
-
71
+
72
72
  # If the server does not already have keys, create them and upload
73
73
  Cheffish.inline_resource(provider) do
74
74
  private_key 'in_memory' do
@@ -109,13 +109,6 @@ module ChefMetal
109
109
  def create_chef_objects(provider, machine, machine_resource, public_key)
110
110
  # Save the node and create the client keys and client.
111
111
  ChefMetal.inline_resource(provider) do
112
- # Create node
113
- # TODO strip automatic attributes first so we don't race with "current state"
114
- chef_node machine.node['name'] do
115
- chef_server machine_resource.chef_server
116
- raw_json machine.node
117
- end
118
-
119
112
  # Create client
120
113
  chef_client machine.node['name'] do
121
114
  chef_server machine_resource.chef_server
@@ -125,6 +118,15 @@ module ChefMetal
125
118
  admin machine_resource.admin
126
119
  validator machine_resource.validator
127
120
  end
121
+
122
+ # Create node
123
+ # TODO strip automatic attributes first so we don't race with "current state"
124
+ chef_node machine.node['name'] do
125
+ chef_server machine_resource.chef_server
126
+ raw_json machine.node
127
+ end
128
+
129
+
128
130
  end
129
131
  end
130
132
 
@@ -2,3 +2,15 @@ require 'chef_metal'
2
2
  require 'chef/resource/fog_key_pair'
3
3
  require 'chef/provider/fog_key_pair'
4
4
  require 'chef_metal/provisioner/fog_provisioner'
5
+
6
+ class Chef
7
+ class Recipe
8
+ def with_fog_provisioner(options = {}, &block)
9
+ ChefMetal.with_provisioner(ChefMetal::Provisioner::FogProvisioner.new(options, &block))
10
+ end
11
+
12
+ def with_fog_ec2_provisioner(options = {}, &block)
13
+ with_fog_provisioner({ :provider => 'AWS' }.merge(options), &block)
14
+ end
15
+ end
16
+ end
@@ -75,5 +75,15 @@ module ChefMetal
75
75
  def disconnect
76
76
  raise "disconnect not overridden on #{self.class}"
77
77
  end
78
+
79
+ # TODO get rid of the provider attribute, that is ridiculous
80
+ # Detect the OS on the machine (assumes the machine is up)
81
+ # Returns a triplet:
82
+ # platform, platform_version, machine_architecture = machine.detect_os(provider)
83
+ # This triplet is suitable for passing to the Chef metadata API:
84
+ # https://www.opscode.com/chef/metadata?p=#{platform}&pv=#{platform_version}&m=#{machine_architecture}
85
+ def detect_os(provider)
86
+ raise "detect_os not overridden on #{self.class}"
87
+ end
78
88
  end
79
89
  end
@@ -76,4 +76,4 @@ module ChefMetal
76
76
  end
77
77
  end
78
78
  end
79
- end
79
+ end
@@ -6,6 +6,8 @@ module ChefMetal
6
6
  class UnixMachine < BasicMachine
7
7
  def initialize(node, transport, convergence_strategy)
8
8
  super
9
+
10
+ @tmp_dir = '/tmp'
9
11
  end
10
12
 
11
13
  # Options include:
@@ -104,5 +106,169 @@ module ChefMetal
104
106
  path.split('/')[0..-2].join('/')
105
107
  end
106
108
  end
109
+
110
+ def detect_os(provider)
111
+ #
112
+ # Use detect.sh to detect the operating system of the remote machine
113
+ #
114
+ # TODO do this in terms of commands rather than writing a shell script
115
+ self.write_file(provider, "#{@tmp_dir}/detect.sh", detect_sh)
116
+ detected = self.execute_always("sh #{@tmp_dir}/detect.sh")
117
+ if detected.exitstatus != 0
118
+ raise "detect.sh exited with nonzero exit status: #{detected.exitstatus}"
119
+ end
120
+ platform = nil
121
+ platform_version = nil
122
+ machine_architecture = nil
123
+ detected.stdout.each_line do |line|
124
+ if line =~ /^PLATFORM: (.+)/
125
+ platform = $1
126
+ elsif line =~ /^PLATFORM_VERSION: (.+)/
127
+ platform_version = $1
128
+ elsif line =~ /^MACHINE: (.+)/
129
+ machine_architecture = $1
130
+ end
131
+ end
132
+ [ platform, platform_version, machine_architecture ]
133
+ end
134
+
135
+ private
136
+
137
+ def detect_sh
138
+ result = <<EOM
139
+ prerelease="false"
140
+
141
+ project="chef"
142
+
143
+ report_bug() {
144
+ echo "Please file a bug report at http://tickets.opscode.com"
145
+ echo "Project: Chef"
146
+ echo "Component: Packages"
147
+ echo "Label: Omnibus"
148
+ echo "Version: $version"
149
+ echo " "
150
+ echo "Please detail your operating system type, version and any other relevant details"
151
+ }
152
+
153
+
154
+ machine=`uname -m`
155
+ os=`uname -s`
156
+
157
+ # Retrieve Platform and Platform Version
158
+ if test -f "/etc/lsb-release" && grep -q DISTRIB_ID /etc/lsb-release; then
159
+ platform=`grep DISTRIB_ID /etc/lsb-release | cut -d "=" -f 2 | tr '[A-Z]' '[a-z]'`
160
+ platform_version=`grep DISTRIB_RELEASE /etc/lsb-release | cut -d "=" -f 2`
161
+ elif test -f "/etc/debian_version"; then
162
+ platform="debian"
163
+ platform_version=`cat /etc/debian_version`
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`
167
+
168
+ # If /etc/redhat-release exists, we act like RHEL by default
169
+ if test "$platform" = "fedora"; then
170
+ # Change platform version for use below.
171
+ platform_version="6.0"
172
+ fi
173
+ platform="el"
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]'`
177
+ # amazon is built off of fedora, so act like RHEL
178
+ if test "$platform" = "amazon linux ami"; then
179
+ platform="el"
180
+ platform_version="6.0"
181
+ fi
182
+ # Apple OS X
183
+ elif test -f "/usr/bin/sw_vers"; then
184
+ platform="mac_os_x"
185
+ # Matching the tab-space with sed is error-prone
186
+ platform_version=`sw_vers | awk '/^ProductVersion:/ { print $2 }'`
187
+
188
+ major_version=`echo $platform_version | cut -d. -f1,2`
189
+ case $major_version in
190
+ "10.6") platform_version="10.6" ;;
191
+ "10.7"|"10.8"|"10.9") platform_version="10.7" ;;
192
+ *) echo "No builds for platform: $major_version"
193
+ report_bug
194
+ exit 1
195
+ ;;
196
+ esac
197
+
198
+ # x86_64 Apple hardware often runs 32-bit kernels (see OHAI-63)
199
+ x86_64=`sysctl -n hw.optional.x86_64`
200
+ if test $x86_64 -eq 1; then
201
+ machine="x86_64"
202
+ fi
203
+ elif test -f "/etc/release"; then
204
+ platform="solaris2"
205
+ machine=`/usr/bin/uname -p`
206
+ platform_version=`/usr/bin/uname -r`
207
+ elif test -f "/etc/SuSE-release"; then
208
+ if grep -q 'Enterprise' /etc/SuSE-release;
209
+ then
210
+ platform="sles"
211
+ platform_version=`awk '/^VERSION/ {V = $3}; /^PATCHLEVEL/ {P = $3}; END {print V "." P}' /etc/SuSE-release`
212
+ else
213
+ platform="suse"
214
+ platform_version=`awk '/^VERSION =/ { print $3 }' /etc/SuSE-release`
215
+ fi
216
+ elif test "x$os" = "xFreeBSD"; then
217
+ platform="freebsd"
218
+ platform_version=`uname -r | sed 's/-.*//'`
219
+ elif test "x$os" = "xAIX"; then
220
+ platform="aix"
221
+ platform_version=`uname -v`
222
+ machine="ppc"
223
+ fi
224
+
225
+ if test "x$platform" = "x"; then
226
+ echo "Unable to determine platform version!"
227
+ report_bug
228
+ exit 1
229
+ fi
230
+
231
+ # Mangle $platform_version to pull the correct build
232
+ # for various platforms
233
+ major_version=`echo $platform_version | cut -d. -f1`
234
+ case $platform in
235
+ "el")
236
+ platform_version=$major_version
237
+ ;;
238
+ "debian")
239
+ case $major_version in
240
+ "5") platform_version="6";;
241
+ "6") platform_version="6";;
242
+ "7") platform_version="6";;
243
+ esac
244
+ ;;
245
+ "freebsd")
246
+ platform_version=$major_version
247
+ ;;
248
+ "sles")
249
+ platform_version=$major_version
250
+ ;;
251
+ "suse")
252
+ platform_version=$major_version
253
+ ;;
254
+ esac
255
+
256
+ if test "x$platform_version" = "x"; then
257
+ echo "Unable to determine platform version!"
258
+ report_bug
259
+ exit 1
260
+ fi
261
+
262
+ if test "x$platform" = "xsolaris2"; then
263
+ # hack up the path on Solaris to find wget
264
+ PATH=/usr/sfw/bin:$PATH
265
+ export PATH
266
+ fi
267
+
268
+ echo "PLATFORM: $platform"
269
+ echo "PLATFORM_VERSION: $platform_version"
270
+ echo "MACHINE: $machine"
271
+ EOM
272
+ end
107
273
  end
108
- end
274
+ end
@@ -213,7 +213,7 @@ module ChefMetal
213
213
  def delete_machine(provider, node)
214
214
  if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
215
215
  server = compute.servers.get(node['normal']['provisioner_output']['server_id'])
216
- provider.converge_by "destroy machine #{node['name']} (#{server.id} at #{provisioner_url}" do
216
+ provider.converge_by "destroy machine #{node['name']} (#{node['normal']['provisioner_output']['server_id']} at #{provisioner_url})" do
217
217
  server.destroy
218
218
  end
219
219
  convergence_strategy_for(node).delete_chef_objects(provider, node)
@@ -345,11 +345,15 @@ module ChefMetal
345
345
 
346
346
  def convergence_strategy_for(node)
347
347
  if node['normal']['provisioner_options'] && node['normal']['provisioner_options']['is_windows']
348
- require 'chef_metal/convergence_strategy/install_msi'
349
- ChefMetal::ConvergenceStrategy::InstallMsi.new
348
+ @windows_convergence_strategy ||= begin
349
+ require 'chef_metal/convergence_strategy/install_msi'
350
+ ChefMetal::ConvergenceStrategy::InstallMsi.new
351
+ end
350
352
  else
351
- require 'chef_metal/convergence_strategy/install_sh'
352
- ChefMetal::ConvergenceStrategy::InstallSh.new
353
+ @unix_convergence_strategy ||= begin
354
+ require 'chef_metal/convergence_strategy/install_cached'
355
+ ChefMetal::ConvergenceStrategy::InstallCached.new
356
+ end
353
357
  end
354
358
  end
355
359
 
@@ -377,7 +381,7 @@ module ChefMetal
377
381
  def create_ssh_transport(server)
378
382
  require 'chef_metal/transport/ssh'
379
383
 
380
- ssh_options, options = ssh_options_for(server)
384
+ ssh_options = ssh_options_for(server)
381
385
  # If we're on AWS, the default is to use ubuntu, not root
382
386
  if compute_options[:provider] == 'AWS'
383
387
  username = compute_options[:ssh_username] || 'ubuntu'
@@ -221,11 +221,15 @@ module ChefMetal
221
221
 
222
222
  def convergence_strategy_for(node)
223
223
  if vagrant_option(node, 'vm.guest').to_s == 'windows'
224
- require 'chef_metal/convergence_strategy/install_msi'
225
- ChefMetal::ConvergenceStrategy::InstallMsi.new
224
+ @windows_convergence_strategy ||= begin
225
+ require 'chef_metal/convergence_strategy/install_msi'
226
+ ChefMetal::ConvergenceStrategy::InstallMsi.new
227
+ end
226
228
  else
227
- require 'chef_metal/convergence_strategy/install_sh'
228
- ChefMetal::ConvergenceStrategy::InstallSh.new
229
+ @unix_convergence_strategy ||= begin
230
+ require 'chef_metal/convergence_strategy/install_cached'
231
+ ChefMetal::ConvergenceStrategy::InstallCached.new
232
+ end
229
233
  end
230
234
  end
231
235
 
@@ -324,4 +328,4 @@ module ChefMetal
324
328
  end
325
329
  end
326
330
  end
327
- end
331
+ end
@@ -1,5 +1,4 @@
1
1
  require 'chef_metal'
2
- require 'chef_metal/provisioner/fog_provisioner'
3
2
 
4
3
  class Chef
5
4
  class Recipe
@@ -10,21 +9,5 @@ class Chef
10
9
  def with_provisioner_options(provisioner_options, &block)
11
10
  ChefMetal.with_provisioner_options(provisioner_options, &block)
12
11
  end
13
-
14
- def with_vagrant_cluster(cluster_path, &block)
15
- ChefMetal.with_vagrant_cluster(cluster_path, &block)
16
- end
17
-
18
- def with_vagrant_box(box_name, vagrant_options = {}, &block)
19
- ChefMetal.with_vagrant_box(box_name, vagrant_options, &block)
20
- end
21
-
22
- def with_fog_provisioner(options = {}, &block)
23
- ChefMetal.with_provisioner(ChefMetal::Provisioner::FogProvisioner.new(options, &block))
24
- end
25
-
26
- def with_fog_ec2_provisioner(options = {}, &block)
27
- with_fog_provisioner({ :provider => 'AWS' }.merge(options), &block)
28
- end
29
12
  end
30
13
  end
@@ -25,3 +25,15 @@ module ChefMetal
25
25
  with_provisioner_options(provisioner_options, &block)
26
26
  end
27
27
  end
28
+
29
+ class Chef
30
+ class Recipe
31
+ def with_vagrant_cluster(cluster_path, &block)
32
+ ChefMetal.with_vagrant_cluster(cluster_path, &block)
33
+ end
34
+
35
+ def with_vagrant_box(box_name, vagrant_options = {}, &block)
36
+ ChefMetal.with_vagrant_box(box_name, vagrant_options, &block)
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module ChefMetal
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3'
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.2.1
4
+ version: '0.3'
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-07 00:00:00.000000000 Z
11
+ date: 2014-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef
@@ -130,6 +130,7 @@ files:
130
130
  - lib/chef/resource/vagrant_box.rb
131
131
  - lib/chef/resource/vagrant_cluster.rb
132
132
  - lib/chef_metal/aws_credentials.rb
133
+ - lib/chef_metal/convergence_strategy/install_cached.rb
133
134
  - lib/chef_metal/convergence_strategy/install_msi.rb
134
135
  - lib/chef_metal/convergence_strategy/install_sh.rb
135
136
  - lib/chef_metal/convergence_strategy/precreate_chef_objects.rb
@@ -141,11 +142,9 @@ files:
141
142
  - lib/chef_metal/machine/windows_machine.rb
142
143
  - lib/chef_metal/machine.rb
143
144
  - lib/chef_metal/provisioner/fog_provisioner.rb
144
- - lib/chef_metal/provisioner/lxc_provisioner.rb
145
145
  - lib/chef_metal/provisioner/vagrant_provisioner.rb
146
146
  - lib/chef_metal/provisioner.rb
147
147
  - lib/chef_metal/recipe_dsl.rb
148
- - lib/chef_metal/transport/lxc.rb
149
148
  - lib/chef_metal/transport/ssh.rb
150
149
  - lib/chef_metal/transport/winrm.rb
151
150
  - lib/chef_metal/transport.rb
@@ -1,135 +0,0 @@
1
- require 'chef/mixin/shell_out'
2
- require 'chef_metal/provisioner'
3
- require 'lxc'
4
-
5
- module ChefMetal
6
- class Provisioner
7
-
8
- # Provisions machines in lxc.
9
- class LXCProvisioner < Provisioner
10
-
11
- include Chef::Mixin::ShellOut
12
-
13
- #
14
- # Acquire a machine, generally by provisioning it. Returns a Machine
15
- # object pointing at the machine, allowing useful actions like setup,
16
- # converge, execute, file and directory. The Machine object will have a
17
- # "node" property which must be saved to the server (if it is any
18
- # different from the original node object).
19
- #
20
- # ## Parameters
21
- # provider - the provider object that is calling this method.
22
- # node - node object (deserialized json) representing this machine. If
23
- # the node has a provisioner_options hash in it, these will be used
24
- # instead of options provided by the provisioner. TODO compare and
25
- # fail if different?
26
- # node will have node['normal']['provisioner_options'] in it with any options.
27
- # It is a hash with this format:
28
- #
29
- # -- provisioner_url: lxc:<lxc_path>
30
- # -- template: template name
31
- # -- template_options: additional arguments for templates
32
- # -- backingstore: backing storage (lvm, thinpools, btrfs etc)
33
- #
34
- # node['normal']['provisioner_output'] will be populated with information
35
- # about the created machine. For lxc, it is a hash with this
36
- # format:
37
- #
38
- # -- provisioner_url: lxc://<lxc_path>
39
- # -- lxc_path: path to lxc root
40
- # -- name: container name
41
- #
42
- def acquire_machine(provider, node)
43
- # TODO verify that the existing provisioner_url in the node is the same as ours
44
-
45
- # Set up the modified node data
46
- provisioner_options = node['normal']['provisioner_options']
47
- provisioner_output = node['normal']['provisioner_output'] || {
48
- 'provisioner_url' => "lxc://#{lxc_path_for(node)}",
49
- 'name' => node['name']
50
- }
51
-
52
-
53
- # Create the container if it does not exist
54
- ct = LXC::Container.new(provisioner_output['name'], lxc_path_for(node))
55
- unless ct.defined?
56
- provider.converge_by "create lxc container #{provisioner_output['name']}" do
57
- ct.create(provisioner_options['template'], provisioner_options['backingstore'], 0, provisioner_options['template_options'])
58
- end
59
- end
60
- unless ct.running?
61
- provider.converge_by "start lxc container #{provisioner_output['name']}" do
62
- ct.start
63
- while ct.ip_addresses.empty?
64
- sleep 1 # wait till dhcp ip allocation is done
65
- end
66
- end
67
- end
68
-
69
- if true # do a check on whether sshd is installed. This is idempotency!
70
- provider.converge_by "install ssh into container #{provisioner_output['name']}" do
71
- end
72
- end
73
-
74
- node['normal']['provisioner_output'] = provisioner_output
75
-
76
- # Create machine object for callers to use
77
- machine_for(node)
78
- end
79
-
80
- # Connect to machine without acquiring it
81
- def connect_to_machine(node)
82
- machine_for(node)
83
- end
84
-
85
- def delete_machine(provider, node)
86
- if node['normal'] && node['normal']['provisioner_output']
87
- provisioner_output = node['normal']['provisioner_output']
88
- ct = LXC::Container.new(provisioner_output['name'], lxc_path_for(node))
89
- if ct.defined?
90
- provider.converge_by "delete lxc container #{provisioner_output['name']}" do
91
- ct.destroy
92
- end
93
- end
94
- end
95
- convergence_strategy_for(node).delete_chef_objects(provider, node)
96
- end
97
-
98
- def stop_machine(provider, node)
99
- provisioner_options = node['normal']['provisioner_options']
100
- if node['normal'] && node['normal']['provisioner_output']
101
- provisioner_output = node['normal']['provisioner_output']
102
- ct = LXC::Container.new(provisioner_output['name'], lxc_path_for(node))
103
- if ct.running?
104
- provider.converge_by "delete lxc container #{provisioner_output['name']}" do
105
- ct.stop
106
- end
107
- end
108
- end
109
- end
110
-
111
- protected
112
-
113
- def lxc_path_for(node)
114
- provisioner_options = node['normal']['provisioner_options']
115
- provisioner_options['lxc_path'] || LXC.global_config_item('lxc.lxcpath')
116
- end
117
-
118
- def machine_for(node)
119
- require 'chef_metal/machine/unix_machine'
120
- ChefMetal::Machine::UnixMachine.new(node, transport_for(node), convergence_strategy_for(node))
121
- end
122
-
123
- def convergence_strategy_for(node)
124
- require 'chef_metal/convergence_strategy/install_sh'
125
- ChefMetal::ConvergenceStrategy::InstallSh.new
126
- end
127
-
128
- def transport_for(node)
129
- require 'chef_metal/transport/lxc'
130
- provisioner_output = node['normal']['provisioner_output']
131
- ChefMetal::Transport::LXCTransport.new(provisioner_output['name'], lxc_path_for(node))
132
- end
133
- end
134
- end
135
- end
@@ -1,90 +0,0 @@
1
- require 'chef_metal/transport'
2
- require 'lxc/extra'
3
- require 'chef/mixin/shell_out'
4
-
5
- module ChefMetal
6
- class Transport
7
- class LXCTransport < Transport
8
-
9
- class LXCExecuteResult < Struct.new(:stdout, :stderr, :exitstatus)
10
- def error!
11
- raise "Error: code #{exitstatus}.\nSTDOUT:#{stdout}\nSTDERR:#{stderr}" if exitstatus != 0
12
- end
13
- end
14
-
15
- attr_reader :name, :options, :lxc_path
16
-
17
- include Chef::Mixin::ShellOut
18
-
19
- def initialize(name, lxc_path, options={})
20
- @options = options
21
- @name = name
22
- @lxc_path = lxc_path
23
- end
24
-
25
- def ct
26
- @container ||= LXC::Container.new(name, lxc_path)
27
- end
28
-
29
- def rootfs
30
- ct.config_item('lxc.rootfs')
31
- end
32
-
33
- def ct_path(path)
34
- File.join(rootfs, path)
35
- end
36
-
37
- def execute(command)
38
- Chef::Log.info("Executing #{command} on #{name}")
39
- res = ct.execute do
40
- begin
41
- out = shell_out(command)
42
- LXCExecuteResult.new(out.stdout,out.stderr, out.exitstatus)
43
- rescue Exception => e
44
- LXCExecuteResult.new('', e.message, -1)
45
- end
46
- end
47
- res
48
- end
49
-
50
- def forward_remote_port_to_local(remote_port, local_port)
51
- warn 'Port forwarding is not implemented in lxc transport'
52
- warn "You can do this on host using:"
53
- warn " 'iptables -t nat -A PREROUTING -p tcp --dport #{remote_port} -j DNAT --to #{ct.ip_addresses.first}:#{local_port}'"
54
- end
55
-
56
- def read_file(path)
57
- if File.exists?(ct_path(path))
58
- File.read(ct_path(path))
59
- end
60
- end
61
-
62
- def download_file(path, local_path)
63
- Chef::Log.debug("Copying file #{path} from #{name} to local #{local_path}")
64
- FileUtils.cp_r(ct_path(path), local_path)
65
- end
66
-
67
- def write_file(path, content)
68
- File.open(ct_path(path), 'w') do |f|
69
- f.write(content)
70
- end
71
- end
72
-
73
- def upload_file(local_path, path)
74
- FileUtils.cp_r(local_path, ct_path(path))
75
- end
76
-
77
- def disconnect
78
- end
79
-
80
- def available?
81
- begin
82
- execute('pwd')
83
- true
84
- rescue Exception =>e
85
- false
86
- end
87
- end
88
- end
89
- end
90
- end