Proposal: A standard arrangement for Molecule scenarios
Molecule can already test a role, a playbook, or a collection. What it lacks is a standard arrangement for how a scenario is put together, so that is left to each author, and there is nothing shared to lint or validate against. The current lack of a unifying standard has resulted in a learning cliff and conflicting examples. The point of a standard is a path an ordinary role author can follow without building that machinery. I want to propose one.
A scenario is three separable things:
- Machinery: how a test host is created and destroyed, plus the boilerplate that wires the run together.
- Inventory: which images and providers you test against.
- Content: the
converge that runs your code and the verify that checks it.
Only the content should differ between scenarios. Today Molecule makes you copy all three into every one. The standard I am proposing is that machinery and inventory live once, and a scenario holds only its content. Nothing here needs new tooling; every piece already exists, and we need to agree on how they are arranged.
Machinery: the platform collection owns create/destroy. A platform collection ships its provisioning once, as ansible-native playbooks callable by fully-qualified name:
containers.podman/
└── playbooks/
├── molecule_create.yml
└── molecule_destroy.yml
Every scenario then looks the same, whether it tests a role or a collection:
# role: <role>/molecule/default/
# collection: <collection>/extensions/molecule/default/
# identical below the parent either way:
├── molecule.yml
├── create.yml # import_playbook: containers.podman.molecule_create
├── destroy.yml # import_playbook: containers.podman.molecule_destroy
├── converge.yml # yours
└── verify.yml # yours
containers.podman owns the podman create/destroy, amazon.aws owns the EC2 one. You import it by name instead of copying it. (david_igou.molecule_provisioners already implements this pattern; it needs to be a standard, not one person’s collection.) The create.yml/destroy.yml files are one-line imports; they are separate files only because Molecule resolves these by filename, and lifting that so molecule.yml can name the playbook directly is a small enhancement worth making later.
Inventory: which images you test is data, not config. Today the image and provider set lives inside each scenario’s molecule.yml, or in shared config that authors inherit through non-standard tricks. The first means editing every scenario to change the set; the second is the kind of unofficial inheritance that has to be excluded from ansible-lint and schema validation, because there is no standard shape for it.
The set should instead be data the create playbook consumes. This falls out of the create/destroy contract below. Updates to what images to test can be centrally maintained in a single inventory. It is flat data you can open and read, not config merged from up the tree.
Content: converge and verify are all that differ. Once machinery and inventory are standardized, a scenario carries only the code that runs the thing under test and the assertions that check it.
What the standard removes.
- One scenario format across roles, playbooks, and collections, all under the molecule directory. No separate
tests/integration/targets/ tree to keep in sync.
- The image inventory lives in one data file, instead of being repeated in every
molecule.yml or inherited through non-standard config.
- Shared files can be linted and schema-validated, because they have a standard shape instead of being ad hoc.
- The silent zero-host success (a run that matches no hosts still reports pass) is closed by the create contract.
- There is a blessed pattern to point people at, instead of the deprecated
driver:/platforms: examples that are currently the most discoverable.
What I am asking this group to ratify. Four things:
- The location and names of a collection’s provisioning playbooks and the inventory:
playbooks/molecule_<stage>.yml.
- One scenario format shared by a role, a playbook, and a collection.
- The convention that the image and provider inventory is data the create playbook consumes, not config repeated per scenario.
- A small create/destroy input/output contract: instances come from inventory, destroy loops the same group, and create fails loudly instead of reporting success over zero hosts. That contract is what makes provisioners interchangeable across platforms and closes the silent-failure gap. I have worked it out and can bring it as a starting point.
This is a light lift for collections. A collection that manages a thing generally already starts and stops it to test itself, so shipping molecule_create/molecule_destroy is mostly naming work, given clear standards and copy-ready examples. It stays optional: a collection that ships nothing loses nothing. Users / teams are also empowered to create and share their own “providers” using their existing collection distribution methods.
What deserves its own thread. A real standard still has to settle: whether create/destroy ships as a playbook or a role and how it surfaces in Galaxy alongside modules and roles; how inputs are expressed and validated (argument specs, defaults); and who owns the contract and how it is versioned without breaking consumers. I will open that thread and bring the fuller contract if there is appetite here.