A module teaches
ankra migrate one source format. It is an executable named ankra-module-<name> that answers three verbs, each with a JSON request on stdin and a JSON reply on stdout, so it can be written in any language. The built-in docker module is the same contract implemented in-process, and is the reference implementation.When to write one
ankra migrate convert turns a deployment description into an ImportCluster manifest plus the manifests its stack refers to. The docker module reads Compose files, bare Dockerfiles and the running Docker daemon, and is the only module that ships with the CLI. Every other source format is a module somebody writes: a Heroku-style Procfile, a Nomad job specification, a systemd unit directory, or the in-house deployment description your team has been maintaining for years.
Write one when you convert the same format more than once. A module makes the translation repeatable and reviewable: the same input produces the same stack, in a pull request, for every service that uses that format.
How the CLI finds your module
The executable is namedankra-module-<name>, where <name> is the token users type after --module. The CLI looks in ~/.ankra/modules first and then in every directory on PATH, so a module can be installed without touching PATH at all. It must be executable, and a name that collides with a built-in is ignored, so no module can shadow docker.
ankra migrate modules runs the describe verb of every candidate it finds and lists the name, version, source (built-in, or the path it was found at) and summary. A module that fails to load is reported on stderr as skipped <path>: <reason> and the listing carries on, so one broken executable never takes the command down. Pass -o json to see the full description, including the capabilities the table does not show.
ankra migrate convert, export and up choose a module by asking every one of them whether it recognises the directory and taking the most confident answer. Ties are broken alphabetically by module name, and a directory nothing recognises is refused with no module recognises <dir>. --module <name> skips detection and picks one explicitly.
The protocol
One verb per invocation, asargv[1]. The request is JSON on stdin, the reply is JSON on stdout. Exiting non-zero fails the verb, and the module’s stderr becomes the reason the user sees (the last five lines of it). The protocol version is 1; the CLI refuses a module that reports any other, rather than guessing at the shape of its output.
export is optional and answered only by a module that lists "export" under capabilities in its description. The CLI trusts the capability, so a module that omits it is never asked.
describe
No input. Report what the module is:version is the module’s own version, not the CLI’s. file_patterns is shown to users so an empty detection is explainable rather than mysterious. describe must not touch the filesystem: it is called for every listing.
detect
The request is{"dir": "/absolute/path"}. Reply with a confidence from 0 (not mine) to 1 (certain), the files found, and a one-line reason - including for a zero score, because “not mine, and here is why” is an answer rather than a failure:
docker module scores a Compose file 1 and a lone Dockerfile 0.6 for exactly that reason.
convert
The request carries the directory, the names the output will use, and every--option key=value the user passed, untouched, so a module can take input the CLI knows nothing about:
clusteris the ImportCluster the CLI writes tocluster.yaml. Everyfrom_filein its stacks must name a key offiles, and it must have a name; the CLI validates both before it writes anything.fileskeys are paths relative to the output directory. An absolute path, or one containing.., is rejected before a byte is written.warningsare for everything the module could not translate faithfully: an unmappable construct, a value the user has to supply, a credential written in plain text. A partial conversion a person can finish beats none, so warn rather than fail wherever you can.
- Never write to the source directory. Convert reads; the CLI writes.
- Be deterministic. Sort everything. Output that reorders itself on each run cannot be reviewed in a pull request.
- Keep secrets out of ConfigMaps. Put credentials in a
Secretand warn that it needs encrypting withankra cluster encryptbefore it is committed. - Encode dependency order as
parents. A workload that must start after another lists it asparents: [{name, kind: manifest}]. That is how a source format’s start order survives the conversion.
enableServiceLinks: false on the pods you generate. Kubernetes injects a <SERVICE>_PORT variable for every Service in the namespace, and applications that read a variable of the same name break on it.
export, for a module that can dump its data
A module that can also dump the databases behind its source lists"capabilities": ["export"] in describe and answers a fourth verb, which backs ankra migrate export. The request adds the output directory, which the CLI has already created:
output_dir and reply with what you wrote and where it restores to - the Service and Secret that convert generated for the same workload, so the restore needs nothing the user has to look up:
engineispostgresormysql, and nothing else can be restored.formatispg_custom(apg_dump -Fcarchive, PostgreSQL only) orsql(plain SQL, includingpg_dumpall --globals-onlyoutput andmysqldumpfiles).kindisdatabase, which must name itsdatabase, orglobals, which only PostgreSQL has.target.hostmust be a Service intarget.namespace. The platform acceptsdb,db.shop,db.shop.svcanddb.shop.svc.cluster.local, and refuses anything else withDatabase db target host "..." is not a Service in namespace shop- the restore Job carries the target’s credentials, and a host the manifest could point anywhere would carry them out of the cluster.- Artifact paths are relative to
output_dirand may not escape it.manifest.jsonandSHA256SUMSare refused as artifact names, because the CLI writes them itself: it measures every file’s size and SHA-256 sum, rejects one that is empty or was never written, and finalises the export into a self-contained directory thatsha256sum -ccan verify. Do not report sizes or checksums yourself. - Narrate progress on stderr. It is relayed to the user live while the verb runs, which matters when a dump takes minutes, and it is shown as the reason on failure.
- A single artifact above 625 GiB cannot be uploaded, so split a database that large across smaller dumps.
A worked example
examples/modules/ankra-module-procfile in the CLI repository is a complete module in about a hundred lines of Python. It reads a Heroku-style Procfile - one name: command per line - and emits a Deployment per process type, plus a Service for web. The shape of it is the whole contract:
convert exits 2 with the procfile module needs --option image=<registry/repo:tag> on stderr when that option is missing. That is the pattern for input the CLI knows nothing about.
Install it and run it end to end:
Testing a module
The verbs are ordinary processes, so test them without the CLI in the way:ankra migrate detect <dir> prints every module’s confidence, files and reason, most confident first, which is exactly what convert picks from. ankra migrate convert --dry-run prints the rendered cluster.yaml and the files that would be written without writing any of them, and still runs the full validation, so an unsafe path or a manifest pointing at a file you did not return fails there rather than on disk.
What an external module does not get
- No plan preflight in
ankra migrate up. The built-in module can describe an export before running it, which is how the plan reports each database’s size against your free disk. An external module is exported without that preflight, and the plan says so:the <name> module does not describe its export up front; sizes are unknown until the dump runs. - No
--stop-source. Stopping the source’s non-database services before the final dump is the built-in module’s, soankra migrate up --stop-sourceis refused for an external module withthe <name> module cannot stop the source's services; stop them yourself, then run without --stop-source. - Restores are PostgreSQL and MySQL only, in the
sqlandpg_customformats, into a Service in the target namespace. A module can dump anything it likes, but the platform will only restore that.
Next steps
Move a Docker deployment
What the built-in module does, end to end.
Stacks
The shape your
cluster reply has to produce.Import a cluster
The ImportCluster manifest a conversion writes.
Ankra CLI
Install the CLI and browse the rest of its commands.