vagrant-lxd 0.1.0
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 +7 -0
- data/.gitignore +3 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +148 -0
- data/LICENSE.md +675 -0
- data/README.md +121 -0
- data/Rakefile +12 -0
- data/lib/vagrant-lxd.rb +23 -0
- data/lib/vagrant-lxd/action.rb +314 -0
- data/lib/vagrant-lxd/capability.rb +27 -0
- data/lib/vagrant-lxd/command.rb +145 -0
- data/lib/vagrant-lxd/config.rb +92 -0
- data/lib/vagrant-lxd/driver.rb +418 -0
- data/lib/vagrant-lxd/plugin.rb +52 -0
- data/lib/vagrant-lxd/provider.rb +49 -0
- data/lib/vagrant-lxd/synced_folder.rb +76 -0
- data/lib/vagrant-lxd/version.rb +26 -0
- data/templates/locales/en.yml +110 -0
- data/vagrant-lxd.gemspec +39 -0
- metadata +76 -0
@@ -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
|