From ae1941d96c23721119761faee156101aff91f707 Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 17:18:02 +0200 Subject: [PATCH 1/4] CI: validate the Vagrantfile on every change The Vagrantfile had no automated check, so a Ruby or environment-handling mistake only surfaced when someone ran `vagrant up`. Check the Ruby syntax, load the configuration under each accepted combination of `LEARN_VM_HOST_IO_CACHE` and `LEARN_VM_STORAGE_CONTROLLER`, and assert that an unrecognized cache value is refused. Without that negative case, deleting the guard would leave the workflow green. No provider is installed on the runner, so `--ignore-provider` is used and the scope is the Vagrantfile itself rather than VirtualBox: a wrong storage controller name still passes and needs a real boot to catch. Runs only on changes to `Vagrantfile` or to the workflow. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/vagrantfile-validation.yml | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/vagrantfile-validation.yml diff --git a/.github/workflows/vagrantfile-validation.yml b/.github/workflows/vagrantfile-validation.yml new file mode 100644 index 000000000..211fdf26f --- /dev/null +++ b/.github/workflows/vagrantfile-validation.yml @@ -0,0 +1,58 @@ +name: Vagrantfile Validation + +on: + push: + paths: + - 'Vagrantfile' + - '.github/workflows/vagrantfile-validation.yml' + pull_request: + branches: + - main + paths: + - 'Vagrantfile' + - '.github/workflows/vagrantfile-validation.yml' + +jobs: + validate: + + runs-on: ubuntu-26.04 + + steps: + - uses: actions/checkout@v4 + + - name: Check Ruby syntax + run: ruby -c Vagrantfile + + - name: Install Vagrant + run: | + sudo apt-get update + sudo apt-get install -y vagrant + + # No provider is installed here, so --ignore-provider is required: the + # goal is to load and validate the Vagrantfile itself, not to talk to + # VirtualBox. A wrong storage controller name therefore still passes; + # only a real boot can catch that. + - name: Default configuration must load + run: vagrant validate --ignore-provider + + - name: Accepted values for the tuning knobs must load + run: | + LEARN_VM_HOST_IO_CACHE=on vagrant validate --ignore-provider + LEARN_VM_HOST_IO_CACHE=off vagrant validate --ignore-provider + LEARN_VM_STORAGE_CONTROLLER="SATA Controller" \ + vagrant validate --ignore-provider + LEARN_VM_STORAGE_CONTROLLER= vagrant validate --ignore-provider + + # The Vagrantfile refuses a cache value it does not recognize, so that a + # typo cannot silently leave the controller in a state the caller did + # not ask for. Assert the refusal, otherwise deleting the check would + # still leave this workflow green. + - name: An unrecognized cache value must be refused + run: | + for value in true false 0 1 ON yes ""; do + if LEARN_VM_HOST_IO_CACHE="$value" \ + vagrant validate --ignore-provider >/dev/null 2>&1; then + echo "LEARN_VM_HOST_IO_CACHE='$value' was accepted; expected refusal" + exit 1 + fi + done From 26bb8b80b51495827d30a9db9a62a700bdd793b2 Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 18:09:44 +0200 Subject: [PATCH 2/4] CI: install Vagrant from the pinned upstream release Ubuntu's archive carries no `vagrant` package, so the workflow failed at `apt-get install -y vagrant` with "Package 'vagrant' has no installation candidate" before reaching any validation. Fetch the release from HashiCorp and pin the version, the way the box version is pinned, rather than depending on the distribution archive or on HashiCorp publishing an apt suite for each Ubuntu codename. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/vagrantfile-validation.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/vagrantfile-validation.yml b/.github/workflows/vagrantfile-validation.yml index 211fdf26f..092a63c0f 100644 --- a/.github/workflows/vagrantfile-validation.yml +++ b/.github/workflows/vagrantfile-validation.yml @@ -23,10 +23,17 @@ jobs: - name: Check Ruby syntax run: ruby -c Vagrantfile + # Ubuntu's archive carries no `vagrant` package, so the release is + # fetched from HashiCorp and pinned, the way the box version is. - name: Install Vagrant + env: + VAGRANT_VERSION: 2.4.9 run: | + curl -fsSLo /tmp/vagrant.deb \ + "https://releases.hashicorp.com/vagrant/${VAGRANT_VERSION}/vagrant_${VAGRANT_VERSION}-1_amd64.deb" sudo apt-get update - sudo apt-get install -y vagrant + sudo apt-get install -y /tmp/vagrant.deb + vagrant --version # No provider is installed here, so --ignore-provider is required: the # goal is to load and validate the Vagrantfile itself, not to talk to From 9a25bec38193285ce8836793104d63818bb56f78 Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 18:10:00 +0200 Subject: [PATCH 3/4] CI: assert the Vagrantfile's own refusal message The negative case accepted any non-zero exit from `vagrant validate`, so it would have passed even with the guard deleted: validation can fail for unrelated reasons and the test could not tell the difference. Capture the output and require the Vagrantfile's own message, reporting the unexpected output when something else fails. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/vagrantfile-validation.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/vagrantfile-validation.yml b/.github/workflows/vagrantfile-validation.yml index 092a63c0f..762fd5c18 100644 --- a/.github/workflows/vagrantfile-validation.yml +++ b/.github/workflows/vagrantfile-validation.yml @@ -57,9 +57,18 @@ jobs: - name: An unrecognized cache value must be refused run: | for value in true false 0 1 ON yes ""; do - if LEARN_VM_HOST_IO_CACHE="$value" \ - vagrant validate --ignore-provider >/dev/null 2>&1; then + if output=$(LEARN_VM_HOST_IO_CACHE="$value" \ + vagrant validate --ignore-provider 2>&1); then echo "LEARN_VM_HOST_IO_CACHE='$value' was accepted; expected refusal" exit 1 fi + # Match the message, not just a non-zero exit: validation can fail + # for unrelated reasons, and a test that accepts any failure would + # pass even if the guard were deleted. + case "$output" in + *'LEARN_VM_HOST_IO_CACHE must be'*) ;; + *) echo "LEARN_VM_HOST_IO_CACHE='$value' failed for another reason:" + echo "$output" + exit 1 ;; + esac done From f62b47a853a9a26ab78fa112c10b026be7317a58 Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 18:26:35 +0200 Subject: [PATCH 4/4] CI: split the Vagrantfile checks and limit the heavy one to PRs Installing Vagrant costs an 87 MB download, which is disproportionate on every push to a branch: changes to the `Vagrantfile` arrive in bursts, so an active month ran it around twenty times. Split the workflow into a `syntax` job, which only parses and needs nothing beyond the Ruby already on the runner, and a `validate` job carrying the download. The syntax check still runs on every push and reports within seconds; validation runs on pull requests, so broken configuration cannot reach `main` either way. `validate` also depends on `syntax`, so a file that does not parse never triggers the download. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/vagrantfile-validation.yml | 23 +++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/vagrantfile-validation.yml b/.github/workflows/vagrantfile-validation.yml index 762fd5c18..f40051fa5 100644 --- a/.github/workflows/vagrantfile-validation.yml +++ b/.github/workflows/vagrantfile-validation.yml @@ -13,7 +13,10 @@ on: - '.github/workflows/vagrantfile-validation.yml' jobs: - validate: + + # Parsing only, and free: Ruby is already on the runner. Runs on every + # push so a broken Vagrantfile is reported within seconds. + syntax: runs-on: ubuntu-26.04 @@ -23,6 +26,24 @@ jobs: - name: Check Ruby syntax run: ruby -c Vagrantfile + # Loads the Vagrantfile, which `ruby -c` does not: the configuration + # executes `ENV.fetch`, the `on`/`off` validation and the port parsing at + # load time, and Vagrant additionally rejects settings that are valid Ruby + # but not valid configuration. + # + # Installing Vagrant costs an 87 MB download, so this is restricted to + # pull requests rather than every push, and gated on the syntax job so a + # file that does not parse never triggers the download. + validate: + + needs: syntax + if: github.event_name == 'pull_request' + + runs-on: ubuntu-26.04 + + steps: + - uses: actions/checkout@v4 + # Ubuntu's archive carries no `vagrant` package, so the release is # fetched from HashiCorp and pinned, the way the box version is. - name: Install Vagrant