Add forge host-setup and macOS/Apple Silicon support - #757
Conversation
c2d9db4 to
ecff58b
Compare
| Vagrant.configure("2") do |config| | ||
| config.vm.synced_folder ".", "/vagrant" | ||
|
|
||
| if ENV['VAGRANT_DEFAULT_PROVIDER'] == 'qemu' && Vagrant.has_plugin?('vagrant-hostmanager') |
There was a problem hiding this comment.
Can you expand on why this is being added? In a previous PR, this plugin was discussed and not implemented. Here are the details to help: #500 (review)
There was a problem hiding this comment.
This part is quite complicated on Mac, we don't have the libvirtd option, I tried exploring with libkrun but I did not found anything for DNS. I had one idea during the implementation of having a small container in the mac host running a dnsServer, but it felt too hacky.
There was a problem hiding this comment.
CC @ekohl given you had strong thoughts about the plugin.
There was a problem hiding this comment.
This was the only working method that I found to keep vagrant as part of the provisioning on M-series, we have https://github.com/libkrun/libkrun, that will use HVF instead of KVM, I could do some exploration to check how the network part would work on HVF, vagrant-qemu also run with HVF but with no libvirt DNS backend 😢
macOS doesn't ship a plain "python" binary, only python3, so setup-environment failed before getting anywhere near installing dependencies.
foremanctl's containers are x86_64-only, and there was no way to run forge on Apple Silicon at all: no host bootstrap step (theforeman#685), and vagrant-qemu needs a different box format and networking setup than vagrant-libvirt. Adds `forge host-setup`, which installs Vagrant and a hypervisor provider (libvirt on Linux, vagrant-qemu on macOS) through forklift's vagrant role. On Apple Silicon the Vagrantfile now boots a native arm64 CentOS Stream 10 guest through vagrant-qemu instead of emulating the whole VM, with socket_vmnet giving it a real IP the way libvirt does on Linux, and vagrant-hostmanager keeping /etc/hosts in sync so the UI is reachable by hostname like it already is on Linux. A new container_binfmt role registers qemu-user-static on the guest so it can still run the existing x86_64 container images. Only the quadlet VM is supported this way for now; client/database/proxy still need libvirt. Running ./forge test against the qemu VM currently fails several API tests, because the suite assumes the Foreman API is reachable at the same address as SSH, which holds for libvirt but not for vagrant-qemu's separate NAT and vmnet interfaces. Not fixed here. Depends on theforeman/forklift#1981.
ecff58b to
8c1ab1d
Compare
Rename forge host-setup to forge bootstrap-vagrant, since host-setup could be read as something broader than what it actually does. container_binfmt.yaml was a thin wrapper around the container_binfmt role plus some extra tasks; move all of it into the role itself and apply the architecture check once, on the role inclusion, instead of on each task. Also drop the systemd reload handler in favor of just running it as the next task, since this is a one-shot provisioning playbook rather than something that benefits from handler semantics. Restructure the development-environment.md prerequisites: a target host over SSH is what's actually required, Vagrant is one way to get one. Vagrant-specific setup, including bootstrap-vagrant and the macOS/qemu details, now lives under "Using Vagrant VMs" instead of being stated as a blanket requirement. Cleaned up the quadlet VM definition in the Vagrantfile: the qemu and libvirt box/network setup is now a single if/else instead of separate unless/if blocks with the hostname assignment sandwiched between them. Verified by destroying and recreating the quadlet VM from scratch, redeploying, and rerunning the test suite; no change in behavior.
client now shares quadlet's qemu setup (image, keypair, network, cloud-init) via a new configure_qemu_guest helper, sized down to match its libvirt memory/cpu. No binfmt on client, it doesn't run foremanctl's containers. Two things needed once there are two qemu guests instead of one: ssh_auto_correct, since both default to SSH port 50022 and would otherwise collide, and hostmanager's manage_guest, so client can resolve quadlet.example.com at all. That surfaced a real conflict: forklift's etc_hosts role also writes guest-to-guest /etc/hosts entries, falling back to each host's ansible_host when it hasn't gathered facts for it yet. On libvirt ansible_host is the real VM IP, so this is fine; on qemu it's 127.0.0.1 (the NAT ssh address), so it was pointing every other guest's hostname at the guest's own loopback. Skipped that provisioner on the qemu path, since hostmanager's manage_guest already covers it correctly. Verified by bringing up quadlet and client together, confirming guest-to-guest ping/DNS resolve correctly, deploying quadlet with remote-execution enabled, and running tests/feature/katello/client_test.py (the same file .github/workflows/test.yml uses for client). Both tests fail during fixture setup with the same pre-existing Connection refused to 127.0.0.1:443 already documented on this PR, not a new client-specific issue; the SSH-based part of that fixture chain succeeds.
Both docs still said only quadlet was supported on macOS. database and proxy remain libvirt-only.
f0b5933 to
257af30
Compare
ekohl
left a comment
There was a problem hiding this comment.
I had kind of hoped that with EL 10 we would also start building ARM packages and avoid the need for all of that emulation.
Right now this feels very complex.
At least all of the code that uses VAGRANT_DEFAULT_PROVIDER needs to be using the proper integration per https://developer.hashicorp.com/vagrant/docs/providers/configuration.
| def fetch_qemu_guest_image | ||
| image_path = File.join(FOREMANCTL_CACHE_DIR, 'CentOS-Stream-GenericCloud-10-latest.aarch64.qcow2') | ||
| unless File.exist?(image_path) | ||
| FileUtils.mkdir_p(FOREMANCTL_CACHE_DIR) | ||
| system('curl', '-fL', '-o', image_path, CENTOS_STREAM10_AARCH64_IMAGE_URL) || raise("Failed to download #{CENTOS_STREAM10_AARCH64_IMAGE_URL}") | ||
| end | ||
| image_path | ||
| end |
There was a problem hiding this comment.
Why can't you just pass in the native file path and let Vagrant do its thing?
| next unless vm.ssh_info && vm.ssh_info[:host] | ||
|
|
||
| result = '' | ||
| vm.communicate.execute('ip addr show eth0') { |type, data| result << data if type == :stdout } |
There was a problem hiding this comment.
result is a string and << relies on mutable strings. Why not:
| vm.communicate.execute('ip addr show eth0') { |type, data| result << data if type == :stdout } | |
| vm.communicate.execute('ip addr show eth0') { |type, data| result = data if type == :stdout } |
| config.vm.provision("etc_hosts", type: 'ansible') do |ansible| | ||
| ansible.playbook = "development/playbooks/etc_host.yml" | ||
| ansible.compatibility_mode = "2.0" | ||
| if ENV['VAGRANT_DEFAULT_PROVIDER'] == 'qemu' && Vagrant.has_plugin?('vagrant-hostmanager') |
There was a problem hiding this comment.
Normally I'd think you'd use:
| if ENV['VAGRANT_DEFAULT_PROVIDER'] == 'qemu' && Vagrant.has_plugin?('vagrant-hostmanager') | |
| config.vm.provider "qemu" do |qemu| | |
| if Vagrant.has_plugin?('vagrant-hostmanager') |
| end | ||
| end | ||
|
|
||
| if ENV['VAGRANT_DEFAULT_PROVIDER'] != 'qemu' |
There was a problem hiding this comment.
I'd love to get rid of this and mandate https://community.theforeman.org/t/setting-up-libvirt-with-dns-resolution-for-vagrant/46567 but otherwise:
| if ENV['VAGRANT_DEFAULT_PROVIDER'] != 'qemu' | |
| config.vm.provider "libvirt" do |libvirt, provider| |
Why are you introducing these changes? (Problem description, related links)
Closes #685. There was no automated way to bootstrap a host for
forge, and no way to run it on Apple Silicon at all: foremanctl's containers are x86_64-only, and vagrant-qemu needs a different box format and networking setup than vagrant-libvirt.What are the changes introduced in this pull request?
forge bootstrap-vagrant, a new command that installs Vagrant and a hypervisor provider (libvirt on Linux, vagrant-qemu on macOS) through forklift's vagrant role, see Add macOS support to the vagrant role forklift#1981/etc/hostsin sync so the UI is reachable by hostnamecontainer_binfmtrole registers qemu-user-static on the guest so it can still run the existing x86_64 container imagessetup-environment, which calledpythoninstead ofpython3and never worked on macOS at allThis depends on theforeman/forklift#1981, which isn't merged yet, so CI/local testing against a fresh checkout won't pick up the new vagrant role until that lands.
How to test this pull request
Steps to reproduce:
./forge bootstrap-vagrantonce./forge vms startto bring up the quadlet VM./foremanctl deploy --initial-admin-password=changeme --tuning developmenthttps://quadlet.example.comloads in a browserTimed a fresh run on an M-series Mac, box image already cached:
forge vms starttook about 1m20s,foremanctl deployabout 16m, UI reachable right after.Only the quadlet VM works this way for now, client/database/proxy still need libvirt. Also worth flagging:
./forge testagainst the qemu VM currently fails several API tests, because the test suite assumes the Foreman API is reachable at the same address as SSH, which is true for libvirt but not for vagrant-qemu's separate NAT and vmnet interfaces. That's a known gap, not something this PR fixes.Checklist