# `Tank.Image`
[🔗](https://github.com/oshlabs/tank/blob/v0.2.0/lib/tank/image.ex#L1)

Pulls an OCI / Docker image and assembles it into a root filesystem a
container can run from.

`pull/2` resolves an image reference, downloads the manifest and blobs from
the registry (`Tank.Image.Registry`), verifies every blob against its
digest, and extracts the layers into a root filesystem directory:

    {:ok, %{rootfs: rootfs, config: config}} = Tank.Image.pull("alpine:latest")

The returned `rootfs` is the directory the container runtime pivots into;
`config` is the parsed image config (entrypoint / env / user / ...), returned
for the runtime to apply — `pull/2` itself does not apply it.

## Caching

Everything is cached on disk and survives restarts. The directory is the
`:cache` option, else `config :tank, image_cache: …`, else `~/.cache/tank`:

  * `blobs/<algo>/<hex>` — layer and config blobs, content-addressed;
  * `rootfs/<digest>` — the assembled rootfs, keyed by the image manifest
    digest, so an already-assembled image is reused;
  * `refs/<ref>.json` — a small sidecar mapping a reference to its resolved
    manifest digest and parsed config, written on every successful online
    pull. It is what lets a fully-cached image be served with **no network
    I/O at all** via `offline: true`.

An online pull always resolves the manifest (to follow a moving tag), but the
heavy layer data downloads only on a cache miss — the log says `pulling` then,
and `using cached <ref> (<digest>)` on a hit. So re-pulling an unchanged image
fetches only the (small) manifest, never the layers or assembled rootfs;
`offline: true` skips even the manifest and serves entirely from cache.

Layers extract as the user running the BEAM, so a pulled image satisfies a
root-remapped (userns) rootfs-ownership rule with no `chown`.

Not yet handled: multi-layer whiteouts beyond a single directory's contents,
and `/dev`-style runtime mounts (those are the container runtime's job).

# `option`

```elixir
@type option() :: {:cache, Path.t()} | {:offline, boolean()} | {:refresh, boolean()}
```

Options for `pull/2`:

  * `:cache` — cache directory (default: `config :tank, image_cache: …`, else
    `~/.cache/tank`).
  * `:offline` — when `true`, never touch the network: resolve the manifest,
    config and rootfs entirely from cache, returning
    `{:error, {:not_cached, ref}}` if the image isn't there. Default `false`.
  * `:refresh` — when `true`, ignore cached blobs and the assembled rootfs and
    re-fetch from the registry (a warm cache is otherwise reused). Default
    `false`. Mutually exclusive with `:offline`.

# `pulled`

```elixir
@type pulled() :: %{rootfs: Path.t(), config: map()}
```

# `pull`

```elixir
@spec pull(String.t(), [option()]) :: {:ok, pulled()} | {:error, term()}
```

Pulls `ref` and returns `{:ok, %{rootfs: path, config: config}}`.

`ref` accepts `name`, `name:tag`, `registry/name:tag` and `name@sha256:...`;
a bare name defaults to Docker Hub's `library/` namespace and the `latest`
tag. `config` is the parsed image config JSON (not yet applied to the
workload -- returned for a later high-level run API).

See `t:option/0` for `:cache`, `:offline` and `:refresh`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
