stages/vagrant: allow configuring synced folders

When guest additions are not installed in VirtualBox Vagrant boxes then
the default shared `/vagrant` directory must be set to `rsync`,
otherwise Vagrant fails to start as the shared directory cannot be
mounted with the `vboxfs` filesystem.

Let's expand the schema to allow for `synced-folders` (currently only
accepted under the `virtualbox` variant of the schema) to allow setting
the bare subset of relevant options to configure this from `images`.

Signed-off-by: Simon de Vlieger <supakeen@redhat.com>
This commit is contained in:
Simon de Vlieger 2025-07-10 10:53:05 +02:00 committed by Achilleas Koutsou
parent 5217c8931f
commit fd6801e380
3 changed files with 114 additions and 1 deletions

View file

@ -59,6 +59,9 @@ def main(tree, options, inputs):
if provider == "virtualbox":
vagrant_content = VAGRANTFILE_VIRTUALBOX.format(mac_address=options["virtualbox"]["mac_address"])
for path, data in options.get("synced_folders", {}).items():
vagrant_content += f' config.vm.synced_folder ".", "{path}", type: "{data["type"]}"\n'
with open(f"{tree}/Vagrantfile", "w", encoding="utf8") as fp:
fp.write(VAGRANTFILE.format(content=vagrant_content))

View file

@ -41,6 +41,25 @@
"virtualbox"
]
},
"synced_folders": {
"type": "object",
"additionalProperties": false,
"properties": {
"/vagrant": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"vboxfs",
"rsync"
]
}
}
}
}
},
"virtualbox": {
"type": "object",
"description": "VirtualBox specific settings",

View file

@ -1,4 +1,5 @@
#!/usr/bin/python3
from unittest import mock
import pytest
@ -15,9 +16,11 @@ STAGE_NAME = "org.osbuild.vagrant"
({"provider": "virtualbox"}, "not valid under any of the given schemas"),
({"provider": "virtualbox", "virtualbox": {}}, "not valid under any of the given schemas"),
({"provider": "libvirt", "virtualbox": {"mac_address": "1"}}, "not valid under any of the given schemas"),
({"provider": "libvirt", "synced_folders": {"/vagrant": {"type": "vboxfs"}}}, "not valid under any of the given schemas"),
# Good API parameters
({"provider": "libvirt"}, ""),
({"provider": "virtualbox", "virtualbox": {"mac_address": "000000000000"}}, ""),
({"provider": "virtualbox", "virtualbox": {"mac_address": "000000000000"},
"synced_folders": {"/vagrant": {"type": "rsync"}}}, ""),
])
# This test validates only API calls using correct and incorrect queries
def test_schema_validation_vagrant(stage_schema, test_data, expected_err):
@ -39,3 +42,91 @@ def test_schema_validation_vagrant(stage_schema, test_data, expected_err):
else:
assert res.valid is False, f"err: {[e.as_dict() for e in res.errors]}"
testutil.assert_jsonschema_error_contains(res, expected_err)
@mock.patch("subprocess.run")
def test_vagrant_writes_file_without_rsync_when_guest_additions(_mock_run, tmp_path, stage_module):
treepath = tmp_path / "tree"
treepath.mkdir()
vagrantfile = treepath / "Vagrantfile"
metadatafile = treepath / "metadata.json"
options = {
"provider": "virtualbox",
"virtualbox": {
"mac_address": "000000000000",
},
"synced_folders": {
"/vagrant": {
"type": "rsync",
},
}
}
inputs = {
"image": {
"path": "/foo",
"data": {
"files": {
"image.vmdk": {"path": "foo.vmdk"}
}
}
}
}
stage_module.main(treepath, options, inputs)
assert vagrantfile.exists()
assert metadatafile.exists()
assert vagrantfile.read_text() == """Vagrant.configure("2") do |config|
config.vm.base_mac = "000000000000"
config.vm.synced_folder ".", "/vagrant", type: "rsync"
end
"""
assert metadatafile.read_text() == '{"provider": "virtualbox"}'
@mock.patch("subprocess.run")
@mock.patch("subprocess.check_output")
def test_vagrant_writes_file_for_libvirt(mock_check_output, _mock_run, tmp_path, stage_module):
mock_check_output.return_value = '{"virtual-size": 1000000000}'
treepath = tmp_path / "tree"
treepath.mkdir()
vagrantfile = treepath / "Vagrantfile"
metadatafile = treepath / "metadata.json"
options = {
"provider": "libvirt",
}
inputs = {
"image": {
"path": "/foo",
"data": {
"files": {
"image.vmdk": {"path": "foo.vmdk"}
}
}
}
}
stage_module.main(treepath, options, inputs)
assert vagrantfile.exists()
assert metadatafile.exists()
assert vagrantfile.read_text() == """Vagrant.configure("2") do |config|
config.vm.provider :libvirt do |libvirt|
libvirt.driver = "kvm"
end
end
"""
assert metadatafile.read_text() == '{"provider": "libvirt", "format": "qcow2", "virtual_size": 1}'