vagrant-lxc-2.1-patch 1.4.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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +31 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/.vimrc +1 -0
  6. data/BOXES.md +47 -0
  7. data/CHANGELOG.md +510 -0
  8. data/CONTRIBUTING.md +24 -0
  9. data/Gemfile +24 -0
  10. data/Guardfile +7 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +187 -0
  13. data/Rakefile +3 -0
  14. data/lib/vagrant-lxc.rb +10 -0
  15. data/lib/vagrant-lxc/action.rb +234 -0
  16. data/lib/vagrant-lxc/action/boot.rb +42 -0
  17. data/lib/vagrant-lxc/action/clear_forwarded_ports.rb +56 -0
  18. data/lib/vagrant-lxc/action/compress_rootfs.rb +30 -0
  19. data/lib/vagrant-lxc/action/create.rb +57 -0
  20. data/lib/vagrant-lxc/action/destroy.rb +18 -0
  21. data/lib/vagrant-lxc/action/destroy_confirm.rb +17 -0
  22. data/lib/vagrant-lxc/action/fetch_ip_with_lxc_info.rb +43 -0
  23. data/lib/vagrant-lxc/action/forced_halt.rb +20 -0
  24. data/lib/vagrant-lxc/action/forward_ports.rb +121 -0
  25. data/lib/vagrant-lxc/action/gc_private_network_bridges.rb +47 -0
  26. data/lib/vagrant-lxc/action/handle_box_metadata.rb +94 -0
  27. data/lib/vagrant-lxc/action/prepare_nfs_settings.rb +64 -0
  28. data/lib/vagrant-lxc/action/prepare_nfs_valid_ids.rb +19 -0
  29. data/lib/vagrant-lxc/action/private_networks.rb +46 -0
  30. data/lib/vagrant-lxc/action/setup_package_files.rb +60 -0
  31. data/lib/vagrant-lxc/action/warn_networks.rb +25 -0
  32. data/lib/vagrant-lxc/command/root.rb +58 -0
  33. data/lib/vagrant-lxc/command/sudoers.rb +97 -0
  34. data/lib/vagrant-lxc/config.rb +73 -0
  35. data/lib/vagrant-lxc/driver.rb +288 -0
  36. data/lib/vagrant-lxc/driver/cli.rb +166 -0
  37. data/lib/vagrant-lxc/errors.rb +62 -0
  38. data/lib/vagrant-lxc/plugin.rb +51 -0
  39. data/lib/vagrant-lxc/provider.rb +101 -0
  40. data/lib/vagrant-lxc/provider/cap/public_address.rb +17 -0
  41. data/lib/vagrant-lxc/sudo_wrapper.rb +104 -0
  42. data/lib/vagrant-lxc/synced_folder.rb +72 -0
  43. data/lib/vagrant-lxc/version.rb +5 -0
  44. data/locales/en.yml +82 -0
  45. data/scripts/lxc-template +171 -0
  46. data/scripts/pipework +422 -0
  47. data/spec/Vagrantfile +26 -0
  48. data/spec/fixtures/sample-ip-addr-output +2 -0
  49. data/spec/spec_helper.rb +35 -0
  50. data/spec/support/.gitkeep +0 -0
  51. data/spec/unit/action/clear_forwarded_ports_spec.rb +43 -0
  52. data/spec/unit/action/compress_rootfs_spec.rb +29 -0
  53. data/spec/unit/action/forward_ports_spec.rb +117 -0
  54. data/spec/unit/action/handle_box_metadata_spec.rb +126 -0
  55. data/spec/unit/action/setup_package_files_spec.rb +83 -0
  56. data/spec/unit/driver/cli_spec.rb +263 -0
  57. data/spec/unit/driver_spec.rb +268 -0
  58. data/spec/unit/support/unit_example_group.rb +38 -0
  59. data/spec/unit_helper.rb +17 -0
  60. data/tasks/spec.rake +40 -0
  61. data/templates/sudoers.rb.erb +129 -0
  62. data/vagrant-lxc.gemspec +20 -0
  63. data/vagrant-spec.config.rb +24 -0
  64. metadata +119 -0
