mix ymer_node.build (Ymer Node v0.2.1)

Copy Markdown View Source

Build the release image and tag it with the commit it was built from.

Usage

mix ymer_node.build

No options and no arguments. The version comes from mix.exs, the image is built for the machine's own architecture, and three tags move to it: the version, latest, and the revision — the short sha of the commit the build was made from, marked when the tree carried uncommitted changes. That same revision rides on the image's OCI revision label and inside the node's own serverInfo.version, so docker images, the label, and a live initialize all name one commit. Where no revision can be named — no git, or a directory that is no work tree — the image gets the version tag and latest only, carries no label, and answers with the bare version.

What moves ymer-node:latest

docker-compose.yml runs that tag and never builds it, so no compose up moves it. Only a build does — this task, or a plain docker build -t ymer-node:latest . for a reader without Elixir — or a deliberate docker tag, which is how an older image is put back under the name compose reads. A container picks the new image up on the next docker compose up -d, and mix ymer_node.deploy is what performs that against the install.

Keeping the image set in hand

A revision tag is minted per commit built, so revision tags accumulate where the version tag and latest merely move. Nothing here deletes any of them. Trimming is a judgement about which builds are still worth going back to — yours to make, not a build's — so the commands live here, beside the code that mints the tags, rather than running on their own.

What has accumulated, with the dates to judge it by. Read the dates rather than the order: this listing is not sorted by creation time, and it prints it only to the second.

docker images ymer-node --format '{{.Tag}} {{.ID}} {{.CreatedAt}}'

Which containers exist and what image each is on — the check to make before removing anything, since a container may be the only thing still holding an image:

docker ps -a --format '{{.Names}} {{.Image}} {{.Status}}'

Dropping one revision tag. Without -f, so Docker itself is the safety net: it removes a tag from an image that has others, and refuses outright to delete an image's last tag while any container — running or stopped — still uses it.

docker rmi ymer-node:<revision>

Whether superseded images linger at all depends on the image store. Measured on Docker's containerd store (docker info names it): moving every tag off an image reclaims it there and then, so nothing is left to prune. On a storage driver that does leave them, docker image prune removes what no tag names.

What stops the build, and what only warns

A missing docker, a daemon that does not answer, and a non-zero build all stop with a message naming the next action. A dirty working tree does not: the revision records the dirt instead, refusing would block every build-to-test cycle, and tagging only some of the tags would make the tag set depend on state the caller never passed. The warning says the image will carry uncommitted code, and the build proceeds. Where the check itself cannot run — no git, or a directory that is no work tree — the task says so in one line and still builds.

The subprocess's own output never stops a build either, whatever bytes it carries: Mix.Tasks.YmerNode.Build.LiveOutput is where that is kept.

A build that dies inside mix deps.get usually means the network intercepts TLS, so hex's certificate no longer chains. This image carries no company certificate by design (the Dockerfile header says why), so the failure message names that case; the network itself is never probed.

The module is wrapped in if Mix.env() in [:dev, :test], which keeps it callable under the default :dev and out of the prod release — the release runs the image this task builds and has no use for the builder.

Summary

Functions

Build the image and apply its tags. Returns the version, the revision — nil where none could be named — and the tags applied.

The docker arguments a build runs.

Whether docker answers the given arguments with a zero exit.

The tags a build applies: the version tag, latest, and the revision tag where a revision could be named. The revision tag is the only one that names a commit rather than a position, so it is the one that accumulates.

Fail unless docker is on PATH and its daemon answers, and return the project root.

The revision a build stamps on its image: the short sha of the commit it was made from, marked when the tree carried uncommitted changes. nil where there is no sha to name, and the image then carries no revision at all.

The OCI label key a build stamps the revision on.

Whether the working tree at root is clean, dirty, or unreadable — the state half of tree_status/1, for a caller with no use for the listing.

The tree's state with the git status --porcelain listing that decided it — "" for :clean and :unknown. mix ymer_node.deploy prints the listing under its dirty-tree refusal, from the one read that classified, so what it lists is what the refusal was decided on.

