vagrant-xapi 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE +21 -0
  4. data/README.md +460 -4
  5. data/docs/architecture.md +1420 -0
  6. data/docs/operations.md +531 -0
  7. data/lib/vagrant-xapi/action/base.rb +103 -0
  8. data/lib/vagrant-xapi/action/cloud_init.rb +130 -0
  9. data/lib/vagrant-xapi/action/create.rb +127 -0
  10. data/lib/vagrant-xapi/action/destroy.rb +68 -0
  11. data/lib/vagrant-xapi/action/network.rb +229 -0
  12. data/lib/vagrant-xapi/action/package.rb +103 -0
  13. data/lib/vagrant-xapi/action/power.rb +185 -0
  14. data/lib/vagrant-xapi/action/snapshots.rb +43 -0
  15. data/lib/vagrant-xapi/action.rb +317 -0
  16. data/lib/vagrant-xapi/api/client.rb +393 -0
  17. data/lib/vagrant-xapi/api/errors.rb +98 -0
  18. data/lib/vagrant-xapi/api/http_transfer.rb +332 -0
  19. data/lib/vagrant-xapi/api/redactor.rb +77 -0
  20. data/lib/vagrant-xapi/api/session_registry.rb +59 -0
  21. data/lib/vagrant-xapi/api/task.rb +156 -0
  22. data/lib/vagrant-xapi/api/transport.rb +249 -0
  23. data/lib/vagrant-xapi/cap.rb +90 -0
  24. data/lib/vagrant-xapi/command/adopt.rb +92 -0
  25. data/lib/vagrant-xapi/command/base.rb +79 -0
  26. data/lib/vagrant-xapi/command/console.rb +87 -0
  27. data/lib/vagrant-xapi/command/diff.rb +267 -0
  28. data/lib/vagrant-xapi/command/hosts.rb +63 -0
  29. data/lib/vagrant-xapi/command/info.rb +94 -0
  30. data/lib/vagrant-xapi/command/marked.rb +85 -0
  31. data/lib/vagrant-xapi/command/orphans.rb +96 -0
  32. data/lib/vagrant-xapi/command/root.rb +55 -0
  33. data/lib/vagrant-xapi/command/templates.rb +91 -0
  34. data/lib/vagrant-xapi/config.rb +333 -0
  35. data/lib/vagrant-xapi/errors.rb +113 -0
  36. data/lib/vagrant-xapi/extensions.rb +80 -0
  37. data/lib/vagrant-xapi/glue.rb +330 -0
  38. data/lib/vagrant-xapi/legacy/plugin.rb +61 -0
  39. data/lib/vagrant-xapi/legacy/xenapi.rb +129 -0
  40. data/lib/vagrant-xapi/plugin.rb +58 -0
  41. data/lib/vagrant-xapi/provider.rb +135 -0
  42. data/lib/vagrant-xapi/services/adoption.rb +73 -0
  43. data/lib/vagrant-xapi/services/base.rb +242 -0
  44. data/lib/vagrant-xapi/services/box_source.rb +218 -0
  45. data/lib/vagrant-xapi/services/console.rb +115 -0
  46. data/lib/vagrant-xapi/services/content_diff.rb +96 -0
  47. data/lib/vagrant-xapi/services/destroyer.rb +104 -0
  48. data/lib/vagrant-xapi/services/diff_disks.rb +346 -0
  49. data/lib/vagrant-xapi/services/differ.rb +382 -0
  50. data/lib/vagrant-xapi/services/disk_image.rb +122 -0
  51. data/lib/vagrant-xapi/services/disks.rb +281 -0
  52. data/lib/vagrant-xapi/services/exporter.rb +128 -0
  53. data/lib/vagrant-xapi/services/file_diff.rb +249 -0
  54. data/lib/vagrant-xapi/services/guest_files.rb +730 -0
  55. data/lib/vagrant-xapi/services/hosts.rb +55 -0
  56. data/lib/vagrant-xapi/services/image_upload.rb +172 -0
  57. data/lib/vagrant-xapi/services/inventory.rb +225 -0
  58. data/lib/vagrant-xapi/services/ip_detector.rb +107 -0
  59. data/lib/vagrant-xapi/services/lock.rb +293 -0
  60. data/lib/vagrant-xapi/services/lookup.rb +181 -0
  61. data/lib/vagrant-xapi/services/networks.rb +248 -0
  62. data/lib/vagrant-xapi/services/power.rb +108 -0
  63. data/lib/vagrant-xapi/services/rfb.rb +129 -0
  64. data/lib/vagrant-xapi/services/snapshots.rb +81 -0
  65. data/lib/vagrant-xapi/services/template_cache.rb +263 -0
  66. data/lib/vagrant-xapi/services/tunnels.rb +217 -0
  67. data/lib/vagrant-xapi/services/vm_builder.rb +215 -0
  68. data/lib/vagrant-xapi/version.rb +7 -0
  69. data/lib/vagrant-xapi/xenserver/config.rb +69 -0
  70. data/lib/vagrant-xapi/xenserver/migrate.rb +103 -0
  71. data/lib/vagrant-xapi/xenserver/plugin.rb +96 -0
  72. data/lib/vagrant-xapi/xenserver/provider.rb +22 -0
  73. data/lib/vagrant-xapi/xenserver/takeover.rb +79 -0
  74. data/lib/vagrant-xapi.rb +21 -2
  75. data/lib/xenapi.rb +22 -0
  76. data/locales/en.yml +517 -0
  77. data/locales/legacy.en.yml +6 -0
  78. metadata +86 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b665195065d9fd736cebdd45799af247bfef431d9a727ac5345a3231cb14d1b7