@@ -0,0 +1,73 @@
1
+ module Vagrant
2
+ module LXC
3
+ class Config < Vagrant.plugin("2", :config)
4
+ # An array of container's configuration overrides to be provided to `lxc-start`.
5
+ #
6
+ # @return [Array]
7
+ attr_reader :customizations
8
+
9
+ # A string that contains the backing store type used with lxc-create -B
10
+ attr_accessor :backingstore
11
+
12
+ # Optional arguments for the backing store, such as --fssize, --fstype, ...
13
+ #
14
+ # @return [Array]
15
+ attr_accessor :backingstore_options
16
+
17
+ # A string to explicitly set the container name. To use the vagrant
18
+ # machine name, set this to :machine
19
+ attr_accessor :container_name
20
+
21
+ # Size (as a string like '400M') of the tmpfs to mount at /tmp on boot.
22
+ # Set to false or nil to disable the tmpfs mount altogether. Defaults to '2G'.
23
+ attr_accessor :tmpfs_mount_size
24
+
25
+ attr_accessor :fetch_ip_tries
26
+
27
+ # Whether the container needs to be privileged. Defaults to true (unprivileged containers
28
+ # is a very new feature in vagrant-lxc). If false, will try creating an unprivileged
29
+ # container. If it can't, will revert to the old "sudo wrapper" method to create a privileged
30
+ # container.
31
+ attr_accessor :privileged
32
+
33
+ def initialize
34
+ @customizations = []
35
+ @backingstore = UNSET_VALUE
36
+ @backingstore_options = []
37
+ @container_name = UNSET_VALUE
38
+ @tmpfs_mount_size = UNSET_VALUE
39
+ @fetch_ip_tries = UNSET_VALUE
40
+ @privileged = UNSET_VALUE
41
+ end
42
+
43
+ # Customize the container by calling `lxc-start` with the given
44
+ # configuration overrides.
45
+ #
46
+ # For example, if you want to set the memory limit, you can use it
47
+ # like: config.customize 'cgroup.memory.limit_in_bytes', '400M'
48
+ #
49
+ # When `lxc-start`ing the container, vagrant-lxc will pass in
50
+ # "-s lxc.cgroup.memory.limit_in_bytes=400M" to it.
51
+ #
52
+ # @param [String] key Configuration key to override
53
+ # @param [String] value Configuration value to override
54
+ def customize(key, value)
55
+ @customizations << [key, value]
56
+ end
57
+
58
+ # Stores options for backingstores like lvm, btrfs, etc
59
+ def backingstore_option(key, value)
60
+ @backingstore_options << [key, value]
61
+ end
62
+
63
+ def finalize!
64
+ @container_name = nil if @container_name == UNSET_VALUE
65
+ @backingstore = nil if @backingstore == UNSET_VALUE
66
+ @existing_container_name = nil if @existing_container_name == UNSET_VALUE
67
+ @tmpfs_mount_size = '2G' if @tmpfs_mount_size == UNSET_VALUE
68
+ @fetch_ip_tries = 10 if @fetch_ip_tries == UNSET_VALUE
69
+ @privileged = true if @privileged == UNSET_VALUE
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,288 @@
1
+ require "vagrant/util/retryable"
2
+ require "vagrant/util/subprocess"
3
+
4
+ require "vagrant-lxc/errors"
5
+ require "vagrant-lxc/driver/cli"
6
+ require "vagrant-lxc/sudo_wrapper"
7
+
8
+ require "etc"
9
+
10
+ require "tempfile"
11
+
12
+ module Vagrant
13
+ module LXC
14
+ class Driver
15
+ # This is raised if the container can't be found when initializing it with
16
+ # a name.
17
+ class ContainerNotFound < StandardError; end
18
+
19
+ # Default root folder where container configs are stored
20
+ attr_reader :container_name,
21
+ :customizations
22
+
23
+ def initialize(container_name, sudo_wrapper = nil, cli = nil, privileged: true)
24
+ @container_name = container_name
25
+ @sudo_wrapper = sudo_wrapper || SudoWrapper.new(privileged: privileged)
26
+ @cli = cli || CLI.new(@sudo_wrapper, container_name)
27
+ @logger = Log4r::Logger.new("vagrant::provider::lxc::driver")
28
+ @customizations = []
29
+ end
30
+
31
+ def validate!
32
+ raise ContainerNotFound if @container_name && ! @cli.list.include?(@container_name)
33
+ end
34
+
35
+ # Root folder where container configs are stored
36
+ def containers_path
37
+ @containers_path ||= @cli.config('lxc.lxcpath')
38
+ end
39
+
40
+ def all_containers
41
+ @cli.list
42
+ end
43
+
44
+ def base_path
45
+ Pathname.new("#{containers_path}/#{@container_name}")
46
+ end
47
+
48
+ def config_path
49
+ base_path.join('config').to_s
50
+ end
51
+
52
+ def rootfs_path
53
+ config_entry = config_string.match(/^lxc\.rootfs\s+=\s+(.+)$/)[1]
54
+ case config_entry
55
+ when /^overlayfs:/
56
+ # Split on colon (:), ignoring any colon escaped by an escape character ( \ )
57
+ # Pays attention to when the escape character is itself escaped.
58
+ fs_type, master_path, overlay_path = config_entry.split(/(?<!\\)(?:\\\\)*:/)
59
+ if overlay_path
60
+ Pathname.new(overlay_path)
61
+ else
62
+ # Malformed: fall back to prior behaviour
63
+ Pathname.new(config_entry)
64
+ end
65
+ else
66
+ Pathname.new(config_entry)
67
+ end
68
+ end
69
+
70
+ def mac_address
71
+ return @mac_address if @mac_address
72
+
73
+ if config_string =~ /^lxc\.network\.hwaddr\s*+=\s*+(.+)$/
74
+ @mac_address = $1
75
+ end
76
+ end
77
+
78
+ def config_string
79
+ @sudo_wrapper.run('cat', config_path)
80
+ end
81
+
82
+ def create(name, backingstore, backingstore_options, template_path, config_file, template_options = {})
83
+ @cli.name = @container_name = name
84
+
85
+ @logger.debug "Creating container..."
86
+ @cli.create template_path, backingstore, backingstore_options, config_file, template_options
87
+ end
88
+
89
+ def share_folders(folders)
90
+ folders.each do |f|
91
+ share_folder(f[:hostpath], f[:guestpath], f.fetch(:mount_options, nil))
92
+ end
93
+ end
94
+
95
+ def share_folder(host_path, guest_path, mount_options = nil)
96
+ guest_path = guest_path.gsub(/^\//, '').gsub(' ', '\\\040')
97
+ mount_options = Array(mount_options || ['bind', 'create=dir'])
98
+ host_path = host_path.to_s.gsub(' ', '\\\040')
99
+ @customizations << ['mount.entry', "#{host_path} #{guest_path} none #{mount_options.join(',')} 0 0"]
100
+ end
101
+
102
+ def start(customizations)
103
+ @logger.info('Starting container...')
104
+
105
+ if ENV['LXC_START_LOG_FILE']
106
+ extra = ['-o', ENV['LXC_START_LOG_FILE'], '-l', 'DEBUG']
107
+ end
108
+
109
+ prune_customizations
110
+ write_customizations(customizations + @customizations)
111
+
112
+ @cli.start(extra)
113
+ end
114
+
115
+ def forced_halt
116
+ @logger.info('Shutting down container...')
117
+ @cli.transition_to(:stopped) { |c| c.stop }
118
+ end
119
+
120
+ def destroy
121
+ @cli.destroy
122
+ end
123
+
124
+ def supports_attach?
125
+ @cli.supports_attach?
126
+ end
127
+
128
+ def attach(*command)
129
+ @cli.attach(*command)
130
+ end
131
+
132
+ def info(*command)
133
+ @cli.info(*command)
134
+ end
135
+
136
+ def configure_private_network(bridge_name, bridge_ip, container_name, address_type, ip)
137
+ @logger.info "Configuring network interface for #{container_name} using #{ip} and bridge #{bridge_name}"
138
+ if ip
139
+ ip += '/24'
140
+ end
141
+
142
+ if ! bridge_exists?(bridge_name)
143
+ if not bridge_ip
144
+ raise "Bridge is missing and no IP was specified!"
145
+ end
146
+
147
+ @logger.info "Creating the bridge #{bridge_name}"
148
+ cmd = [
149
+ 'brctl',
150
+ 'addbr',
151
+ bridge_name
152
+ ]
153
+ @sudo_wrapper.run(*cmd)
154
+ end
155
+
156
+ if ! bridge_has_an_ip?(bridge_name)
157
+ if not bridge_ip
158
+ raise "Bridge has no IP and none was specified!"
159
+ end
160
+ @logger.info "Adding #{bridge_ip} to the bridge #{bridge_name}"
161
+ cmd = [
162
+ 'ip',
163
+ 'addr',
164
+ 'add',
165
+ "#{bridge_ip}/24",
166
+ 'dev',
167
+ bridge_name
168
+ ]
169
+ @sudo_wrapper.run(*cmd)
170
+ @sudo_wrapper.run('ip', 'link', 'set', bridge_name, 'up')
171
+ end
172
+
173
+ cmd = [
174
+ Vagrant::LXC.source_root.join('scripts/pipework').to_s,
175
+ bridge_name,
176
+ container_name,
177
+ ip ||= "dhcp"
178
+ ]
179
+ @sudo_wrapper.run(*cmd)
180
+ end
181
+
182
+ def bridge_has_an_ip?(bridge_name)
183
+ @logger.info "Checking whether the bridge #{bridge_name} has an IP"
184
+ `ip -4 addr show scope global #{bridge_name}` =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
185
+ end
186
+
187
+ def bridge_exists?(bridge_name)
188
+ @logger.info "Checking whether bridge #{bridge_name} exists"
189
+ brctl_output = `ip link | egrep -q " #{bridge_name}:"`
190
+ $?.to_i == 0
191
+ end
192
+
193
+ def bridge_is_in_use?(bridge_name)
194
+ # REFACTOR: This method is **VERY** hacky
195
+ @logger.info "Checking if bridge #{bridge_name} is in use"
196
+ brctl_output = `brctl show #{bridge_name} 2>/dev/null | tail -n +2 | grep -q veth`
197
+ $?.to_i == 0
198
+ end
199
+
200
+ def remove_bridge(bridge_name)
201
+ if ['lxcbr0', 'virbr0'].include? bridge_name
202
+ @logger.info "Skipping removal of system bridge #{bridge_name}"
203
+ return
204
+ end
205
+
206
+ return unless bridge_exists?(bridge_name)
207
+
208
+ @logger.info "Removing bridge #{bridge_name}"
209
+ @sudo_wrapper.run('ip', 'link', 'set', bridge_name, 'down')
210
+ @sudo_wrapper.run('brctl', 'delbr', bridge_name)
211
+ end
212
+
213
+ def version
214
+ @version ||= @cli.version
215
+ end
216
+
217
+ # TODO: This needs to be reviewed and specs needs to be written
218
+ def compress_rootfs
219
+ # TODO: Pass in tmpdir so we can clean up from outside
220
+ target_path = "#{Dir.mktmpdir}/rootfs.tar.gz"
221
+
222
+ @logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
223
+ @sudo_wrapper.run('tar', '--numeric-owner', '-cvzf', target_path, '-C',
224
+ rootfs_path.parent.to_s, "./#{rootfs_path.basename.to_s}")
225
+
226
+ @logger.info "Changing rootfs tarball owner"
227
+ user_details = Etc.getpwnam(Etc.getlogin)
228
+ @sudo_wrapper.run('chown', "#{user_details.uid}:#{user_details.gid}", target_path)
229
+
230
+ target_path
231
+ end
232
+
233
+ def state
234
+ if @container_name
235
+ @cli.state
236
+ end
237
+ end
238
+
239
+ def prune_customizations
240
+ # Use sed to just strip out the block of code which was inserted by Vagrant
241
+ @logger.debug 'Prunning vagrant-lxc customizations'
242
+ contents = config_string
243
+ contents.gsub! /^# VAGRANT-BEGIN(.|\s)*# VAGRANT-END\n/, ''
244
+ write_config(contents)
245
+ end
246
+
247
+ def update_config_keys
248
+ @cli.update_config(config_path)
249
+ rescue Errors::ExecuteError
250
+ # not on LXC 2.1+. Doesn't matter, ignore.
251
+ end
252
+
253
+ protected
254
+
255
+ def write_customizations(customizations)
256
+ customizations = customizations.map do |key, value|
257
+ "lxc.#{key}=#{value}"
258
+ end
259
+ customizations.unshift '# VAGRANT-BEGIN'
260
+ customizations << "# VAGRANT-END\n"
261
+
262
+ contents = config_string
263
+ contents << customizations.join("\n")
264
+
265
+ write_config(contents)
266
+ end
267
+
268
+ def write_config(contents)
269
+ confpath = base_path.join('config').to_s
270
+ begin
271
+ File.open(confpath, File::RDWR) do |file|
272
+ file.write contents
273
+ end
274
+ rescue
275
+ # We don't have permissions to write in the conf file. That's probably because it's a
276
+ # privileged container. Work around that through sudo_wrapper.
277
+ Tempfile.new('lxc-config').tap do |file|
278
+ file.chmod 0644
279
+ file.write contents
280
+ file.close
281
+ @sudo_wrapper.run 'cp', '-f', file.path, confpath
282
+ @sudo_wrapper.run 'chown', 'root:root', confpath
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,166 @@
1
+ require "vagrant/util/retryable"
2
+ require "vagrant/util/subprocess"
3
+
4
+ require "vagrant-lxc/errors"
5
+
6
+ module Vagrant
7
+ module LXC
8
+ class Driver
9
+ class CLI
10
+ attr_accessor :name
11
+
12
+ class TransitionBlockNotProvided < RuntimeError; end
13
+ class TargetStateNotReached < RuntimeError
14
+ def initialize(target_state, state)
15
+ msg = "Target state '#{target_state}' not reached, currently on '#{state}'"
16
+ super(msg)
17
+ end
18
+ end
19
+
20
+ def initialize(sudo_wrapper, name = nil)
21
+ @sudo_wrapper = sudo_wrapper
22
+ @name = name
23
+ @logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
24
+ end
25
+
26
+ def list
27
+ run(:ls).split(/\s+/).uniq
28
+ end
29
+
30
+ def version
31
+ return @version if @version
32
+ @version = run(:create, '--version')
33
+ if @version =~ /(lxc version:\s+|)(.+)\s*$/
34
+ @version = $2.downcase
35
+ else
36
+ # TODO: Raise an user friendly error
37
+ raise 'Unable to parse lxc version!'
38
+ end
39
+ end
40
+
41
+ def config(param)
42
+ run(:config, param).gsub("\n", '')
43
+ end
44
+
45
+ def update_config(path)
46
+ run('update-config', '-c', path)
47
+ end
48
+
49
+ def state
50
+ if @name && run(:info, '--name', @name, retryable: true) =~ /^state:[^A-Z]+([A-Z]+)$/i
51
+ $1.downcase.to_sym
52
+ elsif @name
53
+ :unknown
54
+ end
55
+ end
56
+
57
+ def create(template, backingstore, backingstore_options, config_file, template_opts = {})
58
+ if config_file
59
+ config_opts = ['-f', config_file]
60
+ end
61
+
62
+ extra = template_opts.to_a.flatten
63
+ extra.unshift '--' unless extra.empty?
64
+
65
+ run :create,
66
+ '-B', backingstore,
67
+ '--template', template,
68
+ '--name', @name,
69
+ *(backingstore_options.to_a.flatten),
70
+ *(config_opts),
71
+ *extra
72
+ rescue Errors::ExecuteError => e
73
+ if e.stderr =~ /already exists/i
74
+ raise Errors::ContainerAlreadyExists, name: @name
75
+ else
76
+ raise
77
+ end
78
+ end
79
+
80
+ def destroy
81
+ run :destroy, '--name', @name
82
+ end
83
+
84
+ def start(options = [])
85
+ run :start, '-d', '--name', @name, *Array(options)
86
+ end
87
+
88
+ ## lxc-stop will exit 2 if machine was already stopped
89
+ # Man Page:
90
+ # 2 The specified container exists but was not running.
91
+ def stop
92
+ attach '/sbin/halt' if supports_attach?
93
+ begin
94
+ run :stop, '--name', @name
95
+ rescue LXC::Errors::ExecuteError => e
96
+ if e.exitcode == 2
97
+ @logger.debug "Machine already stopped, lxc-stop returned 2"
98
+ else
99
+ raise e
100
+ end
101
+ end
102
+ end
103
+
104
+ def attach(*cmd)
105
+ cmd = ['--'] + cmd
106
+
107
+ if cmd.last.is_a?(Hash)
108
+ opts = cmd.pop
109
+ namespaces = Array(opts[:namespaces]).map(&:upcase).join('|')
110
+
111
+ # HACK: The wrapper script should be able to handle this
112
+ if @sudo_wrapper.wrapper_path
113
+ namespaces = "'#{namespaces}'"
114
+ end
115
+
116
+ if namespaces
117
+ extra = ['--namespaces', namespaces]
118
+ end
119
+ end
120
+
121
+ run :attach, '--name', @name, *((extra || []) + cmd)
122
+ end
123
+
124
+ def info(*cmd)
125
+ run(:info, '--name', @name, *cmd)
126
+ end
127
+
128
+ def transition_to(target_state, tries = 30, timeout = 1, &block)
129
+ raise TransitionBlockNotProvided unless block_given?
130
+
131
+ yield self
132
+
133
+ while (last_state = self.state) != target_state && tries > 0
134
+ @logger.debug "Target state '#{target_state}' not reached, currently on '#{last_state}'"
135
+ sleep timeout
136
+ tries -= 1
137
+ end
138
+
139
+ unless last_state == target_state
140
+ # TODO: Raise an user friendly message
141
+ raise TargetStateNotReached.new target_state, last_state
142
+ end
143
+ end
144
+
145
+ def supports_attach?
146
+ unless defined?(@supports_attach)
147
+ begin
148
+ @supports_attach = true
149
+ run(:attach, '--name', @name, '--', '/bin/true')
150
+ rescue LXC::Errors::ExecuteError
151
+ @supports_attach = false
152
+ end
153
+ end
154
+
155
+ return @supports_attach
156
+ end
157
+
158
+ private
159
+
160
+ def run(command, *args)
161
+ @sudo_wrapper.run("lxc-#{command}", *args)
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end