Functions

build!()

Build the image and apply its tags. Returns the version, the revision — nil where none could be named — and the tags applied.

That return is what mix ymer_node.deploy needs: the tags to report, and the revision to compose the wire version it then holds the running container to. Callers run preflight!/0 first.

build_argv(tags, revision)

The docker arguments a build runs.

A revision rides twice — as the build argument the runtime stage turns into an environment variable, and as the OCI revision label — so the label, the tag and the running node's serverInfo.version cannot disagree. Without one, neither argument appears: an empty label would claim a revision the image does not have, and an empty environment variable would leave the wire version ending in a separator with nothing after it.

Examples

iex> Mix.Tasks.YmerNode.Build.build_argv(["ymer-node:1.2.3"], nil)
["build", "-t", "ymer-node:1.2.3", "."]

iex> argv = Mix.Tasks.YmerNode.Build.build_argv(["ymer-node:1.2.3"], "b2a7152")
iex> Enum.take(argv, 3)
["build", "--build-arg", "YMER_NODE_REVISION=b2a7152"]

docker_answers?(args)

Whether docker answers the given arguments with a zero exit.

Public for the same one-home reason as preflight!/0, tree_status/1 and revision_label/0: mix ymer_node.deploy asks the same question of the compose plugin and of a revision tag, and two copies would drift into judging "docker answered" differently.

image_tags(version, revision)

The tags a build applies: the version tag, latest, and the revision tag where a revision could be named. The revision tag is the only one that names a commit rather than a position, so it is the one that accumulates.

Examples

iex> Mix.Tasks.YmerNode.Build.image_tags("3.4.5", "b2a7152")
["ymer-node:3.4.5", "ymer-node:latest", "ymer-node:b2a7152"]

iex> Mix.Tasks.YmerNode.Build.image_tags("1.2.3", nil)
["ymer-node:1.2.3", "ymer-node:latest"]

live_output(device)

A Mix.Tasks.YmerNode.Build.LiveOutput over device. Pass it as System.cmd/3's :into.

Public because mix ymer_node.deploy streams compose's output through the same collectable; one home for how a subprocess reaches the terminal beats two that drift.

Examples

iex> Mix.Tasks.YmerNode.Build.live_output(:stdio)
%Mix.Tasks.YmerNode.Build.LiveOutput{device: :stdio}

preflight!()

Fail unless docker is on PATH and its daemon answers, and return the project root.

Public because mix ymer_node.deploy refuses on the same two conditions before it does anything else; one home for the checks beats two that drift. Both entry points call it, and build!/0 deliberately does not — a deploy would otherwise pay for a second docker info.

revision(short_sha, tree_state)

The revision a build stamps on its image: the short sha of the commit it was made from, marked when the tree carried uncommitted changes. nil where there is no sha to name, and the image then carries no revision at all.

Successive dirty builds of one commit re-point that one tag, the way latest moves.

Examples

iex> Mix.Tasks.YmerNode.Build.revision("b2a7152", :clean)
"b2a7152"

iex> Mix.Tasks.YmerNode.Build.revision("b2a7152", :dirty)
"b2a7152-dirty"

iex> Mix.Tasks.YmerNode.Build.revision(nil, :unknown)
nil

revision_label()

The OCI label key a build stamps the revision on.

Public because mix ymer_node.deploy reads the same key back off the install's container to find out which revision it was running. One home for the key means a rename cannot leave the reader silently finding nothing.

tree_state(root)

Whether the working tree at root is clean, dirty, or unreadable — the state half of tree_status/1, for a caller with no use for the listing.

build!/0 warns on it. mix ymer_node.deploy does not borrow it: its dirty-tree refusal prints the listing, so it reads tree_status/1 instead.

tree_status(root)

The tree's state with the git status --porcelain listing that decided it — "" for :clean and :unknown. mix ymer_node.deploy prints the listing under its dirty-tree refusal, from the one read that classified, so what it lists is what the refusal was decided on.