4
- data.tar.gz: 1d828520cad20e7273e4cc46bb096c2d42b53be7f98b010071538242470fde3f
3
+ metadata.gz: a2972d8295d44e74480cd9a521aaee0347efe2198c0bbaac979b6ef5dadd54a8
4
+ data.tar.gz: c67899b85bcee85063cac86e678bc53c7fdc73b18efd8fe2228b6e8cd78638dd
5
5
  SHA512:
6
- metadata.gz: 0c7e262ed79ac66a8c2c9ecd834f007f88f5f46724943ba4c9fcb2e3252b133e2efa90e236ca68c6799a81e2a45b672cd7e7da4729a2766c829fccf7815ac5c6
7
- data.tar.gz: f6a5720932d74e45ffe8b2ffe3a9ca66bc05820db871b9c34f095b51c3eafc22261d6d40ad94284cb00ad1d5734a8562c9f3abec42b6f60b2ba8be4213e52c89
6
+ metadata.gz: 7eb3f9724b380016e93ced343b0ef11561ec7d934e8d45db2eaeba2632c8191f7cc083835359500c94e953a7127e786eb7ffc25e75efb86a5e68f56f00299f7e
7
+ data.tar.gz: 7559cf78e3888f431117ce29b0ec503a260e685554c2e29fcba2eda398f94c332dac18021755e6a4f38a45300babd5de977fda8b9aeb8e1a42a0f0b2b040af70
data/CHANGELOG.md ADDED
@@ -0,0 +1,83 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2026-11-09)
4
+
5
+ First version, published on rubygems.org: `vagrant plugin install
6
+ vagrant-xapi`.
7
+
8
+ - XAPI client over JSON-RPC without runtime gems: TLS with CA or SHA-256
9
+ fingerprint pin, redirect to the pool master, one shared session per
10
+ process, long operations as tasks via `event.from`, streaming transfers with
11
+ the session as cookie, secrets filtered from logs and errors. Waiting for
12
+ a task survives a connection loss of up to a minute, and a transfer logs
13
+ in again when the session expired.
14
+ - Box templates imported once per box and SR, under a pool-wide lock; `xapi`,
15
+ `xenserver` and `libvirt` boxes (XVA, `xva_url`, VHD, qcow2, raw).
16
+ - All Vagrant actions: `up`, `halt`, `reload`, `suspend`, `resume`,
17
+ `destroy`, `provision`, `ssh`, `ssh_run`, `package` (XVA, VHD, qcow2) and
18
+ snapshots (including memory, push and pop). On a machine that was never
19
+ created they do not connect to the pool. `package` fails with bsdtar's
20
+ message when compressing the box fails, instead of leaving a truncated box,
21
+ and when the export fails on the host or its download ends early.
22
+ `resume` keeps a machine on an internal network on the host of its peers,
23
+ like `up`.
24
+ - CPUs, memory, BIOS/UEFI, Secure Boot, vTPM, SR choice, fast or full clones,
25
+ affinity host. Turning `vtpm` off, or moving to `:bios`, removes the vTPM
26
+ the plugin created and Secure Boot on the next `reload`.
27
+ - Networks on existing pool networks (by name, UUID or bridge; the default
28
+ network also from `VAGRANT_XAPI_DEFAULT_NETWORK`), internal networks for
29
+ `private_network`, static IPs, IP detection from guest metrics, cloud-init
30
+ or HIMN, SSH via dom0, port forwarding through SSH tunnels. A network that
31
+ does not resolve on `reload` leaves the VM's network interfaces and MACs
32
+ as they were.
33
+ - `config.vm.disk` (extra disks, growing the primary disk, images, ISOs),
34
+ ISOs from ISO SRs through the `isos` option, and `config.vm.cloud_init`
35
+ with `network-config`. CD drives only go in the device slots 0-3 that a
36
+ guest sees; an uploaded ISO without a free slot becomes a read-only disk.
37
+ - Synced folders with rsync, NFS or SMB; rsync is the default unless the
38
+ Vagrantfile sets `config.vm.allowed_synced_folder_types` (Vagrant would
39
+ otherwise pick SMB on macOS and Windows and ask for credentials).
40
+ - `vagrant xapi info`, `console`, `adopt`, `templates`, `orphans`,
41
+ `marked` (everything the plugin created, optionally only your own), and
42
+ `hosts` (the pool's hosts with their certificate fingerprints). `adopt`
43
+ without `--force` only takes VMs of the same Vagrant installation.
44
+ - `vagrant xapi diff`: which files were added, changed or removed since a
45
+ snapshot (`--from`), between two snapshots (`--to`) or between two running
46
+ machines (`--with`), with `--path`, `--exclude`, `--checksum`, `--plain` and
47
+ `--json`, and with `--content` what changed in them (unified diff, up to
48
+ `--content-limit` per file and 16 MiB per side, or the limit when that is
49
+ larger). By default `/tmp`, `/var/tmp`, `/var/cache`, `/var/log`, the
50
+ systemd random seed and `/usr/src` are left out (`--no-default-excludes`
51
+ compares them too); an `--exclude` without a leading `/` matches at any
52
+ depth, and `.` and `..` in a `--path` are resolved. Symlinks are compared
53
+ as links, also when a `--path` goes through one. Of two file systems on one
54
+ mount point only the visible one is compared. The roots of the file systems
55
+ are compared too; a file system that only one side has is named and not
56
+ compared, also a mount point that only one machine of `--with` has; a
57
+ listing that is cut off stops the run. Snapshot disks are read through
58
+ throwaway clones mounted read-only in the guest itself, while a temporary
59
+ udev rule keeps udev and systemd on the guest's own disks; disks left by an
60
+ interrupted run are removed by the next diff, before the VM boots, before
61
+ `package` (which stop when one cannot be removed) and on `destroy`, and its
62
+ mounts and directories in the guest by the next diff (only directories owned
63
+ by root or the SSH user). LVM works: the copy gets identifiers and a VG name
64
+ of its own (`vgimportclone`, limited to the copy's devices, without metadata
65
+ backups), so the guest's own volume groups are never touched.
66
+ - `tls_fingerprint` takes several pins (the certificates of all hosts), so
67
+ any host of the pool can be `host`, also after the master changed.
68
+ - `VAGRANT_XAPI_TLS_FINGERPRINT` and `VAGRANT_XAPI_TLS_CA_FILE` set the TLS
69
+ options from the environment.
70
+ - Vagrantfiles of vagrant-xenserver keep working: the provider `:xenserver`
71
+ with its `xs_*` options, takeover of machines it created, its `destroy`
72
+ that removes all of the VM's own disks, and `vagrant xapi migrate` to move
73
+ machines to `:xapi`.
74
+ - Templates made from a disk image (libvirt, qcow2, raw, VHD boxes) get 1 GiB
75
+ of memory instead of the 256 MiB of "Other install media"; under UEFI GRUB
76
+ could not load the initramfs with that.
77
+ - `vagrant xapi console` asks viewers for a one-time VNC password and puts
78
+ it in the URL it opens, so macOS Screen Sharing connects without asking
79
+ and other local users cannot connect to the forwarded console.
80
+ - Temporary: a stand-in for `XenApi::Client` of the `xenapi` gem, for
81
+ triggers in those Vagrantfiles that `require "xenapi"`. It says so when
82
+ used and will be removed in a later major release. With vagrant-xenserver
83
+ installed too, `require "xenapi"` loads the real gem.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Exonet B.V.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,462 @@
1
- # vagrant-xapi (placeholder)
1
+ # vagrant-xapi
2
2
 
3
- This version reserves the name `vagrant-xapi` for Exonet B.V. It contains no
4
- Vagrant plugin. If you installed it, remove it again:
3
+ A [Vagrant](https://www.vagrantup.com) provider for **XenServer 8** and **XCP-ng 8.3**.
4
+ It talks to XAPI directly over JSON-RPC and has no runtime gem dependencies.
5
+ It needs no Xen Orchestra.
5
6
 
6
- vagrant plugin uninstall vagrant-xapi
7
+ The provider is called `:xapi`. It replaces
8
+ [vagrant-xenserver](https://github.com/xapi-project/vagrant-xenserver) and
9
+ supports every action and provider capability Vagrant core asks for.
10
+ Vagrantfiles written for vagrant-xenserver keep working unchanged (see
11
+ [Migrating from vagrant-xenserver](#migrating-from-vagrant-xenserver)).
12
+
13
+ | Area | What works |
14
+ | --- | --- |
15
+ | Lifecycle | `up`, `halt` (graceful, then clean, then hard), `reload` (re-applies CPU, memory, firmware, VIFs, disks), `suspend`, `resume`, `destroy`, `provision`, `ssh`, `ssh_run` |
16
+ | Boxes | `xapi` boxes (XVA, VHD, qcow2, raw), boxes of vagrant-xenserver (XVA, VHD, `xva_url`), and `libvirt` boxes (qcow2) |
17
+ | VM | `cpus`, `cores_per_socket`, `memory`, `memory_dynamic_min`, BIOS/UEFI, Secure Boot, vTPM, `affinity_host`, tags |
18
+ | Storage | `sr`, `template_sr`, a different SR per disk, fast (copy-on-write) or full clones |
19
+ | Networks | `public_network` and `private_network` on existing networks (name, UUID or bridge), internal networks for `private_network`, static IPs, `forwarded_port` via SSH tunnels, `vagrant port` |
20
+ | Disks | `config.vm.disk`: extra disks, growing the primary disk, disk images, ISOs |
21
+ | cloud-init | `config.vm.cloud_init`, plus a `network-config` for a static IP on the first network |
22
+ | Snapshots | `save`, `restore`, `delete`, `list`, `push`, `pop`, optionally with memory |
23
+ | Package | `vagrant package` as XVA, VHD or qcow2 |
24
+ | Synced folders | rsync (the default), NFS and SMB |
25
+ | Extra | `vagrant xapi info`, `console`, `adopt`, `templates`, `orphans`, `marked`, `hosts`, `diff` |
26
+
27
+ ## Requirements
28
+
29
+ - Vagrant 2.4.x (Ruby 3.0 to 3.4, as Vagrant's gemspec requires).
30
+ - XenServer 8 or XCP-ng 8.3. Only the current releases are supported.
31
+ - A box with a guest agent (xe-guest-utilities or xen-guest-agent), **or**
32
+ cloud-init and a static IP on the first network. XAPI has no DHCP of its own.
33
+ - On the machine that runs Vagrant ("local machine" below: a workstation,
34
+ a build server or a CI runner): `ssh`; `qemu-img` only for qcow2 boxes
35
+ that the host cannot import itself (no qcow2 support, or compressed
36
+ clusters, which most cloud images and many libvirt boxes use); `rsync` if
37
+ you use rsync synced folders.
38
+
39
+ ## Install
40
+
41
+ From [rubygems.org](https://rubygems.org/gems/vagrant-xapi):
42
+
43
+ ```sh
44
+ vagrant plugin install vagrant-xapi
45
+ vagrant plugin update vagrant-xapi # later, to update
46
+ ```
47
+
48
+ From a checkout (the repository is private):
49
+
50
+ ```sh
51
+ git clone https://github.com/exonet/vagrant-xapi.git
52
+ cd vagrant-xapi
53
+ gem build vagrant-xapi.gemspec
54
+ vagrant plugin install ./vagrant-xapi-1.0.0.gem
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ Only `host`, `username` and `password` are required. All three can come
60
+ from environment variables, so they never end up in a Vagrantfile in git:
61
+
62
+ ```sh
63
+ export VAGRANT_XAPI_HOST=xcp-master.example.com
64
+ export VAGRANT_XAPI_USERNAME=root
65
+ export VAGRANT_XAPI_PASSWORD='...'
66
+ ```
67
+
68
+ ```ruby
69
+ Vagrant.configure("2") do |config|
70
+ config.vm.box = "acme/debian-12"
71
+ # XenServer and XCP-ng only run amd64 guests. Without this, Vagrant on an
72
+ # Mac with Apple silicon looks for an arm64 box.
73
+ config.vm.box_architecture = "amd64"
74
+
75
+ config.vm.provider :xapi do |xapi|
76
+ xapi.cpus = 2
77
+ xapi.memory = 2048
78
+ end
79
+ end
80
+ ```
81
+
82
+ ```sh
83
+ vagrant up --provider=xapi
84
+ ```
85
+
86
+ The provider is never picked as the default by accident: Vagrant only
87
+ chooses it when the Vagrantfile has a `config.vm.provider :xapi` block or
88
+ you pass `--provider=xapi`.
89
+
90
+ ## Provider options
91
+
92
+ | Option | Type | Default | Meaning |
93
+ | --- | --- | --- | --- |
94
+ | `host` | String | `VAGRANT_XAPI_HOST` | Pool master or standalone host. A pool member works too: the plugin follows `HOST_IS_SLAVE` to the master. |
95
+ | `port` | Integer | 443 | HTTPS port |
96
+ | `username` | String | `VAGRANT_XAPI_USERNAME` | XAPI user (root, local or AD with an RBAC role) |
97
+ | `password` | String | `VAGRANT_XAPI_PASSWORD` | Never logged or shown in errors |
98
+ | `tls_verify` | Boolean | true | Cannot be turned off; see TLS below |
99
+ | `tls_ca_file` | String | `VAGRANT_XAPI_TLS_CA_FILE` | Your own CA bundle |
100
+ | `tls_fingerprint` | String, Array | `VAGRANT_XAPI_TLS_FINGERPRINT` | SHA-256 pin of the host certificate, with or without colons; replaces the CA check for self-signed certificates. Several pins (a list, or separated by commas) for the certificates of all hosts of the pool, so any host can be `host`: `vagrant xapi hosts` shows them |
101
+ | `api_timeout` | Integer (s) | 60 | Timeout per API call |
102
+ | `task_timeout` | Integer (s) | 3600 | Timeout for imports, exports and copies |
103
+ | `name_label` | String | `<project dir>_<machine>` | VM name in XAPI |
104
+ | `cpus` | Integer | from template | `VCPUs_max` and `VCPUs_at_startup` |
105
+ | `cores_per_socket` | Integer | from template | Must divide `cpus` (or, without `cpus`, the template's vCPUs) |
106
+ | `memory` | Integer (MiB) | from template | Static and dynamic maximum. Templates the plugin makes from a disk image (libvirt, qcow2, raw, VHD) have 1024, instead of the 256 of XAPI's "Other install media" |
107
+ | `memory_dynamic_min` | Integer (MiB) | `memory` | Lower bound for ballooning |
108
+ | `firmware` | `:bios`, `:uefi` | from template | |
109
+ | `secure_boot` | Boolean | false | Only with `:uefi`; with `:bios` it is turned off |
110
+ | `vtpm` | Boolean | false | Only with `:uefi`, and when the pool supports vTPM. Turned off, `vagrant reload` removes the vTPM the plugin created, with its contents; a vTPM that came with the box or an adopted VM stays |
111
+ | `sr` | String | pool default SR | Name or UUID; target for VM disks |
112
+ | `template_sr` | String | `sr` | SR that holds the box template |
113
+ | `clone_mode` | `:fast`, `:full` | `:fast` | `VM.clone` (copy-on-write, same SR as the template) or `VM.copy` (full copy to `sr`) |
114
+ | `affinity_host` | String | nil | Name or UUID of the preferred host |
115
+ | `default_network` | String | `VAGRANT_XAPI_DEFAULT_NETWORK`, else the master's management network | Network (name, UUID or bridge) of the first VIF when the Vagrantfile names no `public_network` |
116
+ | `ip_detection` | Array | `[:guest_metrics, :static, :himn]` | Order of IP sources, see below |
117
+ | `ssh_via_host` | Boolean | false | SSH to the guest through dom0 (ProxyCommand) |
118
+ | `host_ssh_user` | String | `root` | dom0 user for `ssh_via_host` and `:himn` |
119
+ | `shutdown_timeout` | Integer (s) | 120 | After this, `hard_shutdown` |
120
+ | `xva_url` | String | nil | Let the host download the XVA itself (usually in a box's Vagrantfile) |
121
+ | `isos` | Array | `[]` | ISOs on an ISO SR (name or UUID) to attach as CD drives |
122
+ | `tags` | Array | `[]` | Extra XAPI tags |
123
+ | `platform` | Hash | `{}` | Extra `platform` keys, as an escape hatch |
124
+ | `other_config` | Hash | `{}` | Extra `other_config` keys; `vagrant-xapi.*` is reserved |
125
+ | `forward_ssh_port` | Boolean | false | Also tunnel Vagrant's default 2222 → 22 forward |
126
+ | `snapshot_memory` | Boolean | false | `snapshot save` of a running VM includes memory (checkpoint) |
127
+ | `package_format` | `:xva`, `:vhd`, `:qcow2` | `:xva` | Format of `vagrant package` |
128
+
129
+ Validation runs without a connection. Names of SRs, networks and hosts are
130
+ looked up at `vagrant up`.
131
+
132
+ ## Boxes
133
+
134
+ The provider accepts three kinds of boxes and imports each box **once per
135
+ SR** as an XAPI template named `vagrant-xapi: <box> <version>`. Every VM is a
136
+ clone (or copy) of that template. Templates are shared by all users of a
137
+ pool; an import runs under a pool-wide lock, so two people running
138
+ `vagrant up` with the same new box get one template.
139
+
140
+ | Box provider | Content |
141
+ | --- | --- |
142
+ | `xapi` | `metadata.json` with `provider`, `format` (`xva`, `vhd`, `qcow2` or `raw`) and `architecture: "amd64"`; one file `box.<format>`; optionally a Vagrantfile with provider defaults such as `firmware` |
143
+ | `xenserver` | A box of vagrant-xenserver: `box.xva`, `box.vhd`, or only a Vagrantfile with `xva_url` |
144
+ | `libvirt` | `box.img` (qcow2), or several disks in the newer libvirt format |
145
+
146
+ qcow2 is imported directly when the host supports it and the image has no
147
+ compressed clusters (XAPI's qcow2 import cannot read those); otherwise the
148
+ plugin converts it with `qemu-img convert -f qcow2 -O vpc` on the local
149
+ machine. Files a box names must lie inside the box, and qcow2 images with a backing file are refused.
150
+
151
+ A minimal `xapi` box:
152
+
153
+ ```sh
154
+ echo '{"provider":"xapi","format":"xva","architecture":"amd64"}' > metadata.json
155
+ cp my-vm.xva box.xva
156
+ tar czf my.box metadata.json box.xva
157
+ vagrant box add --architecture amd64 --name my/box my.box
158
+ ```
159
+
160
+ `vagrant package` makes such a box from a VM: the VM is halted, a temporary
161
+ clone is stripped of everything that belongs to this pool (VIFs, our
162
+ markings, ISOs), exported, and removed again. Cleaning the guest for
163
+ publication (SSH host keys, machine-id) is up to you, as with other providers.
164
+
165
+ ## Networks
166
+
167
+ XAPI has no NAT and no DHCP. Every VIF hangs on a real network of the pool,
168
+ the IP comes from the guest, and port forwarding runs over SSH tunnels.
169
+
170
+ - The first `public_network` becomes VIF device 0. Without one, device 0 is a
171
+ VIF on `default_network` (or `VAGRANT_XAPI_DEFAULT_NETWORK`). Then the
172
+ other networks follow in Vagrantfile order. `reload` rebuilds the VIFs (MAC
173
+ addresses are kept per device).
174
+ - `public_network` / `private_network` with `network:` (name, UUID or bridge)
175
+ or `bridge:` (e.g. `xenbr0`) use an existing network. Options: `mac:`, `mtu:`,
176
+ `ip:` (IPv4 or IPv6) with `netmask:` and `gateway:` for a static address,
177
+ `nameservers:` (for the first network), `xapi__locking_mode:`.
178
+ - `private_network` without `network:` becomes an **internal network** (no
179
+ PIF), created when missing, marked with the project, and removed with the
180
+ last VM that uses it. `ip:` is required. An internal network only exists
181
+ per host, so all VMs of a project on it run on the same host (the host of
182
+ a VM that already runs on it). A name that belongs to another project is
183
+ refused.
184
+ - Static addresses on device 0 are set through cloud-init (`network-config`
185
+ in the config drive); others through Vagrant's `configure_networks` guest
186
+ capability after boot.
187
+
188
+ ### IP detection
189
+
190
+ `ip_detection` sets the order of sources; waiting stops after
191
+ `config.vm.boot_timeout`.
192
+
193
+ 1. `:guest_metrics`: `<device>/ipv4/0` of VIF 0 from the guest agent.
194
+ 2. `:static`: the `ip:` of the first network.
195
+ 3. `:himn`: an extra VIF on the host internal management network. Only
196
+ reachable from dom0, so only together with `ssh_via_host = true`.
197
+
198
+ ### SSH and port forwarding
199
+
200
+ SSH goes straight to the guest IP. With `ssh_via_host = true` it goes through
201
+ dom0 as a ProxyCommand (`ssh -W %h:%p`) with normal host key checking: add
202
+ the host's key to your `known_hosts` first.
203
+
204
+ Each `forwarded_port` starts a background `ssh -N -L` through the guest. The
205
+ default `22 → 2222` forward is skipped unless `forward_ssh_port = true`. Host
206
+ ports below 1024 and UDP are refused. Tunnels bind to `127.0.0.1` unless you
207
+ set `host_ip:`.
208
+
209
+ ## Disks and cloud-init
210
+
211
+ ```ruby
212
+ config.vm.disk :disk, primary: true, size: "40GB" # grow (never shrink)
213
+ config.vm.disk :disk, name: "data", size: "100GB", xapi__sr: "fast-ssd"
214
+ config.vm.disk :disk, name: "seed", file: "./seed.qcow2" # import an image
215
+ config.vm.disk :dvd, name: "tools", file: "./tools.iso" # upload an ISO
216
+ ```
217
+
218
+ ISOs that already live on an ISO SR are not a `config.vm.disk` (Vagrant
219
+ requires a local file for every `:dvd`), but a provider option:
220
+
221
+ ```ruby
222
+ config.vm.provider :xapi do |xapi|
223
+ xapi.isos = ["debian-12.iso"]
224
+ end
225
+ ```
226
+
227
+ Disks are only resized while the VM is halted; the filesystem in the guest
228
+ does not grow by itself (cloud-init `growpart` usually takes care of it).
229
+
230
+ A guest only sees CD drives in the device slots 0-3. Slot 0 is the boot
231
+ disk, extra disks take the first free slots, and many boxes have an empty CD
232
+ drive of their own (usually slot 3); `vagrant xapi info` shows the slots. An
233
+ uploaded ISO that finds no free slot is attached as a read-only disk instead,
234
+ with a warning; in the guest it is a disk such as `/dev/xvde` that mounts
235
+ like the CD would. An ISO from `isos` gives an error.
236
+ Disks larger than 2 TiB give a warning, because VHD-based SRs cannot hold them.
237
+
238
+ `config.vm.cloud_init` works as in other providers: the plugin builds the
239
+ NoCloud ISO (volume `cidata`), uploads it and attaches it. When its content
240
+ changes, it is replaced.
241
+
242
+ ## Snapshots
243
+
244
+ All `vagrant snapshot` subcommands work. `snapshot_memory = true` makes
245
+ `save` of a running VM a checkpoint including memory; restoring a checkpoint
246
+ resumes the VM.
247
+
248
+ ## Comparing files
249
+
250
+ `vagrant xapi diff` lists the files that changed since a snapshot:
251
+
252
+ ```sh
253
+ vagrant snapshot save before
254
+ vagrant provision
255
+ vagrant xapi diff --from before
256
+ ```
257
+
258
+ ```
259
+ A /etc/nginx/sites-enabled/app
260
+ M /etc/hosts (content)
261
+ m /etc/sudoers.d/app (mode 0440 -> 0640)
262
+ T /opt/app (directory -> symlink)
263
+ D /var/www/html/index.nginx-debian.html
264
+ ```
265
+
266
+ `A` added, `D` removed, `M` other content, `m` other mode or owner only,
267
+ `T` another type (device nodes are compared by type, mode and owner, not by
268
+ their device numbers). `--to SNAPSHOT` compares two snapshots, `--with MACHINE`
269
+ two running machines of the Vagrantfile. `--path /etc` limits the
270
+ comparison (symlinks are compared as links, also in a `--path`: nothing
271
+ below a symlink matches), `--exclude '*.log'` leaves paths out (a pattern
272
+ without a leading `/` matches at any depth, so `--exclude node_modules`
273
+ leaves out every `node_modules`; by default `/tmp`, `/var/tmp`, `/var/cache`,
274
+ `/var/log`, the systemd random seed and `/usr/src` are left out, and
275
+ `--no-default-excludes` compares them too), `--checksum` compares the content
276
+ of every file instead of trusting size and modification time, and `--plain`
277
+ or `--json` are for scripts.
278
+
279
+ `--content` also shows what changed in each file, as a unified diff after
280
+ the list:
281
+
282
+ ```sh
283
+ vagrant xapi diff --from before --content --path /etc
284
+ ```
285
+
286
+ ```
287
+ --- a/etc/hosts
288
+ +++ b/etc/hosts
289
+ @@ -1 +1,2 @@
290
+ 127.0.0.1 localhost
291
+ +10.0.0.5 db
292
+ ```
293
+
294
+ Added and removed files show their whole content. Binary files and files
295
+ larger than `--content-limit` (default `1M`) are named but not shown, also
296
+ when a file grew past it while the diff ran. Each side copies at most
297
+ 16 MiB in all, or `--content-limit` when that is larger; after a large
298
+ upgrade the files beyond that are named too. The content appears in your
299
+ terminal, also of files
300
+ such as `/etc/shadow` when they changed; narrow it with `--path`. It needs
301
+ `diff` on the local machine.
302
+ With `--json` each change gets a `diff` field, or `diff_skipped` with the
303
+ reason; `--plain` does not take `--content`.
304
+
305
+ The machine must be running: the plugin attaches throwaway copies of the
306
+ snapshot's disks to the VM, mounts them read-only in the guest, compares
307
+ there and removes the copies again. It needs a Linux guest with GNU
308
+ findutils, coreutils and util-linux, a guest agent (XAPI only attaches disks
309
+ to a running VM with PV drivers), and ext4, XFS or vfat file systems, also on
310
+ LVM (with lvm2 in the guest: the copy gets LVM identifiers of its own, the
311
+ guest's own volume groups are not touched). Guests with LUKS, btrfs, ZFS or
312
+ RAID are refused; compare two machines with `--with` instead.
313
+
314
+ ## `vagrant xapi`
315
+
316
+ | Command | Purpose |
317
+ | --- | --- |
318
+ | `vagrant xapi info [vm]` | UUID, host, SR, template, VIFs with IPs, snapshots |
319
+ | `vagrant xapi console [vm]` | VNC console through the XAPI console tunnel on a local port, with a one-time VNC password; opens the viewer (`open vnc://` on macOS with the password in the URL, so Screen Sharing connects without asking; `xdg-open` on Linux, where the viewer asks for the password the terminal shows) |
320
+ | `vagrant xapi adopt [vm] UUID` | Attach an existing VM to this machine (or re-adopt its own VM after moving the project); a VM without vagrant-xapi marking, or one that belongs to someone else, needs `--force` |
321
+ | `vagrant xapi templates list` | Templates with box, version, SR, size and number of clones |
322
+ | `vagrant xapi templates prune [--keep N]` | Keep only the newest N versions per box (default 1) |
323
+ | `vagrant xapi templates remove NAME\|UUID` | Remove one template |
324
+ | `vagrant xapi orphans [--destroy]` | VMs of this Vagrant installation whose Vagrant machine no longer exists |
325
+ | `vagrant xapi migrate` | Moves this project's machines of a vagrant-xenserver Vagrantfile from `:xenserver` to `:xapi`, after the Vagrantfile was changed (see Migrating) |
326
+ | `vagrant xapi hosts [--plain\|--json\|--pem]` | The pool's hosts with address, role and the SHA-256 fingerprint of their TLS certificate, as the master reports them; `--pem` prints the certificates, e.g. for `tls_ca_file` |
327
+ | `vagrant xapi diff [vm] --from SNAPSHOT [--to SNAPSHOT]` or `--with MACHINE` | Files that differ between a snapshot and the running machine, two snapshots, or two running machines (see above) |
328
+ | `vagrant xapi marked [--mine] [--project DIR] [--vm UUID] [--box NAME]` | Everything in the pool that vagrant-xapi created (all users); `--mine` for only your own VMs with their snapshots, disks and networks, or narrow it to a project, VM or box; `--plain` or `--json` for scripts |
329
+
330
+ Everything that deletes asks for confirmation unless `--force` is given.
331
+ `prune` and `remove` first show which other users still have clones.
332
+ Removing a template is safe while clones exist: the storage layer keeps the
333
+ shared base disk.
334
+
335
+ ## Security
336
+
337
+ - **TLS** is always verified. For a self-signed certificate set
338
+ `tls_fingerprint` (SHA-256 of the host certificate) or `tls_ca_file`. There
339
+ is no "insecure" option. Get the fingerprint with:
340
+
341
+ ```sh
342
+ openssl s_client -connect xcp-master:443 </dev/null 2>/dev/null \
343
+ | openssl x509 -noout -fingerprint -sha256
344
+ ```
345
+
346
+ Each pool member has its own certificate. Pin the **master** and use it as
347
+ `host`; with a pin on a member the plugin stops and names the master.
348
+ Transfers and consoles that run on another pool member use the
349
+ certificate the master reports for that member.
350
+ - **Secrets**: the password and the session id never appear in logs
351
+ (also not with `VAGRANT_LOG=debug`), error messages, URLs or process
352
+ arguments. Uploads and downloads stream in Ruby with the session as a
353
+ cookie; there is no `curl`.
354
+ - **Subprocesses** (`ssh` for tunnels, `qemu-img`) always run with an argv
355
+ array, never through a shell.
356
+ - **Cleanup** only touches objects carrying the plugin's marking: the tag
357
+ `vagrant` and `other_config` keys `vagrant-xapi.*` (machine, project, user,
358
+ local hostname, installation id, version). `destroy` removes the VM, its snapshots, its own disks and
359
+ its cloud-init ISO; never a template's disks.
360
+ - **Downloads**: an `http://` `xva_url` gives a warning, because the host
361
+ then downloads the image unencrypted.
362
+ - **Supply chain**: no runtime gems.
363
+
364
+ ### Which XAPI role is needed?
365
+
366
+ The plugin creates and destroys VMs, VDIs, VBDs and VIFs, uses
367
+ `pool.other_config` for its locks, and creates internal networks for
368
+ `private_network`. With `pool-admin` everything works.
369
+
370
+ `vm-admin` covers the VM lifecycle, but may not be allowed to write the
371
+ pool's `other_config` or to create networks. In that case the plugin stops
372
+ with a readable message (`lock_permission_denied`).
373
+
374
+ ## Migrating from vagrant-xenserver
375
+
376
+ Vagrantfiles written for vagrant-xenserver keep working unchanged:
377
+
378
+ ```sh
379
+ vagrant plugin uninstall vagrant-xenserver # both cannot be installed
380
+ # install vagrant-xapi as described under Install
381
+ export VAGRANT_XAPI_TLS_FINGERPRINT=AB:CD:...,12:34:... # once; see below
382
+ vagrant up
383
+ ```
384
+
385
+ - `config.vm.provider "xenserver"` with `xs.xs_host`, `xs.name` and the
386
+ other `xs_*` options works, and so do boxes of vagrant-xenserver. Every run
387
+ says once that the compatibility with vagrant-xenserver is in use.
388
+ - Existing machines are taken over: vagrant-xapi finds their VMs by the
389
+ reference vagrant-xenserver stored, marks them and their own disks, and
390
+ carries on with them. No second VM is created.
391
+ - `vagrant destroy` removes all disks of the VM that no other VM uses, as
392
+ vagrant-xenserver did, including disks that triggers attached.
393
+ - The one new setting is TLS. vagrant-xenserver sent its API calls, the
394
+ login included, over plain HTTP on port 80 (also with `xs_use_ssl = true`);
395
+ vagrant-xapi always uses HTTPS and checks the certificate. Set
396
+ `VAGRANT_XAPI_TLS_FINGERPRINT` to the fingerprints of all hosts of the
397
+ pool, separated by commas, or `VAGRANT_XAPI_TLS_CA_FILE`, unless your
398
+ machine already trusts the pool's certificates. Once connected to the
399
+ master with one pin, `vagrant xapi hosts` shows the fingerprints of all
400
+ hosts.
401
+
402
+ **Triggers that call XAPI themselves** (`require "xenapi"` and
403
+ `XenApi::Client`, e.g. to add extra disks or set vCPUs) keep working in 1.x,
404
+ through a stand-in for that gem. The stand-in is **temporary** and says so
405
+ when a trigger uses it. Replace such triggers with the provider's own
406
+ options, which `xs` has as well: `xs.cpus` for vCPUs and `config.vm.disk`
407
+ for extra disks. The plugin chooses the disk slots itself, so check device
408
+ names such as `/dev/xvdi` that other tools rely on.
409
+
410
+ `:xapi` is the provider for new Vagrantfiles. To move a project to it,
411
+ change the provider block with the table below and run `vagrant xapi
412
+ migrate`: it moves the machines from `.vagrant/machines/<name>/xenserver` to
413
+ `xapi`, without which Vagrant would create new VMs.
414
+
415
+ | vagrant-xenserver | vagrant-xapi |
416
+ | --- | --- |
417
+ | `config.vm.provider :xenserver do \|xs\|` | `config.vm.provider :xapi do \|xapi\|` |
418
+ | `xs.xs_host` | `xapi.host` (or `VAGRANT_XAPI_HOST`) |
419
+ | `xs.xs_username` | `xapi.username` |
420
+ | `xs.xs_password` | `xapi.password` |
421
+ | `xs.xs_port` / `xs.xs_use_ssl` | `xapi.port`; always HTTPS (port 443) |
422
+ | `xs.name` | `xapi.name_label` |
423
+ | `xs.memory` | `xapi.memory` |
424
+ | `xs.api_timeout` | `xapi.api_timeout` |
425
+ | `xs.pv = true` | not supported; HVM only (PV boxes are switched to HVM) |
426
+ | `xs.use_himn = true` | `xapi.ip_detection = [:himn]` plus `xapi.ssh_via_host = true` |
427
+ | `xs.xva_url` | `xapi.xva_url` |
428
+ | `config.vm.network :public_network, bridge: "xenbr0"` | the same |
429
+ | Bridges from your own variables, e.g. `bridge: ENV["VAGRANT_XS_BRIDGE_EXTERNAL"]` | The same works; or set `VAGRANT_XAPI_DEFAULT_NETWORK=xenbr1` and leave the external `public_network` out |
430
+
431
+ New compared to vagrant-xenserver: CPUs, UEFI and vTPM, choice of SR,
432
+ private networks, port forwarding, disks, cloud-init, snapshots, package,
433
+ proper TLS, and the `vagrant xapi` commands.
434
+
435
+ ## Development
436
+
437
+ ```sh
438
+ bundle install
439
+ bundle exec rake # unit tests (rspec, against a fake XAPI)
440
+ bundle exec vagrant ... # run Vagrant with the plugin from this checkout
441
+ ```
442
+
443
+ The unit tests run the real Vagrant action stacks against an in-memory XAPI
444
+ (`spec/support/fake_xapi.rb`). Acceptance tests against a real pool are in
445
+ `test/acceptance`; see the README there.
446
+
447
+ ## Documentation
448
+
449
+ - [docs/architecture.md](docs/architecture.md): scope, design, how every
450
+ action and feature maps to XAPI, security, tests and decisions.
451
+ - [docs/operations.md](docs/operations.md): connecting, RBAC, boxes,
452
+ templates, finding and cleaning up objects, locks, troubleshooting,
453
+ development and releasing.
454
+ - [test/acceptance/README.md](test/acceptance/README.md): acceptance tests
455
+ against a real pool.
456
+
457
+ Both files under `docs/` are also in the gem, in
458
+ `~/.vagrant.d/gems/<ruby version>/gems/vagrant-xapi-<version>/docs/`.
459
+
460
+ ## License
461
+
462
+ MIT, like vagrant-xenserver and Vagrant itself.