Generates a Encryptor.Ecto.Migration plan skeleton from the host's schemas.
mix encryptor.ecto.gen.plan [--app APP] [--repo REPO]
[--module MODULE] [--output PATH]The generator loads the host's application, reads every Ecto schema in it,
and emits one rewrite block per schema that has at least one field whose
type is a module rather than a built-in Ecto type, with one field line per
such field.
The file it writes does not compile, and that is the feature
ADR-0004 decision 7 fixes three behaviours that each read as a defect once, and this task implements them deliberately. All three come from the same place: a plan decides what happens to production rows, so the facts a generator cannot know are left visibly unanswered rather than guessed.
| What it does | Why | |
|---|---|---|
| Tenant | Emits tenant_from :TODO_tenant_column | Which column identifies the tenant is a fact about the host's domain model that no generator can read off a schema, and guessing it re-encrypts every row under one tenant's key. tenant_from is checked against the schema at mix compile (ADR-0002 decision 2), so the wrong answer cannot reach a row. A skeleton that compiled would be a skeleton somebody ran |
| Target type | Emits to: as a comment, never a value | The generator cannot know which type module a field is being rewritten into, and a half-guess produces a diff that looks reviewed |
| Field list | Over-reports | Per ADR-0004 decision 1 this package tests for no other library's marker function - not cloak_ecto's __cloak__/0, not any other - so a custom Ecto.Type of the host's that encrypts nothing is listed too. A false positive a human deletes is strictly better than a field nobody noticed |
The generated file carries a comment saying all of this, because the person
who runs mix compile next may not be the person who ran this task.
What counts as a candidate
A field is a candidate when its declared type is a module rather than one of
Ecto's own types: a plain Ecto.Type module, or an Ecto.ParameterizedType
(which is what use Encryptor.Ecto.Binary and cloak_ecto's type modules
both produce). Ecto's own type modules - Ecto.Enum, Ecto.UUID - are not
candidates, which is a fact about which application a module belongs to
rather than an inspection of its interface.
Two limits are worth stating rather than discovering:
- Composite types are not candidates. A field typed
{:array, MyType}is skipped, becausefrom:names a module that reads one field's bytes and the array's element type is not that module. Check such fields by hand. - Embedded schemas are skipped. A rewrite names a table, and an embedded schema has none.
This package's own types are candidates like any other, which is correct
rather than incidental: a field moving between tenant strategies is a full
rewrite with from: and to: naming the same module (ADR-0002 decision 3).
Flags
| Flag | Default | |
|---|---|---|
--app APP | the current Mix project's application | Which application's modules to read schemas from |
--repo REPO | the app's :ecto_repos, when it names exactly one | The repo the plan names. Unlike the tenant column this is a fact the host has already declared, so a single configured repo is read rather than guessed; anything else is a usage error |
--module MODULE | <App>.Encryption.Migration | The plan module to generate |
--output PATH | derived from --module under lib/ | Where to write the file |
Exit codes
0 | The file was written; its path is printed |
2 | Usage error; the host's application would not compile; no candidate field was found; the repo could not be resolved; or the output file already exists - the generator never overwrites a plan |
No release equivalent, and that is acknowledged
Every other verb in the family wraps a function on
Encryptor.Ecto.Migrator, so a release can do everything a laptop can
(ADR-0004 decision 6). This one cannot: reading __schema__(:type, field)
needs the host's schema modules compiled and loaded, which a Mix task in the
host's project has and a release command does not. ADR-0004 Q6 records that
as stated rather than decided. It is the right trade here because this verb
writes source into a working tree, which is not a thing a release does.
Summary
Functions
The schemas of modules that have at least one candidate field.
The plan module --module defaults to: <App>.Encryption.Migration.
Where --output defaults to for plan_module: its conventional path under lib/.
The source of the plan skeleton, as it lands in the host's tree.
Functions
The schemas of modules that have at least one candidate field.
Ordered by module name so that re-running the generator against an unchanged application produces an unchanged file.
@spec default_module() :: module()
The plan module --module defaults to: <App>.Encryption.Migration.
Where --output defaults to for plan_module: its conventional path under lib/.
The source of the plan skeleton, as it lands in the host's tree.