vagrant-lxd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ #
2
+ # Copyright (c) 2017 Catalyst.net Ltd
3
+ #
4
+ # This file is part of vagrant-lxd.
5
+ #
6
+ # vagrant-lxd is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or (at
9
+ # your option) any later version.
10
+ #
11
+ # vagrant-lxd is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with vagrant-lxd. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ module VagrantLXD
21
+ class Capability
22
+ def Capability.snapshot_list(machine)
23
+ env = machine.action(:snapshot_list)
24
+ env[:machine_snapshot_list] || []
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,145 @@
1
+ #
2
+ # Copyright (c) 2017 Catalyst.net Ltd
3
+ #
4
+ # This file is part of vagrant-lxd.
5
+ #
6
+ # vagrant-lxd is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or (at
9
+ # your option) any later version.
10
+ #
11
+ # vagrant-lxd is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with vagrant-lxd. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ require 'vagrant/machine_state'
21
+
22
+ require 'vagrant-lxd/driver'
23
+ require 'vagrant-lxd/version'
24
+
25
+ module VagrantLXD
26
+ class Command < Vagrant.plugin('2', :command)
27
+ def Command.synopsis
28
+ 'manages the LXD provider'
29
+ end
30
+
31
+ def execute
32
+ main, subcommand, args = split_main_and_subcommand(@argv)
33
+
34
+ opts = OptionParser.new do |o|
35
+ o.banner = 'Usage: vagrant lxd <command>'
36
+ o.separator ''
37
+ o.separator 'Commands:'
38
+ o.separator ' attach associate machine with a running container'
39
+ o.separator ' detach disassociate machine from a running container'
40
+ o.separator ' version print current plugin version'
41
+ o.separator ''
42
+ o.separator 'For help on a specific command, run `vagrant lxd <command> -h`'
43
+ end
44
+
45
+ if main.include?('-h') or main.include?('--help')
46
+ @env.ui.info opts.help
47
+ exit 0
48
+ end
49
+
50
+ case subcommand
51
+ when 'attach'
52
+ attach(args)
53
+ when 'detach'
54
+ detach(args)
55
+ when 'version'
56
+ @env.ui.info 'Vagrant LXD Provider'
57
+ @env.ui.info 'Version ' << Version::VERSION
58
+ else
59
+ fail Vagrant::Errors::CLIInvalidUsage, help: opts.help
60
+ end
61
+ end
62
+
63
+ def attach(args)
64
+ opts = OptionParser.new do |o|
65
+ o.banner = 'Usage: vagrant lxd attach [machine ...] <container>'
66
+ o.separator ''
67
+ o.separator 'Associates a VM with a preexisting LXD container.'
68
+ o.separator ''
69
+ o.separator 'This command can be used to attach an inactive (not created) VM to a'
70
+ o.separator 'preexisting LXD container. Once it has been associated with a container,'
71
+ o.separator 'the machine can be used just like it had been created with `vagrant up`'
72
+ o.separator 'or detached from the container again with `vagrant lxd detach`.'
73
+ end
74
+
75
+ if args.include?('-h') or args.include?('--help')
76
+ @env.ui.info opts.help
77
+ exit 0
78
+ end
79
+
80
+ unless container = args.pop
81
+ fail Vagrant::Errors::CLIInvalidUsage, help: opts.help
82
+ end
83
+
84
+ with_target_machines(args) do |machine|
85
+ if machine.id == container
86
+ machine.ui.warn "Machine is already attached to container '#{container}', skipping..."
87
+ elsif machine.state.id == Vagrant::MachineState::NOT_CREATED_ID
88
+ machine.ui.info "Attaching to container '#{container}'..."
89
+ Driver.new(machine).attach(container)
90
+ else
91
+ machine.ui.error "Machine is already attached to container '#{machine.id}'"
92
+ fail Driver::DuplicateAttachmentFailure, machine_name: machine.name, container: container
93
+ end
94
+ end
95
+ end
96
+
97
+ def detach(args)
98
+ opts = OptionParser.new do |o|
99
+ o.banner = 'Usage: vagrant lxd detach [machine ...]'
100
+ o.separator ''
101
+ o.separator 'Disassociates a VM from its LXD container.'
102
+ o.separator ''
103
+ o.separator 'This command can be used to deactivate a VM without destroying the'
104
+ o.separator 'underlying container. Once detached, the machine can be recreated'
105
+ o.separator 'from scratch with `vagrant up` or associated to a different container'
106
+ o.separator 'by using `vagrant lxd attach`.'
107
+ end
108
+
109
+ if args.include?('-h') or args.include?('--help')
110
+ @env.ui.info opts.help
111
+ exit 0
112
+ end
113
+
114
+ with_target_machines(args) do |machine|
115
+ if machine.id.nil? or machine.state.id == Vagrant::MachineState::NOT_CREATED_ID
116
+ machine.ui.warn "Machine is not attached to a container, skipping..."
117
+ else
118
+ driver = Driver.new(machine)
119
+ machine.ui.info "Detaching from container '#{driver.machine_id}'..."
120
+ driver.detach
121
+ end
122
+ end
123
+ end
124
+
125
+ def with_target_machines(args)
126
+ machines = args.map(&:to_sym)
127
+
128
+ # NOTE We collect all vm names here in order to force Vagrant to
129
+ # load a full local environment, including provider configurations.
130
+ vms = with_target_vms { |_| }.map(&:name)
131
+
132
+ # When no machines are given, act on all of them.
133
+ machines = vms if machines.empty?
134
+
135
+ # Validate machine names.
136
+ unless vms | machines == vms
137
+ fail Vagrant::Errors::MachineNotFound, name: (machines - vms).first
138
+ end
139
+
140
+ machines.each do |name|
141
+ yield @env.machine(name, :lxd)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,92 @@
1
+ #
2
+ # Copyright (c) 2017 Catalyst.net Ltd
3
+ #
4
+ # This file is part of vagrant-lxd.
5
+ #
6
+ # vagrant-lxd is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or (at
9
+ # your option) any later version.
10
+ #
11
+ # vagrant-lxd is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with vagrant-lxd. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ require 'uri'
21
+
22
+ module VagrantLXD
23
+ class Config < Vagrant.plugin('2', :config)
24
+ attr_accessor :api_endpoint
25
+ attr_accessor :name
26
+ attr_accessor :ephemeral
27
+ attr_accessor :timeout
28
+
29
+ def initialize
30
+ @name = UNSET_VALUE
31
+ @timeout = UNSET_VALUE
32
+ @ephemeral = UNSET_VALUE
33
+ @api_endpoint = UNSET_VALUE
34
+ end
35
+
36
+ def validate(machine)
37
+ errors = _detected_errors
38
+
39
+ unless [UNSET_VALUE, nil].include? name
40
+ if not name.is_a? String
41
+ errors << "Invalid `name' (value must be a string): #{name.inspect}"
42
+ elsif name.size >= 64
43
+ errors << "Invalid `name' (value must be less than 64 characters): #{name.inspect}"
44
+ elsif name =~ /[^a-zA-Z0-9-]/
45
+ errors << "Invalid `name' (value must contain only letters, numbers, and hyphens): #{name.inspect}"
46
+ end
47
+ end
48
+
49
+ unless timeout == UNSET_VALUE
50
+ if not timeout.is_a? Fixnum
51
+ errors << "Invalid `timeout' (value must be an integer): #{timeout.inspect}"
52
+ elsif timeout < 1
53
+ errors << "Invalid `timeout' (value must be positive): #{timeout.inspect}"
54
+ end
55
+ end
56
+
57
+ unless api_endpoint == UNSET_VALUE
58
+ begin
59
+ URI(api_endpoint).scheme == 'https' or raise URI::InvalidURIError
60
+ rescue URI::InvalidURIError
61
+ errors << "Invalid `api_endpoint' (value must be a valid HTTPS address): #{api_endpoint.inspect}"
62
+ end
63
+ end
64
+
65
+ unless [UNSET_VALUE, true, false].include? ephemeral
66
+ errors << "Invalid `ephemeral' (value must be true or false): #{ephemeral.inspect}"
67
+ end
68
+
69
+ { Version::NAME => errors }
70
+ end
71
+
72
+ def finalize!
73
+ if name == UNSET_VALUE
74
+ @name = nil
75
+ end
76
+
77
+ if ephemeral == UNSET_VALUE
78
+ @ephemeral = false
79
+ end
80
+
81
+ if timeout == UNSET_VALUE
82
+ @timeout = 10
83
+ end
84
+
85
+ if api_endpoint == UNSET_VALUE
86
+ @api_endpoint = URI('https://127.0.0.1:8443')
87
+ else
88
+ @api_endpoint = URI(api_endpoint)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,418 @@
1
+ #
2
+ # Copyright (c) 2017 Catalyst.net Ltd
3
+ #
4
+ # This file is part of vagrant-lxd.
5
+ #
6
+ # vagrant-lxd is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or (at
9
+ # your option) any later version.
10
+ #
11
+ # vagrant-lxd is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with vagrant-lxd. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ require 'hyperkit'
21
+ require 'securerandom'
22
+ require 'tempfile'
23
+ require 'timeout'
24
+ require 'vagrant/machine_state'
25
+
26
+ module VagrantLXD
27
+ class Driver
28
+ include Vagrant::Util
29
+
30
+ VAGRANT_UID = 1000 # TODO Make this configurable.
31
+
32
+ class OperationTimeout < Vagrant::Errors::VagrantError
33
+ error_key 'lxd_operation_timeout'
34
+ end
35
+
36
+ class NetworkAddressAcquisitionTimeout < OperationTimeout
37
+ error_key 'lxd_network_address_acquisition_timeout'
38
+ end
39
+
40
+ class ConnectionFailure < Vagrant::Errors::ProviderNotUsable
41
+ error_key 'lxd_connection_failure'
42
+ end
43
+
44
+ class AuthenticationFailure < Vagrant::Errors::ProviderNotUsable
45
+ error_key 'lxd_authentication_failure'
46
+ end
47
+
48
+ class ContainerCreationFailure < Vagrant::Errors::VagrantError
49
+ error_key 'lxd_container_creation_failure'
50
+ end
51
+
52
+ class ImageCreationFailure < ContainerCreationFailure
53
+ error_key 'lxd_image_creation_failure'
54
+ end
55
+
56
+ class ContainerNotFound < Vagrant::Errors::VagrantError
57
+ error_key 'lxd_container_not_found'
58
+ end
59
+
60
+ class ContainerAlreadyExists < Vagrant::Errors::VagrantError
61
+ error_key 'lxd_container_already_exists'
62
+ end
63
+
64
+ class DuplicateAttachmentFailure < Vagrant::Errors::VagrantError
65
+ error_key 'lxd_duplicate_attachment_failure'
66
+ end
67
+
68
+ class Hyperkit::BadRequest
69
+ def reason
70
+ return unless data.is_a? Hash
71
+
72
+ if error = data[:error]
73
+ return error unless error.empty?
74
+ end
75
+
76
+ if metadata = data[:metadata] and err = metadata[:err]
77
+ return err unless err.empty?
78
+ end
79
+
80
+ 'No reason could be determined'
81
+ end
82
+ end
83
+
84
+ NOT_CREATED = Vagrant::MachineState::NOT_CREATED_ID
85
+
86
+ attr_reader :api_endpoint
87
+ attr_reader :ephemeral
88
+ attr_reader :name
89
+ attr_reader :timeout
90
+
91
+ def initialize(machine)
92
+ @machine = machine
93
+ @timeout = machine.provider_config.timeout
94
+ @api_endpoint = machine.provider_config.api_endpoint
95
+ @ephemeral = machine.provider_config.ephemeral
96
+ @name = machine.provider_config.name
97
+ @logger = Log4r::Logger.new('vagrant::lxd')
98
+ @lxd = Hyperkit::Client.new(api_endpoint: api_endpoint.to_s, verify_ssl: false)
99
+ end
100
+
101
+ def validate!
102
+ raise error(ConnectionFailure) unless connection_usable?
103
+ raise error(AuthenticationFailure) unless authentication_usable?
104
+ end
105
+
106
+ def synced_folders_usable?
107
+ # Check whether we've registered an idmap for the current user.
108
+ if map = container[:config][:'raw.idmap']
109
+ true if map =~ /^uid #{Process.uid} #{VAGRANT_UID}$/
110
+ end
111
+ rescue Vagrant::Errors::ProviderNotUsable
112
+ false
113
+ end
114
+
115
+ def mount(name, options)
116
+ container = @lxd.container(machine_id)
117
+ devices = container[:devices].to_hash
118
+ devices[name] = { type: 'disk', path: options[:guestpath], source: options[:hostpath] }
119
+ container[:devices] = devices
120
+ @lxd.update_container(machine_id, container)
121
+ end
122
+
123
+ def mounted?(name, options)
124
+ container = @lxd.container(machine_id)
125
+ devices = container[:devices].to_hash
126
+ name = name.to_sym
127
+ begin
128
+ devices[name] and
129
+ devices[name][:type] == 'disk' and
130
+ devices[name][:path] == options[:guestpath] and
131
+ devices[name][:source] == options[:hostpath]
132
+ end
133
+ end
134
+
135
+ def unmount(name, options)
136
+ container = @lxd.container(machine_id)
137
+ devices = container[:devices].to_hash
138
+ devices.delete(name.to_sym)
139
+ container[:devices] = devices
140
+ @lxd.update_container(machine_id, container)
141
+ end
142
+
143
+ def attach(container)
144
+ @lxd.container(container) # Query LXD to make sure the container exists.
145
+
146
+ if in_state? NOT_CREATED
147
+ @machine.id = container
148
+ else
149
+ fail DuplicateAttachmentFailure, machine_name: @machine.name, container: container
150
+ end
151
+ rescue Hyperkit::NotFound
152
+ @machine.ui.error "Container doesn't exist: #{container}"
153
+ fail ContainerNotFound, container: container
154
+ end
155
+
156
+ def detach
157
+ @machine.id = nil
158
+ end
159
+
160
+ #
161
+ # The following methods correspond directly to middleware actions.
162
+ #
163
+
164
+ def snapshot_list
165
+ @lxd.snapshots(machine_id)
166
+ end
167
+
168
+ def snapshot_save(name)
169
+ @lxd.create_snapshot(machine_id, name)
170
+ end
171
+
172
+ def snapshot_restore(name)
173
+ @lxd.restore_snapshot(machine_id, name)
174
+ end
175
+
176
+ def snapshot_delete(name)
177
+ @lxd.delete_snapshot(machine_id, name)
178
+ end
179
+
180
+ def state
181
+ return NOT_CREATED if machine_id.nil?
182
+ container_state = @lxd.container_state(machine_id)
183
+ container_state[:status].downcase.to_sym
184
+ rescue Hyperkit::NotFound
185
+ NOT_CREATED
186
+ end
187
+
188
+ def create
189
+ if in_state? NOT_CREATED
190
+ machine_id = generate_machine_id
191
+ file, fingerprint = prepare_image_file
192
+
193
+ begin
194
+ image = @lxd.image(fingerprint)
195
+ @logger.debug 'Using image: ' << image.inspect
196
+ rescue Hyperkit::NotFound
197
+ image = @lxd.create_image_from_file(file)
198
+ image_alias = @lxd.create_image_alias(fingerprint, machine_id)
199
+ @logger.debug 'Created image: ' << image.inspect
200
+ @logger.debug 'Created image alias: ' << image_alias.inspect
201
+ end
202
+
203
+ container = @lxd.create_container(machine_id, ephemeral: ephemeral, fingerprint: fingerprint, config: config)
204
+ @logger.debug 'Created container: ' << container.inspect
205
+
206
+ @machine.id = machine_id
207
+ end
208
+ rescue Hyperkit::Error => e
209
+ @lxd.delete_container(id) rescue nil unless container.nil?
210
+ @lxd.delete_image(image[:metadata][:fingerprint]) rescue nil unless image.nil?
211
+ if e.reason =~ /Container '([^']+)' already exists/
212
+ @machine.ui.error e.reason
213
+ fail ContainerAlreadyExists, machine_name: @machine.name, container: $1
214
+ else
215
+ @machine.ui.error "Failed to create container"
216
+ fail ContainerCreationFailure, machine_name: @machine.name, reason: e.reason
217
+ end
218
+ end
219
+
220
+ def resume
221
+ case state
222
+ when :stopped
223
+ @lxd.start_container(machine_id)
224
+ when :frozen
225
+ @lxd.unfreeze_container(machine_id, timeout: timeout)
226
+ end
227
+ rescue Hyperkit::BadRequest
228
+ @machine.ui.warn "Container failed to start within #{timeout} seconds"
229
+ fail OperationTimeout, time_limit: timeout, operation: 'start', machine_id: machine_id
230
+ end
231
+
232
+ def halt
233
+ if in_state? :running, :frozen
234
+ @lxd.stop_container(machine_id, timeout: timeout)
235
+ end
236
+ rescue Hyperkit::BadRequest
237
+ @machine.ui.warn "Container failed to stop within #{timeout} seconds, forcing shutdown..."
238
+ @lxd.stop_container(machine_id, timeout: timeout, force: true)
239
+ end
240
+
241
+ def suspend
242
+ if in_state? :running
243
+ @lxd.freeze_container(machine_id, timeout: timeout)
244
+ end
245
+ rescue Hyperkit::BadRequest
246
+ @machine.ui.warn "Container failed to suspend within #{timeout} seconds"
247
+ fail OperationTimeout, time_limit: timeout, operation: 'info', machine_id: machine_id
248
+ end
249
+
250
+ def destroy
251
+ if in_state? :stopped
252
+ delete_image
253
+ delete_container
254
+ else
255
+ @logger.debug "Skipped container destroy (#{machine_id} is not stopped)"
256
+ end
257
+ end
258
+
259
+ def info
260
+ if in_state? :running, :frozen
261
+ {
262
+ host: ipv4_address,
263
+ port: ipv4_port,
264
+ }
265
+ end
266
+ end
267
+
268
+ private
269
+
270
+ def delete_container
271
+ @lxd.delete_container(machine_id)
272
+ rescue Hyperkit::NotFound
273
+ @logger.warn "Container '#{machine_id}' not found, unable to destroy"
274
+ end
275
+
276
+ def delete_image
277
+ @lxd.delete_image(container[:config][:'volatile.base_image'])
278
+ rescue Hyperkit::NotFound
279
+ @logger.warn "Image for '#{machine_id}' not found, unable to destroy"
280
+ rescue Hyperkit::BadRequest
281
+ @logger.error "Unable to delete image for '#{machine_id}'"
282
+ end
283
+
284
+ def container
285
+ @lxd.container(machine_id)
286
+ end
287
+
288
+ def connection_usable?
289
+ @lxd.images
290
+ rescue Faraday::ConnectionFailed
291
+ false
292
+ else
293
+ true
294
+ end
295
+
296
+ def authentication_usable?
297
+ connection_usable? and @lxd.containers
298
+ rescue Hyperkit::Forbidden
299
+ false
300
+ else
301
+ true
302
+ end
303
+
304
+ def machine_id
305
+ @machine.id
306
+ end
307
+
308
+ def generate_machine_id
309
+ @name || begin
310
+ id = "vagrant-#{File.basename(Dir.pwd)}-#{@machine.name}-#{SecureRandom.hex(8)}"
311
+ id = id.slice(0...63).gsub(/[^a-zA-Z0-9]/, '-')
312
+ id
313
+ end
314
+ end
315
+
316
+ def in_state?(*any)
317
+ any.include?(state)
318
+ end
319
+
320
+ def ipv4_port
321
+ 22
322
+ end
323
+
324
+ def ipv4_address
325
+ @logger.debug "Looking up ipv4 address for #{machine_id}..."
326
+ Timeout.timeout(timeout) do
327
+ loop do
328
+ container_state = @lxd.container_state(machine_id)
329
+ if address = container_state[:network][:eth0][:addresses].find { |a| a[:family] == 'inet' }
330
+ return address[:address]
331
+ else
332
+ @logger.debug 'No ipv4 address found, sleeping 1s before trying again...'
333
+ sleep(1)
334
+ end
335
+ end
336
+ end
337
+ rescue Timeout::Error
338
+ @logger.warn "Failed to find ipv4 address for #{machine_id} within #{timeout} seconds!"
339
+ fail NetworkAddressAcquisitionTimeout, time_limit: timeout, lxd_bridge: 'lxdbr0' # FIXME Hardcoded bridge name
340
+ end
341
+
342
+ def config
343
+ config = {}
344
+
345
+ # Set "raw.idmap" if the host's sub{u,g}id configuration allows it.
346
+ # This allows sharing folders via LXD (see synced_folder.rb).
347
+ begin
348
+ %w(uid gid).each do |type|
349
+ value = Process.send(type)
350
+ if File.readlines("/etc/sub#{type}").grep(/^root:#{value}:[1-9]/).any?
351
+ config[:'raw.idmap'] ||= ''
352
+ config[:'raw.idmap'] << "#{type} #{value} #{VAGRANT_UID}\n"
353
+ end
354
+ end
355
+ rescue StandardError => e
356
+ @logger.warn "Cannot read subordinate permissions file: #{e.message}"
357
+ end
358
+
359
+ @logger.debug 'Resulting configuration: ' << config.inspect
360
+
361
+ config
362
+ end
363
+
364
+ def prepare_image_file
365
+ tmpdir = Dir.mktmpdir
366
+
367
+ lxc_dir = @machine.box.directory
368
+ lxc_rootfs = lxc_dir / 'rootfs.tar.gz'
369
+ lxc_fingerprint = Digest::SHA256.file(lxc_rootfs).hexdigest
370
+
371
+ lxd_dir = @machine.box.directory / '..' / 'lxd'
372
+ lxd_rootfs = lxd_dir / 'rootfs.tar.gz'
373
+ lxd_metadata = YAML.load(File.read(lxd_dir / 'metadata.yaml')) rescue nil
374
+
375
+ if lxd_rootfs.exist? and lxd_metadata.is_a? Hash and lxd_metadata['source_fingerprint'] == lxc_fingerprint
376
+ @machine.ui.info 'Importing LXC image...'
377
+ else
378
+ @machine.ui.info 'Converting LXC image to LXD format...'
379
+
380
+ SafeChdir.safe_chdir(tmpdir) do
381
+ FileUtils.cp(lxc_rootfs, tmpdir)
382
+
383
+ File.open('metadata.yaml', 'w') do |metadata|
384
+ metadata.puts 'architecture: ' << `uname -m`.strip
385
+ metadata.puts 'creation_date: ' << Time.now.strftime('%s')
386
+ metadata.puts 'source_fingerprint: ' << lxc_fingerprint
387
+ end
388
+
389
+ Subprocess.execute('gunzip', 'rootfs.tar.gz')
390
+ Subprocess.execute('tar', '-rf', 'rootfs.tar', 'metadata.yaml')
391
+ Subprocess.execute('gzip', 'rootfs.tar')
392
+
393
+ FileUtils.mkdir_p(lxd_dir)
394
+ FileUtils.mv('rootfs.tar.gz', lxd_dir)
395
+ FileUtils.mv('metadata.yaml', lxd_dir)
396
+ end
397
+ end
398
+
399
+ return lxd_rootfs, Digest::SHA256.file(lxd_rootfs).hexdigest
400
+ rescue Exception => e
401
+ @machine.ui.error 'Failed to create LXD image for container'
402
+ @logger.error 'Error preparing LXD image: ' << e.message << "\n" << e.backtrace.join("\n")
403
+ fail ImageCreationFailure, machine_name: @machine.name, error_message: e.message
404
+ ensure
405
+ FileUtils.rm_rf(tmpdir)
406
+ end
407
+
408
+ def error(klass)
409
+ klass.new(
410
+ provider: Version::NAME,
411
+ machine: @machine.name,
412
+ api_endpoint: @api_endpoint.to_s,
413
+ https_address: @api_endpoint.host,
414
+ client_cert: File.expand_path('.config/lxc/client.crt', ENV['HOME']),
415
+ )
416
+ end
417
+ end
418
+ end