├── .gitignore ├── .rspec ├── .travis.yml ├── .vimrc ├── BOXES.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── vagrant-lxc.rb └── vagrant-lxc │ ├── action.rb │ ├── action │ ├── boot.rb │ ├── clear_forwarded_ports.rb │ ├── compress_rootfs.rb │ ├── create.rb │ ├── destroy.rb │ ├── destroy_confirm.rb │ ├── fetch_ip_with_lxc_info.rb │ ├── forced_halt.rb │ ├── forward_ports.rb │ ├── gc_private_network_bridges.rb │ ├── handle_box_metadata.rb │ ├── prepare_nfs_settings.rb │ ├── prepare_nfs_valid_ids.rb │ ├── private_networks.rb │ ├── setup_package_files.rb │ └── warn_networks.rb │ ├── command │ ├── root.rb │ └── sudoers.rb │ ├── config.rb │ ├── driver.rb │ ├── driver │ └── cli.rb │ ├── errors.rb │ ├── plugin.rb │ ├── provider.rb │ ├── provider │ └── cap │ │ └── public_address.rb │ ├── sudo_wrapper.rb │ ├── synced_folder.rb │ └── version.rb ├── locales └── en.yml ├── scripts ├── lxc-template └── pipework ├── spec ├── Vagrantfile ├── fixtures │ └── sample-ip-addr-output ├── spec_helper.rb ├── support │ └── .gitkeep ├── unit │ ├── action │ │ ├── clear_forwarded_ports_spec.rb │ │ ├── compress_rootfs_spec.rb │ │ ├── forward_ports_spec.rb │ │ ├── handle_box_metadata_spec.rb │ │ └── setup_package_files_spec.rb │ ├── driver │ │ └── cli_spec.rb │ ├── driver_spec.rb │ └── support │ │ └── unit_example_group.rb └── unit_helper.rb ├── tasks └── spec.rake ├── templates └── sudoers.rb.erb ├── vagrant-lxc.gemspec └── vagrant-spec.config.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | coverage 6 | InstalledFiles 7 | lib/bundler/man 8 | pkg 9 | rdoc 10 | spec/reports 11 | test/tmp 12 | test/version_tmp 13 | tmp 14 | 15 | # YARD artifacts 16 | .yardoc 17 | _yardoc 18 | doc/ 19 | 20 | /tags 21 | /gems.tags 22 | /Gemfile.lock 23 | 24 | .vagrant 25 | /cache 26 | 27 | /boxes/**/*.tar.gz 28 | /boxes/**/partial/ 29 | /boxes/**/rootfs/ 30 | /boxes/temp/ 31 | /boxes/output/ 32 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2 4 | - 2.3 5 | - 2.4 6 | - 2.5 7 | install: 8 | - gem install -v 1.12.5 bundler 9 | - bundle _1.12.5_ install --jobs=3 --retry=3 10 | script: "bundle exec rake ci" 11 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | set wildignore+=*/boxes/*/rootfs/*,*/boxes/*/partial/* 2 | -------------------------------------------------------------------------------- /BOXES.md: -------------------------------------------------------------------------------- 1 | # vagrant-lxc base boxes 2 | 3 | Although the official documentation says it is only supported for VirtualBox 4 | environments, you can use the [`vagrant package`](http://docs.vagrantup.com/v2/cli/package.html) 5 | command to export a `.box` file from an existing vagrant-lxc container. 6 | 7 | There is also a set of [bash scripts](https://github.com/fgrehm/vagrant-lxc-base-boxes) 8 | that you can use to build base boxes as needed. By default it won't include any 9 | provisioning tool and you can pick the ones you want by providing some environment 10 | variables. Please refer to the [base boxes repository](https://github.com/fgrehm/vagrant-lxc-base-boxes) 11 | for more information. 12 | 13 | ## "Anatomy" of a box 14 | 15 | If you need to go deeper and build your scripts from scratch or if you are interested 16 | on knowing what makes a base box for vagrant-lxc, here's what's needed: 17 | 18 | ### Expected `.box` contents 19 | 20 | | FILE | REQUIRED? | DESCRIPTION | 21 | | --- | --- | --- | 22 | | `metadata.json` | Yes | Required by Vagrant | 23 | | `rootfs.tar.gz` | Yes | Compressed container rootfs tarball (need to remeber to pass in `--numeric-owner` when creating it) | 24 | | `lxc-template` | No, a ["generic script"](scripts/lxc-template) is provided by the plugin if it doesn't exist on the base box | Script responsible for creating and setting up the container (used with `lxc-create`). | 25 | | `lxc-config` | No | Box specific configuration to be _appended_ to the system's generated container config file | 26 | | `lxc.conf` | No | File passed in to `lxc-create -f` | 27 | 28 | ### metadata.json 29 | 30 | ```json 31 | { 32 | "provider": "lxc", 33 | "version": "1.0.0", 34 | "built-on": "Sat Sep 21 21:10:00 UTC 2013", 35 | "template-opts": { 36 | "--arch": "amd64", 37 | "--release": "quantal" 38 | } 39 | } 40 | ``` 41 | 42 | | KEY | REQUIRED? | DESCRIPTION | 43 | | --- | --- | --- | 44 | | `provider` | Yes | Required by Vagrant | 45 | | `version` | Yes | Tracks backward incompatibilities | 46 | | `built-on` | No | Date / time when the box was packaged for the first time | 47 | | `template-opts` | No | Extra options to be passed to the `lxc-template` script | 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.4.2](https://github.com/fgrehm/vagrant-lxc/compare/v1.4.1...v1.4.2) (Jul 17, 2018) 2 | 3 | FIXES: 4 | - Fix problems with `redir` 3.x command line. [[GH-467]] 5 | 6 | ## [1.4.1](https://github.com/fgrehm/vagrant-lxc/compare/v1.4.0...v1.4.1) (Apr 30, 2018) 7 | 8 | FEATURES: 9 | - Add support for LXC v3.0 10 | - Add support for `redir` 3.x command line. [[GH-460]] 11 | 12 | [GH-460]: https://github.com/fgrehm/vagrant-lxc/issues/460 13 | 14 | ## [1.4.0](https://github.com/fgrehm/vagrant-lxc/compare/v1.3.1...v1.4.0) (Mar 04, 2018) 15 | 16 | FEATURES: 17 | - Add support for unprivileged containers. [[GH-312]] 18 | 19 | [GH-312]: https://github.com/fgrehm/vagrant-lxc/issues/312 20 | 21 | ## [1.3.1](https://github.com/fgrehm/vagrant-lxc/compare/v1.3.0...v1.3.1) (Fev 06, 2018) 22 | 23 | FIXES: 24 | - Fix problems with `tmpfs` fiddling in v1.3.0. [[GH-455]] 25 | 26 | [GH-455]: https://github.com/fgrehm/vagrant-lxc/pull/455 27 | 28 | ## [1.3.0](https://github.com/fgrehm/vagrant-lxc/compare/v1.2.4...v1.3.0) (Jan 20, 2018) 29 | 30 | FEATURES: 31 | - lxc-template: make runnable by unprivileged users [[GH-447]] 32 | - Use `lxc-info` instead of `lxc-attach` to retrieve container IP 33 | - Add support for LXC v2.1+ [[GH-445]] 34 | - Remove 2Gb limitation on `/tmp`. [[GH-406]] 35 | 36 | OTHERS: 37 | - Bump Vagrant requirements to v1.8+ 38 | - Bump LXC requirements to v1.0+ 39 | 40 | 41 | [GH-447]: https://github.com/fgrehm/vagrant-lxc/pull/447 42 | [GH-445]: https://github.com/fgrehm/vagrant-lxc/pull/445 43 | [GH-406]: https://github.com/fgrehm/vagrant-lxc/pull/406 44 | 45 | ## [1.2.4](https://github.com/fgrehm/vagrant-lxc/compare/v1.2.3...v1.2.4) (Dec 20, 2017) 46 | 47 | BUGFIX: 48 | - Support alternative `lxcpath` [[GH-413]] 49 | - Update `pipework` regexp in sudo wrapper for Vagrant 1.9+ [[GH-438]] 50 | - Work around restrictive `umask` values [[GH-435]] 51 | - Make `--config` in `lxc-template` optional [[GH-421]] 52 | - Fix sudo wrapper binpath construction logic [[GH-410]] 53 | - Fix bug causing CTRL-C on `vagrant up` to destroy the VM [[GH-449]] 54 | 55 | [GH-413]: https://github.com/fgrehm/vagrant-lxc/pull/413 56 | [GH-438]: https://github.com/fgrehm/vagrant-lxc/pull/438 57 | [GH-435]: https://github.com/fgrehm/vagrant-lxc/pull/435 58 | [GH-421]: https://github.com/fgrehm/vagrant-lxc/pull/421 59 | [GH-410]: https://github.com/fgrehm/vagrant-lxc/pull/410 60 | [GH-449]: https://github.com/fgrehm/vagrant-lxc/pull/449 61 | 62 | ## [1.2.3](https://github.com/fgrehm/vagrant-lxc/compare/v1.2.2...v1.2.3) (Dec 20, 2016) 63 | 64 | - Fix bug in Gemfile.lock 65 | 66 | ## [1.2.2](https://github.com/fgrehm/vagrant-lxc/compare/v1.2.1...v1.2.2) (Dec 20, 2016) 67 | 68 | BUGFIX: 69 | - Make the timeout for fetching container IP's configurable [[GH-426]] 70 | - Load locale file only once [[GH-423]] 71 | - Preserve xattrs in container filesystems [[GH-411]] 72 | - Forward port latest pipework script [[GH-408]] 73 | - Fix handling of non-fatal lxc-stop return code [[GH-405]] 74 | 75 | [GH-426]: https://github.com/fgrehm/vagrant-lxc/pull/426 76 | [GH-423]: https://github.com/fgrehm/vagrant-lxc/pull/423 77 | [GH-411]: https://github.com/fgrehm/vagrant-lxc/pull/411 78 | [GH-408]: https://github.com/fgrehm/vagrant-lxc/pull/408 79 | [GH-405]: https://github.com/fgrehm/vagrant-lxc/pull/405 80 | 81 | ## [1.2.1](https://github.com/fgrehm/vagrant-lxc/compare/v1.2.0...v1.2.1) (Sep 24, 2015) 82 | 83 | BUGFIX: 84 | - Fix sudo Wrapper [[GH-393]] 85 | 86 | [GH-393]: https://github.com/fgrehm/vagrant-lxc/pull/393 87 | 88 | ## [1.2.0](https://github.com/fgrehm/vagrant-lxc/compare/v1.1.0...v1.2.0) (Sep 15, 2015) 89 | 90 | FEATURES: 91 | - Support private networking using DHCP [[GH-352]] 92 | 93 | [GH-352]: https://github.com/fgrehm/vagrant-lxc/pull/352 94 | 95 | IMPROVEMENTS: 96 | 97 | - Move mountpoint creation to lxc template for lvm rootfs support [[GH-361]] / [[GH-359]] 98 | - Mount selinux sys dir read-only [[GH-357]] / [[GH-301]] 99 | - Use correct ruby interpreter when generating sudoers file [[GH-355]] 100 | - Fix shebangs to be more portable [[GH-376]] 101 | - Fix removal of lxcbr0/virbr0 when using private networking [[GH-383]] 102 | - Improve /tmp handling by using tmpfs [[GH-362]] 103 | 104 | [GH-301]: https://github.com/fgrehm/vagrant-lxc/issues/301 105 | [GH-355]: https://github.com/fgrehm/vagrant-lxc/pull/355 106 | [GH-357]: https://github.com/fgrehm/vagrant-lxc/pull/357 107 | [GH-359]: https://github.com/fgrehm/vagrant-lxc/issues/359 108 | [GH-361]: https://github.com/fgrehm/vagrant-lxc/pull/361 109 | [GH-376]: https://github.com/fgrehm/vagrant-lxc/pull/376 110 | [GH-383]: https://github.com/fgrehm/vagrant-lxc/pull/383 111 | [GH-362]: https://github.com/fgrehm/vagrant-lxc/pull/362 112 | 113 | ## [1.1.0](https://github.com/fgrehm/vagrant-lxc/compare/v1.0.1...v1.1.0) (Jan 14, 2015) 114 | 115 | BACKWARDS INCOMPATIBILITIES: 116 | 117 | - Support for Vagrant versions prior to 1.5 have been removed. The plugin now targets 118 | Vagrant 1.7+ but it _might_ work on 1.5+. 119 | 120 | FEATURES: 121 | 122 | - New experimental support for private networking [[GH-298]] / [[GH-120]]. 123 | - Support for formatted overlayfs path [[GH-329]] 124 | 125 | 126 | [GH-298]: https://github.com/fgrehm/vagrant-lxc/pull/298 127 | [GH-120]: https://github.com/fgrehm/vagrant-lxc/issues/120 128 | [GH-329]: https://github.com/fgrehm/vagrant-lxc/pull/329 129 | 130 | IMPROVEMENTS: 131 | 132 | - The provider will now have a higher priority over the VirtualBox provider 133 | in case VirtualBox is installed alongside lxc dependecies. 134 | - Show an user friendly message when trying to use the plugin on non-Linux 135 | environments. 136 | 137 | BUG FIXES: 138 | 139 | - Allow backingstore options to be used along with the sudo wrapper script [[GH-310]] 140 | - Trim automatically generated container names to 64 chars [[GH-337]] 141 | 142 | [GH-337]: https://github.com/fgrehm/vagrant-lxc/issues/337 143 | [GH-310]: https://github.com/fgrehm/vagrant-lxc/issues/310 144 | 145 | 146 | ## [1.0.1](https://github.com/fgrehm/vagrant-lxc/compare/v1.0.0...v1.0.1) (Oct 15, 2014) 147 | 148 | IMPROVEMENTS: 149 | 150 | - Avoid lock race condition when fetching container's IP [[GH-318]] and SSH execution [[GH-321]] 151 | - Support for custom containers storage path by reading `lxc.lxcpath` [[GH-317]] 152 | 153 | 154 | [GH-317]: https://github.com/fgrehm/vagrant-lxc/pull/317 155 | [GH-318]: https://github.com/fgrehm/vagrant-lxc/pull/318 156 | [GH-321]: https://github.com/fgrehm/vagrant-lxc/issues/321 157 | 158 | ## [1.0.0](https://github.com/fgrehm/vagrant-lxc/compare/v1.0.0.alpha.3...v1.0.0) (Sep 23, 2014) 159 | 160 | DEPRECATIONS: 161 | 162 | - Support to **all Vagrant versions prior to 1.5 are deprecated**, there is a 163 | [small layer](lib/vagrant-backports) that ensures compatibility with versions 164 | starting with 1.1.5 that will be removed on a future release. 165 | - Official base boxes that were made available from http://bit.ly are no longer 166 | supported and were removed from @fgrehm's Dropbox, please upgrade your Vagrant 167 | and vagrant-lxc installation and use a base box from [VagrantCloud](https://vagrantcloud.com/search?provider=lxc) 168 | 169 | BACKWARDS INCOMPATIBILITIES: 170 | 171 | - Remove plugin version from config file name generated by the `vagrant lxc sudoers` 172 | command. Manual removal of `/usr/local/bin/vagrant-lxc-wrapper-*` / `/etc/sudoers.d/vagrant-lxc-*` 173 | files are required. 174 | 175 | IMPROVEMENTS: 176 | 177 | - `vagrant-mounted` upstart event is now emited on containers that support it [[GH-302]] 178 | - Add support for specifying the `--strip-parameters` used by the [default template](scripts/lxc-template) 179 | when extracting rootfs tarballs [[GH-311]] 180 | 181 | [GH-302]: https://github.com/fgrehm/vagrant-lxc/issues/302 182 | 183 | BUG FIXES: 184 | 185 | - Check for outdated base boxes when starting containers [[GH-314]] 186 | 187 | [GH-311]: https://github.com/fgrehm/vagrant-lxc/pull/311 188 | [GH-314]: https://github.com/fgrehm/vagrant-lxc/pull/314 189 | 190 | 191 | ## [1.0.0.alpha.3](https://github.com/fgrehm/vagrant-lxc/compare/v1.0.0.alpha.2...v1.0.0.alpha.3) (Aug 9, 2014) 192 | 193 | IMPROVEMENTS: 194 | 195 | - Remove `lxc-shutdown` usage in favor of Vagrant's built in graceful halt 196 | - Add fallback mechanism for platforms without `lxc-attach` support [[GH-294]] 197 | 198 | [GH-294]: https://github.com/fgrehm/vagrant-lxc/pull/294 199 | 200 | BUG FIXES: 201 | 202 | - Figure out the real executable paths for whitelisted commands on the sudo 203 | wrapper script instead of hardcoding Ubuntu paths [[GH-304]] / [[GH-305]] 204 | - Attach to containers using the `MOUNT` namespace when attempting to fetch 205 | container's IP [[GH-300]] 206 | - Escape space characters for synced folders [[GH-291]] 207 | - Use Vagrant's ruby on the sudoers file so that it works on systems that don't 208 | have a global ruby installation [[GH-289]] 209 | 210 | [GH-304]: https://github.com/fgrehm/vagrant-lxc/issues/304 211 | [GH-305]: https://github.com/fgrehm/vagrant-lxc/issues/305 212 | [GH-300]: https://github.com/fgrehm/vagrant-lxc/issues/300 213 | [GH-291]: https://github.com/fgrehm/vagrant-lxc/issues/291 214 | [GH-289]: https://github.com/fgrehm/vagrant-lxc/issues/289 215 | 216 | 217 | ## [1.0.0.alpha.2](https://github.com/fgrehm/vagrant-lxc/compare/v1.0.0.alpha.1...v1.0.0.alpha.2) (May 13, 2014) 218 | 219 | BACKWARDS INCOMPATIBILITIES: 220 | 221 | - The `sudo_wrapper` provider configuration was removed in favor of using the 222 | secure wrapper generated by `vagrant lxc sudoers` [[GH-272]] 223 | - Support for specifying backingstore parameters from `Vagrantfile`s for `lxc-create` 224 | was added and it defaults to the `best` option. On older lxc versions that does not 225 | support that value, it needs to be set to `none`. 226 | 227 | FEATURES: 228 | 229 | - Add support for specifying backingstore parameters from `Vagrantfile`s [[GH-277]] 230 | 231 | IMPROVEMENTS: 232 | 233 | - Make `dnsmasq` leases MAC address regex check case insensitive [[GH-283]] 234 | - Use relative paths for `lxc.mount.entry` to avoid issues with `lxc-clone` [[GH-258]]. 235 | - Sort synced folders when mounting [[GH-271]] 236 | - Privileged ports can now be forwarded with `sudo` [[GH-259]] 237 | - The `vagrant lxc sudoers` generated sudoers configuration and wrapper script 238 | are safer and properly whitelists the commands required by vagrant-lxc to run. 239 | [[GH-272]] / [[GH-269]] 240 | 241 | BUG FIXES: 242 | 243 | - Fix `lxc-create` issues with pre 1.0.0 versions [[GH-282]] 244 | 245 | [GH-283]: https://github.com/fgrehm/vagrant-lxc/pull/283 246 | [GH-282]: https://github.com/fgrehm/vagrant-lxc/pull/282 247 | [GH-269]: https://github.com/fgrehm/vagrant-lxc/issues/269 248 | [GH-272]: https://github.com/fgrehm/vagrant-lxc/pull/272 249 | [GH-259]: https://github.com/fgrehm/vagrant-lxc/pull/259 250 | [GH-271]: https://github.com/fgrehm/vagrant-lxc/pull/271 251 | [GH-277]: https://github.com/fgrehm/vagrant-lxc/pull/277 252 | [GH-258]: https://github.com/fgrehm/vagrant-lxc/issues/258 253 | 254 | 255 | ## [1.0.0.alpha.1](https://github.com/fgrehm/vagrant-lxc/compare/v0.8.0...v1.0.0.alpha.1) (Apr 06, 2014) 256 | 257 | DEPRECATIONS: 258 | 259 | - Support to **all Vagrant versions prior to 1.5 are now deprecated**, there is a 260 | [small layer](lib/vagrant-backports) that ensures compatibility with versions 261 | starting with 1.1.5 but there is no guarantee that it will stick for too long. 262 | - Boxes released prior to this version are now deprecated and won't be available 263 | after the final 1.0.0 release. 264 | - `--auth-key` argument is no longer provided to `lxc-template`. This will cause 265 | all official base boxes prior to 09/28/2013 to break. 266 | 267 | FEATURES: 268 | 269 | - New `vagrant lxc sudoers` command for creating a policy for users in order to 270 | avoid `sudo` passwords [[GH-237]] / [[GH-257]] 271 | - Support for NFS and rsync synced folders. 272 | - Support for synced folder mount options allowing for using read only synced 273 | folders [[GH-193]] 274 | 275 | [GH-237]: https://github.com/fgrehm/vagrant-lxc/issues/237 276 | [GH-257]: https://github.com/fgrehm/vagrant-lxc/pull/257 277 | [GH-193]: https://github.com/fgrehm/vagrant-lxc/issues/193 278 | 279 | IMPROVEMENTS: 280 | 281 | - `lxc-template` is now optional for base boxes and are bundled with the plugin, 282 | allowing us to roll out updates without the need to rebuild boxes [[GH-254]] 283 | - Set container's `utsname` to `config.vm.hostname` by default [[GH-253]] 284 | - Added libvirt dnsmasq leases file to the lookup paths [[GH-251]] 285 | - Improved compatibility with Vagrant 1.4 / 1.5 including the ability 286 | to use `rsync` and `nfs` shared folders to work around synced folders 287 | permission problems. More information can be found on the following 288 | issues: [[GH-151]] [[GH-191]] [[GH-241]] [[GH-242]] 289 | - Warn in case `:group` or `:owner` are specified for synced folders [[GH-196]] 290 | - Acceptance specs are now powered by `vagrant-spec` [[GH-213]] 291 | - Base boxes creation scripts were moved out to https://github.com/fgrehm/vagrant-lxc-base-boxes. 292 | 293 | [GH-254]: https://github.com/fgrehm/vagrant-lxc/issues/254 294 | [GH-196]: https://github.com/fgrehm/vagrant-lxc/issues/196 295 | [GH-251]: https://github.com/fgrehm/vagrant-lxc/pull/251 296 | [GH-253]: https://github.com/fgrehm/vagrant-lxc/pull/253 297 | [GH-151]: https://github.com/fgrehm/vagrant-lxc/issues/151 298 | [GH-213]: https://github.com/fgrehm/vagrant-lxc/issues/213 299 | [GH-191]: https://github.com/fgrehm/vagrant-lxc/issues/191 300 | [GH-241]: https://github.com/fgrehm/vagrant-lxc/issues/241 301 | [GH-242]: https://github.com/fgrehm/vagrant-lxc/issues/242 302 | 303 | 304 | ## [0.8.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.7.0...v0.8.0) (Feb 26, 2014) 305 | 306 | FEATURES: 307 | 308 | - Support for naming containers from Vagrantfiles [#132](https://github.com/fgrehm/vagrant-lxc/issues/132) 309 | 310 | IMPROVEMENTS: 311 | 312 | - Use a safer random name for containers [#152](https://github.com/fgrehm/vagrant-lxc/issues/152) 313 | - Improve Ubuntu 13.10 compatibility [#190](https://github.com/fgrehm/vagrant-lxc/pull/190) / [#197](https://github.com/fgrehm/vagrant-lxc/pull/197) 314 | - Improved mac address detection from lxc configs [#226](https://github.com/fgrehm/vagrant-lxc/pull/226) 315 | 316 | BUG FIXES: 317 | 318 | - Properly detect if lxc is installed on hosts that do not have `lxc-version` on their paths [#186](https://github.com/fgrehm/vagrant-lxc/issues/186) 319 | 320 | 321 | ## [0.7.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.6.4...v0.7.0) (Nov 8, 2013) 322 | 323 | IMPROVEMENTS: 324 | 325 | - Support for `vagrant up` in parallel [#152](https://github.com/fgrehm/vagrant-lxc/issues/152) 326 | - Warn users about unsupported private / public networking configs [#154](https://github.com/fgrehm/vagrant-lxc/issues/154) 327 | - Respect Vagrantfile options to disable forwarded port [#149](https://github.com/fgrehm/vagrant-lxc/issues/149) 328 | 329 | BUG FIXES: 330 | 331 | - Nicely handle blank strings provided to `:host_ip` when specifying forwarded ports [#170](https://github.com/fgrehm/vagrant-lxc/issues/170) 332 | - Fix "Permission denied" when starting/destroying containers after lxc 333 | security update in Ubuntu [#180](https://github.com/fgrehm/vagrant-lxc/issues/180) 334 | - Fix `vagrant package` [#172](https://github.com/fgrehm/vagrant-lxc/issues/172) 335 | 336 | 337 | ## [0.6.4](https://github.com/fgrehm/vagrant-lxc/compare/v0.6.3...v0.6.4) (Oct 27, 2013) 338 | 339 | FEATURES: 340 | 341 | - New script for building OpenMandriva base boxes [#167](https://github.com/fgrehm/vagrant-lxc/issues/167) 342 | 343 | IMPROVEMENTS: 344 | 345 | - Make `lxc-template` compatible with Ubuntu 13.10 [#150](https://github.com/fgrehm/vagrant-lxc/issues/150) 346 | 347 | BUG FIXES: 348 | 349 | - Fix force halt for hosts that do not have `lxc-shutdown` around (like Ubuntu 13.10) [#150](https://github.com/fgrehm/vagrant-lxc/issues/150) 350 | 351 | ## [0.6.3](https://github.com/fgrehm/vagrant-lxc/compare/v0.6.2...v0.6.3) (Oct 12, 2013) 352 | 353 | IMPROVEMENTS: 354 | 355 | - Respect Vagrantfile option to disable synced folders [#147](https://github.com/fgrehm/vagrant-lxc/issues/147) 356 | 357 | BUG FIXES: 358 | 359 | - Fix error raised when fetching container's IP with the sudo wrapper disabled [#157](https://github.com/fgrehm/vagrant-lxc/issues/157) 360 | 361 | ## [0.6.2](https://github.com/fgrehm/vagrant-lxc/compare/v0.6.1...v0.6.2) (Oct 03, 2013) 362 | 363 | IMPROVEMENTS: 364 | 365 | - Cache the result of `lxc-attach --namespaces` parameter support checking to 366 | avoid excessive logging. 367 | 368 | BUG FIXES: 369 | 370 | - Fix detection of `lxc-attach --namespaces` parameter support checking. 371 | 372 | ## [0.6.1](https://github.com/fgrehm/vagrant-lxc/compare/v0.6.0...v0.6.1) (Oct 03, 2013) 373 | 374 | IMPROVEMENTS: 375 | 376 | - Fall back to `dnsmasq` leases file if not able to fetch IP with `lxc-attach` [#118](https://github.com/fgrehm/vagrant-lxc/issues/118) 377 | - Make sure lxc templates are executable prior to `lxc-create` [#128](https://github.com/fgrehm/vagrant-lxc/issues/128) 378 | - New base boxes with support for lxc 1.0+ 379 | 380 | BUG FIXES: 381 | 382 | - Fix various issues related to detecting whether the container is running 383 | and is "SSHable" [#142](https://github.com/fgrehm/vagrant-lxc/issues/142) 384 | - Nicely handle missing templates path [#139](https://github.com/fgrehm/vagrant-lxc/issues/139) 385 | 386 | ## [0.6.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.5.0...v0.6.0) (Sep 12, 2013) 387 | 388 | IMPROVEMENTS: 389 | 390 | - Compatibility with Vagrant 1.3+ [#136](https://github.com/fgrehm/vagrant-lxc/pull/136) 391 | - Set plugin name to `vagrant-lxc` so that it is easier to check if the plugin is 392 | installed with the newly added `Vagrant.has_plugin?` 393 | 394 | BUG FIXES: 395 | 396 | - Fix box package ownership on `vagrant package` [#140](https://github.com/fgrehm/vagrant-lxc/pull/140) 397 | - Fix error while compressing container's rootfs under Debian hosts [#131](https://github.com/fgrehm/vagrant-lxc/issues/131) / 398 | [#133](https://github.com/fgrehm/vagrant-lxc/issues/133) 399 | 400 | ## [0.5.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.4.0...v0.5.0) (Aug 1, 2013) 401 | 402 | BACKWARDS INCOMPATIBILITIES: 403 | 404 | - To align with Vagrant's core behaviour, forwarded ports are no longer attached 405 | to 127.0.0.1 and `redir`'s `--laddr` parameter is skipped in case the `:host_ip` 406 | config is not provided, that means `redir` will listen on connections coming 407 | from any of the host's IPs. 408 | 409 | FEATURES: 410 | 411 | - Add support for salt-minion and add latest dev release for ubuntu codenamed saucy [#116](https://github.com/fgrehm/vagrant-lxc/pull/116) 412 | - Add support for using a sudo wrapper script [#90](https://github.com/fgrehm/vagrant-lxc/issues/90) 413 | - `redir` will log to `/var/log/syslog` if `REDIR_LOG` env var is provided 414 | 415 | IMPROVEMENTS: 416 | 417 | - Error out if dependencies are not installed [#11](https://github.com/fgrehm/vagrant-lxc/issues/11) / [#112](https://github.com/fgrehm/vagrant-lxc/issues/112) 418 | - Support for specifying host interface/ip for binding `redir` [#76](https://github.com/fgrehm/vagrant-lxc/issues/76) 419 | - Add Vagrantfile VM name to the container name [#115](https://github.com/fgrehm/vagrant-lxc/issues/115) 420 | - Properly handle forwarded port collisions [#5](https://github.com/fgrehm/vagrant-lxc/issues/5) 421 | - Container's customizations are now written to the config file (usually 422 | kept under `/var/lib/lxc/CONTAINER/config`) instead of passed in as a `-s` 423 | parameter to `lxc-start` 424 | 425 | ## [0.4.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.3.4...v0.4.0) (Jul 18, 2013) 426 | 427 | FEATURES: 428 | 429 | - New box format [#89](https://github.com/fgrehm/vagrant-lxc/issues/89) 430 | 431 | BUG FIXES: 432 | 433 | - Add translation for stopped status [#97](https://github.com/fgrehm/vagrant-lxc/issues/97) 434 | - Enable retries when fetching container state [#74](https://github.com/fgrehm/vagrant-lxc/issues/74) 435 | - Fix error when setting Debian boxes hostname from Vagrantfile [#91](https://github.com/fgrehm/vagrant-lxc/issues/91) 436 | - BTRFS-friendly base boxes [#81](https://github.com/fgrehm/vagrant-lxc/issues/81) 437 | - Extended templates path lookup [#77](https://github.com/fgrehm/vagrant-lxc/issues/77) (tks to @aries1980) 438 | - Fix default group for packaged boxes tarballs on the rake task [#82](https://github.com/fgrehm/vagrant-lxc/issues/82) (tks to @cduez) 439 | 440 | ## [0.3.4](https://github.com/fgrehm/vagrant-lxc/compare/v0.3.3...v0.3.4) (May 08, 2013) 441 | 442 | FEATURES: 443 | 444 | - Support for building Debian boxes (tks to @Val) 445 | - Support for installing babushka on base boxes (tks to @Val) 446 | 447 | IMPROVEMENTS: 448 | 449 | - Replace `lxc-wait` usage with a "[retry mechanism](https://github.com/fgrehm/vagrant-lxc/commit/3cca16824879731315dac32bc2df1c643f30d461#L2R88)" [#22](https://github.com/fgrehm/vagrant-lxc/issues/22) 450 | - Remove `/tmp` files after the machine has been successfully shut down [#68](https://github.com/fgrehm/vagrant-lxc/issues/68) 451 | - Clean up base boxes files after they've been configured, resulting in smaller packages 452 | - Bump development dependency to Vagrant 1.2+ series 453 | 454 | BUG FIXES: 455 | 456 | - Issue a `lxc-stop` in case the container cannot shutdown gracefully [#72](https://github.com/fgrehm/vagrant-lxc/issues/72) 457 | 458 | ## [0.3.3](https://github.com/fgrehm/vagrant-lxc/compare/v0.3.2...v0.3.3) (April 23, 2013) 459 | 460 | BUG FIXES: 461 | 462 | - Properly kill `redir` child processes [#59](https://github.com/fgrehm/vagrant-lxc/issues/59) 463 | - Use `uname -m` on base Ubuntu lxc-template [#53](https://github.com/fgrehm/vagrant-lxc/issues/53) 464 | 465 | IMPROVEMENTS: 466 | 467 | - Initial acceptance test suite 468 | - New rake tasks for building Ubuntu precise and raring base amd64 boxes 469 | 470 | ## [0.3.2](https://github.com/fgrehm/vagrant-lxc/compare/v0.3.1...v0.3.2) (April 18, 2013) 471 | 472 | - Do not display port forwarding message in case no forwarded ports were set 473 | 474 | ## [0.3.1](https://github.com/fgrehm/vagrant-lxc/compare/v0.3.0...v0.3.1) (April 18, 2013) 475 | 476 | - Improved output to match lxc "verbiage" 477 | 478 | ## [0.3.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.2.0...v0.3.0) (April 10, 2013) 479 | 480 | BACKWARDS INCOMPATIBILITIES: 481 | 482 | - Boxes `lxc-template` should support a `--tarball` parameter 483 | - `start_opts` config was renamed to `customize`, please check the README for the expected parameters 484 | - V1 boxes are no longer supported 485 | - `target_rootfs_path` is no longer supported, just symlink `/var/lib/lxc` to the desired folder in case you want to point it to another partition 486 | - Removed support for configuring private networks. It will come back at some point in the future but if you need it you should be able to set using `customize 'network.ipv4', '1.2.3.4/24'` 487 | 488 | IMPROVEMENTS: 489 | 490 | - lxc templates are removed from lxc template dir after container is created 491 | - Treat NFS shared folders as a normal shared folder instead of ignoring it so we can share the same Vagrantfile with VBox environments 492 | - Support for lxc 0.7.5 (tested on Ubuntu 12.04) [#49](https://github.com/fgrehm/vagrant-lxc/issues/49) 493 | - Remove `/tmp` files when packaging quantal64 base box [#48](https://github.com/fgrehm/vagrant-lxc/issues/48) 494 | - Avoid picking the best mirror on quantal64 base box [#38](https://github.com/fgrehm/vagrant-lxc/issues/38) 495 | 496 | BUG FIXES: 497 | 498 | - Redirect `redir`'s stderr output to `/dev/null` [#51](https://github.com/fgrehm/vagrant-lxc/issues/51) 499 | - Switch from `ifconfig` to `ip` to grab container's IP to avoid localization issues [#50](https://github.com/fgrehm/vagrant-lxc/issues/50) 500 | 501 | ## [0.2.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.1.1...v0.2.0) (March 30, 2013) 502 | 503 | - Experimental box packaging (only tested with Ubuntu 64 base box) 504 | 505 | ## [0.1.1](https://github.com/fgrehm/vagrant-lxc/compare/v0.1.0...v0.1.1) (March 29, 2013) 506 | 507 | - Removed support for development under Vagrant < 1.1 508 | - Removed rsync from base quantal64 box to speed up containers creation [#40](https://github.com/fgrehm/vagrant-lxc/issues/40) 509 | - Containers are now named after project's root dir [#14](https://github.com/fgrehm/vagrant-lxc/issues/14) 510 | - Skip Vagrant's built in SSH redirect 511 | - Allow setting rootfs from Vagrantfile [#30](https://github.com/fgrehm/vagrant-lxc/issues/30) 512 | 513 | ## [0.1.0](https://github.com/fgrehm/vagrant-lxc/compare/v0.0.3...v0.1.0) (March 27, 2013) 514 | 515 | - Support for chef added to base quantal64 box 516 | - Puppet upgraded to 3.1.1 on base quantal64 box 517 | - Port forwarding support added [#6](https://github.com/fgrehm/vagrant-lxc/issues/6) 518 | 519 | ## Previous 520 | 521 | The changelog began with version 0.1.0 so any changes prior to that 522 | can be seen by checking the tagged releases and reading git commit 523 | messages. 524 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Please read before contributing 2 | 3 | * If you have an issue with base boxes, please create it on https://github.com/fgrehm/vagrant-lxc-base-boxes, 4 | this repository is for the Vagrant plugin only. 5 | 6 | * Try not to post questions in the issues tracker. I will probably answer you 7 | but I'll most likely close the issue right away and will continue the discussion 8 | with the issue closed. If you have any questions about the plugin, make sure 9 | you go through the [Wiki](https://github.com/fgrehm/vagrant-lxc/wiki) pages 10 | first (specially the [Troubleshooting Section](https://github.com/fgrehm/vagrant-lxc/wiki/Troubleshooting)) 11 | and if you still need answers please ask a question on [Stack Overflow](http://stackoverflow.com/questions/tagged/vagrant-lxc) 12 | using the `vagrant` / `lxc` tag on it so that I get notified :) 13 | 14 | * Please do a search on the issues tracker before submitting your issue to 15 | check if it was already reported / fixed. 16 | 17 | * When reporting a bug, please include **all** information that you can get 18 | about your environment. Things like vagrant / vagrant-lxc / kernel / lxc / 19 | distro versions, the list of plugins you have installed, a [gist](https://gist.github.com/) 20 | with the output of running `VAGRANT_LOG=debug vagrant COMMAND`, the `Vagrantfile` 21 | you are using and / or base box URL are really useful when tracking down what's 22 | going on on your side of the globe and will get bonus points :) 23 | 24 | Thanks! 25 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development do 4 | gem 'vagrant', git: 'https://github.com/mitchellh/vagrant.git' 5 | gem 'guard' 6 | gem 'guard-rspec' 7 | gem 'rb-inotify' 8 | end 9 | 10 | group :development, :test do 11 | gem 'rake', '~> 10.4.2' 12 | gem 'rspec', '~> 3.5.0' 13 | gem 'coveralls', '~> 0.7.2', require: (ENV['COVERAGE'] == 'true') 14 | gem 'vagrant-spec', git: 'https://github.com/mitchellh/vagrant-spec.git' 15 | end 16 | 17 | group :plugins do 18 | acceptance = (ENV['ACCEPTANCE'] == 'true') 19 | gem 'vagrant-cachier', git: 'https://github.com/fgrehm/vagrant-cachier.git', require: !acceptance 20 | gem 'vagrant-pristine', git: 'https://github.com/fgrehm/vagrant-pristine.git', require: !acceptance 21 | gem 'vagrant-omnibus', require: !acceptance 22 | gemspec 23 | end 24 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'rspec', :spec_paths => ["spec/unit"] do 2 | watch(%r{^spec/unit/.+_spec\.rb$}) 3 | watch(%r{^lib/vagrant-lxc/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" } 4 | watch('spec/unit_helper.rb') { "spec/unit" } 5 | watch('spec/spec_helper.rb') { "spec/unit" } 6 | watch(%r{^spec/support/(.+)\.rb$}) { "spec/unit" } 7 | end 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 Fábio Rehm 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :warning: PROJECT UNMAINTAINED :warning: 2 | 3 | This project has been archived. Thanks a lot to everyone that contributed itwith over the years :heart: 4 | 5 | If anyone else wants to resurrect it please reach out by emails! 6 | 7 | # vagrant-lxc 8 | 9 | [![Build Status](https://travis-ci.org/fgrehm/vagrant-lxc.png?branch=master)](https://travis-ci.org/fgrehm/vagrant-lxc) [![Gem Version](https://badge.fury.io/rb/vagrant-lxc.png)](http://badge.fury.io/rb/vagrant-lxc) [![Code Climate](https://codeclimate.com/github/fgrehm/vagrant-lxc.png)](https://codeclimate.com/github/fgrehm/vagrant-lxc) [![Coverage Status](https://coveralls.io/repos/fgrehm/vagrant-lxc/badge.png?branch=master)](https://coveralls.io/r/fgrehm/vagrant-lxc) [![Gitter chat](https://badges.gitter.im/fgrehm/vagrant-lxc.png)](https://gitter.im/fgrehm/vagrant-lxc) 10 | 11 | [LXC](http://lxc.sourceforge.net/) provider for [Vagrant](http://www.vagrantup.com/) 1.9+ 12 | 13 | This is a Vagrant plugin that allows it to control and provision Linux Containers 14 | as an alternative to the built in VirtualBox provider for Linux hosts. Check out 15 | [this blog post](http://fabiorehm.com/blog/2013/04/28/lxc-provider-for-vagrant/) 16 | to see it in action. 17 | 18 | ## Features 19 | 20 | * Provides the same workflow as the Vagrant VirtualBox provider 21 | * Port forwarding via [`redir`](https://github.com/troglobit/redir) 22 | * Private networking via [`pipework`](https://github.com/jpetazzo/pipework) 23 | 24 | ## Requirements 25 | 26 | * [Vagrant 1.9+](http://www.vagrantup.com/downloads.html) 27 | * lxc >=2.1 28 | * `redir` (if you are planning to use port forwarding) 29 | * `brctl` (if you are planning to use private networks, on Ubuntu this means `apt-get install bridge-utils`) 30 | 31 | The plugin is known to work better and pretty much out of the box on Ubuntu 14.04+ 32 | hosts and installing the dependencies on it basically means a 33 | `apt-get install lxc lxc-templates cgroup-lite redir`. For setting up other 34 | types of hosts please have a look at the [Wiki](https://github.com/fgrehm/vagrant-lxc/wiki). 35 | 36 | If you are on a Mac or Windows machine, you might want to have a look at [this](http://the.taoofmac.com/space/HOWTO/Vagrant) 37 | blog post for some ideas on how to set things up or check out [this other repo](https://github.com/fgrehm/vagrant-lxc-vbox-hosts) 38 | for a set of Vagrant VirtualBox machines ready for vagrant-lxc usage. 39 | 40 | 41 | ## Installation 42 | 43 | ``` 44 | vagrant plugin install vagrant-lxc 45 | ``` 46 | 47 | 48 | ## Quick start 49 | 50 | ``` 51 | vagrant init fgrehm/precise64-lxc 52 | vagrant up --provider=lxc 53 | ``` 54 | 55 | _More information about skipping the `--provider` argument can be found at the 56 | "DEFAULT PROVIDER" section of [Vagrant docs](https://docs.vagrantup.com/v2/providers/basic_usage.html)_ 57 | 58 | ## Base boxes 59 | 60 | Base boxes provided on Atlas haven't been refreshed for a good while and shouldn't be relied on. 61 | Your best best is to build your boxes yourself. Some scripts to build your own are available at 62 | [hsoft/vagrant-lxc-base-boxes](https://github.com/hsoft/vagrant-lxc-base-boxes). 63 | 64 | If you want to build your own boxes, please have a look at [`BOXES.md`](https://github.com/fgrehm/vagrant-lxc/tree/master/BOXES.md) 65 | for more information. 66 | 67 | ## Advanced configuration 68 | 69 | You can modify container configurations from within your Vagrantfile using the 70 | [provider block](http://docs.vagrantup.com/v2/providers/configuration.html): 71 | 72 | ```ruby 73 | Vagrant.configure("2") do |config| 74 | config.vm.box = "fgrehm/trusty64-lxc" 75 | config.vm.provider :lxc do |lxc| 76 | # Same effect as 'customize ["modifyvm", :id, "--memory", "1024"]' for VirtualBox 77 | lxc.customize 'cgroup.memory.limit_in_bytes', '1024M' 78 | end 79 | end 80 | ``` 81 | 82 | vagrant-lxc will then write out `lxc.cgroup.memory.limit_in_bytes='1024M'` to the 83 | container config file (usually kept under `/var/lib/lxc//config`) 84 | prior to starting it. 85 | 86 | For other configuration options, please check the [lxc.conf manpages](http://manpages.ubuntu.com/manpages/precise/man5/lxc.conf.5.html). 87 | 88 | ### Private Networks 89 | 90 | Starting with vagrant-lxc 1.1.0, there is some rudimentary support for configuring 91 | [Private Networks](https://docs.vagrantup.com/v2/networking/private_network.html) 92 | by leveraging the [pipework](https://github.com/jpetazzo/pipework) project. 93 | 94 | On its current state, there is a requirement for setting the bridge name that 95 | will be created and will allow your machine to comunicate with the container 96 | 97 | For example: 98 | 99 | ```ruby 100 | Vagrant.configure("2") do |config| 101 | config.vm.network "private_network", ip: "192.168.2.100", lxc__bridge_name: 'vlxcbr1' 102 | end 103 | ``` 104 | 105 | Will create a new `veth` device for the container and will set up (or reuse) 106 | a `vlxcbr1` bridge between your machine and the `veth` device. Once the last 107 | vagrant-lxc container attached to the bridge gets `vagrant halt`ed, the plugin 108 | will delete the bridge. 109 | 110 | ### Container naming 111 | 112 | By default vagrant-lxc will attempt to generate a unique container name 113 | for you. However, if the container name is important to you, you may use the 114 | `container_name` attribute to set it explicitly from the `provider` block: 115 | 116 | ```ruby 117 | Vagrant.configure("2") do |config| 118 | config.vm.define "db" do |node| 119 | node.vm.provider :lxc do |lxc| 120 | lxc.container_name = :machine # Sets the container name to 'db' 121 | lxc.container_name = 'mysql' # Sets the container name to 'mysql' 122 | end 123 | end 124 | end 125 | ``` 126 | 127 | _Please note that there is a 64 chars limit and the container name will be 128 | trimmed down to that to ensure we can always bring the container up. 129 | 130 | ### Backingstore options 131 | 132 | Support for setting `lxc-create`'s backingstore option (`-B` and related) can be 133 | specified from the provider block and it defaults to `best`, to change it: 134 | 135 | ```ruby 136 | Vagrant.configure("2") do |config| 137 | config.vm.provider :lxc do |lxc| 138 | lxc.backingstore = 'lvm' # or 'btrfs', 'overlayfs', ... 139 | # lvm specific options 140 | lxc.backingstore_option '--vgname', 'schroots' 141 | lxc.backingstore_option '--fssize', '5G' 142 | lxc.backingstore_option '--fstype', 'xfs' 143 | end 144 | end 145 | ``` 146 | 147 | ## Unprivileged containers support 148 | 149 | Since v1.4.0, `vagrant-lxc` gained support for unprivileged containers. For now, since it's a new 150 | feature, privileged containers are still the default, but you can have your `Vagrantfile` use 151 | unprivileged containers with the `privileged` flag (which defaults to `true`). Example: 152 | 153 | ```ruby 154 | Vagrant.configure("2") do |config| 155 | config.vm.provider :lxc do |lxc| 156 | lxc.privileged = false 157 | end 158 | end 159 | ``` 160 | 161 | For unprivileged containers to work with `vagrant-lxc`, you need a properly configured system. On 162 | some distros, it can be somewhat of a challenge. Your journey to configuring your system can start 163 | with [Stéphane Graber's blog post about it](https://stgraber.org/2014/01/17/lxc-1-0-unprivileged-containers/). 164 | 165 | ## Avoiding `sudo` passwords 166 | 167 | If you're not using unprivileged containers, this plugin requires **a lot** of `sudo`ing To work 168 | around that, you can use the `vagrant lxc sudoers` command which will create a file under 169 | `/etc/sudoers.d/vagrant-lxc` whitelisting all commands required by `vagrant-lxc` to run. 170 | 171 | If you are interested on what will be generated by that command, please check 172 | [this code](lib/vagrant-lxc/command/sudoers.rb). 173 | 174 | 175 | ## More information 176 | 177 | Please refer the [wiki](https://github.com/fgrehm/vagrant-lxc/wiki). 178 | 179 | 180 | ## Problems / ideas? 181 | 182 | Please review the [Troubleshooting](https://github.com/fgrehm/vagrant-lxc/wiki/Troubleshooting) 183 | wiki page + [known bugs](https://github.com/fgrehm/vagrant-lxc/issues?labels=bug&page=1&state=open) 184 | list if you have a problem and feel free to use the [issue tracker](https://github.com/fgrehm/vagrant-lxc/issues) 185 | propose new functionality and / or report bugs. 186 | 187 | 188 | ## Contributing 189 | 190 | 1. Fork it 191 | 2. Create your feature branch (`git checkout -b my-new-feature`) 192 | 3. Commit your changes (`git commit -am 'Add some feature'`) 193 | 4. Push to the branch (`git push origin my-new-feature`) 194 | 5. Create new Pull Request 195 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | Dir['./tasks/**/*.rake'].each { |f| load f } 2 | 3 | require 'bundler/gem_tasks' 4 | -------------------------------------------------------------------------------- /lib/vagrant-lxc.rb: -------------------------------------------------------------------------------- 1 | require "vagrant-lxc/version" 2 | require "vagrant-lxc/plugin" 3 | 4 | module Vagrant 5 | module LXC 6 | def self.source_root 7 | @source_root ||= Pathname.new(File.dirname(__FILE__)).join('..').expand_path 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-lxc/action/boot' 2 | require 'vagrant-lxc/action/clear_forwarded_ports' 3 | require 'vagrant-lxc/action/create' 4 | require 'vagrant-lxc/action/destroy' 5 | require 'vagrant-lxc/action/destroy_confirm' 6 | require 'vagrant-lxc/action/compress_rootfs' 7 | require 'vagrant-lxc/action/fetch_ip_with_lxc_info' 8 | require 'vagrant-lxc/action/forced_halt' 9 | require 'vagrant-lxc/action/forward_ports' 10 | require 'vagrant-lxc/action/gc_private_network_bridges' 11 | require 'vagrant-lxc/action/handle_box_metadata' 12 | require 'vagrant-lxc/action/prepare_nfs_settings' 13 | require 'vagrant-lxc/action/prepare_nfs_valid_ids' 14 | require 'vagrant-lxc/action/private_networks' 15 | require 'vagrant-lxc/action/setup_package_files' 16 | require 'vagrant-lxc/action/warn_networks' 17 | 18 | module Vagrant 19 | module LXC 20 | module Action 21 | # Shortcuts 22 | Builtin = Vagrant::Action::Builtin 23 | Builder = Vagrant::Action::Builder 24 | 25 | # This action is responsible for reloading the machine, which 26 | # brings it down, sucks in new configuration, and brings the 27 | # machine back up with the new configuration. 28 | def self.action_reload 29 | Builder.new.tap do |b| 30 | b.use Builtin::Call, Builtin::IsState, :not_created do |env1, b2| 31 | if env1[:result] 32 | b2.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_created") 33 | next 34 | end 35 | 36 | b2.use Builtin::ConfigValidate 37 | b2.use action_halt 38 | b2.use action_start 39 | end 40 | end 41 | end 42 | 43 | # This action boots the VM, assuming the VM is in a state that requires 44 | # a bootup (i.e. not saved). 45 | def self.action_boot 46 | Builder.new.tap do |b| 47 | b.use Builtin::Provision 48 | b.use Builtin::EnvSet, :port_collision_repair => true 49 | b.use Builtin::HandleForwardedPortCollisions 50 | b.use PrepareNFSValidIds 51 | b.use Builtin::SyncedFolderCleanup 52 | b.use Builtin::SyncedFolders 53 | b.use PrepareNFSSettings 54 | b.use Builtin::SetHostname 55 | b.use WarnNetworks 56 | b.use ForwardPorts 57 | b.use PrivateNetworks 58 | b.use Boot 59 | b.use Builtin::WaitForCommunicator 60 | end 61 | end 62 | 63 | # This action just runs the provisioners on the machine. 64 | def self.action_provision 65 | Builder.new.tap do |b| 66 | b.use Builtin::ConfigValidate 67 | b.use Builtin::Call, Builtin::IsState, :not_created do |env1, b2| 68 | if env1[:result] 69 | b2.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_created") 70 | next 71 | end 72 | 73 | b2.use Builtin::Call, Builtin::IsState, :running do |env2, b3| 74 | if !env2[:result] 75 | b3.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_running") 76 | next 77 | end 78 | 79 | b3.use Builtin::Provision 80 | end 81 | end 82 | end 83 | end 84 | 85 | # This action starts a container, assuming it is already created and exists. 86 | # A precondition of this action is that the container exists. 87 | def self.action_start 88 | Builder.new.tap do |b| 89 | b.use Builtin::ConfigValidate 90 | b.use Builtin::BoxCheckOutdated 91 | b.use Builtin::Call, Builtin::IsState, :running do |env, b2| 92 | # If the VM is running, then our work here is done, exit 93 | next if env[:result] 94 | b2.use action_boot 95 | end 96 | end 97 | end 98 | 99 | # This action brings the machine up from nothing, including creating the 100 | # container, configuring metadata, and booting. 101 | def self.action_up 102 | Builder.new.tap do |b| 103 | b.use Builtin::ConfigValidate 104 | b.use Builtin::Call, Builtin::IsState, :not_created do |env, b2| 105 | # If the VM is NOT created yet, then do the setup steps 106 | if env[:result] 107 | b2.use Builtin::HandleBox 108 | b2.use HandleBoxMetadata 109 | b2.use Create 110 | end 111 | end 112 | b.use action_start 113 | end 114 | end 115 | 116 | # This is the action that is primarily responsible for halting 117 | # the virtual machine, gracefully or by force. 118 | def self.action_halt 119 | Builder.new.tap do |b| 120 | b.use Builtin::Call, Builtin::IsState, :not_created do |env, b2| 121 | if env[:result] 122 | b2.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_created") 123 | next 124 | end 125 | 126 | b2.use ClearForwardedPorts 127 | b2.use GcPrivateNetworkBridges 128 | b2.use Builtin::Call, Builtin::GracefulHalt, :stopped, :running do |env2, b3| 129 | if !env2[:result] 130 | b3.use ForcedHalt 131 | end 132 | end 133 | end 134 | end 135 | end 136 | 137 | # This is the action that is primarily responsible for completely 138 | # freeing the resources of the underlying virtual machine. 139 | def self.action_destroy 140 | Builder.new.tap do |b| 141 | b.use Builtin::Call, Builtin::IsState, :not_created do |env1, b2| 142 | if env1[:result] 143 | b2.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_created") 144 | next 145 | end 146 | 147 | b2.use Builtin::Call, DestroyConfirm do |env2, b3| 148 | if env2[:result] 149 | b3.use Builtin::ConfigValidate 150 | b3.use Builtin::EnvSet, :force_halt => true 151 | b3.use action_halt 152 | b3.use Destroy 153 | b3.use Builtin::ProvisionerCleanup 154 | else 155 | b3.use Builtin::Message, I18n.t("vagrant_lxc.messages.will_not_destroy") 156 | end 157 | end 158 | end 159 | end 160 | end 161 | 162 | # This action packages the virtual machine into a single box file. 163 | def self.action_package 164 | Builder.new.tap do |b| 165 | b.use Builtin::Call, Builtin::IsState, :not_created do |env1, b2| 166 | if env1[:result] 167 | b2.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_created") 168 | next 169 | end 170 | 171 | b2.use action_halt 172 | b2.use CompressRootFS 173 | b2.use SetupPackageFiles 174 | b2.use Vagrant::Action::General::Package 175 | end 176 | end 177 | end 178 | 179 | # This action is called to read the IP of the container. The IP found 180 | # is expected to be put into the `:machine_ip` key. 181 | def self.action_ssh_ip 182 | Builder.new.tap do |b| 183 | b.use Builtin::Call, Builtin::ConfigValidate do |env, b2| 184 | b2.use FetchIpWithLxcInfo 185 | end 186 | end 187 | end 188 | 189 | # This is the action that will exec into an SSH shell. 190 | def self.action_ssh 191 | Builder.new.tap do |b| 192 | b.use Builtin::ConfigValidate 193 | b.use Builtin::Call, Builtin::IsState, :not_created do |env, b2| 194 | if env[:result] 195 | b2.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_created") 196 | next 197 | end 198 | 199 | b2.use Builtin::Call, Builtin::IsState, :running do |env1, b3| 200 | if !env1[:result] 201 | b3.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_running") 202 | next 203 | end 204 | 205 | b3.use Builtin::SSHExec 206 | end 207 | end 208 | end 209 | end 210 | 211 | # This is the action that will run a single SSH command. 212 | def self.action_ssh_run 213 | Builder.new.tap do |b| 214 | b.use Builtin::ConfigValidate 215 | b.use Builtin::Call, Builtin::IsState, :not_created do |env, b2| 216 | if env[:result] 217 | b2.use Builtin::Message, I18n.t("vagrant_lxc.messages.not_created") 218 | next 219 | end 220 | 221 | b2.use Builtin::Call, Builtin::IsState, :running do |env1, b3| 222 | if !env1[:result] 223 | raise Vagrant::Errors::VMNotRunningError 224 | next 225 | end 226 | 227 | b3.use Builtin::SSHRun 228 | end 229 | end 230 | end 231 | end 232 | end 233 | end 234 | end 235 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/boot.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class Boot 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | @env = env 11 | driver = env[:machine].provider.driver 12 | config = env[:machine].provider_config 13 | 14 | utsname = env[:machine].config.vm.hostname || env[:machine].id 15 | if driver.supports_new_config_format 16 | config.customize 'uts.name', utsname 17 | else 18 | config.customize 'utsname', utsname 19 | end 20 | 21 | # Fix apparmor issues when starting Ubuntu 14.04 containers 22 | # See https://github.com/fgrehm/vagrant-lxc/issues/278 for more information 23 | if Dir.exists?('/sys/fs/pstore') 24 | config.customize 'mount.entry', '/sys/fs/pstore sys/fs/pstore none bind,optional 0 0' 25 | end 26 | 27 | # Make selinux read-only, see 28 | # https://github.com/fgrehm/vagrant-lxc/issues/301 29 | if Dir.exists?('/sys/fs/selinux') 30 | config.customize 'mount.entry', '/sys/fs/selinux sys/fs/selinux none bind,ro 0 0' 31 | end 32 | 33 | if config.tmpfs_mount_size && !config.tmpfs_mount_size.empty? 34 | # Make /tmp a tmpfs to prevent init scripts from nuking synced folders mounted in here 35 | config.customize 'mount.entry', "tmpfs tmp tmpfs nodev,nosuid,size=#{config.tmpfs_mount_size} 0 0" 36 | end 37 | 38 | env[:ui].info I18n.t("vagrant_lxc.messages.starting") 39 | driver.start(config.customizations) 40 | 41 | @app.call env 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/clear_forwarded_ports.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class ClearForwardedPorts 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new("vagrant::lxc::action::clear_forwarded_ports") 8 | end 9 | 10 | def call(env) 11 | @env = env 12 | 13 | if redir_pids.any? 14 | env[:ui].info I18n.t("vagrant.actions.vm.clear_forward_ports.deleting") 15 | redir_pids.each do |pid| 16 | next unless is_redir_pid?(pid[0]) 17 | @logger.debug "Killing pid #{pid[0]}" 18 | if pid[1] 19 | system "sudo pkill -TERM -P #{pid[0]}" 20 | else 21 | system "pkill -TERM -P #{pid[0]}" 22 | end 23 | end 24 | 25 | @logger.info "Removing redir pids files" 26 | remove_redir_pids 27 | else 28 | @logger.info "No redir pids found" 29 | end 30 | 31 | @app.call env 32 | end 33 | 34 | protected 35 | 36 | def redir_pids 37 | @redir_pids = Dir[@env[:machine].data_dir.join('pids').to_s + "/redir_*.pid"].map do |file| 38 | port_number = File.basename(file).split(/[^\d]/).join 39 | [ File.read(file).strip.chomp , Integer(port_number) <= 1024 ] 40 | end 41 | end 42 | 43 | def is_redir_pid?(pid) 44 | @logger.debug "Checking if #{pid} is a redir process with `ps -o cmd= #{pid}`" 45 | `ps -o cmd= #{pid}`.strip.chomp =~ /redir/ 46 | end 47 | 48 | def remove_redir_pids 49 | Dir[@env[:machine].data_dir.join('pids').to_s + "/redir_*.pid"].each do |file| 50 | File.delete file 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/compress_rootfs.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | 3 | module Vagrant 4 | module LXC 5 | module Action 6 | class CompressRootFS 7 | def initialize(app, env) 8 | @app = app 9 | end 10 | 11 | def call(env) 12 | raise Vagrant::Errors::VMPowerOffToPackage if env[:machine].provider.state.id != :stopped 13 | 14 | env[:ui].info I18n.t("vagrant.actions.lxc.compressing_rootfs") 15 | @rootfs = env['package.rootfs'] = env[:machine].provider.driver.compress_rootfs 16 | 17 | @app.call env 18 | 19 | recover # called to remove the rootfs tarball 20 | end 21 | 22 | def recover(*) 23 | if @rootfs && File.exist?(@rootfs) 24 | FileUtils.rm_rf(File.dirname @rootfs) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/create.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class Create 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | config = env[:machine].provider_config 11 | container_name = config.container_name 12 | 13 | case container_name 14 | when :machine 15 | container_name = env[:machine].name.to_s 16 | when String 17 | # Nothing to do here, move along... 18 | else 19 | container_name = generate_container_name(env) 20 | end 21 | 22 | backingstore = config.backingstore 23 | if backingstore.nil? 24 | backingstore = config.privileged ? "best" : "dir" 25 | end 26 | driver = env[:machine].provider.driver 27 | template_options = env[:lxc_template_opts] 28 | if driver.supports_new_config_format 29 | if env[:lxc_box_config] 30 | driver.update_config_keys(env[:lxc_box_config]) 31 | end 32 | else 33 | template_options['--oldconfig'] = '' 34 | end 35 | driver.create( 36 | container_name, 37 | backingstore, 38 | config.backingstore_options, 39 | env[:lxc_template_src], 40 | env[:lxc_template_config], 41 | template_options 42 | ) 43 | driver.update_config_keys 44 | 45 | env[:machine].id = container_name 46 | 47 | @app.call env 48 | end 49 | 50 | def generate_container_name(env) 51 | container_name = "#{env[:root_path].basename}_#{env[:machine].name}" 52 | container_name.gsub!(/[^-a-z0-9_]/i, "") 53 | 54 | # milliseconds + random number suffix to allow for simultaneous 55 | # `vagrant up` of the same box in different dirs 56 | container_name << "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}" 57 | 58 | # Trim container name to 64 chars, keeping "randomness" 59 | trim_point = container_name.size > 64 ? -64 : -(container_name.size) 60 | container_name[trim_point..-1] 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/destroy.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class Destroy 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info I18n.t("vagrant.actions.vm.destroy.destroying") 11 | env[:machine].provider.driver.destroy 12 | env[:machine].id = nil 13 | @app.call env 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/destroy_confirm.rb: -------------------------------------------------------------------------------- 1 | require "vagrant/action/builtin/confirm" 2 | 3 | module Vagrant 4 | module LXC 5 | module Action 6 | class DestroyConfirm < Vagrant::Action::Builtin::Confirm 7 | def initialize(app, env) 8 | force_key = :force_confirm_destroy 9 | message = I18n.t("vagrant.commands.destroy.confirmation", 10 | :name => env[:machine].name) 11 | 12 | super(app, env, message, force_key) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/fetch_ip_with_lxc_info.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class FetchIpWithLxcInfo 5 | # Include this so we can use `Subprocess` more easily. 6 | include Vagrant::Util::Retryable 7 | 8 | def initialize(app, env) 9 | @app = app 10 | @logger = Log4r::Logger.new("vagrant::lxc::action::fetch_ip_with_lxc_info") 11 | end 12 | 13 | def call(env) 14 | env[:machine_ip] ||= assigned_ip(env) 15 | ensure 16 | @app.call(env) 17 | end 18 | 19 | def assigned_ip(env) 20 | config = env[:machine].provider_config 21 | fetch_ip_tries = config.fetch_ip_tries 22 | driver = env[:machine].provider.driver 23 | ip = '' 24 | return config.ssh_ip_addr if not config.ssh_ip_addr.nil? 25 | retryable(:on => LXC::Errors::ExecuteError, :tries => fetch_ip_tries, :sleep => 3) do 26 | unless ip = get_container_ip_from_ip_addr(driver) 27 | # retry 28 | raise LXC::Errors::ExecuteError, :command => "lxc-info" 29 | end 30 | end 31 | ip 32 | end 33 | 34 | # From: https://github.com/lxc/lxc/blob/staging/src/python-lxc/lxc/__init__.py#L371-L385 35 | def get_container_ip_from_ip_addr(driver) 36 | output = driver.info '-iH' 37 | if output =~ /^([0-9.]+)/ 38 | return $1.to_s 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/forced_halt.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class ForcedHalt 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | if env[:machine].provider.state.id == :running 11 | env[:ui].info I18n.t("vagrant_lxc.messages.force_shutdown") 12 | env[:machine].provider.driver.forced_halt 13 | end 14 | 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/forward_ports.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | module Vagrant 4 | module LXC 5 | module Action 6 | class ForwardPorts 7 | def initialize(app, env) 8 | @app = app 9 | @logger = Log4r::Logger.new("vagrant::lxc::action::forward_ports") 10 | end 11 | 12 | def call(env) 13 | @env = env 14 | 15 | # Get the ports we're forwarding 16 | env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config) 17 | 18 | if @env[:forwarded_ports].any? and not redir_installed? 19 | raise Errors::RedirNotInstalled 20 | end 21 | 22 | # Warn if we're port forwarding to any privileged ports 23 | env[:forwarded_ports].each do |fp| 24 | if fp[:host] <= 1024 25 | env[:ui].warn I18n.t("vagrant.actions.vm.forward_ports.privileged_ports") 26 | break 27 | end 28 | end 29 | 30 | # Continue, we need the VM to be booted in order to grab its IP 31 | @app.call env 32 | 33 | if @env[:forwarded_ports].any? 34 | env[:ui].info I18n.t("vagrant.actions.vm.forward_ports.forwarding") 35 | forward_ports 36 | end 37 | end 38 | 39 | def forward_ports 40 | @env[:forwarded_ports].each do |fp| 41 | message_attributes = { 42 | # TODO: Add support for multiple adapters 43 | :adapter => 'eth0', 44 | :guest_port => fp[:guest], 45 | :host_port => fp[:host] 46 | } 47 | 48 | # TODO: Remove adapter from logging 49 | @env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.forwarding_entry", 50 | message_attributes)) 51 | 52 | redir_pid = redirect_port( 53 | fp[:host_ip], 54 | fp[:host], 55 | fp[:guest_ip] || @env[:machine].provider.ssh_info[:host], 56 | fp[:guest] 57 | ) 58 | store_redir_pid(fp[:host], redir_pid) 59 | end 60 | end 61 | 62 | private 63 | 64 | def compile_forwarded_ports(config) 65 | mappings = {} 66 | 67 | config.vm.networks.each do |type, options| 68 | next if options[:disabled] 69 | 70 | # TODO: Deprecate this behavior of "automagically" skipping ssh forwarded ports 71 | if type == :forwarded_port && options[:id] != 'ssh' 72 | if options.fetch(:host_ip, '').to_s.strip.empty? 73 | options[:host_ip] = '127.0.0.1' 74 | end 75 | mappings[options[:host]] = options 76 | end 77 | end 78 | 79 | mappings.values 80 | end 81 | 82 | def redirect_port(host_ip, host_port, guest_ip, guest_port) 83 | if redir_version >= 3 84 | params = %W( -n #{host_ip}:#{host_port} #{guest_ip}:#{guest_port} ) 85 | else 86 | params = %W( --lport=#{host_port} --caddr=#{guest_ip} --cport=#{guest_port} ) 87 | params.unshift "--laddr=#{host_ip}" if host_ip 88 | end 89 | params << '--syslog' if ENV['REDIR_LOG'] 90 | if host_port < 1024 91 | redir_cmd = "sudo redir #{params.join(' ')} 2>/dev/null" 92 | else 93 | redir_cmd = "redir #{params.join(' ')} 2>/dev/null" 94 | end 95 | @logger.debug "Forwarding port with `#{redir_cmd}`" 96 | spawn redir_cmd 97 | end 98 | 99 | def store_redir_pid(host_port, redir_pid) 100 | data_dir = @env[:machine].data_dir.join('pids') 101 | data_dir.mkdir unless data_dir.directory? 102 | 103 | data_dir.join("redir_#{host_port}.pid").open('w') do |pid_file| 104 | pid_file.write(redir_pid) 105 | end 106 | end 107 | 108 | def redir_version 109 | stdout, stderr, _ = Open3.capture3 "redir --version" 110 | # For some weird reason redir printed version information in STDERR prior to 3.2 111 | version = stdout.empty? ? stderr : stdout 112 | version.split('.')[0].to_i 113 | end 114 | 115 | def redir_installed? 116 | system "which redir > /dev/null" 117 | end 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/gc_private_network_bridges.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class GcPrivateNetworkBridges 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | was_running = env[:machine].provider.state.id == :running 11 | 12 | # Continue execution, we need the container to be stopped 13 | @app.call(env) 14 | 15 | was_running = was_running && env[:machine].provider.state.id != :running 16 | 17 | if was_running && private_network_configured?(env[:machine].config) 18 | private_network_configured?(env[:machine].config) 19 | remove_bridges_that_are_not_in_use(env) 20 | end 21 | end 22 | 23 | def private_network_configured?(config) 24 | config.vm.networks.find do |type, _| 25 | type.to_sym == :private_network 26 | end 27 | end 28 | 29 | def remove_bridges_that_are_not_in_use(env) 30 | env[:machine].config.vm.networks.find do |type, config| 31 | next if type.to_sym != :private_network 32 | 33 | bridge = config.fetch(:lxc__bridge_name) 34 | driver = env[:machine].provider.driver 35 | 36 | if ! driver.bridge_is_in_use?(bridge) 37 | env[:ui].info I18n.t("vagrant_lxc.messages.remove_bridge", name: bridge) 38 | unless ['lxcbr0', 'virbr0'].include? bridge 39 | driver.remove_bridge(bridge) 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/handle_box_metadata.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | # Prepare arguments to be used for lxc-create 5 | class HandleBoxMetadata 6 | SUPPORTED_VERSIONS = ['1.0.0', '2', '3'] 7 | 8 | def initialize(app, env) 9 | @app = app 10 | @logger = Log4r::Logger.new("vagrant::lxc::action::handle_box_metadata") 11 | end 12 | 13 | def call(env) 14 | @env = env 15 | @box = @env[:machine].box 16 | 17 | @env[:ui].info I18n.t("vagrant.actions.vm.import.importing", 18 | :name => @env[:machine].box.name) 19 | 20 | @logger.info 'Validating box contents' 21 | validate_box 22 | 23 | @logger.info 'Setting box options on environment' 24 | @env[:lxc_template_src] = template_src 25 | @env[:lxc_template_opts] = template_opts 26 | 27 | # FIXME: Remove support for pre 1.0.0 boxes 28 | if box_version != '1.0.0' 29 | @env[:ui].warn "WARNING: You are using a base box that has a format that has been deprecated, please upgrade to a new one." 30 | @env[:lxc_template_opts].merge!( 31 | '--auth-key' => Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s 32 | ) 33 | end 34 | 35 | if template_config_file.exist? 36 | @env[:lxc_box_config] = template_config_file.to_s 37 | @env[:lxc_template_opts].merge!('--config' => template_config_file.to_s) 38 | elsif old_template_config_file.exist? 39 | @env[:lxc_box_config] = old_template_config_file.to_s 40 | @env[:lxc_template_config] = old_template_config_file.to_s 41 | end 42 | 43 | @app.call env 44 | end 45 | 46 | def template_src 47 | @template_src ||= 48 | if (box_template = @box.directory.join('lxc-template')).exist? 49 | box_template.to_s 50 | else 51 | Vagrant::LXC.source_root.join('scripts/lxc-template').to_s 52 | end 53 | end 54 | 55 | def template_config_file 56 | @template_config_file ||= @box.directory.join('lxc-config') 57 | end 58 | 59 | # TODO: Remove this once we remove compatibility for < 1.0.0 boxes 60 | def old_template_config_file 61 | @old_template_config_file ||= @box.directory.join('lxc.conf') 62 | end 63 | 64 | def template_opts 65 | @template_opts ||= @box.metadata.fetch('template-opts', {}).dup.merge!( 66 | '--tarball' => rootfs_tarball 67 | ) 68 | end 69 | 70 | def rootfs_tarball 71 | @rootfs_tarball ||= @box.directory.join('rootfs.tar.gz').to_s 72 | end 73 | 74 | def validate_box 75 | unless SUPPORTED_VERSIONS.include? box_version 76 | raise Errors::IncompatibleBox.new name: @box.name, 77 | found: box_version, 78 | supported: SUPPORTED_VERSIONS.join(', ') 79 | end 80 | 81 | unless File.exists?(template_src) 82 | raise Errors::TemplateFileMissing.new name: @box.name 83 | end 84 | 85 | unless File.exists?(rootfs_tarball) 86 | raise Errors::RootFSTarballMissing.new name: @box.name 87 | end 88 | end 89 | 90 | def box_version 91 | @box.metadata.fetch('version') 92 | end 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/prepare_nfs_settings.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class PrepareNFSSettings 5 | include Vagrant::Util::Retryable 6 | 7 | def initialize(app, env) 8 | @app = app 9 | @logger = Log4r::Logger.new("vagrant::action::vm::nfs") 10 | end 11 | 12 | def call(env) 13 | @machine = env[:machine] 14 | 15 | @app.call(env) 16 | 17 | # if using_nfs? # TODO: && !privileged_container? 18 | # raise Errors::NfsWithoutPrivilegedError 19 | # end 20 | 21 | if using_nfs? 22 | @logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP") 23 | add_ips_to_env!(env) 24 | end 25 | end 26 | 27 | # We're using NFS if we have any synced folder with NFS configured. If 28 | # we are not using NFS we don't need to do the extra work to 29 | # populate these fields in the environment. 30 | def using_nfs? 31 | @machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs } 32 | end 33 | 34 | # TODO: 35 | # def privileged_container? 36 | # @machine.provider.driver.privileged?(@machine.id) 37 | # end 38 | 39 | # Extracts the proper host and guest IPs for NFS mounts and stores them 40 | # in the environment for the SyncedFolder action to use them in 41 | # mounting. 42 | # 43 | # The ! indicates that this method modifies its argument. 44 | def add_ips_to_env!(env) 45 | provider = @machine.provider 46 | 47 | host_ip = read_host_ip 48 | machine_ip = provider.ssh_info[:host] 49 | 50 | raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip 51 | 52 | env[:nfs_host_ip] = host_ip 53 | env[:nfs_machine_ip] = machine_ip 54 | end 55 | 56 | def read_host_ip 57 | @machine.communicate.execute 'echo $SSH_CLIENT' do |buffer, output| 58 | return output.chomp.split(' ')[0] if buffer == :stdout 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/prepare_nfs_valid_ids.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class PrepareNFSValidIds 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new("vagrant::action::vm::nfs") 8 | end 9 | 10 | def call(env) 11 | machine = env[:machine] 12 | env[:nfs_valid_ids] = machine.provider.driver.all_containers 13 | 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/private_networks.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class PrivateNetworks 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | @app.call(env) 11 | 12 | if private_network_configured?(env[:machine].config) 13 | env[:ui].output(I18n.t("vagrant_lxc.messages.setup_private_network")) 14 | configure_private_networks(env) 15 | end 16 | end 17 | 18 | def private_network_configured?(config) 19 | config.vm.networks.find do |type, _| 20 | type.to_sym == :private_network 21 | end 22 | end 23 | 24 | def configure_private_networks(env) 25 | env[:machine].config.vm.networks.find do |type, config| 26 | next if type.to_sym != :private_network 27 | 28 | container_name = env[:machine].provider.driver.container_name 29 | address_type = config[:type] 30 | ip = config[:ip] 31 | bridge_ip = config.fetch(:lxc__bridge_ip) { build_bridge_ip(ip) } 32 | bridge = config.fetch(:lxc__bridge_name) 33 | 34 | env[:machine].provider.driver.configure_private_network(bridge, bridge_ip, container_name, address_type, ip) 35 | end 36 | end 37 | 38 | def build_bridge_ip(ip) 39 | if ip 40 | ip.sub(/^(\d+\.\d+\.\d+)\.\d+/, '\1.254') 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/setup_package_files.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module Vagrant 4 | module LXC 5 | module Action 6 | class SetupPackageFiles 7 | def initialize(app, env) 8 | @app = app 9 | 10 | env["package.include"] ||= [] 11 | env["package.vagrantfile"] ||= nil 12 | end 13 | 14 | def call(env) 15 | @env = env 16 | 17 | create_package_temp_dir 18 | move_rootfs_to_pkg_dir 19 | copy_box_files_to_pkg_dir 20 | 21 | @app.call env 22 | 23 | recover # called to cleanup temp directory 24 | end 25 | 26 | def recover(*) 27 | if @temp_dir && File.exist?(@temp_dir) 28 | FileUtils.rm_rf(@temp_dir) 29 | end 30 | end 31 | 32 | private 33 | 34 | def create_package_temp_dir 35 | @env[:ui].info I18n.t("vagrant.actions.vm.export.create_dir") 36 | @temp_dir = @env["package.directory"] = @env[:tmp_path].join("container-export-#{Time.now.to_i.to_s}") 37 | FileUtils.mkpath(@temp_dir) 38 | end 39 | 40 | def move_rootfs_to_pkg_dir 41 | FileUtils.mv @env['package.rootfs'].to_s, @env['package.directory'].to_s 42 | end 43 | 44 | def copy_box_files_to_pkg_dir 45 | box_dir = @env[:machine].box.directory 46 | FileUtils.cp box_dir.join('metadata.json').to_s, @env['package.directory'].to_s 47 | if (template = box_dir.join('lxc-template')).exist? 48 | FileUtils.cp template.to_s, @env['package.directory'].to_s 49 | end 50 | if (conf = box_dir.join('lxc.conf')).exist? 51 | FileUtils.cp conf.to_s, @env['package.directory'].to_s 52 | end 53 | if (conf = box_dir.join('lxc-config')).exist? 54 | FileUtils.cp conf.to_s, @env['package.directory'].to_s 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/action/warn_networks.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Action 4 | class WarnNetworks 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | if public_network_configured?(env[:machine].config) 11 | env[:ui].warn(I18n.t("vagrant_lxc.messages.warn_networks")) 12 | end 13 | 14 | @app.call(env) 15 | end 16 | 17 | def public_network_configured?(config) 18 | config.vm.networks.find do |type, _| 19 | type.to_sym == :public_network 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/command/root.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | module Command 4 | class Root < Vagrant.plugin("2", :command) 5 | def self.synopsis 6 | 'vagrant-lxc specific commands' 7 | end 8 | 9 | def initialize(argv, env) 10 | @args, @sub_command, @sub_args = split_main_and_subcommand(argv) 11 | @subcommands = Vagrant::Registry.new.tap do |registry| 12 | registry.register(:sudoers) do 13 | require_relative 'sudoers' 14 | Sudoers 15 | end 16 | end 17 | super(argv, env) 18 | end 19 | 20 | def execute 21 | # Print the help 22 | return help if @args.include?("-h") || @args.include?("--help") 23 | 24 | klazz = @subcommands.get(@sub_command.to_sym) if @sub_command 25 | return help unless klazz 26 | 27 | @logger.debug("Executing command: #{klazz} #{@sub_args.inspect}") 28 | 29 | # Initialize and execute the command class 30 | klazz.new(@sub_args, @env).execute 31 | end 32 | 33 | def help 34 | opts = OptionParser.new do |opts| 35 | opts.banner = "Usage: vagrant lxc []" 36 | opts.separator "" 37 | opts.separator "Available subcommands:" 38 | 39 | # REFACTOR Use @subcommands.keys.sort 40 | # https://github.com/mitchellh/vagrant/commit/4194da19c60956f6e59239c0145f772be257e79d 41 | keys = [] 42 | @subcommands.each { |key, value| keys << key } 43 | 44 | keys.sort.each do |key| 45 | opts.separator " #{key}" 46 | end 47 | 48 | opts.separator "" 49 | opts.separator "For help on any individual subcommand run `vagrant lxc -h`" 50 | end 51 | 52 | @env.ui.info(opts.help, :prefix => false) 53 | end 54 | 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/command/sudoers.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | require "vagrant-lxc/driver" 4 | require "vagrant-lxc/sudo_wrapper" 5 | 6 | module Vagrant 7 | module LXC 8 | module Command 9 | class Sudoers < Vagrant.plugin("2", :command) 10 | 11 | def initialize(argv, env) 12 | super 13 | @argv 14 | @env = env 15 | end 16 | 17 | def execute 18 | options = { user: ENV['USER'] } 19 | 20 | opts = OptionParser.new do |opts| 21 | opts.banner = "Usage: vagrant lxc sudoers" 22 | opts.separator "" 23 | opts.on('-u user', '--user user', String, "The user for which to create the policy (defaults to '#{options[:user]}')") do |u| 24 | options[:user] = u 25 | end 26 | end 27 | 28 | argv = parse_options(opts) 29 | return unless argv 30 | 31 | wrapper_path = SudoWrapper.dest_path 32 | wrapper = create_wrapper! 33 | sudoers = create_sudoers!(options[:user], wrapper_path) 34 | 35 | su_copy([ 36 | {source: wrapper, target: wrapper_path, mode: "0555"}, 37 | {source: sudoers, target: sudoers_path, mode: "0440"} 38 | ]) 39 | end 40 | 41 | def sudoers_path 42 | "/etc/sudoers.d/vagrant-lxc" 43 | end 44 | 45 | private 46 | 47 | # This requires vagrant 1.5.2+ https://github.com/mitchellh/vagrant/commit/3371c3716278071680af9b526ba19235c79c64cb 48 | def create_wrapper! 49 | lxc_base_path = Driver.new("").containers_path 50 | wrapper = Tempfile.new('lxc-wrapper').tap do |file| 51 | template = Vagrant::Util::TemplateRenderer.new( 52 | 'sudoers.rb', 53 | :template_root => Vagrant::LXC.source_root.join('templates').to_s, 54 | :cmd_paths => build_cmd_paths_hash, 55 | :lxc_base_path => lxc_base_path, 56 | :pipework_regex => "#{ENV['HOME']}/\.vagrant\.d/gems/(?:\\d+?\\.\\d+?\\.\\d+?/)?gems/vagrant-lxc.+/scripts/pipework" 57 | ) 58 | file.puts template.render 59 | end 60 | wrapper.close 61 | wrapper.path 62 | end 63 | 64 | def create_sudoers!(user, command) 65 | sudoers = Tempfile.new('vagrant-lxc-sudoers').tap do |file| 66 | file.puts "# Automatically created by vagrant-lxc" 67 | file.puts "#{user} ALL=(root) NOPASSWD: #{command}" 68 | end 69 | sudoers.close 70 | sudoers.path 71 | end 72 | 73 | def su_copy(files) 74 | commands = files.map { |file| 75 | [ 76 | "rm -f #{file[:target]}", 77 | "cp #{file[:source]} #{file[:target]}", 78 | "chown root:root #{file[:target]}", 79 | "chmod #{file[:mode]} #{file[:target]}" 80 | ] 81 | }.flatten 82 | system "echo \"#{commands.join("; ")}\" | sudo sh" 83 | end 84 | 85 | def build_cmd_paths_hash 86 | {}.tap do |hash| 87 | %w( which cat mkdir cp chown chmod rm tar chown ip ifconfig brctl ).each do |cmd| 88 | hash[cmd] = `sudo which #{cmd}`.strip 89 | end 90 | hash['lxc_bin'] = Pathname(`sudo which lxc-create`.strip).parent.to_s 91 | hash['ruby'] = Gem.ruby 92 | end 93 | end 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/config.rb: -------------------------------------------------------------------------------- 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 | attr_accessor :ssh_ip_addr 28 | 29 | # Whether the container needs to be privileged. Defaults to true (unprivileged containers 30 | # is a very new feature in vagrant-lxc). If false, will try creating an unprivileged 31 | # container. If it can't, will revert to the old "sudo wrapper" method to create a privileged 32 | # container. 33 | attr_accessor :privileged 34 | 35 | def initialize 36 | @customizations = [] 37 | @backingstore = UNSET_VALUE 38 | @backingstore_options = [] 39 | @container_name = UNSET_VALUE 40 | @tmpfs_mount_size = UNSET_VALUE 41 | @fetch_ip_tries = UNSET_VALUE 42 | @ssh_ip_addr = UNSET_VALUE 43 | @privileged = UNSET_VALUE 44 | end 45 | 46 | # Customize the container by calling `lxc-start` with the given 47 | # configuration overrides. 48 | # 49 | # For example, if you want to set the memory limit, you can use it 50 | # like: config.customize 'cgroup.memory.limit_in_bytes', '400M' 51 | # 52 | # When `lxc-start`ing the container, vagrant-lxc will pass in 53 | # "-s lxc.cgroup.memory.limit_in_bytes=400M" to it. 54 | # 55 | # @param [String] key Configuration key to override 56 | # @param [String] value Configuration value to override 57 | def customize(key, value) 58 | @customizations << [key, value] 59 | end 60 | 61 | # Stores options for backingstores like lvm, btrfs, etc 62 | def backingstore_option(key, value) 63 | @backingstore_options << [key, value] 64 | end 65 | 66 | def finalize! 67 | @container_name = nil if @container_name == UNSET_VALUE 68 | @backingstore = nil if @backingstore == UNSET_VALUE 69 | @existing_container_name = nil if @existing_container_name == UNSET_VALUE 70 | @tmpfs_mount_size = '2G' if @tmpfs_mount_size == UNSET_VALUE 71 | @fetch_ip_tries = 10 if @fetch_ip_tries == UNSET_VALUE 72 | @ssh_ip_addr = nil if @ssh_ip_addr == UNSET_VALUE 73 | @privileged = true if @privileged == UNSET_VALUE 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/driver.rb: -------------------------------------------------------------------------------- 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 | pathtype, path = config_string.match(/^lxc\.rootfs(?:\.path)?\s+=\s+(.+:)?(.+)$/)[1..2] 54 | case pathtype 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 | _, overlay_path = config_entry.split(/(?/dev/null | tail -n +2 | grep -q veth` 194 | $?.to_i == 0 195 | end 196 | 197 | def remove_bridge(bridge_name) 198 | if ['lxcbr0', 'virbr0'].include? bridge_name 199 | @logger.info "Skipping removal of system bridge #{bridge_name}" 200 | return 201 | end 202 | 203 | return unless bridge_exists?(bridge_name) 204 | 205 | @logger.info "Removing bridge #{bridge_name}" 206 | @sudo_wrapper.run('ip', 'link', 'set', bridge_name, 'down') 207 | @sudo_wrapper.run('brctl', 'delbr', bridge_name) 208 | end 209 | 210 | def version 211 | @version ||= @cli.version 212 | end 213 | 214 | def supports_new_config_format 215 | Gem::Version.new(version) >= Gem::Version.new('2.1.0') 216 | end 217 | 218 | # TODO: This needs to be reviewed and specs needs to be written 219 | def compress_rootfs 220 | # TODO: Pass in tmpdir so we can clean up from outside 221 | target_path = "#{Dir.mktmpdir}/rootfs.tar.gz" 222 | 223 | @logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}" 224 | @sudo_wrapper.run('tar', '--numeric-owner', '-cvzf', target_path, '-C', 225 | rootfs_path.parent.to_s, "./#{rootfs_path.basename.to_s}") 226 | 227 | @logger.info "Changing rootfs tarball owner" 228 | user_details = Etc.getpwnam(Etc.getlogin) 229 | @sudo_wrapper.run('chown', "#{user_details.uid}:#{user_details.gid}", target_path) 230 | 231 | target_path 232 | end 233 | 234 | def state 235 | if @container_name 236 | @cli.state 237 | end 238 | end 239 | 240 | def prune_customizations 241 | # Use sed to just strip out the block of code which was inserted by Vagrant 242 | @logger.debug 'Prunning vagrant-lxc customizations' 243 | contents = config_string 244 | contents.gsub! /^# VAGRANT-BEGIN(.|\s)*# VAGRANT-END\n/, '' 245 | write_config(contents) 246 | end 247 | 248 | def update_config_keys(path = nil) 249 | path = path || config_path 250 | @cli.update_config(path) 251 | rescue Errors::ExecuteError 252 | # not on LXC 2.1+. Doesn't matter, ignore. 253 | end 254 | 255 | protected 256 | 257 | def write_customizations(customizations) 258 | customizations = customizations.map do |key, value| 259 | "lxc.#{key}=#{value}" 260 | end 261 | customizations.unshift '# VAGRANT-BEGIN' 262 | customizations << "# VAGRANT-END\n" 263 | 264 | contents = config_string 265 | contents << customizations.join("\n") 266 | 267 | write_config(contents) 268 | end 269 | 270 | def write_config(contents) 271 | confpath = base_path.join('config').to_s 272 | begin 273 | File.open(confpath, File::RDWR) do |file| 274 | file.write contents 275 | end 276 | rescue 277 | # We don't have permissions to write in the conf file. That's probably because it's a 278 | # privileged container. Work around that through sudo_wrapper. 279 | Tempfile.new('lxc-config').tap do |file| 280 | file.chmod 0644 281 | file.write contents 282 | file.close 283 | @sudo_wrapper.run 'cp', '-f', file.path, confpath 284 | @sudo_wrapper.run 'chown', 'root:root', confpath 285 | end 286 | end 287 | end 288 | end 289 | end 290 | end 291 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/driver/cli.rb: -------------------------------------------------------------------------------- 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.reject { |elem| elem.empty? } 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 | begin 93 | run :stop, '--name', @name 94 | rescue LXC::Errors::ExecuteError => e 95 | if e.exitcode == 2 96 | @logger.debug "Machine already stopped, lxc-stop returned 2" 97 | else 98 | raise e 99 | end 100 | end 101 | end 102 | 103 | def attach(*cmd) 104 | cmd = ['--'] + cmd 105 | 106 | if cmd.last.is_a?(Hash) 107 | opts = cmd.pop 108 | namespaces = Array(opts[:namespaces]).map(&:upcase).join('|') 109 | 110 | # HACK: The wrapper script should be able to handle this 111 | if @sudo_wrapper.wrapper_path 112 | namespaces = "'#{namespaces}'" 113 | end 114 | 115 | if namespaces 116 | extra = ['--namespaces', namespaces] 117 | end 118 | end 119 | 120 | run :attach, '--name', @name, *((extra || []) + cmd) 121 | end 122 | 123 | def info(*cmd) 124 | run(:info, '--name', @name, *cmd) 125 | end 126 | 127 | def transition_to(target_state, tries = 30, timeout = 1, &block) 128 | raise TransitionBlockNotProvided unless block_given? 129 | 130 | yield self 131 | 132 | while (last_state = self.state) != target_state && tries > 0 133 | @logger.debug "Target state '#{target_state}' not reached, currently on '#{last_state}'" 134 | sleep timeout 135 | tries -= 1 136 | end 137 | 138 | unless last_state == target_state 139 | # TODO: Raise an user friendly message 140 | raise TargetStateNotReached.new target_state, last_state 141 | end 142 | end 143 | 144 | private 145 | 146 | def run(command, *args) 147 | @sudo_wrapper.run("lxc-#{command}", *args) 148 | end 149 | end 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/errors.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant/errors' 2 | 3 | module Vagrant 4 | module LXC 5 | module Errors 6 | class ExecuteError < Vagrant::Errors::VagrantError 7 | error_key(:lxc_execute_error) 8 | attr_reader :stderr, :stdout, :exitcode 9 | def initialize(message, *args) 10 | super 11 | if message.is_a?(Hash) 12 | @stderr = message[:stderr] 13 | @stdout = message[:stdout] 14 | @exitcode = message[:exitcode] 15 | end 16 | end 17 | end 18 | 19 | # Raised when user interrupts a subprocess 20 | class SubprocessInterruptError < Vagrant::Errors::VagrantError 21 | error_key(:lxc_interrupt_error) 22 | def initialize(message, *args) 23 | super 24 | end 25 | end 26 | 27 | 28 | class LxcLinuxRequired < Vagrant::Errors::VagrantError 29 | error_key(:lxc_linux_required) 30 | end 31 | 32 | class LxcNotInstalled < Vagrant::Errors::VagrantError 33 | error_key(:lxc_not_installed) 34 | end 35 | 36 | class ContainerAlreadyExists < Vagrant::Errors::VagrantError 37 | error_key(:lxc_container_already_exists) 38 | end 39 | 40 | class CommandNotSupported < Vagrant::Errors::VagrantError 41 | error_key(:lxc_command_not_supported) 42 | end 43 | 44 | # Box related errors 45 | class TemplateFileMissing < Vagrant::Errors::VagrantError 46 | error_key(:lxc_template_file_missing) 47 | end 48 | class TemplatesDirMissing < Vagrant::Errors::VagrantError 49 | error_key(:lxc_templates_dir_missing) 50 | end 51 | class RootFSTarballMissing < Vagrant::Errors::VagrantError 52 | error_key(:lxc_invalid_box_version) 53 | end 54 | class IncompatibleBox < Vagrant::Errors::VagrantError 55 | error_key(:lxc_incompatible_box) 56 | end 57 | class RedirNotInstalled < Vagrant::Errors::VagrantError 58 | error_key(:lxc_redir_not_installed) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/plugin.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant' 2 | 3 | module Vagrant 4 | module LXC 5 | class Plugin < Vagrant.plugin("2") 6 | name "vagrant-lxc" 7 | description <<-EOF 8 | The LXC provider allows Vagrant to manage and control 9 | LXC-based virtual machines. 10 | EOF 11 | 12 | provider(:lxc, parallel: true, priority: 7) do 13 | require_relative 'provider' 14 | init! 15 | Provider 16 | end 17 | 18 | command "lxc" do 19 | require_relative 'command/root' 20 | init! 21 | Command::Root 22 | end 23 | 24 | config(:lxc, :provider) do 25 | require_relative 'config' 26 | init! 27 | Config 28 | end 29 | 30 | synced_folder(:lxc) do 31 | require_relative 'synced_folder' 32 | SyncedFolder 33 | end 34 | 35 | provider_capability("lxc", "public_address") do 36 | require_relative "provider/cap/public_address" 37 | Provider::Cap::PublicAddress 38 | end 39 | 40 | protected 41 | 42 | def self.init! 43 | return if defined?(@_init) 44 | I18n.load_path << File.expand_path(File.dirname(__FILE__) + '/../../locales/en.yml') 45 | I18n.reload! 46 | @_init = true 47 | end 48 | 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/provider.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | require "vagrant-lxc/action" 4 | require "vagrant-lxc/driver" 5 | 6 | module Vagrant 7 | module LXC 8 | class Provider < Vagrant.plugin("2", :provider) 9 | attr_reader :driver 10 | 11 | def self.usable?(raise_error=false) 12 | if !Vagrant::Util::Platform.linux? 13 | raise Errors::LxcLinuxRequired 14 | end 15 | 16 | true 17 | end 18 | 19 | def initialize(machine) 20 | @logger = Log4r::Logger.new("vagrant::provider::lxc") 21 | @machine = machine 22 | 23 | ensure_lxc_installed! 24 | machine_id_changed 25 | end 26 | 27 | def ensure_lxc_installed! 28 | begin 29 | SudoWrapper.new(privileged: @machine.provider_config.privileged).run("which", "lxc-create") 30 | rescue Vagrant::LXC::Errors::ExecuteError 31 | raise Errors::LxcNotInstalled 32 | end 33 | end 34 | 35 | # If the machine ID changed, then we need to rebuild our underlying 36 | # container. 37 | def machine_id_changed 38 | id = @machine.id 39 | 40 | begin 41 | @logger.debug("Instantiating the container for: #{id.inspect}") 42 | @driver = Driver.new(id, privileged: @machine.provider_config.privileged) 43 | @driver.validate! 44 | rescue Driver::ContainerNotFound 45 | # The container doesn't exist, so we probably have a stale 46 | # ID. Just clear the id out of the machine and reload it. 47 | @logger.debug("Container not found! Clearing saved machine ID and reloading.") 48 | id = nil 49 | retry 50 | end 51 | end 52 | 53 | # @see Vagrant::Plugin::V2::Provider#action 54 | def action(name) 55 | # Attempt to get the action method from the Action class if it 56 | # exists, otherwise return nil to show that we don't support the 57 | # given action. 58 | action_method = "action_#{name}" 59 | return LXC::Action.send(action_method) if LXC::Action.respond_to?(action_method) 60 | nil 61 | end 62 | 63 | # Returns the SSH info for accessing the Container. 64 | def ssh_info 65 | # If the Container is not running then we cannot possibly SSH into it, so 66 | # we return nil. 67 | return nil if state.id != :running 68 | 69 | # Run a custom action called "ssh_ip" which does what it says and puts 70 | # the IP found into the `:machine_ip` key in the environment. 71 | env = @machine.action("ssh_ip") 72 | 73 | # If we were not able to identify the container's IP, we return nil 74 | # here and we let Vagrant core deal with it ;) 75 | return nil unless env[:machine_ip] 76 | 77 | { 78 | :host => env[:machine_ip], 79 | :port => @machine.config.ssh.guest_port 80 | } 81 | end 82 | 83 | def state 84 | state_id = nil 85 | state_id = :not_created if !@driver.container_name 86 | state_id = @driver.state if !state_id 87 | state_id = :unknown if !state_id 88 | 89 | short = state_id.to_s.gsub("_", " ") 90 | long = I18n.t("vagrant.commands.status.#{state_id}") 91 | 92 | Vagrant::MachineState.new(state_id, short, long) 93 | end 94 | 95 | def to_s 96 | id = @machine.id ? @machine.id : "new VM" 97 | "LXC (#{id})" 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/provider/cap/public_address.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | class Provider 4 | module Cap 5 | module PublicAddress 6 | def self.public_address(machine) 7 | return nil if machine.state.id != :running 8 | 9 | ssh_info = machine.ssh_info 10 | return nil if !ssh_info 11 | ssh_info[:host] 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/sudo_wrapper.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | class SudoWrapper 4 | # Include this so we can use `Subprocess` more easily. 5 | include Vagrant::Util::Retryable 6 | 7 | attr_reader :wrapper_path 8 | 9 | def self.dest_path 10 | "/usr/local/bin/vagrant-lxc-wrapper" 11 | end 12 | 13 | def initialize(privileged: true) 14 | @wrapper_path = Pathname.new(SudoWrapper.dest_path).exist? && SudoWrapper.dest_path || nil 15 | @privileged = privileged 16 | @logger = Log4r::Logger.new("vagrant::lxc::sudo_wrapper") 17 | end 18 | 19 | def run(*command) 20 | options = command.last.is_a?(Hash) ? command.last : {} 21 | 22 | # Avoid running LXC commands with a restrictive umask. 23 | # Otherwise disasters occur, like the container root directory 24 | # having permissions `rwxr-x---` which prevents the `vagrant` 25 | # user from accessing its own home directory; among other 26 | # problems, SSH cannot then read `authorized_keys`! 27 | old_mask = File.umask 28 | File.umask(old_mask & 022) # allow all `r` and `x` bits 29 | 30 | begin 31 | if @privileged 32 | if @wrapper_path && !options[:no_wrapper] 33 | command.unshift @wrapper_path 34 | execute *(['sudo'] + command) 35 | else 36 | execute *(['sudo', '/usr/bin/env'] + command) 37 | end 38 | else 39 | execute *(['/usr/bin/env'] + command) 40 | end 41 | ensure 42 | File.umask(old_mask) 43 | end 44 | end 45 | 46 | private 47 | 48 | # TODO: Review code below this line, it was pretty much a copy and 49 | # paste from VirtualBox base driver and has no tests 50 | def execute(*command, &block) 51 | # Get the options hash if it exists 52 | opts = {} 53 | opts = command.pop if command.last.is_a?(Hash) 54 | 55 | tries = 0 56 | tries = 3 if opts[:retryable] 57 | 58 | sleep = opts.fetch(:sleep, 1) 59 | 60 | # Variable to store our execution result 61 | r = nil 62 | 63 | retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => sleep) do 64 | # Execute the command 65 | r = raw(*command, &block) 66 | 67 | # If the command was a failure, then raise an exception that is 68 | # nicely handled by Vagrant. 69 | if r.exit_code != 0 70 | if @interrupted 71 | raise LXC::Errors::SubprocessInterruptError, command.inspect 72 | else 73 | raise LXC::Errors::ExecuteError, 74 | command: command.inspect, stderr: r.stderr, stdout: r.stdout, exitcode: r.exit_code 75 | end 76 | end 77 | end 78 | 79 | # Return the output, making sure to replace any Windows-style 80 | # newlines with Unix-style. 81 | stdout = r.stdout.gsub("\r\n", "\n") 82 | if opts[:show_stderr] 83 | { :stdout => stdout, :stderr => r.stderr.gsub("\r\n", "\n") } 84 | else 85 | stdout 86 | end 87 | end 88 | 89 | def raw(*command, &block) 90 | int_callback = lambda do 91 | @interrupted = true 92 | @logger.info("Interrupted.") 93 | end 94 | 95 | # Append in the options for subprocess 96 | command << { :notify => [:stdout, :stderr] } 97 | 98 | Vagrant::Util::Busy.busy(int_callback) do 99 | Vagrant::Util::Subprocess.execute(*command, &block) 100 | end 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/synced_folder.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | class SyncedFolder < Vagrant.plugin("2", :synced_folder) 4 | def usable?(machine) 5 | # These synced folders only work if the provider is LXC 6 | machine.provider_name == :lxc 7 | end 8 | 9 | def prepare(machine, folders, _opts) 10 | machine.ui.output(I18n.t("vagrant.actions.lxc.share_folders.preparing")) 11 | # short guestpaths first, so we don't step on ourselves 12 | folders = folders.sort_by do |id, data| 13 | if data[:guestpath] 14 | data[:guestpath].length 15 | else 16 | # A long enough path to just do this at the end. 17 | 10000 18 | end 19 | end 20 | 21 | folders.each do |id, data| 22 | host_path = Pathname.new(File.expand_path(data[:hostpath], machine.env.root_path)) 23 | guest_path = data[:guestpath] 24 | 25 | machine.env.ui.warn(I18n.t("vagrant_lxc.messages.warn_owner")) if data[:owner] 26 | machine.env.ui.warn(I18n.t("vagrant_lxc.messages.warn_group")) if data[:group] 27 | 28 | if !host_path.directory? && data[:create] 29 | # Host path doesn't exist, so let's create it. 30 | @logger.info("Host path doesn't exist, creating: #{host_path}") 31 | 32 | begin 33 | host_path.mkpath 34 | rescue Errno::EACCES 35 | raise Vagrant::Errors::SharedFolderCreateFailed, 36 | :path => hostpath.to_s 37 | end 38 | end 39 | 40 | mount_opts = data[:mount_options] 41 | machine.provider.driver.share_folder(host_path, guest_path, mount_opts) 42 | # Guest path specified, so mount the folder to specified point 43 | machine.ui.detail(I18n.t("vagrant.actions.vm.share_folders.mounting_entry", 44 | guestpath: data[:guestpath], 45 | hostpath: data[:hostpath], 46 | guest_path: data[:guestpath])) 47 | end 48 | end 49 | 50 | def enable(machine, folders, _opts) 51 | # Emit an upstart event if we can 52 | return unless machine.communicate.test("test -x /sbin/initctl") 53 | 54 | # short guestpaths first, so we don't step on ourselves 55 | folders = folders.sort_by do |id, data| 56 | if data[:guestpath] 57 | data[:guestpath].length 58 | else 59 | # A long enough path to just do this at the end. 60 | 10000 61 | end 62 | end 63 | 64 | folders.each do |id, data| 65 | guest_path = data[:guestpath] 66 | machine.communicate.sudo( 67 | "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path}") 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/vagrant-lxc/version.rb: -------------------------------------------------------------------------------- 1 | module Vagrant 2 | module LXC 3 | VERSION = "1.4.2" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_lxc: 3 | messages: 4 | not_created: |- 5 | The container hasn't been created yet. 6 | not_running: |- 7 | The container is not currently running. 8 | will_not_destroy: |- 9 | The container '%{name}' will not be destroyed, since the confirmation 10 | was declined. 11 | starting: |- 12 | Starting container... 13 | force_shutdown: |- 14 | Forcing shutdown of container... 15 | warn_networks: |- 16 | Warning! The LXC provider doesn't support public networks, the settings 17 | will be silently ignored. 18 | warn_group: |- 19 | Warning! The LXC provider doesn't support the :group parameter for synced 20 | folders. It will be silently ignored. 21 | warn_owner: |- 22 | Warning! The LXC provider doesn't support the :owner parameter for synced 23 | folders. It will be silently ignored. 24 | setup_private_network: |- 25 | Setting up private networks... 26 | remove_bridge: |- 27 | Removing bridge '%{name}'... 28 | 29 | vagrant: 30 | commands: 31 | status: 32 | stopped: |- 33 | The container is currently stopped. Run `vagrant up` to bring it up again. 34 | 35 | actions: 36 | lxc: 37 | compressing_rootfs: Compressing container's rootfs... 38 | 39 | share_folders: 40 | preparing: Setting up mount entries for shared folders... 41 | 42 | errors: 43 | lxc_interrupt_error: |- 44 | Interrupted 45 | 46 | lxc_execute_error: |- 47 | There was an error executing %{command} 48 | 49 | For more information on the failure, enable detailed logging by setting 50 | the environment variable VAGRANT_LOG to DEBUG. 51 | 52 | lxc_incompatible_box: |- 53 | The base box you are trying to use is not compatible with the installed 54 | vagrant-lxc version. Supported box versions are %{supported} but %{found} was found. 55 | 56 | lxc_template_file_missing: |- 57 | The template file used for creating the container was not found for %{name} 58 | box. 59 | 60 | lxc_templates_dir_missing: |- 61 | Unable to identify lxc templates path. 62 | 63 | Looked up under: %{paths} 64 | 65 | lxc_linux_required: |- 66 | The LXC provider only works on Linux. Please try to use 67 | another provider. 68 | 69 | lxc_not_installed: |- 70 | The `lxc` package does not seem to be installed or `lxc-create` is not accessible on the PATH. 71 | 72 | lxc_redir_not_installed: |- 73 | `redir` is not installed or is not accessible on the PATH. 74 | 75 | lxc_container_already_exists: |- 76 | There is container on your system with the same name you've specified 77 | on your Vagrantfile (%{name}), please choose a different one or 78 | run `lxc-destroy --name %{name}` and try again. 79 | 80 | lxc_command_not_supported: |- 81 | Command (lxc-%{command}) not supported in version %{version}. 82 | This command is available with version %{available_version}. 83 | -------------------------------------------------------------------------------- /scripts/lxc-template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is a modified version of /usr/share/lxc/templates/lxc-download 4 | # that comes with ubuntu-lxc 1.0.0 stable from ppa changed to suit vagrant-lxc needs 5 | # 6 | # Copyright © 2014 Stéphane Graber 7 | # Copyright © 2014 Fábio Rehm 8 | # 9 | # This library is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU Lesser General Public 11 | # License as published by the Free Software Foundation; either 12 | # version 2.1 of the License, or (at your option) any later version. 13 | 14 | # This library is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | # Lesser General Public License for more details. 18 | 19 | # You should have received a copy of the GNU Lesser General Public 20 | # License along with this library; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 22 | # USA 23 | 24 | set -eu 25 | 26 | LXC_HOOK_DIR="/usr/share/lxc/hooks" 27 | LXC_TEMPLATE_CONFIG="/usr/share/lxc/config" 28 | 29 | LXC_MAPPED_GID= 30 | LXC_MAPPED_UID= 31 | LXC_NAME= 32 | LXC_PATH= 33 | LXC_ROOTFS= 34 | LXC_TARBALL= 35 | LXC_CONFIG= 36 | LXC_USE_OLDCONFIG= 37 | LXC_STRIP_COMPONENTS=2 38 | 39 | 40 | usage() { 41 | cat < ]: The full path of the rootfs tarball 46 | 47 | Optional arguments: 48 | [ --config ]: Configuration file to be used when building the container 49 | [ --oldconfig ]: Use pre LXC 2.1 config format 50 | [ -h | --help ]: This help message 51 | 52 | LXC internal arguments (do not pass manually!): 53 | [ --name ]: The container name 54 | [ --path ]: The path to the container 55 | [ --rootfs ]: The path to the container's rootfs 56 | [ --mapped-uid ]: A uid map (user namespaces) 57 | [ --mapped-gid ]: A gid map (user namespaces) 58 | [ --strip-components ]: Number of path components to strip from tarball 59 | EOF 60 | return 0 61 | } 62 | 63 | options=$(getopt -o h -l tarball:,config:,oldconfig,help:,name:,path:,rootfs:,mapped-uid:,mapped-gid:,strip-components: -- "$@")SS 64 | 65 | if [ $? -ne 0 ]; then 66 | usage $(basename $0) 67 | exit 1 68 | fi 69 | eval set -- "$options" 70 | 71 | while true 72 | do 73 | case "$1" in 74 | -h|--help) usage $0 && exit 0;; 75 | --config) LXC_CONFIG=$2; shift 2;; 76 | --oldconfig) LXC_USE_OLDCONFIG=1; shift 1;; 77 | --tarball) LXC_TARBALL=$2; shift 2;; 78 | --name) LXC_NAME=$2; shift 2;; 79 | --path) LXC_PATH=$2; shift 2;; 80 | --rootfs) LXC_ROOTFS=$2; shift 2;; 81 | --mapped-uid) LXC_MAPPED_UID=$2; shift 2;; 82 | --mapped-gid) LXC_MAPPED_GID=$2; shift 2;; 83 | --strip-components) LXC_STRIP_COMPONENTS=$2; shift 2;; 84 | *) break;; 85 | esac 86 | done 87 | 88 | if [ -z "${LXC_NAME}" ]; then 89 | echo "'name' parameter is required" 90 | exit 1 91 | fi 92 | 93 | if [ -z "${LXC_TARBALL}" ]; then 94 | echo "'tarball' parameter is required" 95 | exit 1 96 | fi 97 | 98 | if [ -z "${LXC_PATH}" ]; then 99 | echo "'path' parameter is required" 100 | exit 1 101 | fi 102 | 103 | # if $LXC_ROOTFS exists here, it was passed in with --rootfs 104 | if [ -z "${LXC_ROOTFS}" ]; then 105 | config=${LXC_PATH}/config 106 | if grep -q '^lxc.rootfs' $config 2>/dev/null ; then 107 | LXC_ROOTFS=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` 108 | else 109 | LXC_ROOTFS=$LXC_PATH/rootfs 110 | echo "lxc.rootfs = ${LXC_ROOTFS}" >> $config 111 | fi 112 | fi 113 | 114 | # Unpack the rootfs 115 | echo "Unpacking the rootfs" 116 | 117 | ( 118 | flock -x 200 119 | if [ $? -ne 0 ]; then 120 | echo "Cache repository is busy." 121 | exit 1 122 | fi 123 | 124 | mkdir -p ${LXC_ROOTFS} 125 | (cd ${LXC_ROOTFS} && tar xfz ${LXC_TARBALL} --strip-components=${LXC_STRIP_COMPONENTS} --xattrs --xattrs-include=* || true) 126 | if [ ! -f ${LXC_ROOTFS}/bin/true ]; then 127 | echo "Failed to extract rootfs" 128 | exit 1 129 | fi 130 | 131 | ) 200>${LXC_PATH}/vagrant_lock 132 | rm ${LXC_PATH}/vagrant_lock 133 | 134 | mkdir -p ${LXC_ROOTFS}/dev/pts/ 135 | 136 | ## Extract all the network config entries 137 | sed -i -e "/lxc.network/{w ${LXC_PATH}/config-network" -e "d}" \ 138 | ${LXC_PATH}/config 139 | 140 | ## Extract any other config entry 141 | sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" ${LXC_PATH}/config 142 | 143 | ## Add the container-specific config 144 | echo "" >> ${LXC_PATH}/config 145 | echo "##############################################" >> ${LXC_PATH}/config 146 | echo "# Container specific configuration (automatically set)" >> ${LXC_PATH}/config 147 | if [ -e "${LXC_PATH}/config-auto" ]; then 148 | cat ${LXC_PATH}/config-auto >> ${LXC_PATH}/config 149 | rm ${LXC_PATH}/config-auto 150 | fi 151 | 152 | if [ $LXC_USE_OLDCONFIG ]; then 153 | echo "lxc.utsname = ${LXC_NAME}" >> ${LXC_PATH}/config 154 | else 155 | echo "lxc.uts.name = ${LXC_NAME}" >> ${LXC_PATH}/config 156 | fi 157 | 158 | ## Re-add the previously removed network config 159 | if [ -e "${LXC_PATH}/config-network" ]; then 160 | echo "" >> ${LXC_PATH}/config 161 | echo "##############################################" >> ${LXC_PATH}/config 162 | echo "# Network configuration (automatically set)" >> ${LXC_PATH}/config 163 | cat ${LXC_PATH}/config-network >> ${LXC_PATH}/config 164 | rm ${LXC_PATH}/config-network 165 | fi 166 | 167 | if [ -n "${LXC_CONFIG}" ]; then 168 | ## Append the defaults 169 | echo "" >> ${LXC_PATH}/config 170 | echo "##############################################" >> ${LXC_PATH}/config 171 | echo "# vagrant-lxc base box specific configuration" >> ${LXC_PATH}/config 172 | cat ${LXC_CONFIG} >> ${LXC_PATH}/config 173 | fi 174 | 175 | # Empty section for lxc.customize calls from vagrantfile 176 | echo "" >> ${LXC_PATH}/config 177 | echo "##############################################" >> ${LXC_PATH}/config 178 | echo "# vagrant-lxc container specific configuration" >> ${LXC_PATH}/config 179 | 180 | exit 0 181 | -------------------------------------------------------------------------------- /scripts/pipework: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This code should (try to) follow Google's Shell Style Guide 3 | # (https://google-styleguide.googlecode.com/svn/trunk/shell.xml) 4 | set -e 5 | 6 | case "$1" in 7 | --wait) 8 | WAIT=1 9 | ;; 10 | esac 11 | 12 | IFNAME=$1 13 | 14 | # default value set further down if not set here 15 | CONTAINER_IFNAME= 16 | if [ "$2" = "-i" ]; then 17 | CONTAINER_IFNAME=$3 18 | shift 2 19 | fi 20 | 21 | if [ "$2" = "-l" ]; then 22 | LOCAL_IFNAME=$3 23 | shift 2 24 | fi 25 | 26 | GUESTNAME=$2 27 | IPADDR=$3 28 | MACADDR=$4 29 | 30 | case "$MACADDR" in 31 | *@*) 32 | VLAN="${MACADDR#*@}" 33 | VLAN="${VLAN%%@*}" 34 | MACADDR="${MACADDR%%@*}" 35 | ;; 36 | *) 37 | VLAN= 38 | ;; 39 | esac 40 | 41 | # did they ask to generate a custom MACADDR? 42 | # generate the unique string 43 | case "$MACADDR" in 44 | U:*) 45 | macunique="${MACADDR#*:}" 46 | # now generate a 48-bit hash string from $macunique 47 | MACADDR=$(echo $macunique|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/') 48 | ;; 49 | esac 50 | 51 | 52 | [ "$IPADDR" ] || [ "$WAIT" ] || { 53 | echo "Syntax:" 54 | echo "pipework [-i containerinterface] [-l localinterfacename] /[@default_gateway] [macaddr][@vlan]" 55 | echo "pipework [-i containerinterface] [-l localinterfacename] dhcp [macaddr][@vlan]" 56 | echo "pipework route " 57 | echo "pipework --wait [-i containerinterface]" 58 | exit 1 59 | } 60 | 61 | # Succeed if the given utility is installed. Fail otherwise. 62 | # For explanations about `which` vs `type` vs `command`, see: 63 | # http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212 64 | # (Thanks to @chenhanxiao for pointing this out!) 65 | installed () { 66 | command -v "$1" >/dev/null 2>&1 67 | } 68 | 69 | # Google Styleguide says error messages should go to standard error. 70 | warn () { 71 | echo "$@" >&2 72 | } 73 | die () { 74 | status="$1" 75 | shift 76 | warn "$@" 77 | exit "$status" 78 | } 79 | 80 | # First step: determine type of first argument (bridge, physical interface...), 81 | # Unless "--wait" is set (then skip the whole section) 82 | if [ -z "$WAIT" ]; then 83 | if [ -d "/sys/class/net/$IFNAME" ] 84 | then 85 | if [ -d "/sys/class/net/$IFNAME/bridge" ]; then 86 | IFTYPE=bridge 87 | BRTYPE=linux 88 | elif installed ovs-vsctl && ovs-vsctl list-br|grep -q "^${IFNAME}$"; then 89 | IFTYPE=bridge 90 | BRTYPE=openvswitch 91 | elif [ "$(cat "/sys/class/net/$IFNAME/type")" -eq 32 ]; then # InfiniBand IPoIB interface type 32 92 | IFTYPE=ipoib 93 | # The IPoIB kernel module is fussy, set device name to ib0 if not overridden 94 | CONTAINER_IFNAME=${CONTAINER_IFNAME:-ib0} 95 | PKEY=$VLAN 96 | else IFTYPE=phys 97 | fi 98 | else 99 | case "$IFNAME" in 100 | br*) 101 | IFTYPE=bridge 102 | BRTYPE=linux 103 | ;; 104 | ovs*) 105 | if ! installed ovs-vsctl; then 106 | die 1 "Need OVS installed on the system to create an ovs bridge" 107 | fi 108 | IFTYPE=bridge 109 | BRTYPE=openvswitch 110 | ;; 111 | route*) 112 | IFTYPE=route 113 | ;; 114 | dummy*) 115 | IFTYPE=dummy 116 | ;; 117 | *) die 1 "I do not know how to setup interface $IFNAME." ;; 118 | esac 119 | fi 120 | fi 121 | 122 | # Set the default container interface name to eth1 if not already set 123 | CONTAINER_IFNAME=${CONTAINER_IFNAME:-eth1} 124 | 125 | [ "$WAIT" ] && { 126 | while true; do 127 | # This first method works even without `ip` or `ifconfig` installed, 128 | # but doesn't work on older kernels (e.g. CentOS 6.X). See #128. 129 | grep -q '^1$' "/sys/class/net/$CONTAINER_IFNAME/carrier" && break 130 | # This method hopefully works on those older kernels. 131 | ip link ls dev "$CONTAINER_IFNAME" && break 132 | sleep 1 133 | done > /dev/null 2>&1 134 | exit 0 135 | } 136 | 137 | [ "$IFTYPE" = bridge ] && [ "$BRTYPE" = linux ] && [ "$VLAN" ] && { 138 | die 1 "VLAN configuration currently unsupported for Linux bridge." 139 | } 140 | 141 | [ "$IFTYPE" = ipoib ] && [ "$MACADDR" ] && { 142 | die 1 "MACADDR configuration unsupported for IPoIB interfaces." 143 | } 144 | 145 | # Second step: find the guest (for now, we only support LXC containers) 146 | while read _ mnt fstype options _; do 147 | [ "$fstype" != "cgroup" ] && continue 148 | echo "$options" | grep -qw devices || continue 149 | CGROUPMNT=$mnt 150 | done < /proc/mounts 151 | 152 | [ "$CGROUPMNT" ] || { 153 | die 1 "Could not locate cgroup mount point." 154 | } 155 | 156 | # Try to find a cgroup matching exactly the provided name. 157 | N=$(find "$CGROUPMNT" -name "$GUESTNAME" | wc -l) 158 | case "$N" in 159 | 0) 160 | # If we didn't find anything, try to lookup the container with Docker. 161 | if installed docker; then 162 | RETRIES=3 163 | while [ "$RETRIES" -gt 0 ]; do 164 | DOCKERPID=$(docker inspect --format='{{ .State.Pid }}' "$GUESTNAME") 165 | [ "$DOCKERPID" != 0 ] && break 166 | sleep 1 167 | RETRIES=$((RETRIES - 1)) 168 | done 169 | 170 | [ "$DOCKERPID" = 0 ] && { 171 | die 1 "Docker inspect returned invalid PID 0" 172 | } 173 | 174 | [ "$DOCKERPID" = "" ] && { 175 | die 1 "Container $GUESTNAME not found, and unknown to Docker." 176 | } 177 | else 178 | die 1 "Container $GUESTNAME not found, and Docker not installed." 179 | fi 180 | ;; 181 | 1) true ;; 182 | *) die 1 "Found more than one container matching $GUESTNAME." ;; 183 | esac 184 | 185 | # only check IPADDR if we are not in a route mode 186 | [ "$IFTYPE" != route ] && { 187 | case "$IPADDR" in 188 | # Let's check first if the user asked for DHCP allocation. 189 | dhcp|dhcp:*) 190 | # Use Docker-specific strategy to run the DHCP client 191 | # from the busybox image, in the network namespace of 192 | # the container. 193 | if ! [ "$DOCKERPID" ]; then 194 | warn "You asked for a Docker-specific DHCP method." 195 | warn "However, $GUESTNAME doesn't seem to be a Docker container." 196 | warn "Try to replace 'dhcp' with another option?" 197 | die 1 "Aborting." 198 | fi 199 | DHCP_CLIENT=${IPADDR%%:*} 200 | ;; 201 | udhcpc|udhcpc:*|udhcpc-f|udhcpc-f:*|dhcpcd|dhcpcd:*|dhclient|dhclient:*|dhclient-f|dhclient-f:*) 202 | DHCP_CLIENT=${IPADDR%%:*} 203 | # did they ask for the client to remain? 204 | DHCP_FOREGROUND= 205 | [ "${DHCP_CLIENT: -2}" = '-f' ] && { 206 | DHCP_FOREGROUND=true 207 | } 208 | DHCP_CLIENT=${DHCP_CLIENT%-f} 209 | if ! installed "$DHCP_CLIENT"; then 210 | die 1 "You asked for DHCP client $DHCP_CLIENT, but I can't find it." 211 | fi 212 | ;; 213 | # Alright, no DHCP? Then let's see if we have a subnet *and* gateway. 214 | */*@*) 215 | GATEWAY="${IPADDR#*@}" GATEWAY="${GATEWAY%%@*}" 216 | IPADDR="${IPADDR%%@*}" 217 | ;; 218 | # No gateway? We need at least a subnet, anyway! 219 | */*) : ;; 220 | # ... No? Then stop right here. 221 | *) 222 | warn "The IP address should include a netmask." 223 | die 1 "Maybe you meant $IPADDR/24 ?" 224 | ;; 225 | esac 226 | } 227 | 228 | # If a DHCP method was specified, extract the DHCP options. 229 | if [ "$DHCP_CLIENT" ]; then 230 | case "$IPADDR" in 231 | *:*) DHCP_OPTIONS="${IPADDR#*:}" ;; 232 | esac 233 | fi 234 | 235 | if [ "$DOCKERPID" ]; then 236 | NSPID=$DOCKERPID 237 | else 238 | NSPID=$(head -n 1 "$(find "$CGROUPMNT" -name "$GUESTNAME" | head -n 1)/tasks") 239 | [ "$NSPID" ] || { 240 | # it is an alternative way to get the pid 241 | NSPID=$(lxc-info -n "$GUESTNAME" | grep PID | grep -Eo '[0-9]+') 242 | [ "$NSPID" ] || { 243 | die 1 "Could not find a process inside container $GUESTNAME." 244 | } 245 | } 246 | fi 247 | 248 | # Check if an incompatible VLAN device already exists 249 | [ "$IFTYPE" = phys ] && [ "$VLAN" ] && [ -d "/sys/class/net/$IFNAME.VLAN" ] && { 250 | ip -d link show "$IFNAME.$VLAN" | grep -q "vlan.*id $VLAN" || { 251 | die 1 "$IFNAME.VLAN already exists but is not a VLAN device for tag $VLAN" 252 | } 253 | } 254 | 255 | [ ! -d /var/run/netns ] && mkdir -p /var/run/netns 256 | rm -f "/var/run/netns/$NSPID" 257 | ln -s "/proc/$NSPID/ns/net" "/var/run/netns/$NSPID" 258 | 259 | # Check if we need to create a bridge. 260 | [ "$IFTYPE" = bridge ] && [ ! -d "/sys/class/net/$IFNAME" ] && { 261 | [ "$BRTYPE" = linux ] && { 262 | (ip link add dev "$IFNAME" type bridge > /dev/null 2>&1) || (brctl addbr "$IFNAME") 263 | ip link set "$IFNAME" up 264 | } 265 | [ "$BRTYPE" = openvswitch ] && { 266 | ovs-vsctl add-br "$IFNAME" 267 | } 268 | } 269 | 270 | [ "$IFTYPE" != "route" ] && [ "$IFTYPE" != "dummy" ] && MTU=$(ip link show "$IFNAME" | awk '{print $5}') 271 | 272 | # If it's a bridge, we need to create a veth pair 273 | [ "$IFTYPE" = bridge ] && { 274 | if [ -z "$LOCAL_IFNAME" ]; then 275 | LOCAL_IFNAME="v${CONTAINER_IFNAME}pl${NSPID}" 276 | fi 277 | GUEST_IFNAME="v${CONTAINER_IFNAME}pg${NSPID}" 278 | # Does the link already exist? 279 | if ip link show "$LOCAL_IFNAME" >/dev/null 2>&1; then 280 | # link exists, is it in use? 281 | if ip link show "$LOCAL_IFNAME" up | grep -q "UP"; then 282 | echo "Link $LOCAL_IFNAME exists and is up" 283 | exit 1 284 | fi 285 | # delete the link so we can re-add it afterwards 286 | ip link del "$LOCAL_IFNAME" 287 | fi 288 | ip link add name "$LOCAL_IFNAME" mtu "$MTU" type veth peer name "$GUEST_IFNAME" mtu "$MTU" 289 | case "$BRTYPE" in 290 | linux) 291 | (ip link set "$LOCAL_IFNAME" master "$IFNAME" > /dev/null 2>&1) || (brctl addif "$IFNAME" "$LOCAL_IFNAME") 292 | ;; 293 | openvswitch) 294 | if ! ovs-vsctl list-ports "$IFNAME" | grep -q "^${LOCAL_IFNAME}$"; then 295 | ovs-vsctl add-port "$IFNAME" "$LOCAL_IFNAME" ${VLAN:+tag="$VLAN"} 296 | fi 297 | ;; 298 | esac 299 | ip link set "$LOCAL_IFNAME" up 300 | } 301 | 302 | # If it's a physical interface, create a macvlan subinterface 303 | [ "$IFTYPE" = phys ] && { 304 | [ "$VLAN" ] && { 305 | [ ! -d "/sys/class/net/${IFNAME}.${VLAN}" ] && { 306 | ip link add link "$IFNAME" name "$IFNAME.$VLAN" mtu "$MTU" type vlan id "$VLAN" 307 | } 308 | ip link set "$IFNAME" up 309 | IFNAME=$IFNAME.$VLAN 310 | } 311 | GUEST_IFNAME=ph$NSPID$CONTAINER_IFNAME 312 | ip link add link "$IFNAME" dev "$GUEST_IFNAME" mtu "$MTU" type macvlan mode bridge 313 | ip link set "$IFNAME" up 314 | } 315 | 316 | # If it's an IPoIB interface, create a virtual IPoIB interface (the IPoIB 317 | # equivalent of a macvlan device) 318 | # 319 | # Note: no macvlan subinterface nor Ethernet bridge can be created on top of an 320 | # IPoIB interface. InfiniBand is not Ethernet. IPoIB is an IP layer on top of 321 | # InfiniBand, without an intermediate Ethernet layer. 322 | [ "$IFTYPE" = ipoib ] && { 323 | GUEST_IFNAME="${IFNAME}.${NSPID}" 324 | 325 | # If a partition key is provided, use it 326 | [ "$PKEY" ] && { 327 | GUEST_IFNAME="${IFNAME}.${PKEY}.${NSPID}" 328 | PKEY="pkey 0x$PKEY" 329 | } 330 | 331 | ip link add link "$IFNAME" name "$GUEST_IFNAME" type ipoib $PKEY 332 | ip link set "$IFNAME" up 333 | } 334 | 335 | # If its a dummy interface, create a dummy interface. 336 | [ "$IFTYPE" = dummy ] && { 337 | GUEST_IFNAME=du$NSPID$CONTAINER_IFNAME 338 | ip link add dev "$GUEST_IFNAME" type dummy 339 | } 340 | 341 | # If the `route` command was specified ... 342 | if [ "$IFTYPE" = route ]; then 343 | # ... discard the first two arguments and pass the rest to the route command. 344 | shift 2 345 | ip netns exec "$NSPID" ip route "$@" 346 | else 347 | # Otherwise, run normally. 348 | ip link set "$GUEST_IFNAME" netns "$NSPID" 349 | ip netns exec "$NSPID" ip link set "$GUEST_IFNAME" name "$CONTAINER_IFNAME" 350 | [ "$MACADDR" ] && ip netns exec "$NSPID" ip link set dev "$CONTAINER_IFNAME" address "$MACADDR" 351 | 352 | # When using any of the DHCP methods, we start a DHCP client in the 353 | # network namespace of the container. With the 'dhcp' method, the 354 | # client used is taken from the Docker busybox image (therefore 355 | # requiring no specific client installed on the host). Other methods 356 | # use a locally installed client. 357 | case "$DHCP_CLIENT" in 358 | dhcp) 359 | docker run -d --net container:$GUESTNAME --cap-add NET_ADMIN \ 360 | busybox udhcpc -i "$CONTAINER_IFNAME" -x "hostname:$GUESTNAME" \ 361 | $DHCP_OPTIONS \ 362 | >/dev/null 363 | ;; 364 | udhcpc) 365 | DHCP_Q="-q" 366 | [ "$DHCP_FOREGROUND" ] && { 367 | DHCP_OPTIONS="$DHCP_OPTIONS -f" 368 | } 369 | ip netns exec "$NSPID" "$DHCP_CLIENT" -qi "$CONTAINER_IFNAME" \ 370 | -x "hostname:$GUESTNAME" \ 371 | -p "/var/run/udhcpc.$GUESTNAME.pid" \ 372 | $DHCP_OPTIONS 373 | [ ! "$DHCP_FOREGROUND" ] && { 374 | rm "/var/run/udhcpc.$GUESTNAME.pid" 375 | } 376 | ;; 377 | dhclient) 378 | ip netns exec "$NSPID" "$DHCP_CLIENT" "$CONTAINER_IFNAME" \ 379 | -pf "/var/run/dhclient.$GUESTNAME.pid" \ 380 | -lf "/etc/dhclient/dhclient.$GUESTNAME.leases" \ 381 | $DHCP_OPTIONS 382 | # kill dhclient after get ip address to prevent device be used after container close 383 | [ ! "$DHCP_FOREGROUND" ] && { 384 | kill "$(cat "/var/run/dhclient.$GUESTNAME.pid")" 385 | rm "/var/run/dhclient.$GUESTNAME.pid" 386 | } 387 | ;; 388 | dhcpcd) 389 | ip netns exec "$NSPID" "$DHCP_CLIENT" -q "$CONTAINER_IFNAME" -h "$GUESTNAME" 390 | ;; 391 | "") 392 | if installed ipcalc; then 393 | eval $(ipcalc -b $IPADDR) 394 | ip netns exec "$NSPID" ip addr add "$IPADDR" brd "$BROADCAST" dev "$CONTAINER_IFNAME" 395 | else 396 | ip netns exec "$NSPID" ip addr add "$IPADDR" dev "$CONTAINER_IFNAME" 397 | fi 398 | 399 | [ "$GATEWAY" ] && { 400 | ip netns exec "$NSPID" ip route delete default >/dev/null 2>&1 && true 401 | } 402 | ip netns exec "$NSPID" ip link set "$CONTAINER_IFNAME" up 403 | [ "$GATEWAY" ] && { 404 | ip netns exec "$NSPID" ip route get "$GATEWAY" >/dev/null 2>&1 || \ 405 | ip netns exec "$NSPID" ip route add "$GATEWAY/32" dev "$CONTAINER_IFNAME" 406 | ip netns exec "$NSPID" ip route replace default via "$GATEWAY" 407 | } 408 | ;; 409 | esac 410 | 411 | # Give our ARP neighbors a nudge about the new interface 412 | if installed arping; then 413 | IPADDR=$(echo "$IPADDR" | cut -d/ -f1) 414 | ip netns exec "$NSPID" arping -c 1 -A -I "$CONTAINER_IFNAME" "$IPADDR" > /dev/null 2>&1 || true 415 | else 416 | echo "Warning: arping not found; interface may not be immediately reachable" 417 | fi 418 | fi 419 | # Remove NSPID to avoid `ip netns` catch it. 420 | rm -f "/var/run/netns/$NSPID" 421 | 422 | # vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab : 423 | -------------------------------------------------------------------------------- /spec/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.require_plugin 'vagrant-lxc' 5 | Vagrant.require_plugin 'vagrant-cachier' 6 | 7 | ENV['BOX_NAME'] ||= 'quantal64' 8 | puts "Running vagrant commands using #{ENV['BOX_NAME']} box" 9 | 10 | Vagrant.configure("2") do |config| 11 | config.vm.box = ENV['BOX_NAME'] 12 | config.vm.hostname = 'lxc-test-box' 13 | config.vm.box_url = ENV['BOX_URL'] 14 | config.vm.network :forwarded_port, guest: 80, host: 8080 15 | 16 | config.cache.auto_detect = true 17 | 18 | config.vm.provision :shell, 19 | inline: 'mkdir -p /vagrant/tmp && echo -n "Provisioned" > /vagrant/tmp/provisioning' 20 | 21 | config.vm.provision :shell, 22 | inline: 'apt-get install apache2 -y' 23 | 24 | config.vm.provision :shell, privileged: false, 25 | inline: "if ! [ -f $HOME/original-box ]; then echo '#{ENV['BOX_NAME']}' > $HOME/original-box; fi" 26 | end 27 | -------------------------------------------------------------------------------- /spec/fixtures/sample-ip-addr-output: -------------------------------------------------------------------------------- 1 | 49: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 2 | inet 10.0.254.137/24 brd 10.0.254.255 scope global eth0 3 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] 2 | require 'simplecov' 3 | require 'coveralls' 4 | 5 | SimpleCov.start { add_filter '/spec/' } 6 | SimpleCov.merge_timeout 300 7 | end 8 | 9 | require 'bundler/setup' 10 | 11 | require 'i18n' 12 | 13 | require 'vagrant-lxc' 14 | 15 | Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f } 16 | 17 | RSpec.configure do |config| 18 | config.treat_symbols_as_metadata_keys_with_true_values = true 19 | config.run_all_when_everything_filtered = true 20 | config.filter_run :focus 21 | 22 | # Run specs in random order to surface order dependencies. If you find an 23 | # order dependency and want to debug it, you can fix the order by providing 24 | # the seed, which is printed after each run. 25 | # --seed 1234 26 | config.order = 'random' 27 | 28 | config.mock_with :rspec do |c| 29 | c.yield_receiver_to_any_instance_implementation_blocks = true 30 | end 31 | config.expect_with :rspec do |c| 32 | c.syntax = :expect 33 | end 34 | config.raise_errors_for_deprecations! 35 | end 36 | -------------------------------------------------------------------------------- /spec/support/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgrehm/vagrant-lxc/ff58ecd5f2278f0e3314db1fb0a7862dc61ad5d1/spec/support/.gitkeep -------------------------------------------------------------------------------- /spec/unit/action/clear_forwarded_ports_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit_helper' 2 | 3 | require 'tmpdir' 4 | require 'vagrant-lxc/action/clear_forwarded_ports' 5 | 6 | describe Vagrant::LXC::Action::ClearForwardedPorts do 7 | let(:app) { double(:app, call: true) } 8 | let(:env) { {machine: machine, ui: double(info: true)} } 9 | let(:machine) { double(:machine, data_dir: data_dir) } 10 | let!(:data_dir) { Pathname.new(Dir.mktmpdir) } 11 | let(:pids_dir) { data_dir.join('pids') } 12 | let(:pid) { 'a-pid' } 13 | let(:pid_cmd) { 'redir' } 14 | 15 | subject { described_class.new(app, env) } 16 | 17 | before do 18 | pids_dir.mkdir 19 | pids_dir.join('redir_1234.pid').open('w') { |f| f.write(pid) } 20 | subject.stub(system: true, :` => pid_cmd) 21 | subject.call(env) 22 | end 23 | 24 | after { FileUtils.rm_rf data_dir.to_s } 25 | 26 | it 'removes all files under pid directory' do 27 | expect(Dir[pids_dir.to_s + "/redir_*.pid"]).to be_empty 28 | end 29 | 30 | context 'with a valid redir pid' do 31 | it 'kills known processes' do 32 | expect(subject).to have_received(:system).with("pkill -TERM -P #{pid}") 33 | end 34 | end 35 | 36 | context 'with an invalid pid' do 37 | let(:pid_cmd) { 'sudo ls' } 38 | 39 | it 'does not kill the process' do 40 | expect(subject).not_to have_received(:system).with("pkill -TERM -P #{pid}") 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/action/compress_rootfs_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit_helper' 2 | 3 | require 'vagrant-lxc/plugin' 4 | require 'vagrant-lxc/provider' 5 | require 'vagrant-lxc/action/compress_rootfs' 6 | 7 | describe Vagrant::LXC::Action::CompressRootFS do 8 | let(:app) { double(:app, call: true) } 9 | let(:env) { {machine: machine, ui: double(info: true)} } 10 | let(:machine) { double(Vagrant::Machine, provider: provider) } 11 | let(:provider) { double(Vagrant::LXC::Provider, driver: driver) } 12 | let(:driver) { double(Vagrant::LXC::Driver, compress_rootfs: compressed_rootfs_path) } 13 | let(:compressed_rootfs_path) { '/path/to/rootfs.tar.gz' } 14 | 15 | subject { described_class.new(app, env) } 16 | 17 | before do 18 | provider.stub_chain(:state, :id).and_return(:stopped) 19 | subject.call(env) 20 | end 21 | 22 | it "asks the driver to compress container's rootfs" do 23 | expect(driver).to have_received(:compress_rootfs) 24 | end 25 | 26 | it 'sets export.temp_dir on action env' do 27 | expect(env['package.rootfs']).to eq(compressed_rootfs_path) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/unit/action/forward_ports_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit_helper' 2 | 3 | require 'tmpdir' 4 | require 'vagrant-lxc/provider' 5 | require 'vagrant-lxc/action/forward_ports' 6 | 7 | describe Vagrant::LXC::Action::ForwardPorts do 8 | let(:app) { double(:app, call: true) } 9 | let(:env) { {machine: machine, ui: double(info: true, warn: true)} } 10 | let(:machine) { double(:machine) } 11 | let!(:data_dir) { Pathname.new(Dir.mktmpdir) } 12 | let(:provider) { double(Vagrant::LXC::Provider, ssh_info: {host: container_ip}) } 13 | let(:host_ip) { '127.0.0.1' } 14 | let(:host_port) { 8080 } 15 | let(:guest_port) { 80 } 16 | let(:container_ip) { '10.0.1.234' } 17 | let(:pid) { 'a-pid' } 18 | let(:forward_conf) { {guest: guest_port, host: host_port, host_ip: host_ip} } 19 | let(:networks) { [[:other_config, {}], [:forwarded_port, forward_conf]] } 20 | 21 | subject { described_class.new(app, env) } 22 | 23 | before do 24 | machine.stub_chain(:config, :vm, :networks).and_return(networks) 25 | machine.stub(provider: provider, data_dir: data_dir) 26 | 27 | subject.stub(redir_version: 3) 28 | subject.stub(exec: true) 29 | subject.stub(spawn: pid) 30 | end 31 | 32 | after { FileUtils.rm_rf data_dir.to_s } 33 | 34 | it 'forwards ports using redir' do 35 | subject.stub(system: true) 36 | subject.call(env) 37 | expect(subject).to have_received(:spawn).with( 38 | "redir -n #{host_ip}:#{host_port} #{container_ip}:#{guest_port} 2>/dev/null" 39 | ) 40 | end 41 | 42 | it 'Uses 127.0.0.1 as default if host_ip is nil' do 43 | forward_conf.delete(:host_ip) 44 | subject.stub(system: true) 45 | subject.call(env) 46 | expect(subject).to have_received(:spawn).with( 47 | "redir -n 127.0.0.1:#{host_port} #{container_ip}:#{guest_port} 2>/dev/null" 48 | ) 49 | end 50 | 51 | it 'Uses 127.0.0.1 by default if host_ip is a blank string' do 52 | forward_conf[:host_ip] = ' ' 53 | subject.stub(system: true) 54 | subject.call(env) 55 | expect(subject).to have_received(:spawn).with( 56 | "redir -n 127.0.0.1:#{host_port} #{container_ip}:#{guest_port} 2>/dev/null" 57 | ) 58 | end 59 | 60 | it "stores redir pids on machine's data dir" do 61 | subject.stub(system: true) 62 | subject.call(env) 63 | pid_file = data_dir.join('pids', "redir_#{host_port}.pid").read 64 | expect(pid_file).to eq(pid) 65 | end 66 | 67 | it 'allows disabling a previously forwarded port' do 68 | forward_conf[:disabled] = true 69 | subject.stub(system: true) 70 | subject.call(env) 71 | expect(subject).not_to have_received(:spawn) 72 | end 73 | 74 | it 'uses redir 2.x command line interface' do 75 | subject.stub(system: true) 76 | subject.stub(redir_version: 2) 77 | subject.call(env) 78 | expect(subject).to have_received(:spawn).with( 79 | "redir --laddr=#{host_ip} --lport=#{host_port} --caddr=#{container_ip} --cport=#{guest_port} 2>/dev/null" 80 | ) 81 | end 82 | 83 | it 'raises RedirNotInstalled error if `redir` is not installed' do 84 | subject.stub(system: false) 85 | expect { subject.call(env) }.to raise_error(Vagrant::LXC::Errors::RedirNotInstalled) 86 | end 87 | 88 | context 'when a privileged port is used' do 89 | let(:host_port) { 80 } 90 | 91 | it 'forwards ports using redir' do 92 | subject.stub(system: true) 93 | subject.call(env) 94 | expect(subject).to have_received(:spawn).with( 95 | "sudo redir -n #{host_ip}:#{host_port} #{container_ip}:#{guest_port} 2>/dev/null" 96 | ) 97 | end 98 | 99 | it 'Uses 127.0.0.1 by default if host_ip is nil' do 100 | forward_conf.delete(:host_ip) 101 | subject.stub(system: true) 102 | subject.call(env) 103 | expect(subject).to have_received(:spawn).with( 104 | "sudo redir -n 127.0.0.1:#{host_port} #{container_ip}:#{guest_port} 2>/dev/null" 105 | ) 106 | end 107 | 108 | it 'Uses 127.0.0.1 by default if host_ip is a blank string' do 109 | forward_conf[:host_ip] = ' ' 110 | subject.stub(system: true) 111 | subject.call(env) 112 | expect(subject).to have_received(:spawn).with( 113 | "sudo redir -n 127.0.0.1:#{host_port} #{container_ip}:#{guest_port} 2>/dev/null" 114 | ) 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /spec/unit/action/handle_box_metadata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit_helper' 2 | 3 | require 'vagrant' 4 | require 'vagrant-lxc/errors' 5 | require 'vagrant-lxc/action/handle_box_metadata' 6 | 7 | describe Vagrant::LXC::Action::HandleBoxMetadata do 8 | let(:app) { double(:app, call: true) } 9 | let(:env) { {machine: machine, ui: double(info: true, warn: true)} } 10 | let(:machine) { double(:machine, box: box) } 11 | let(:box) { double(:box, name: 'box-name', metadata: metadata, directory: box_directory) } 12 | let(:box_directory) { Pathname.new('/path/to/box') } 13 | let(:version) { '2' } 14 | let(:metadata) { {'template-opts' => {'--foo' => 'bar'}, 'version' => version} } 15 | let(:vagrant_key) { Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s } 16 | 17 | subject { described_class.new(app, env) } 18 | 19 | context 'with 1.0.0 box' do 20 | let(:version) { '1.0.0' } 21 | 22 | before do 23 | File.stub(exists?: true) 24 | # REFACTOR: This is pretty bad 25 | subject.stub_chain(:template_config_file, :exist?).and_return(true) 26 | subject.stub_chain(:template_config_file, :to_s).and_return(box_directory.join('lxc-config').to_s) 27 | subject.call(env) 28 | end 29 | 30 | it 'sets the tarball argument for the template' do 31 | expect(env[:lxc_template_opts]).to include( 32 | '--tarball' => box_directory.join('rootfs.tar.gz').to_s 33 | ) 34 | end 35 | 36 | it 'sets the template --config parameter' do 37 | expect(env[:lxc_template_opts]).to include( 38 | '--config' => box_directory.join('lxc-config').to_s 39 | ) 40 | end 41 | 42 | it 'does not set the auth key argument for the template' do 43 | expect(env[:lxc_template_opts]).not_to include( 44 | '--auth-key' => vagrant_key 45 | ) 46 | end 47 | 48 | it 'sets the template options from metadata on env hash' do 49 | expect(env[:lxc_template_opts]).to include(metadata['template-opts']) 50 | end 51 | 52 | xit 'sets the template source path on env hash' do 53 | expect(env[:lxc_template_src]).to eq(box_directory.join('lxc-template').to_s) 54 | end 55 | 56 | it 'does not warn about deprecation' do 57 | expect(env[:ui]).not_to have_received(:warn) 58 | end 59 | end 60 | 61 | context 'with valid pre 1.0.0 box' do 62 | before do 63 | File.stub(exists?: true) 64 | # REFACTOR: This is pretty bad 65 | subject.stub_chain(:old_template_config_file, :exist?).and_return(true) 66 | subject.stub_chain(:old_template_config_file, :to_s).and_return(box_directory.join('lxc.conf').to_s) 67 | subject.call(env) 68 | end 69 | 70 | it 'sets the tarball argument for the template' do 71 | expect(env[:lxc_template_opts]).to include( 72 | '--tarball' => box_directory.join('rootfs.tar.gz').to_s 73 | ) 74 | end 75 | 76 | it 'sets the auth key argument for the template' do 77 | expect(env[:lxc_template_opts]).to include( 78 | '--auth-key' => vagrant_key 79 | ) 80 | end 81 | 82 | it 'sets the lxc config file parameter' do 83 | expect(env[:lxc_template_config]).to eq(box_directory.join('lxc.conf').to_s) 84 | end 85 | 86 | it 'sets the template options from metadata on env hash' do 87 | expect(env[:lxc_template_opts]).to include(metadata['template-opts']) 88 | end 89 | 90 | xit 'sets the template source path on env hash' do 91 | expect(env[:lxc_template_src]).to eq(box_directory.join('lxc-template').to_s) 92 | end 93 | 94 | it 'warns about deprecation' do 95 | expect(env[:ui]).to have_received(:warn) 96 | end 97 | end 98 | 99 | describe 'with invalid contents' do 100 | before { File.stub(exists?: true) } 101 | 102 | it 'validates box versions' do 103 | %w( 2 3 1.0.0 ).each do |v| 104 | metadata['version'] = v 105 | expect { subject.call(env) }.to_not raise_error 106 | end 107 | 108 | metadata['version'] = '1' 109 | expect { subject.call(env) }.to raise_error 110 | end 111 | 112 | it 'raises an error if the rootfs tarball cant be found' do 113 | allow(File).to receive(:exists?).with(box_directory.join('rootfs.tar.gz').to_s).and_return(false) 114 | expect { 115 | subject.call(env) 116 | }.to raise_error(Vagrant::LXC::Errors::RootFSTarballMissing) 117 | end 118 | 119 | it 'does not raise an error if the lxc-template script cant be found' do 120 | allow(File).to receive(:exists?).with(box_directory.join('lxc-template').to_s).and_return(false) 121 | expect { 122 | subject.call(env) 123 | }.to_not raise_error 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /spec/unit/action/setup_package_files_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit_helper' 2 | 3 | require 'vagrant-lxc/action/setup_package_files' 4 | 5 | describe Vagrant::LXC::Action::SetupPackageFiles do 6 | let(:app) { double(:app, call: true) } 7 | let(:env) { {machine: machine, tmp_path: tmp_path, ui: double(info: true), 'package.rootfs' => rootfs_path} } 8 | let(:machine) { double(Vagrant::Machine, box: box) } 9 | let!(:tmp_path) { Pathname.new(Dir.mktmpdir) } 10 | let(:box) { double(Vagrant::Box, directory: tmp_path.join('box')) } 11 | let(:rootfs_path) { tmp_path.join('rootfs-amd64.tar.gz') } 12 | 13 | subject { described_class.new(app, env) } 14 | 15 | before do 16 | box.directory.mkdir 17 | files = %w( lxc-template metadata.json lxc.conf lxc-config ).map { |f| box.directory.join(f) } 18 | (files + [rootfs_path]).each do |file| 19 | file.open('w') { |f| f.puts file.to_s } 20 | end 21 | 22 | subject.stub(recover: true) # Prevents files from being removed on specs 23 | end 24 | 25 | after do 26 | FileUtils.rm_rf(tmp_path.to_s) 27 | end 28 | 29 | context 'when all files exist' do 30 | before { subject.call(env) } 31 | 32 | it 'copies box lxc-template to package directory' do 33 | expect(env['package.directory'].join('lxc-template')).to be_file 34 | end 35 | 36 | it 'copies metadata.json to package directory' do 37 | expect(env['package.directory'].join('metadata.json')).to be_file 38 | end 39 | 40 | it 'copies box lxc.conf to package directory' do 41 | expect(env['package.directory'].join('lxc-template')).to be_file 42 | end 43 | 44 | it 'copies box lxc-config to package directory' do 45 | expect(env['package.directory'].join('lxc-config')).to be_file 46 | end 47 | 48 | it 'moves the compressed rootfs to package directory' do 49 | expect(env['package.directory'].join(rootfs_path.basename)).to be_file 50 | expect(env['package.rootfs']).not_to be_file 51 | end 52 | end 53 | 54 | context 'when lxc-template file is not present' do 55 | before do 56 | box.directory.join('lxc-template').delete 57 | end 58 | 59 | it 'does not blow up' do 60 | expect { subject.call(env) }.to_not raise_error 61 | end 62 | end 63 | 64 | context 'when lxc.conf file is not present' do 65 | before do 66 | box.directory.join('lxc.conf').delete 67 | end 68 | 69 | it 'does not blow up' do 70 | expect { subject.call(env) }.to_not raise_error 71 | end 72 | end 73 | 74 | context 'when lxc-config file is not present' do 75 | before do 76 | box.directory.join('lxc-config').delete 77 | end 78 | 79 | it 'does not blow up' do 80 | expect { subject.call(env) }.to_not raise_error 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/unit/driver/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit_helper' 2 | 3 | require 'vagrant-lxc/sudo_wrapper' 4 | require 'vagrant-lxc/driver/cli' 5 | 6 | describe Vagrant::LXC::Driver::CLI do 7 | let(:sudo_wrapper) { double(Vagrant::LXC::SudoWrapper, run: true, wrapper_path: nil) } 8 | 9 | subject { described_class.new(sudo_wrapper) } 10 | 11 | describe 'list' do 12 | let(:lxc_ls_out) { "dup-container\na-container dup-container" } 13 | let(:result) { @result } 14 | 15 | before do 16 | allow(subject).to receive(:run).with(:ls).and_return(lxc_ls_out) 17 | @result = subject.list 18 | end 19 | 20 | it 'grabs previously created containers from lxc-ls output' do 21 | expect(result).to be_an Enumerable 22 | expect(result).to include 'a-container' 23 | expect(result).to include 'dup-container' 24 | end 25 | 26 | it 'removes duplicates from lxc-ls output' do 27 | expect(result.uniq).to eq(result) 28 | end 29 | end 30 | 31 | describe 'version' do 32 | before do 33 | allow(subject).to receive(:run).with(:create, '--version').and_return(lxc_version_out) 34 | end 35 | 36 | describe 'lxc version after 1.x.x' do 37 | let(:lxc_version_out) { "1.0.0\n" } 38 | 39 | it 'parses the version from the output' do 40 | expect(subject.version).to eq('1.0.0') 41 | end 42 | end 43 | end 44 | 45 | describe 'config' do 46 | before do 47 | allow(subject).to receive(:run).with(:config, 'lxc.lxcpath').and_return(lxc_config_out) 48 | allow(subject).to receive(:run).with(:create, '--version').and_return(lxc_version_out) 49 | end 50 | 51 | describe 'lxc version after 1.x.x'do 52 | let(:lxc_config_out) { "/var/lib/lxc\n" } 53 | let(:lxc_version_out) { "1.0.0\n" } 54 | 55 | it 'parser the lxc.lxcpath value' do 56 | expect(subject.config('lxc.lxcpath')).not_to end_with("\n") 57 | end 58 | end 59 | end 60 | 61 | describe 'create' do 62 | let(:template) { 'quantal-64' } 63 | let(:name) { 'quantal-container' } 64 | let(:backingstore) { 'btrfs' } 65 | let(:backingstore_opts) { [['--dir', '/tmp/foo'], ['--foo', 'bar']] } 66 | let(:config_file) { 'config' } 67 | let(:template_args) { { '--extra-param' => 'param', '--other' => 'value' } } 68 | 69 | subject { described_class.new(sudo_wrapper, name) } 70 | 71 | before do 72 | allow(subject).to receive(:run) { |*args| @run_args = args } 73 | end 74 | 75 | it 'issues a lxc-create with provided template, container name and hash of arguments' do 76 | subject.create(template, backingstore, backingstore_opts, config_file, template_args) 77 | expect(subject).to have_received(:run).with( 78 | :create, 79 | '-B', backingstore, 80 | '--template', template, 81 | '--name', name, 82 | *(backingstore_opts.flatten), 83 | '-f', config_file, 84 | '--', 85 | '--extra-param', 'param', 86 | '--other', 'value' 87 | ) 88 | end 89 | 90 | it 'wraps a low level error into something more meaningful in case the container already exists' do 91 | allow(subject).to receive(:run) { raise Vagrant::LXC::Errors::ExecuteError, stderr: 'alreAdy Exists' } 92 | expect { 93 | subject.create(template, backingstore, backingstore_opts, config_file, template_args) 94 | }.to raise_error(Vagrant::LXC::Errors::ContainerAlreadyExists) 95 | end 96 | end 97 | 98 | describe 'destroy' do 99 | let(:name) { 'a-container-for-destruction' } 100 | 101 | subject { described_class.new(sudo_wrapper, name) } 102 | 103 | before do 104 | allow(subject).to receive(:run) 105 | subject.destroy 106 | end 107 | 108 | it 'issues a lxc-destroy with container name' do 109 | expect(subject).to have_received(:run).with(:destroy, '--name', name) 110 | end 111 | end 112 | 113 | describe 'start' do 114 | let(:name) { 'a-container' } 115 | subject { described_class.new(sudo_wrapper, name) } 116 | 117 | before do 118 | allow(subject).to receive(:run) 119 | end 120 | 121 | it 'starts container on the background' do 122 | subject.start 123 | expect(subject).to have_received(:run).with( 124 | :start, 125 | '-d', 126 | '--name', name 127 | ) 128 | end 129 | end 130 | 131 | describe 'stop' do 132 | let(:name) { 'a-running-container' } 133 | subject { described_class.new(sudo_wrapper, name) } 134 | 135 | before do 136 | allow(subject).to receive(:run) 137 | subject.stop 138 | end 139 | 140 | it 'issues a lxc-stop with provided container name' do 141 | expect(subject).to have_received(:run).with(:stop, '--name', name) 142 | end 143 | end 144 | 145 | describe 'state' do 146 | let(:name) { 'a-container' } 147 | subject { described_class.new(sudo_wrapper, name) } 148 | 149 | before do 150 | allow(subject).to receive(:run).and_return("state: STOPPED\npid: 2") 151 | end 152 | 153 | it 'calls lxc-info with the right arguments' do 154 | subject.state 155 | expect(subject).to have_received(:run).with(:info, '--name', name, retryable: true) 156 | end 157 | 158 | it 'maps the output of lxc-info status out to a symbol' do 159 | expect(subject.state).to eq(:stopped) 160 | end 161 | 162 | it 'is not case sensitive' do 163 | allow(subject).to receive(:run).and_return("StatE: STarTED\npid: 2") 164 | expect(subject.state).to eq(:started) 165 | end 166 | end 167 | 168 | describe 'attach' do 169 | let(:name) { 'a-running-container' } 170 | let(:command) { ['ls', 'cat /tmp/file'] } 171 | let(:command_output) { 'folders list' } 172 | subject { described_class.new(sudo_wrapper, name) } 173 | 174 | before do 175 | subject.stub(run: command_output) 176 | end 177 | 178 | it 'calls lxc-attach with specified command' do 179 | subject.attach(*command) 180 | expect(subject).to have_received(:run).with(:attach, '--name', name, '--', *command) 181 | end 182 | 183 | it 'supports a "namespaces" parameter' do 184 | allow(subject).to receive(:run).with(:attach, '-h', :show_stderr => true).and_return({:stdout => '', :stderr => '--namespaces'}) 185 | subject.attach *(command + [{namespaces: ['network', 'mount']}]) 186 | expect(subject).to have_received(:run).with(:attach, '--name', name, '--namespaces', 'NETWORK|MOUNT', '--', *command) 187 | end 188 | end 189 | 190 | describe 'transition block' do 191 | before do 192 | subject.stub(run: true, sleep: true, state: :stopped) 193 | end 194 | 195 | it 'yields a cli object' do 196 | allow(subject).to receive(:shutdown) 197 | subject.transition_to(:stopped) { |c| c.shutdown } 198 | expect(subject).to have_received(:shutdown) 199 | end 200 | 201 | it 'throws an exception if block is not provided' do 202 | expect { 203 | subject.transition_to(:running) 204 | }.to raise_error(described_class::TransitionBlockNotProvided) 205 | end 206 | 207 | skip 'waits for the expected container state' 208 | end 209 | end 210 | -------------------------------------------------------------------------------- /spec/unit/driver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'unit_helper' 2 | 3 | require 'vagrant-lxc/driver' 4 | require 'vagrant-lxc/driver/cli' 5 | require 'vagrant-lxc/sudo_wrapper' 6 | 7 | describe Vagrant::LXC::Driver do 8 | describe 'container name validation' do 9 | let(:unknown_container) { described_class.new('unknown', nil, cli) } 10 | let(:valid_container) { described_class.new('valid', nil, cli) } 11 | let(:new_container) { described_class.new(nil, nil) } 12 | let(:cli) { double(Vagrant::LXC::Driver::CLI, list: ['valid']) } 13 | 14 | it 'raises a ContainerNotFound error if an unknown container name gets provided' do 15 | expect { 16 | unknown_container.validate! 17 | }.to raise_error 18 | end 19 | 20 | it 'does not raise a ContainerNotFound error if a valid container name gets provided' do 21 | expect { 22 | valid_container.validate! 23 | }.not_to raise_error 24 | end 25 | 26 | it 'does not raise a ContainerNotFound error if nil is provider as name' do 27 | expect { 28 | new_container.validate! 29 | }.not_to raise_error 30 | end 31 | end 32 | 33 | describe 'creation' do 34 | let(:name) { 'container-name' } 35 | let(:backingstore) { 'btrfs' } 36 | let(:backingstore_opts) { [['--dir', '/tmp/foo'], ['--foo', 'bar']] } 37 | let(:template_name) { 'auto-assigned-template-id' } 38 | let(:template_path) { '/path/to/lxc-template-from-box' } 39 | let(:template_opts) { {'--some' => 'random-option'} } 40 | let(:config_file) { '/path/to/lxc-config-from-box' } 41 | let(:rootfs_tarball) { '/path/to/cache/rootfs.tar.gz' } 42 | let(:cli) { double(Vagrant::LXC::Driver::CLI, :create => true, :name= => true) } 43 | 44 | subject { described_class.new(nil, nil, cli) } 45 | 46 | before do 47 | allow(subject).to receive(:import_template).and_yield(template_name) 48 | subject.create name, backingstore, backingstore_opts, template_path, config_file, template_opts 49 | end 50 | 51 | it 'sets the cli object container name' do 52 | expect(cli).to have_received(:name=).with(name) 53 | end 54 | 55 | it 'creates container with the right arguments' do 56 | expect(cli).to have_received(:create).with( 57 | template_path, 58 | backingstore, 59 | backingstore_opts, 60 | config_file, 61 | template_opts 62 | ) 63 | end 64 | end 65 | 66 | describe 'destruction' do 67 | let(:cli) { double(Vagrant::LXC::Driver::CLI, destroy: true) } 68 | 69 | subject { described_class.new('name', nil, cli) } 70 | 71 | before { subject.destroy } 72 | 73 | it 'delegates to cli object' do 74 | expect(cli).to have_received(:destroy) 75 | end 76 | end 77 | 78 | describe 'start' do 79 | let(:customizations) { [['a', '1'], ['b', '2']] } 80 | let(:internal_customization) { ['internal', 'customization'] } 81 | let(:cli) { double(Vagrant::LXC::Driver::CLI, start: true) } 82 | let(:sudo) { double(Vagrant::LXC::SudoWrapper) } 83 | 84 | subject { described_class.new('name', sudo, cli) } 85 | 86 | before do 87 | sudo.should_receive(:run).with('cat', '/var/lib/lxc/name/config').exactly(2).times. 88 | and_return('# CONFIGURATION') 89 | sudo.should_receive(:run).twice.with('cp', '-f', %r{/(run|tmp)/.*}, '/var/lib/lxc/name/config') 90 | sudo.should_receive(:run).twice.with('chown', 'root:root', '/var/lib/lxc/name/config') 91 | expect(cli).to receive(:config).with("lxc.lxcpath").and_return("/var/lib/lxc") 92 | 93 | subject.customizations << internal_customization 94 | subject.start(customizations) 95 | end 96 | 97 | it 'prunes previous customizations before writing' 98 | 99 | it 'writes configurations to config file' 100 | 101 | it 'starts container with configured customizations' do 102 | expect(cli).to have_received(:start) 103 | end 104 | end 105 | 106 | describe 'halt' do 107 | let(:cli) { double(Vagrant::LXC::Driver::CLI, stop: true) } 108 | 109 | subject { described_class.new('name', nil, cli) } 110 | 111 | before do 112 | allow(cli).to receive(:transition_to).and_yield(cli) 113 | end 114 | 115 | it 'delegates to cli stop' do 116 | expect(cli).to receive(:stop) 117 | subject.forced_halt 118 | end 119 | 120 | it 'expects a transition to running state to take place' do 121 | expect(cli).to receive(:transition_to).with(:stopped) 122 | subject.forced_halt 123 | end 124 | 125 | it 'attempts to force the container to stop in case a shutdown doesnt work' do 126 | allow(cli).to receive(:shutdown).and_raise(Vagrant::LXC::Driver::CLI::TargetStateNotReached.new :target, :source) 127 | expect(cli).to receive(:transition_to).with(:stopped) 128 | expect(cli).to receive(:stop) 129 | subject.forced_halt 130 | end 131 | end 132 | 133 | describe 'state' do 134 | let(:cli_state) { :something } 135 | let(:cli) { double(Vagrant::LXC::Driver::CLI, state: cli_state) } 136 | 137 | subject { described_class.new('name', nil, cli) } 138 | 139 | it 'delegates to cli' do 140 | expect(subject.state).to eq(cli_state) 141 | end 142 | end 143 | 144 | describe 'containers_path' do 145 | let(:cli) { double(Vagrant::LXC::Driver::CLI, config: cli_config_value) } 146 | 147 | subject { described_class.new('name', nil, cli) } 148 | 149 | describe 'lxc version after 1.x.x' do 150 | let(:cli_config_value) { '/etc/lxc' } 151 | 152 | it 'delegates to cli' do 153 | expect(subject.containers_path).to eq(cli_config_value) 154 | end 155 | end 156 | end 157 | 158 | describe 'folder sharing' do 159 | let(:shared_folder) { {guestpath: '/vagrant', hostpath: '/path/to/host/dir'} } 160 | let(:ro_rw_folder) { {guestpath: '/vagrant/ro_rw', hostpath: '/path/to/host/dir', mount_options: ['ro', 'rw']} } 161 | let(:with_space_folder) { {guestpath: '/tmp/with space', hostpath: '/path/with space'} } 162 | let(:folders) { [shared_folder, ro_rw_folder, with_space_folder] } 163 | let(:expected_guest_path) { "vagrant" } 164 | let(:sudo_wrapper) { double(Vagrant::LXC::SudoWrapper, run: true) } 165 | let(:rootfs_path) { Pathname('/path/to/rootfs') } 166 | 167 | subject { described_class.new('name', sudo_wrapper) } 168 | 169 | describe "with fixed rootfs" do 170 | before do 171 | subject.stub(rootfs_path: Pathname('/path/to/rootfs'), system: true) 172 | subject.share_folders(folders) 173 | end 174 | 175 | it 'adds a mount.entry to its local customizations' do 176 | expect(subject.customizations).to include [ 177 | 'mount.entry', 178 | "#{shared_folder[:hostpath]} #{expected_guest_path} none bind,create=dir 0 0" 179 | ] 180 | end 181 | 182 | it 'supports additional mount options' do 183 | expect(subject.customizations).to include [ 184 | 'mount.entry', 185 | "#{ro_rw_folder[:hostpath]} vagrant/ro_rw none ro,rw 0 0" 186 | ] 187 | end 188 | 189 | it 'supports directories with spaces' do 190 | expect(subject.customizations).to include [ 191 | 'mount.entry', 192 | "/path/with\\040space tmp/with\\040space none bind,create=dir 0 0" 193 | ] 194 | end 195 | end 196 | 197 | describe "with directory-based LXC config" do 198 | let(:config_string) { 199 | <<-ENDCONFIG.gsub(/^\s+/, '') 200 | # Blah blah comment 201 | lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0 202 | lxc.mount.entry = sysfs sys sysfs defaults 0 0 203 | lxc.tty.max = 4 204 | lxc.pty.max = 1024 205 | lxc.rootfs.path = #{rootfs_path} 206 | # VAGRANT-BEGIN 207 | lxc.network.type=veth 208 | lxc.network.name=eth1 209 | # VAGRANT-END 210 | ENDCONFIG 211 | } 212 | 213 | before do 214 | subject { described_class.new('name', sudo_wrapper) } 215 | subject.stub(config_string: config_string) 216 | subject.share_folders(folders) 217 | end 218 | 219 | it 'adds a mount.entry to its local customizations' do 220 | expect(subject.customizations).to include [ 221 | 'mount.entry', 222 | "#{shared_folder[:hostpath]} #{expected_guest_path} none bind,create=dir 0 0" 223 | ] 224 | end 225 | end 226 | 227 | describe "with overlayfs-based LXC config" do 228 | let(:config_string) { 229 | <<-ENDCONFIG.gsub(/^\s+/, '') 230 | # Blah blah comment 231 | lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0 232 | lxc.mount.entry = sysfs sys sysfs defaults 0 0 233 | lxc.tty.max = 4 234 | lxc.pty.max = 1024 235 | lxc.rootfs.path = overlayfs:/path/to/master/directory:#{rootfs_path} 236 | # VAGRANT-BEGIN 237 | lxc.network.type=veth 238 | lxc.network.name=eth1 239 | # VAGRANT-END 240 | ENDCONFIG 241 | } 242 | 243 | before do 244 | subject { described_class.new('name', sudo_wrapper) } 245 | subject.stub(config_string: config_string) 246 | subject.share_folders(folders) 247 | end 248 | 249 | it 'adds a mount.entry to its local customizations' do 250 | expect(subject.customizations).to include [ 251 | 'mount.entry', 252 | "#{shared_folder[:hostpath]} #{expected_guest_path} none bind,create=dir 0 0" 253 | ] 254 | end 255 | end 256 | end 257 | end 258 | -------------------------------------------------------------------------------- /spec/unit/support/unit_example_group.rb: -------------------------------------------------------------------------------- 1 | module UnitExampleGroup 2 | def self.included(base) 3 | base.metadata[:type] = :unit 4 | base.before do 5 | allow_any_instance_of(Object).to receive(:system) { |instance, *args, &block| 6 | UnitExampleGroup.prevent_system_calls(*args, &block) 7 | } 8 | allow_any_instance_of(Object).to receive(:`) { |instance, *args, &block| 9 | UnitExampleGroup.prevent_system_calls(*args, &block) 10 | } 11 | allow_any_instance_of(Object).to receive(:exec) { |instance, *args, &block| 12 | UnitExampleGroup.prevent_system_calls(*args, &block) 13 | } 14 | allow_any_instance_of(Object).to receive(:fork) { |instance, *args, &block| 15 | UnitExampleGroup.prevent_system_calls(*args, &block) 16 | } 17 | allow_any_instance_of(Object).to receive(:spawn) { |instance, *args, &block| 18 | UnitExampleGroup.prevent_system_calls(*args, &block) 19 | } 20 | require 'vagrant/util/subprocess' 21 | allow(Vagrant::Util::Subprocess).to receive(:execute) { |*args, &block| 22 | UnitExampleGroup.prevent_system_calls(*args, &block) 23 | } 24 | end 25 | end 26 | 27 | def self.prevent_system_calls(*args, &block) 28 | args.pop if args.last.is_a?(Hash) 29 | 30 | raise <<-MSG 31 | Somehow your code under test is trying to execute a command on your system, 32 | please stub it out or move your spec code to an acceptance spec. 33 | 34 | Block: #{block.inspect} 35 | Command: "#{args.join(' ')}" 36 | MSG 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit_helper.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Dir[File.dirname(__FILE__) + "/unit/support/**/*.rb"].each { |f| require f } 4 | 5 | if defined? SimpleCov 6 | SimpleCov.command_name 'unit' 7 | end 8 | 9 | RSpec.configure do |config| 10 | config.include UnitExampleGroup, :type => :unit, :example_group => { 11 | :file_path => /\bspec\/unit\// 12 | } 13 | 14 | config.mock_with :rspec do |c| 15 | c.yield_receiver_to_any_instance_implementation_blocks = true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /tasks/spec.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'rspec/core/rake_task' 3 | require 'coveralls/rake/task' 4 | 5 | desc 'Run all specs' 6 | task :spec => ['spec:set_coverage', 'spec:unit', 'spec:acceptance'] 7 | 8 | desc 'Default task which runs all specs with code coverage enabled' 9 | task :default => ['spec:set_coverage', 'spec:unit'] 10 | 11 | Coveralls::RakeTask.new 12 | task :ci => ['spec:set_coverage', 'spec:unit', 'coveralls:push'] 13 | rescue LoadError; end 14 | 15 | namespace :spec do 16 | task :set_coverage do 17 | ENV['COVERAGE'] = 'true' 18 | end 19 | 20 | desc 'Run acceptance specs using vagrant-spec' 21 | task :acceptance do 22 | components = %w( 23 | basic 24 | network/forwarded_port 25 | synced_folder 26 | synced_folder/nfs 27 | synced_folder/rsync 28 | provisioner/shell 29 | provisioner/puppet 30 | provisioner/chef-solo 31 | package 32 | ).map{|s| "provider/lxc/#{s}" } 33 | sh "export ACCEPTANCE=true && bundle exec vagrant-spec test --components=#{components.join(' ')}" 34 | end 35 | 36 | desc "Run unit specs with rspec" 37 | RSpec::Core::RakeTask.new(:unit) do |t| 38 | t.pattern = "./unit/**/*_spec.rb" 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /templates/sudoers.rb.erb: -------------------------------------------------------------------------------- 1 | #!<%= cmd_paths['ruby'] %> 2 | # Automatically created by vagrant-lxc 3 | 4 | class Whitelist 5 | class << self 6 | def add(command, *args) 7 | list[command] ||= [] 8 | list[command] << args 9 | end 10 | 11 | def add_regex(regex, *args) 12 | regex_list << [regex, [args]] 13 | end 14 | 15 | def list 16 | @list ||= {} 17 | end 18 | 19 | def regex_list 20 | @regex_list ||= [] 21 | end 22 | 23 | def allowed(command) 24 | list[command] || allowed_regex(command) || [] 25 | end 26 | 27 | def allowed_regex(command) 28 | found = regex_list.find { |r| r[0] =~ command } 29 | return found[1] if found 30 | end 31 | 32 | def run!(argv) 33 | begin 34 | command, args = `which #{argv.shift}`.chomp, argv || [] 35 | check!(command, args) 36 | system "#{command} #{args.join(" ")}" 37 | 38 | exit_code = $?.to_i 39 | exit_code = 1 if exit_code == 256 40 | 41 | exit exit_code 42 | rescue => e 43 | STDERR.puts e.message 44 | exit 1 45 | end 46 | end 47 | 48 | private 49 | def check!(command, args) 50 | allowed(command).each do |checks| 51 | return if valid_args?(args, checks) 52 | end 53 | raise_invalid(command, args) 54 | end 55 | 56 | def valid_args?(args, checks) 57 | return false unless valid_length?(args, checks) 58 | check = nil 59 | args.each_with_index do |provided, i| 60 | check = checks[i] unless check == '**' 61 | return false unless match?(provided, check) 62 | end 63 | true 64 | end 65 | 66 | def valid_length?(args, checks) 67 | args.length == checks.length || checks.last == '**' 68 | end 69 | 70 | def match?(arg, check) 71 | check == '**' || check.is_a?(Regexp) && !!check.match(arg) || arg == check 72 | end 73 | 74 | def raise_invalid(command, args) 75 | raise "Invalid arguments for command #{command}, " << 76 | "provided args: #{args.inspect}" 77 | end 78 | end 79 | end 80 | 81 | base = "<%= lxc_base_path %>" 82 | base_path = %r{\A#{base}/.*\z} 83 | 84 | ## 85 | # Commands from provider.rb 86 | # - Check lxc is installed 87 | Whitelist.add '<%= cmd_paths['which'] %>', /\Alxc-\w+\z/ 88 | 89 | ## 90 | # Commands from driver.rb 91 | # - Container config file 92 | Whitelist.add '<%= cmd_paths['cat'] %>', base_path 93 | # - Shared folders 94 | Whitelist.add '<%= cmd_paths['mkdir'] %>', '-p', base_path 95 | # - Container config customizations and pruning 96 | Whitelist.add '<%= cmd_paths['cp'] %>', '-f', %r{/tmp/.*}, base_path 97 | Whitelist.add '<%= cmd_paths['chown'] %>', 'root:root', base_path 98 | # - Packaging 99 | Whitelist.add '<%= cmd_paths['tar'] %>', '--numeric-owner', '-cvzf', %r{/tmp/.*/rootfs.tar.gz}, '-C', base_path, './rootfs' 100 | Whitelist.add '<%= cmd_paths['chown'] %>', /\A\d+:\d+\z/, %r{\A/tmp/.*/rootfs\.tar\.gz\z} 101 | # - Private network script and commands 102 | Whitelist.add '<%= cmd_paths['ip'] %>', 'addr', 'add', /(\d+|\.)+\/24/, 'dev', /.+/ 103 | Whitelist.add '<%= cmd_paths['ip'] %>', 'link', 'set', /.+/, /(up|down)/ 104 | Whitelist.add '<%= cmd_paths['brctl'] %>', /(addbr|delbr)/, /.+/ 105 | Whitelist.add_regex %r{<%= pipework_regex %>}, '**' 106 | 107 | ## 108 | # Commands from driver/cli.rb 109 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-version' 110 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-ls' 111 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-info', '--name', /.*/ 112 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-info', '--name', /.*/, '-iH' 113 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-create', '-B', /.*/, '--template', /.*/, '--name', /.*/, '**' 114 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-create', '--version' 115 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-destroy', '--name', /.*/ 116 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-start', '-d', '--name', /.*/, '**' 117 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-stop', '--name', /.*/ 118 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-shutdown', '--name', /.*/ 119 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-attach', '--name', /.*/, '**' 120 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-attach', '-h' 121 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-config', 'lxc.lxcpath' 122 | Whitelist.add '<%= cmd_paths['lxc_bin'] %>/lxc-update-config', '-c', /.*/ 123 | 124 | ## 125 | # Commands from driver/action/remove_temporary_files.rb 126 | Whitelist.add '<%= cmd_paths['rm'] %>', '-rf', %r{\A#{base}/.*/rootfs/tmp/.*} 127 | 128 | # Watch out for stones 129 | Whitelist.run!(ARGV) 130 | -------------------------------------------------------------------------------- /vagrant-lxc.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'vagrant-lxc/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "vagrant-lxc" 8 | gem.version = Vagrant::LXC::VERSION 9 | gem.authors = ["Fabio Rehm"] 10 | gem.email = ["fgrehm@gmail.com"] 11 | gem.description = %q{Linux Containers provider for Vagrant} 12 | gem.summary = gem.description 13 | gem.license = 'MIT' 14 | gem.homepage = "https://github.com/fgrehm/vagrant-lxc" 15 | 16 | gem.files = `git ls-files`.split($/) 17 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 18 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 19 | gem.require_paths = ["lib"] 20 | end 21 | -------------------------------------------------------------------------------- /vagrant-spec.config.rb: -------------------------------------------------------------------------------- 1 | # FIXME: Figure out why this doesn't work 2 | if ENV['COVERAGE'] == 'true' 3 | require 'simplecov' 4 | require 'coveralls' 5 | 6 | SimpleCov.start { add_filter '/spec/' } 7 | SimpleCov.command_name 'acceptance' 8 | end 9 | 10 | if ENV['BOX_PATH'] == nil 11 | latest = ENV.fetch('LATEST_BOXES','2014-03-21') 12 | release = ENV.fetch('RELEASE', 'acceptance') 13 | local_path ="#{File.expand_path("../", __FILE__)}/boxes/output/#{latest}/vagrant-lxc-#{release}-amd64.box" 14 | if File.exists?(local_path) 15 | ENV['BOX_PATH'] = local_path 16 | else 17 | raise 'Set $BOX_PATH to the latest released boxes' 18 | end 19 | end 20 | 21 | Vagrant::Spec::Acceptance.configure do |c| 22 | c.component_paths << "spec/acceptance" 23 | c.provider 'lxc', box: ENV['BOX_PATH'], features: ['!suspend'] 24 | end 25 | --------------------------------------------------------------------------------