Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v1.0.4

* Update docs.

## v1.0.3

* Correct License notice in README.md.
Expand Down
120 changes: 88 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ During development, you can also depend on this repository directly:
```elixir
def deps do
[
{:docker_availability,
github: "zacky1972/docker_availability",
branch: "main"}
{:docker_availability, github: "zacky1972/docker_availability", branch: "main"}
]
end
```
Expand All @@ -34,6 +32,20 @@ Then fetch dependencies:
mix deps.get
```

## Which function should I use?

| Need | Use | Result |
| --- | --- | --- |
| A simple yes/no answer | `DockerAvailability.available?/0` | `true` or `false` |
| The resolved Docker CLI path | `DockerAvailability.executable/0` | `{:ok, path}` or `{:error, :docker_not_found}` |
| Diagnostics, version information, or structured error handling | `DockerAvailability.check/0` | `{:ok, info}` or `{:error, reason}` |

Use `available?/0` when a boolean is enough, for example when deciding whether to skip Docker-dependent work.

Use `executable/0` when you only need to know whether the Docker CLI is installed and where it is located. This function does not check whether the Docker daemon is running or reachable.

Use `check/0` when the caller needs diagnostic details, Docker client and server version information, or structured error handling.

## Usage

Use `available?/0` when you only need a boolean answer:
Expand All @@ -46,6 +58,16 @@ else
end
```

Use `executable/0` when you only need to know whether the `docker` executable exists in `PATH`:

```elixir
DockerAvailability.executable()
#=> {:ok, "/usr/bin/docker"}

DockerAvailability.executable()
#=> {:error, :docker_not_found}
```

Use `check/0` when you need diagnostic details:

```elixir
Expand All @@ -68,16 +90,6 @@ case DockerAvailability.check() do
end
```

Use `executable/0` when you only need to know whether the `docker` executable exists in `PATH`:

```elixir
DockerAvailability.executable()
#=> {:ok, "/usr/bin/docker"}

DockerAvailability.executable()
#=> {:error, :docker_not_found}
```

## API

### `DockerAvailability.executable/0`
Expand All @@ -86,40 +98,76 @@ Returns the path to the `docker` executable.

It only checks the current process `PATH` by using `System.find_executable/1`. It does not check whether the Docker daemon is running.

Returns:
Return values:

```elixir
{:ok, "/usr/bin/docker"}
{:error, :docker_not_found}
```

`{:ok, path}` means the executable was found. `path` is the resolved path returned by the current process environment.

- `{:ok, path}` when the executable is found
- `{:error, :docker_not_found}` when the executable is not available in `PATH`
`{:error, :docker_not_found}` means no `docker` executable was available in `PATH`.

### `DockerAvailability.available?/0`

Returns `true` when Docker is installed and usable by the current process.
Returns `true` when Docker is installed and usable by the current process. This is a convenience wrapper around `check/0`.

This is a convenience wrapper around `check/0`. It returns `false` for all error cases, including a missing executable, a failed Docker client command, or an unreachable Docker daemon.
Return values:

### `DockerAvailability.check/0`
```elixir
true
false
```

Performs the full availability check.
It returns `false` for all error cases, including a missing executable, a failed Docker client command, or an unreachable Docker daemon.

Use `check/0` instead when the caller needs to know why Docker is not available.

### `DockerAvailability.check/0`

It verifies that:
Performs the full availability check. It verifies that:

1. the `docker` executable exists in `PATH`
2. the Docker client version can be queried
3. the Docker server version can be queried

Returns `{:ok, info}` when Docker is usable. The `info` map contains:
Returns `{:ok, info}` when Docker is usable:

- `:executable` - the resolved path to the Docker executable
- `:client_version` - the Docker client version reported by the executable
- `:server_version` - the Docker server version reported by the daemon
```elixir
{:ok,
%{
executable: "/usr/bin/docker",
client_version: "24.0.0",
server_version: "24.0.0"
}}
```

The `info` map contains:

