Rewrite the driver for modern Test Kitchen - #46
Merged
Conversation
The driver subclassed Kitchen::Driver::SSHBase, removed in Test Kitchen 4.0, so the gem installed against a current Test Kitchen and then failed at load time with a NameError. It is now built on Kitchen::Driver::Base and the transport API. The driver no longer opens its own SSH connections. It works out an address and credentials, puts them into instance state, and lets the configured transport connect. That removes wait_for_sshd, the Fog::SSH usage, and deploy_private_key, which copied ~/.ssh/id_rsa.pub into every instance so that SSHBase's later commands would authenticate. Because the transport is now the thing that connects, Windows works by setting `transport: name: winrm`. Port forwarding and firewall rules use the transport's port instead of a hardcoded 22, and the password CloudStack generates is handed to WinRM the same way it is to SSH. Split into focused units: Client owns the API connection and the async job protocol, ServerOptions builds the deploy parameters, Networking manages public addresses and rules, and Credentials resolves how to log in. Driver#status is implemented, so `kitchen list` reports state from CloudStack rather than assuming. username and port are no longer defaulted by the driver. State overrides transport configuration, so defaulting them meant a driver default of "root" silently beat an explicit `transport: username:` setting. They are now only sent when set on the driver. Fixes found while consolidating the duplicated code: - Four of the six async job checks tested `jobstatus == 0`, treating "still running" as success and reporting an error on the successful result. Failures to create port forwards and firewall rules, and to release public addresses, were silently ignored. - Teardown rescued Fog::Compute::Cloudstack::BadRequest, which does not exist; it is Fog::Cloudstack::Compute::BadRequest. An error during teardown raised NameError from the rescue clause itself. - Instance name generation could loop forever. It shortened until the name fit 64 characters, but the per-part floors totalled 67, so a login longer than 16 characters hung kitchen create. - associate_public_ip returned a variable only assigned on the success path, raising NameError when allocation failed. - Job ids are passed to fog as strings. Fog mutates a hash argument in place, which the old code worked around by re-cloning it every poll. Adds an RSpec suite; the gem previously had no tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 22, 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 driver subclassed
Kitchen::Driver::SSHBase, which Test Kitchen removed in 4.0. The gemspec allowedtest-kitchen < 5, so the gem installed happily against a current Test Kitchen and then failed at load time:That made it unusable with current Cinc Workstation and Chef Workstation. This ports it onto
Kitchen::Driver::Baseand the transport API.What changed
The driver no longer connects to instances. It works out an address and credentials, puts them into instance state, and lets the configured transport connect.
wait_for_sshd, the directFog::SSHusage, anddeploy_private_keyare all gone — the last of those copied your~/.ssh/id_rsa.pubinto every instance it built, purely so SSHBase's later commands would authenticate.Windows/WinRM works by setting
transport: name: winrm. Port forwarding and firewall rules follow the transport's port instead of a hardcoded 22, and the password CloudStack generates for a password-enabled template is handed to WinRM the same way it is to SSH.kitchen listreports real state.Driver#statusis implemented, so an instance destroyed outside Test Kitchen is reported accurately instead of assumed.Split into focused units, largely because the async-job polling was copy-pasted six times:
cloudstack.rbcreate/destroy/status, transport handoffcloudstack/client.rbcloudstack/server_options.rbcloudstack/networking.rbcloudstack/credentials.rbBugs fixed along the way
Consolidating the six duplicated job loops surfaced several real defects:
jobstatus == 0, but CloudStack uses0=running,1=success,2=failed — so they logged an error on success and stayed silent on failure. And since they queried once without polling, they usually read0and said nothing at all. Failures to create port forwards, create firewall rules, and release public addresses were silently ignored.Fog::Compute::Cloudstack::BadRequest— the real one isFog::Cloudstack::Compute::BadRequest. AnyBadRequestduring teardown raisedNameErrorfrom inside the rescue.until joined.length <= 64, but each branch stopped shortening at floors totalling 67 characters. A login longer than 16 characters hungkitchen createindefinitely. Verified: the old algorithm doesn't terminate, stuck at[16, 16, 24, 8] = 67.associate_public_ipreturned a variable only assigned on the success path, raisingNameErrorwhen allocation failed.merge!s a hash argument in place; the old code worked around that by re-cloning the hash on every poll.Breaking changes
test-kitchen >= 3.0.~/.ssh/id_rsa.pubinto instances. Usecloudstack_userdataor a CloudStack keypair if you relied on it.username/portare no longer defaulted toroot/22by the driver. This fixes a silent bug: state overrides transport config, so the old driver default beat an explicittransport: username:setting. Set on the driver they behave as before; left unset yourtransport:config now applies.nameoption is removed — it never had any effect, as the driver set it but readserver_namewhen deploying.Version bumped to 1.0.0 accordingly.
Testing
The gem had no tests. This adds 63 examples:
Verified locally: 63 examples / 0 failures (stable across randomized seeds),
cookstyle --chefstyleclean,markdownlintclean, and the driver resolves by name through Test Kitchen's plugin loader reporting api_version 2.Not verified against a live CloudStack — I don't have one to point at. The API-shape assumptions are drawn from the previous implementation and
fog-cloudstack, so a review from someone with a real deployment would be valuable, particularly on the WinRM path.🤖 Generated with Claude Code