Conversation
The unit suite stubs HTTP at the wire. That proves the driver sends the right request; it says nothing about whether DigitalOcean accepts it. PLATFORM_SLUG_MAP, in particular, is a claim about the image catalogue that nothing in CI has ever checked. Ten suites under integration/, each creating a real Droplet and asserting on the Droplet that the driver configured it as asked: default, image-slug, server-name, tags, tags-string, user-data, ipv6, monitoring, size-region and firewalls. Assertions live in the shell provisioner rather than a verifier. The script is transferred over the driver's own transport and run on the Droplet, so reaching the machine is part of every assertion, a non-zero exit fails the suite, and there is no verifier licence to satisfy. The assertions read the metadata service, which is the closest thing to reading back the request the driver sent. .github/workflows/integration.yml runs them weekly against main and on demand, never on a pull request: secrets are unavailable to forks and every run costs money. It needs one secret. The SSH key pair and the cloud firewall are created for the run and deleted afterwards, so no long-lived key is stored. Destroy and a tag sweep both run with if: always(), because a suite that leaks a Droplet turns a red build into a recurring bill. Signed-off-by: Tim Smith <tim@mondoo.com>
…line Signed-off-by: Tim Smith <tim@mondoo.com>
This was referenced Aug 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The unit suite stubs HTTP at the wire. That proves the driver sends the right request — it says nothing about whether DigitalOcean accepts it. Nothing in CI has ever reached the v2 API.
PLATFORM_SLUG_MAPis the sharpest example. It is a hardcoded claim about what is in DigitalOcean's image catalogue,spec/readme_spec.rbchecks it against the README, and neither of those checks it against DigitalOcean. Half the map names images that have almost certainly been retired (centos-7,debian-9,fedora-32,ubuntu-16), and the only way anyone finds out is a user's 404. The same is true ofnormalize_list: the unit suite proves"a, b"becomes["a", "b"]in the request body, and the mock happily accepts either.What this adds
Ten suites in
integration/, each creating a real Droplet and asserting on the Droplet that the driver configured it as asked:defaultubuntu-24resolves to an image that still exists.image-slugdebian-13-x64) reaches the API untouched.server-nameserver_namesurvives to the Droplet unchanged.tagstagsas a YAML list reach the Droplet.tags-stringtagsas a delimited string are split, not sent as one tag with a comma in it.user-datauser_datareaches cloud-init verbatim and runs.ipv6ipv6: truegets a routable v6 address configured on the interface.monitoringmonitoring: trueinstalls and startsdo-agent.size-regionfirewallsattach_firewallsplaces the Droplet behind a firewall without locking the transport out.Assertions live in the shell provisioner, not a verifier. The script is transferred over the driver's own transport and executed on the Droplet, so reaching the machine is part of every assertion, a non-zero exit fails the suite, and there is no verifier licence to satisfy. Each script reads the metadata service, which is as close as you can get to reading back the request the driver sent, and needs no credentials on the Droplet.
Not part of
rake default— these cost money.CI
.github/workflows/integration.ymlruns weekly againstmainand on demand, never on a pull request: secrets are unavailable to forks and every run costs money. It also guards ongithub.repository, so a fork's scheduled run does not fire.Setup is one repository secret,
DIGITALOCEAN_ACCESS_TOKEN. Everything else is created for the run and deleted after it — an ed25519 key pair uploaded to the account, and a cloud firewall for thefirewallssuite — so there is no long-lived SSH key sitting in the repository secrets.Three operational details that matter more than they look:
Destroy everythingruns withif: always(). A suite that leaks Droplets on failure turns a red build into a recurring bill.if: always(). Every Droplet is taggedrun-<run_id>, so ifkitchen destroycould not run at all — a cancelled job, a crashed runner —DELETE /v2/droplets?tag_name=is the way back.concurrency: digitalocean-integrationwithcancel-in-progress: false. Droplet limits are per account; two overlapping runs exhaust them and both fail.A full run is ten Droplets, nine of them
s-1vcpu-1gb, alive for a few minutes each — cents, not dollars.Notes for review
kitchen listresolves all ten instances andkitchen diagnoseshows every setting landing where it should — but a maintainer should trigger the workflow once before trusting the schedule. I would expect the first run to shake out at least one stale slug, which is the point.integration/kitchen.ymlis linted as YAML by the shared workflow before ERB runs, so every ERB expression in it uses single quotes internally.arguments:.Verification