| Field | Meaning |
| --- | --- |
| `:executable` | The resolved path to the Docker executable. |
| `:client_version` | The Docker client version reported by the executable. |
| `:server_version` | The Docker server version reported by the daemon. |

The version fields are intended to be strings returned by Docker version commands.

Returns one of the following errors:

- `{:error, :docker_not_found}` when `docker` is not found in `PATH`
- `{:error, {:docker_command_failed, status, output}}` when a Docker client command fails before daemon availability is established
- `{:error, {:docker_unavailable, status, output}}` when the Docker server version cannot be queried
```elixir
{:error, :docker_not_found}
{:error, {:docker_command_failed, status, output}}
{:error, {:docker_unavailable, status, output}}
```

| Error | Meaning |
| --- | --- |
| `:docker_not_found` | No `docker` executable could be found in `PATH`. |
| `{:docker_command_failed, status, output}` | The Docker executable was found, but a Docker command failed while retrieving client information. |
| `{:docker_unavailable, status, output}` | The Docker client exists, but the Docker server or daemon is stopped, unreachable, or inaccessible to the current user. |

`status` is the command exit status. `output` is the trimmed combined standard output and standard error from the Docker command.
`status` is the Docker command exit status. `output` is the trimmed combined standard output and standard error from the Docker command.

## What this library does not do

Expand Down Expand Up @@ -187,12 +235,16 @@ Run the project checks:
mix check
```

Run the maintainer pre-commit checks:
`mix check` validates the project from a contributor-facing perspective. It runs dependency auditing, compilation with warnings treated as errors, formatting checks, static analysis, dependency-lock validation, spelling checks, and Dialyzer.

Run the maintainer pre-commit checks before opening a pull request:

```sh
mix precommit
```

`mix precommit` runs the maintainer workflow, including formatting, static checks, and tests. Contributors should run this command before submitting changes when the full toolchain is available locally.

## Documentation

Generate documentation locally with:
Expand All @@ -201,6 +253,8 @@ Generate documentation locally with:
mix docs
```

The README is the primary user-facing guide and is included in the generated documentation. Keep the README and module documentation in sync when changing public API behavior, examples, or error descriptions.

After the package is published, documentation should be available on HexDocs.

## Requirements
Expand All @@ -212,6 +266,8 @@ After the package is published, documentation should be available on HexDocs.

Copyright (c) 2026 University of Kitakyushu

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
126 changes: 103 additions & 23 deletions lib/docker_availability.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,64 @@ defmodule DockerAvailability do
the `docker` command installed while the daemon is stopped, unreachable, or
inaccessible to the current user.

`executable/0` only checks whether the `docker` executable can be found in
`PATH`. `check/0` performs the full availability check by running Docker
version commands and collecting the client and server versions. `available?/0`
is a boolean convenience wrapper around `check/0`.
## Which function should I use?

* Use `available?/0` when the caller only needs a boolean yes/no answer.
* Use `executable/0` when the caller needs to know whether the Docker CLI
is installed and where it is located.
* Use `check/0` when the caller needs diagnostics, version information, or
structured error handling.

## Return shapes

`executable/0` returns either:

{:ok, "/usr/bin/docker"}
{:error, :docker_not_found}

`available?/0` returns either:

true
false

`check/0` returns either:

{:ok,
%{
executable: "/usr/bin/docker",
client_version: "24.0.0",
server_version: "24.0.0"
}}

or one of these error tuples:

{:error, :docker_not_found}
{:error, {:docker_command_failed, status, output}}
{:error, {:docker_unavailable, status, output}}

## Error reasons

* `:docker_not_found` means no `docker` executable could be found in `PATH`.
* `{:docker_command_failed, status, output}` means the Docker executable was
found, but a Docker command failed while retrieving client information.
* `{:docker_unavailable, status, output}` means the Docker client exists,
but the Docker server or daemon is stopped, unreachable, or inaccessible
to the current user.

`status` is the Docker command exit status. `output` is the trimmed combined
standard output and standard error from the Docker command.

The functions in this module do not install Docker, start the Docker daemon,
or modify Docker state.
"""

@typedoc """
Result returned by `check/0`.

