Skip to content

Latest commit

 

History

History
155 lines (127 loc) · 7.76 KB

cli.md

File metadata and controls

155 lines (127 loc) · 7.76 KB

The eezo command line

Every command bin/eezo and sbt "run <command>" answer, with flags, exit codes and what each one touches. The outputs on this page were captured from the bookshelf application the tutorials build; the version string in them changes with every commit.

Two layers

bin/eezo is a shell script. Four of its commands are its own: new, build, deploy and upgrade. dev runs the sbt task eezoDev and shows a console built from its output: a spinner for the compile, the application's boot lines and logs, and none of sbt's narration. Everything else forwards to the application's own main as sbt "run <command> <args>", so eezo db status and sbt "run db status" are the same code path, and so is the sbt task eezoStatus.

The application's main is inherited from its entry trait. Each layer adds its commands: the http layer's two at the top level, and the database layer's eight under one namespace, db, so that eezo db sync --apply reads as what it touches. help is matched before either, so no layer can shadow it. No arguments runs the application.

Commands

command layer needs what it does
new <name> launcher ~/.eezo/eezo-version scaffolds ./<name>
dev sbt task serves with restart on save, the route listing and the reload client; Ctrl-C stops it
routes [--json] http layer the mounted table and boot's warnings
db database layer the eight schema commands, listed; db help is the same
db status [--json] database layer a database the model against the live database
db sync [--apply] [--force] [--json] database layer a database the diff, applied directly with --apply
db freeze <name...> [--accept-all|--skip-destructive] [--json] database layer writes a migration from the snapshot diff
db migrate [--apply] [--json] database layer a database lists pending migrations, applies them, verifies
db reset [--json] database layer a database drops every table and recreates from the model
db drop [--json] database layer a database drops every table and the ledger
db dump [--json] database layer prints the model's snapshot
db ddl [--json] database layer prints the full DDL, one statement per line
build launcher stages target/eezo/stage/
deploy [--app <name>] [--target fly] launcher the Fly CLI builds, deploys, migrates, waits for health
upgrade launcher Maven Central moves the command line, what new scaffolds, and the project in the current directory to the latest release
help both layers the command table

db freeze takes everything after it that isn't a flag as the migration's name, joined with spaces, so eezo db freeze add isbn to book names the migration add_isbn_to_book. Without a flag it prompts on each destructive change, [d] to drop, [k] to keep and skip.

The application also answers the eight without the db in front, and help doesn't list that spelling. It exists for one caller: a fly.toml written by eezo deploy before the namespace, whose release command says migrate --apply and runs inside the new image. The launcher refuses the bare form and names the db one.

deploy on a first run, with no fly.toml, runs fly launch, and in a session with no terminal that needs --app <name>. Before deploying an application with migrations it checks that a DATABASE_URL or EEZO_DB_URL secret exists. After deploying it polls /eezo/health for up to ninety seconds.

Which commands open a database

status, sync, migrate, reset and drop run with a database installed, and so does the application itself. freeze, dump, ddl, routes and help never touch one: freeze diffs the model against db/schema.json, and the rest read the model alone. dev on the umbrella runs the drift check first, and serves with the database down after a warning.

--json

Every layer command takes --json, which renders the same result through the JSON renderer. Exit codes are the same in both modes. Each body carries a "command" discriminator. A failure line goes to stderr as {"error": "..."}.

$ eezo db status --json
{
  "command": "status",
  "inSync": false,
  "changes": [
    {
      "describe": "- book.notes",
      "destructive": true,
      "risky": false,
      "sql": "alter table \"book\" drop column \"notes\""
    }
  ]
}

The shapes, one per command:

command fields
routes routes, overridden, shadowed (pairs of earlier, later), orphans; a route is method, path, provenance
status inSync, changes
sync applied, refused, changes, blocked
freeze migration (path or null), resolutions (each a change and a decision: accept, skip, manual)
migrate outcome: tampered with problems; upToDate with drift; pending with pending; applied with applied and drift
reset dropped, ddl
drop dropped
dump schema, the snapshot
ddl statements

A change is describe, destructive, risky and sql. A pending migration is number, file and statements.

--json on freeze implies --skip-destructive, because a machine caller must never hang on a prompt.

Exit codes

code meaning
0 done, or in sync
1 db status found drift; db sync --apply refused over destructive or risky changes; db migrate found the files tampered, or drift remains after the ledger is settled; a schema error; a failed deploy or build
2 unknown command, or unknown db command

The launcher passes the application's exit code through. sbt prints three lines of its own when a forked application exits 1, and the launcher drops exactly those, because that exit code is the command's answer and not a build failure:

[error] Nonzero exit code returned from runner: 1
[error] (Compile / run) Nonzero exit code: 1
[error] Total time: ...

What the launcher reads

  • ~/.eezo/eezo-version, written by the installer: the version, scalaVersion, jdkFloor and sbt.version that new writes into a scaffold. The installer reads all four off Maven Central: the latest release from the artifact's metadata, the Scala version from the release's POM, and the JDK floor and sbt version from two properties the POM carries. upgrade runs the installer again and then rewrites the versions in the build.sbt and project/plugins.sbt of the current directory, when they name an eezo artifact. In a checkout of the repository, .eezo-version at its root wins, written by sbt publishLocalForExample with the locally published version, and sbt.version comes from examples/project/build.properties; upgrade refuses to run there.
  • SBT_OPTS, to which it appends --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow, so sbt's own JVM prints no native-access warnings. sbt itself runs with -error -batch, so only compile errors and the application's output reach the terminal. The forked application gets run / javaOptions, not SBT_OPTS.
  • fly.toml, for the app name and region on deploy.

What new writes

<name>/
  project/build.properties      sbt.version
  project/plugins.sbt           addSbtPlugin("io.eezo" % "sbt-eezo" % <version>)
  build.sbt                     the umbrella dependency, EezoPlugin, run / fork := true
  src/main/scala/Main.scala     object Main extends EezoApp, Schema.empty, Routes.table()
  src/main/scala/app/Index.scala   GET /
  src/main/scala/models/        empty
  .gitignore

Related

Configuration is what the commands read from the environment. Migrations is the files the schema commands write and the ledger they keep.