Successful results contain the resolved Docker executable path and the client
and server version values reported by Docker. Error results contain one of the
documented Docker availability reasons.
"""
@type check_result ::
{:ok,
%{
Expand All @@ -25,6 +74,15 @@ defmodule DockerAvailability do
}}
| {:error, reason()}

@typedoc """
Error reason returned by `check/0`.

* `:docker_not_found` means no `docker` executable could be found in `PATH`.
* `{:docker_command_failed, status, output}` means a Docker command failed
while retrieving client information.
* `{:docker_unavailable, status, output}` means the Docker server or daemon
was not available to the current process.
"""
@type reason ::
:docker_not_found
| {:docker_command_failed, non_neg_integer(), String.t()}
Expand All @@ -36,9 +94,15 @@ defmodule DockerAvailability do
This function searches for `docker` in the current process `PATH` by using
`System.find_executable/1`.

Returns `{:ok, path}` when the executable is found.
Return values:

{:ok, "/usr/bin/docker"}
{:error, :docker_not_found}

`{:ok, path}` means the executable was found. `path` is the resolved path
returned by the current process environment.

Returns `{:error, :docker_not_found}` when the executable is not available in
`{:error, :docker_not_found}` means no `docker` executable was available in
`PATH`.

This function does not check whether the Docker daemon is running. Use
Expand All @@ -57,6 +121,11 @@ defmodule DockerAvailability do

This is a boolean convenience wrapper around `check/0`.

Return values:

true
false

Returns `true` only when all of the following conditions are satisfied:

* the `docker` executable is found in `PATH`
Expand All @@ -81,36 +150,48 @@ defmodule DockerAvailability do
executable with `executable/0`, then runs Docker version commands to obtain
both the client and server versions.

Returns `{:ok, info}` when Docker is usable. The returned map contains:
Returns `{:ok, info}` when Docker is usable:

{:ok,
%{
executable: "/usr/bin/docker",
client_version: "24.0.0",
server_version: "24.0.0"
}}

The returned map contains:

* `:executable` - the resolved path to the Docker executable
* `:client_version` - the Docker client version reported by the executable
* `:server_version` - the Docker server version reported by the daemon

The version fields are intended to be strings returned by Docker version
commands.

Returns one of the following error tuples:

* `{:error, :docker_not_found}` when the `docker` executable cannot be found
in `PATH`
* `{:error, {:docker_command_failed, status, output}}` when a Docker client
command fails before daemon availability is established
* `{:error, {:docker_unavailable, status, output}}` when the Docker server
version cannot be queried, typically because the Docker daemon is stopped,
unreachable, or inaccessible to the current user
{:error, :docker_not_found}
{:error, {:docker_command_failed, status, output}}
{:error, {:docker_unavailable, status, output}}

Error reasons:

`status` is the command exit status and `output` is the trimmed combined
* `:docker_not_found` means no `docker` executable could be found in `PATH`.
* `{:docker_command_failed, status, output}` means the Docker executable was
found, but a Docker command failed while retrieving client information.
* `{:docker_unavailable, status, output}` means the Docker client exists,
but the Docker server or daemon is stopped, unreachable, or inaccessible
to the current user.

`status` is the Docker command exit status. `output` is the trimmed combined
standard output and standard error from the Docker command.
"""
@spec check() :: check_result()
def check() do
with {:ok, docker} <- executable(),
{:ok, client_version} <- docker_version(docker, "Client.Version"),
{:ok, server_version} <- docker_version(docker, "Server.Version") do
{:ok,
%{
executable: docker,
client_version: client_version,
server_version: server_version
}}
{:ok, %{executable: docker, client_version: client_version, server_version: server_version}}
end
end

Expand All @@ -128,7 +209,6 @@ defmodule DockerAvailability do
{:error, {:docker_command_failed, status, String.trim(output)}}
end
rescue
e in ErlangError ->
{:error, {:docker_command_failed, 127, Exception.message(e)}}
e in ErlangError -> {:error, {:docker_command_failed, 127, Exception.message(e)}}
end
end
Loading