Simplify the Seriatim role

This commit is contained in:
2026-09-08 01:07:23 +00:00
parent 6fb1aa24cc
commit 325a5c7901
10 changed files with 200 additions and 916 deletions

132
README.md
View File

@@ -1,119 +1,49 @@
# Ansible Role: Seriatim
Installs the [Seriatim](https://gitea.maximumdirect.net/eric/seriatim)
transcript processor from source. The role supports Ubuntu 26.04 on `amd64`
and `arm64` hosts and is independently usable outside the D&D pipeline role.
The role manages its build dependencies and a root-owned source checkout,
builds a native static executable, validates it, and safely installs it.
Role source and issue tracking are hosted in the
[ansible-role-seriatim repository](https://gitea.maximumdirect.net/eric/ansible-role-seriatim)
and its [issue tracker](https://gitea.maximumdirect.net/eric/ansible-role-seriatim/issues).
## Requirements
Ansible Core 2.18 or newer is required. Install the pinned collection
dependency before using or validating the role:
```sh
ansible-galaxy collection install -r collections/requirements.yml
```
The consuming play must set `become: true` or provide equivalent root
privileges. The role manages system packages, classic snaps, root-owned source
and state directories, and the installed executable under `/usr/local` by
default.
Installs [Seriatim](https://gitea.maximumdirect.net/eric/seriatim) from source
on Ubuntu 26.04 (`x86_64` or `aarch64`). The role requires gathered facts and
privilege escalation for its default paths and dependency installation.
## Variables
| Variable | Default | Description |
| --- | --- | --- |
| `seriatim_version` | `v1.5.0` | Release identity expected from the installed binary. |
| `seriatim_repository_url` | `https://gitea.maximumdirect.net/eric/seriatim.git` | Source repository URL. |
| `seriatim_source_ref` | `{{ seriatim_version }}` | Git tag, branch, or commit to check out. |
| `seriatim_build_version` | `{{ seriatim_version }}` | Version injected into build metadata. |
| `seriatim_source_dir` | `/usr/local/src/seriatim` | Fully managed source checkout. |
| `seriatim_binary_path` | `/usr/local/bin/seriatim` | Installed executable path. |
| `seriatim_build_state_dir` | `/usr/local/src/seriatim.ansible-build-state` | Sidecar directory for build inputs and output state. |
| `seriatim_manage_build_dependencies` | `true` | Install Git and Go when enabled. |
| `seriatim_go_snap_channel` | `1.26/stable` | Go Snap channel. |
| `seriatim_go_binary` | `/snap/bin/go` | Go executable used to inspect and build. |
| `seriatim_go_build_flags` | `[-trimpath]` | Ordered Go build arguments before `-ldflags`. |
| `seriatim_go_ldflags` | `-s -w -X gitea.maximumdirect.net/eric/seriatim/internal/buildinfo.Version={{ seriatim_build_version }}` | Linker flags passed as one argument. |
| `seriatim_go_package` | `./cmd/seriatim` | Go package built from the checkout. |
```yaml
seriatim_version: v1.5.0
seriatim_repository_url: https://gitea.maximumdirect.net/eric/seriatim.git
seriatim_source_dir: /usr/local/src/seriatim
seriatim_binary_path: /usr/local/bin/seriatim
seriatim_manage_build_dependencies: true
seriatim_go_snap_channel: 1.26/stable
```
For development revisions, set both `seriatim_source_ref` and an honest
`seriatim_build_version`. Released deployments should select immutable tags.
When `seriatim_manage_build_dependencies` is `false`, Git and `/snap/bin/go`
must already be available. Otherwise, the role installs Git with APT and the
classic Go Snap from `seriatim_go_snap_channel`.
When `seriatim_manage_build_dependencies` is enabled, the role installs Git
and converges the classic Go Snap to `seriatim_go_snap_channel`. When disabled,
both Git and the configured Go executable must already be available, and Go
must be version 1.25 or newer.
The checkout is authoritative: local source changes are discarded while
selecting `seriatim_version`. The role builds `./cmd/seriatim` directly to
`seriatim_binary_path` using `/snap/bin/go`, `CGO_ENABLED=0`, `GOWORK=off`,
`GOTOOLCHAIN=local`, `-trimpath`, and release linker metadata. It rebuilds when
the checkout changes, the binary is absent, or `seriatim --version` does not
exactly match the selected version.
The checkout is authoritative: tracked changes and all untracked or ignored
files beneath `seriatim_source_dir` are discarded during convergence. The
repository URL may point to a private remote, but authentication must be
provided independently through the managed host's Git configuration or SSH
environment; this role does not manage credentials or accept unknown host
keys.
The source tree is recursively owned by `root:root`. The installed binary is a
regular file owned by `root:root` with mode `0755`.
## Build and installation behavior
Builds run from the selected checkout with `GOWORK=off`,
`GOTOOLCHAIN=local`, `CGO_ENABLED=0`, the configured ordered build flags, and
the upstream version-injection linker flags. The resulting executable is
staged and must report the exact configured version before it atomically
replaces `seriatim_binary_path`. It is installed as `root:root` mode `0755`.
The role records readable inputs, their SHA-256 fingerprint, and the installed
binary checksum beneath `seriatim_build_state_dir`. A source commit, compiler
identity, Go path, version, flags, linker flags, package, or fixed environment
change triggers a rebuild. Missing or corrupt state, a modified binary, or an
incorrect installed version also triggers rebuilding. Failed compilation or
staged validation leaves the previous installed binary and successful state
untouched.
For example, development from a branch must identify both source and output:
## Example
```yaml
seriatim_source_ref: feature/example
seriatim_build_version: v1.5.0-dev
- hosts: seriatim_hosts
become: true
roles:
- role: ansible-role-seriatim
```
## Static validation
## Validation
```sh
ANSIBLE_COLLECTIONS_PATH="$PWD/.ansible/collections" \
../.venv/bin/ansible-playbook -i tests/inventory tests/converge.yml \
--syntax-check
ANSIBLE_COLLECTIONS_PATH="$PWD/.ansible/collections" \
../.venv/bin/ansible-lint .
```
## Development and integration validation
Create the workspace virtual environment described by the control repository
and run the complete local suite from this repository with:
The integration driver installs pinned collection dependencies, runs syntax
checking and ansible-lint, converges and verifies the default release, then
requires an immediate repeat with `changed=0` and `failed=0`:
```sh
tests/integration
```
The driver installs the pinned collection into the ignored local `.ansible`
directory, runs static checks, and then mutates the local host using privilege
escalation. It manages `/usr/local/src/seriatim`,
`/usr/local/src/seriatim.ansible-build-state`, and
`/usr/local/bin/seriatim`; do not run it where those paths contain data that
must be preserved. It verifies convergence, idempotence, platform guards,
authoritative source cleanup, compiler-version invalidation, and preservation
of a working installation after a failed build.
The driver uses temporary Go wrappers and removes them on exit. It attempts to
restore the default role state after a failure. Recovery consists only of
rerunning `tests/integration` or the default `tests/converge.yml`; no manual
deletion of managed paths is required.
## License
BSD-3-Clause

View File

@@ -1,16 +1,7 @@
---
seriatim_version: v1.5.0
seriatim_repository_url: https://gitea.maximumdirect.net/eric/seriatim.git
seriatim_source_ref: "{{ seriatim_version }}"
seriatim_build_version: "{{ seriatim_version }}"
seriatim_source_dir: /usr/local/src/seriatim
seriatim_binary_path: /usr/local/bin/seriatim
seriatim_build_state_dir: /usr/local/src/seriatim.ansible-build-state
seriatim_manage_build_dependencies: true
seriatim_go_snap_channel: 1.26/stable
seriatim_go_binary: /snap/bin/go
seriatim_go_build_flags:
- -trimpath
seriatim_go_ldflags: >-
-s -w -X gitea.maximumdirect.net/eric/seriatim/internal/buildinfo.Version={{ seriatim_build_version }}
seriatim_go_package: ./cmd/seriatim

View File

@@ -6,19 +6,11 @@ argument_specs:
seriatim_version:
type: str
required: true
description: Release identity expected from the installed binary.
description: Git revision and version injected into the executable.
seriatim_repository_url:
type: str
required: true
description: Git repository containing Seriatim source.
seriatim_source_ref:
type: str
required: true
description: Git tag, branch, or commit to check out.
seriatim_build_version:
type: str
required: true
description: Version value injected into build metadata.
seriatim_source_dir:
type: path
required: true
@@ -27,10 +19,6 @@ argument_specs:
type: path
required: true
description: Installed executable path.
seriatim_build_state_dir:
type: path
required: true
description: Sidecar directory holding successful build state.
seriatim_manage_build_dependencies:
type: bool
required: true
@@ -39,20 +27,3 @@ argument_specs:
type: str
required: true
description: Go Snap channel to install.
seriatim_go_binary:
type: path
required: true
description: Go executable used for discovery and builds.
seriatim_go_build_flags:
type: list
elements: str
required: true
description: Ordered arguments passed to go build before ldflags.
seriatim_go_ldflags:
type: str
required: true
description: Linker flags passed to go build as one argument.
seriatim_go_package:
type: str
required: true
description: Go package built from the source checkout.

View File

@@ -1,316 +0,0 @@
---
- name: Construct canonical Seriatim build inputs
ansible.builtin.set_fact:
seriatim_build_inputs: >-
{{ {
'source_commit': seriatim_source_commit,
'go_version': seriatim_go_version_result.stdout,
'go_binary': seriatim_go_binary,
'build_version': seriatim_build_version,
'build_flags': seriatim_go_build_flags,
'ldflags': seriatim_go_ldflags,
'package': seriatim_go_package,
'environment': {
'GOWORK': 'off',
'GOTOOLCHAIN': 'local',
'CGO_ENABLED': '0'
}
} | to_nice_json(indent=2, sort_keys=true) }}
- name: Calculate desired Seriatim build fingerprint
ansible.builtin.set_fact:
seriatim_desired_fingerprint: "{{ seriatim_build_inputs | hash('sha256') }}"
seriatim_stored_build_inputs: ""
seriatim_stored_fingerprint: ""
seriatim_stored_checksum: ""
- name: Inspect Seriatim binary parent directory
ansible.builtin.stat:
path: "{{ seriatim_binary_path | dirname }}"
register: seriatim_binary_parent_stat
- name: Require an existing Seriatim binary parent to be a directory
ansible.builtin.assert:
that:
- not seriatim_binary_parent_stat.stat.exists or seriatim_binary_parent_stat.stat.isdir
fail_msg: >-
Seriatim binary parent exists but is not a directory:
{{ seriatim_binary_path | dirname }}.
- name: Create missing Seriatim binary parent directory
ansible.builtin.file:
path: "{{ seriatim_binary_path | dirname }}"
state: directory
owner: root
group: root
mode: "0755"
when: not seriatim_binary_parent_stat.stat.exists
- name: Inspect installed Seriatim binary
ansible.builtin.stat:
path: "{{ seriatim_binary_path }}"
checksum_algorithm: sha256
register: seriatim_installed_binary_stat
- name: Require a regular installed Seriatim binary path
ansible.builtin.assert:
that:
- not seriatim_installed_binary_stat.stat.exists or seriatim_installed_binary_stat.stat.isreg
fail_msg: >-
Seriatim binary path exists but is not a regular file:
{{ seriatim_binary_path }}. Move the unexpected object before rerunning the role.
- name: Inspect Seriatim build state directory
ansible.builtin.stat:
path: "{{ seriatim_build_state_dir }}"
register: seriatim_build_state_dir_stat
- name: Remove malformed Seriatim build state directory object
ansible.builtin.file:
path: "{{ seriatim_build_state_dir }}"
state: absent
when:
- seriatim_build_state_dir_stat.stat.exists or
(seriatim_build_state_dir_stat.stat.islnk | default(false))
- not (seriatim_build_state_dir_stat.stat.isdir | default(false))
- name: Create missing Seriatim build state directory
ansible.builtin.file:
path: "{{ seriatim_build_state_dir }}"
state: directory
owner: root
group: root
mode: "0755"
when: not (seriatim_build_state_dir_stat.stat.isdir | default(false))
- name: Inspect Seriatim build input state
ansible.builtin.stat:
path: "{{ seriatim_build_state_dir }}/build-inputs.json"
register: seriatim_build_inputs_state_stat
- name: Inspect Seriatim fingerprint state
ansible.builtin.stat:
path: "{{ seriatim_build_state_dir }}/fingerprint.sha256"
register: seriatim_fingerprint_state_stat
- name: Inspect Seriatim checksum state
ansible.builtin.stat:
path: "{{ seriatim_build_state_dir }}/binary.sha256"
register: seriatim_checksum_state_stat
- name: Remove malformed Seriatim build state objects
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop:
- path: "{{ seriatim_build_state_dir }}/build-inputs.json"
exists: "{{ seriatim_build_inputs_state_stat.stat.exists }}"
is_regular: "{{ seriatim_build_inputs_state_stat.stat.isreg | default(false) }}"
- path: "{{ seriatim_build_state_dir }}/fingerprint.sha256"
exists: "{{ seriatim_fingerprint_state_stat.stat.exists }}"
is_regular: "{{ seriatim_fingerprint_state_stat.stat.isreg | default(false) }}"
- path: "{{ seriatim_build_state_dir }}/binary.sha256"
exists: "{{ seriatim_checksum_state_stat.stat.exists }}"
is_regular: "{{ seriatim_checksum_state_stat.stat.isreg | default(false) }}"
when:
- item.exists | bool
- not item.is_regular | bool
- name: Read stored Seriatim fingerprint
ansible.builtin.slurp:
src: "{{ seriatim_build_state_dir }}/fingerprint.sha256"
register: seriatim_stored_fingerprint_result
failed_when: false
when:
- seriatim_fingerprint_state_stat.stat.isreg | default(false)
- seriatim_fingerprint_state_stat.stat.readable | default(false)
- name: Read stored Seriatim build inputs
ansible.builtin.slurp:
src: "{{ seriatim_build_state_dir }}/build-inputs.json"
register: seriatim_stored_build_inputs_result
failed_when: false
when:
- seriatim_build_inputs_state_stat.stat.isreg | default(false)
- seriatim_build_inputs_state_stat.stat.readable | default(false)
- name: Read stored Seriatim checksum
ansible.builtin.slurp:
src: "{{ seriatim_build_state_dir }}/binary.sha256"
register: seriatim_stored_checksum_result
failed_when: false
when:
- seriatim_checksum_state_stat.stat.isreg | default(false)
- seriatim_checksum_state_stat.stat.readable | default(false)
- name: Record stored Seriatim state values
ansible.builtin.set_fact:
seriatim_stored_build_inputs: >-
{{ seriatim_stored_build_inputs_result.content | b64decode | trim }}
seriatim_stored_fingerprint: >-
{{ seriatim_stored_fingerprint_result.content | b64decode | trim }}
seriatim_stored_checksum: >-
{{ seriatim_stored_checksum_result.content | b64decode | trim }}
when:
- seriatim_build_inputs_state_stat.stat.isreg | default(false)
- seriatim_build_inputs_state_stat.stat.readable | default(false)
- seriatim_fingerprint_state_stat.stat.isreg | default(false)
- seriatim_fingerprint_state_stat.stat.readable | default(false)
- seriatim_checksum_state_stat.stat.isreg | default(false)
- seriatim_checksum_state_stat.stat.readable | default(false)
- not (seriatim_stored_build_inputs_result.failed | default(true))
- not (seriatim_stored_fingerprint_result.failed | default(true))
- not (seriatim_stored_checksum_result.failed | default(true))
- name: Read installed Seriatim version
ansible.builtin.command:
argv:
- "{{ seriatim_binary_path }}"
- --version
register: seriatim_installed_version_result
changed_when: false
failed_when: false
when: seriatim_installed_binary_stat.stat.exists
- name: Decide whether Seriatim requires rebuilding
ansible.builtin.set_fact:
seriatim_build_required: >-
{{ not seriatim_installed_binary_stat.stat.exists
or not seriatim_build_inputs_state_stat.stat.exists
or not seriatim_fingerprint_state_stat.stat.exists
or not seriatim_checksum_state_stat.stat.exists
or seriatim_stored_build_inputs != seriatim_build_inputs
or (seriatim_stored_build_inputs | hash('sha256')) != seriatim_stored_fingerprint
or (seriatim_stored_build_inputs | hash('sha256')) != seriatim_desired_fingerprint
or seriatim_stored_fingerprint != seriatim_desired_fingerprint
or seriatim_stored_checksum != (seriatim_installed_binary_stat.stat.checksum | default(''))
or (seriatim_installed_version_result.rc | default(1)) != 0
or (seriatim_installed_version_result.stdout | default('')) != 'seriatim version ' ~ seriatim_build_version }}
- name: Build, validate, and install Seriatim
when: seriatim_build_required
block:
- name: Create temporary Seriatim build directory
ansible.builtin.tempfile:
state: directory
prefix: ansible-seriatim-build-
register: seriatim_build_tempdir
- name: Build staged Seriatim binary
ansible.builtin.command:
argv: >-
{{ [seriatim_go_binary, 'build']
+ seriatim_go_build_flags
+ ['-ldflags', seriatim_go_ldflags,
'-o', seriatim_build_tempdir.path ~ '/seriatim',
seriatim_go_package] }}
chdir: "{{ seriatim_source_dir }}"
environment:
GOWORK: "off"
GOTOOLCHAIN: local
CGO_ENABLED: "0"
changed_when: true
- name: Validate staged Seriatim binary
ansible.builtin.command:
argv:
- "{{ seriatim_build_tempdir.path }}/seriatim"
- --version
register: seriatim_staged_version_result
changed_when: false
failed_when: >-
seriatim_staged_version_result.rc != 0 or
seriatim_staged_version_result.stdout != 'seriatim version ' ~ seriatim_build_version
- name: Install validated Seriatim binary
ansible.builtin.copy:
src: "{{ seriatim_build_tempdir.path }}/seriatim"
dest: "{{ seriatim_binary_path }}"
remote_src: true
owner: root
group: root
mode: "0755"
- name: Validate installed Seriatim binary
ansible.builtin.command:
argv:
- "{{ seriatim_binary_path }}"
- --version
register: seriatim_new_installed_version_result
changed_when: false
failed_when: >-
seriatim_new_installed_version_result.rc != 0 or
seriatim_new_installed_version_result.stdout != 'seriatim version ' ~ seriatim_build_version
- name: Calculate installed Seriatim checksum
ansible.builtin.stat:
path: "{{ seriatim_binary_path }}"
checksum_algorithm: sha256
register: seriatim_new_binary_stat
- name: Create Seriatim build state directory
ansible.builtin.file:
path: "{{ seriatim_build_state_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Record Seriatim build inputs
ansible.builtin.copy:
content: "{{ seriatim_build_inputs }}\n"
dest: "{{ seriatim_build_state_dir }}/build-inputs.json"
owner: root
group: root
mode: "0644"
- name: Record Seriatim build fingerprint
ansible.builtin.copy:
content: "{{ seriatim_desired_fingerprint }}\n"
dest: "{{ seriatim_build_state_dir }}/fingerprint.sha256"
owner: root
group: root
mode: "0644"
- name: Record installed Seriatim checksum
ansible.builtin.copy:
content: "{{ seriatim_new_binary_stat.stat.checksum }}\n"
dest: "{{ seriatim_build_state_dir }}/binary.sha256"
owner: root
group: root
mode: "0644"
always:
- name: Remove temporary Seriatim build directory
ansible.builtin.file:
path: "{{ seriatim_build_tempdir.path }}"
state: absent
when: seriatim_build_tempdir.path is defined
- name: Converge installed Seriatim binary metadata
ansible.builtin.file:
path: "{{ seriatim_binary_path }}"
state: file
owner: root
group: root
mode: "0755"
- name: Converge Seriatim build state directory metadata
ansible.builtin.file:
path: "{{ seriatim_build_state_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Converge Seriatim build state file metadata
ansible.builtin.file:
path: "{{ seriatim_build_state_dir }}/{{ item }}"
state: file
owner: root
group: root
mode: "0644"
loop:
- build-inputs.json
- fingerprint.sha256
- binary.sha256

View File

@@ -1,66 +0,0 @@
---
- name: Install Git build dependency
ansible.builtin.apt:
name: git
state: present
update_cache: true
cache_valid_time: 3600
when: seriatim_manage_build_dependencies
- name: Install Go build dependency
community.general.snap:
name: go
state: present
classic: true
channel: "{{ seriatim_go_snap_channel }}"
when: seriatim_manage_build_dependencies
- name: Resolve Git executable
ansible.builtin.command:
argv:
- /usr/bin/which
- git
register: seriatim_git_path_result
changed_when: false
failed_when: seriatim_git_path_result.rc != 0
- name: Verify configured Go executable exists
ansible.builtin.stat:
path: "{{ seriatim_go_binary }}"
follow: true
register: seriatim_go_binary_stat
- name: Require configured Go executable
ansible.builtin.assert:
that:
- seriatim_go_binary_stat.stat.exists
- seriatim_go_binary_stat.stat.isreg
- seriatim_go_binary_stat.stat.executable
fail_msg: "Configured Go executable is unavailable: {{ seriatim_go_binary }}"
- name: Read Go compiler version
ansible.builtin.command:
argv:
- "{{ seriatim_go_binary }}"
- version
register: seriatim_go_version_result
changed_when: false
- name: Extract Go compiler version components
ansible.builtin.set_fact:
seriatim_go_version_parts: >-
{{ seriatim_go_version_result.stdout
| regex_findall('go([0-9]+)\.([0-9]+)')
| first
| default([]) }}
- name: Require supported Go compiler version
ansible.builtin.assert:
that:
- seriatim_go_version_result.stdout is regex('^go version go[0-9]+\.[0-9]+(?:\.[0-9]+)? ')
- seriatim_go_version_parts | length == 2
- (seriatim_go_version_parts[0] | int > 1) or
(seriatim_go_version_parts[0] | int == 1 and seriatim_go_version_parts[1] | int >= 25)
fail_msg: >-
Seriatim requires Go 1.25 or newer; detected
{{ seriatim_go_version_result.stdout | default('no usable version output') }}.

View File

@@ -1,12 +1,109 @@
---
- name: Validate Seriatim role inputs
ansible.builtin.import_tasks: validate.yml
- name: Validate the supported platform
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Ubuntu"
- ansible_facts.distribution_version == "26.04"
- ansible_facts.architecture in ["x86_64", "aarch64"]
fail_msg: Seriatim supports Ubuntu 26.04 on x86_64 or aarch64.
- name: Manage Seriatim build dependencies
ansible.builtin.import_tasks: dependencies.yml
- name: Install Git
ansible.builtin.apt:
name: git
state: present
update_cache: true
when: seriatim_manage_build_dependencies
- name: Manage Seriatim source checkout
ansible.builtin.import_tasks: source.yml
- name: Install Go
community.general.snap:
name: go
channel: "{{ seriatim_go_snap_channel }}"
classic: true
state: present
when: seriatim_manage_build_dependencies
- name: Build and install Seriatim
ansible.builtin.import_tasks: build.yml
- name: Create installation parent directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ seriatim_source_dir | dirname }}"
- "{{ seriatim_binary_path | dirname }}"
- name: Check out Seriatim source
ansible.builtin.git:
repo: "{{ seriatim_repository_url }}"
dest: "{{ seriatim_source_dir }}"
version: "{{ seriatim_version }}"
force: true
register: seriatim_checkout
- name: Inspect the installed binary
ansible.builtin.stat:
path: "{{ seriatim_binary_path }}"
register: seriatim_binary
- name: Read the installed Seriatim version
ansible.builtin.command:
argv:
- "{{ seriatim_binary_path }}"
- --version
register: seriatim_installed_version
changed_when: false
failed_when: false
when: seriatim_binary.stat.exists
- name: Build Seriatim
ansible.builtin.command:
argv:
- /snap/bin/go
- build
- -trimpath
- -ldflags
- "-s -w -X gitea.maximumdirect.net/eric/seriatim/internal/buildinfo.Version={{ seriatim_version }}"
- -o
- "{{ seriatim_binary_path }}"
- ./cmd/seriatim
chdir: "{{ seriatim_source_dir }}"
environment:
CGO_ENABLED: "0"
GOTOOLCHAIN: local
GOWORK: "off"
register: seriatim_build
changed_when: true
when: >-
seriatim_checkout.changed or
not seriatim_binary.stat.exists or
seriatim_installed_version.rc | default(1) != 0 or
seriatim_installed_version.stdout | default('') != 'seriatim version ' ~ seriatim_version
- name: Converge installed metadata
ansible.builtin.file:
path: "{{ item.path }}"
state: "{{ item.state }}"
owner: root
group: root
mode: "{{ item.mode | default(omit) }}"
recurse: "{{ item.recurse | default(omit) }}"
loop:
- path: "{{ seriatim_source_dir }}"
state: directory
recurse: true
- path: "{{ seriatim_binary_path }}"
state: file
mode: "0755"
- name: Validate the built Seriatim version # noqa no-handler
ansible.builtin.command:
argv:
- "{{ seriatim_binary_path }}"
- --version
register: seriatim_built_version
changed_when: false
failed_when: >-
seriatim_built_version.rc != 0 or
seriatim_built_version.stdout != 'seriatim version ' ~ seriatim_version
when: seriatim_build.changed

View File

@@ -1,76 +0,0 @@
---
- name: Create Seriatim source parent directory
ansible.builtin.file:
path: "{{ seriatim_source_dir | dirname }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Check out selected Seriatim source
ansible.builtin.git:
repo: "{{ seriatim_repository_url }}"
dest: "{{ seriatim_source_dir }}"
version: "{{ seriatim_source_ref }}"
update: true
force: true
accept_hostkey: false
- name: Inspect Seriatim checkout cleanliness
ansible.builtin.command:
argv:
- "{{ seriatim_git_path_result.stdout }}"
- -C
- "{{ seriatim_source_dir }}"
- status
- --porcelain
- --untracked-files=all
- --ignored
register: seriatim_source_status
changed_when: false
- name: Reset tracked Seriatim source changes
ansible.builtin.command:
argv:
- "{{ seriatim_git_path_result.stdout }}"
- -C
- "{{ seriatim_source_dir }}"
- reset
- --hard
- HEAD
when: seriatim_source_status.stdout | length > 0
changed_when: true
- name: Remove untracked and ignored Seriatim source content
ansible.builtin.command:
argv:
- "{{ seriatim_git_path_result.stdout }}"
- -C
- "{{ seriatim_source_dir }}"
- clean
- -dffx
when: seriatim_source_status.stdout | length > 0
changed_when: true
- name: Enforce Seriatim source ownership
ansible.builtin.file:
path: "{{ seriatim_source_dir }}"
state: directory
owner: root
group: root
recurse: true
- name: Resolve selected Seriatim source commit
ansible.builtin.command:
argv:
- "{{ seriatim_git_path_result.stdout }}"
- -C
- "{{ seriatim_source_dir }}"
- rev-parse
- HEAD
register: seriatim_source_commit_result
changed_when: false
- name: Record selected Seriatim source commit
ansible.builtin.set_fact:
seriatim_source_commit: "{{ seriatim_source_commit_result.stdout }}"

View File

@@ -1,38 +0,0 @@
---
- name: Verify the managed host is supported
ansible.builtin.assert:
that:
- ansible_facts.distribution == 'Ubuntu'
- ansible_facts.distribution_version is version('26.04', '==')
- ansible_facts.architecture in ['x86_64', 'aarch64']
fail_msg: >-
Seriatim supports Ubuntu 26.04 on x86_64 and aarch64; detected
{{ ansible_facts.distribution }} {{ ansible_facts.distribution_version }}
on {{ ansible_facts.architecture }}.
- name: Verify required Seriatim values are nonempty
ansible.builtin.assert:
that:
- seriatim_version | length > 0
- seriatim_repository_url | length > 0
- seriatim_source_ref | length > 0
- seriatim_build_version | length > 0
- seriatim_go_snap_channel | length > 0
- seriatim_go_binary | length > 0
- seriatim_go_build_flags | length > 0
- seriatim_go_build_flags | select('equalto', '') | list | length == 0
- seriatim_go_ldflags | length > 0
- seriatim_go_package | length > 0
- seriatim_build_version is not regex('.*\s+.*')
fail_msg: Seriatim repository, revision, build, and Go values must be nonempty, and the build version cannot contain whitespace.
- name: Verify managed Seriatim paths are safe and distinct
ansible.builtin.assert:
that:
- seriatim_source_dir is match('^/')
- seriatim_binary_path is match('^/')
- seriatim_build_state_dir is match('^/')
- seriatim_source_dir != seriatim_binary_path
- seriatim_source_dir != seriatim_build_state_dir
- seriatim_binary_path != seriatim_build_state_dir
fail_msg: Seriatim source, binary, and state paths must be absolute and pairwise distinct.

View File

@@ -2,197 +2,26 @@
set -euo pipefail
repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
workspace_venv=$(cd "$repository_root/../.venv" && pwd)
workspace_venv="$repository_root/../.venv"
test_tmp=$(mktemp -d)
trap 'rm -rf -- "$test_tmp"' EXIT
export ANSIBLE_COLLECTIONS_PATH="$repository_root/.ansible/collections"
export ANSIBLE_COLLECTIONS_PATH="$test_tmp/collections"
export ANSIBLE_ROLES_PATH="$(dirname "$repository_root")"
playbook="$workspace_venv/bin/ansible-playbook"
inventory="$repository_root/tests/inventory"
converge="$repository_root/tests/converge.yml"
run_converge() {
"$playbook" -i "$inventory" "$converge" "$@"
}
restore_default() {
run_converge >/dev/null 2>&1
}
cleanup() {
exit_status=$?
restore_default || true
sudo rm -rf -- "$test_tmp"
exit "$exit_status"
}
trap cleanup EXIT
require_clean_recap() {
recap_file=$1
grep -Eq 'changed=0 .*failed=0' "$recap_file"
}
require_changed_recap() {
recap_file=$1
grep -Eq 'changed=[1-9][0-9]* .*failed=0' "$recap_file"
}
binary_digest() {
sha256sum /usr/local/bin/seriatim | awk '{print $1}'
}
state_digest() {
sudo sha256sum /usr/local/src/seriatim.ansible-build-state/* |
sha256sum | awk '{print $1}'
}
verify="$repository_root/tests/verify.yml"
cd "$repository_root"
"$workspace_venv/bin/ansible-galaxy" collection install \
-p .ansible/collections -r collections/requirements.yml
"$playbook" -i "$inventory" "$converge" --syntax-check
"$workspace_venv/bin/ansible-lint" .
-r collections/requirements.yml \
-p "$ANSIBLE_COLLECTIONS_PATH"
"$workspace_venv/bin/ansible-playbook" -i "$inventory" --syntax-check "$converge"
"$workspace_venv/bin/ansible-lint" "$converge" "$verify"
"$workspace_venv/bin/ansible-playbook" -i "$inventory" "$converge"
"$workspace_venv/bin/ansible-playbook" -i "$inventory" "$verify"
run_converge
"$playbook" -i "$inventory" tests/verify.yml
run_converge | tee "$test_tmp/default-repeat.log"
require_clean_recap "$test_tmp/default-repeat.log"
printf '%s\n' 'corrupt build inputs' |
sudo tee /usr/local/src/seriatim.ansible-build-state/build-inputs.json >/dev/null
run_converge | tee "$test_tmp/build-input-repair.log"
require_changed_recap "$test_tmp/build-input-repair.log"
"$playbook" -i "$inventory" tests/verify.yml
run_converge | tee "$test_tmp/build-input-repair-repeat.log"
require_clean_recap "$test_tmp/build-input-repair-repeat.log"
sudo chown "$(id -u):$(id -g)" /usr/local/bin/seriatim
sudo chmod 0777 /usr/local/bin/seriatim
sudo chown "$(id -u):$(id -g)" /usr/local/src/seriatim.ansible-build-state
sudo chmod 0700 /usr/local/src/seriatim.ansible-build-state
sudo chown "$(id -u):$(id -g)" /usr/local/src/seriatim.ansible-build-state/*
sudo chmod 0600 /usr/local/src/seriatim.ansible-build-state/*
run_converge | tee "$test_tmp/metadata-repair.log"
require_changed_recap "$test_tmp/metadata-repair.log"
"$playbook" -i "$inventory" tests/verify.yml
run_converge | tee "$test_tmp/metadata-repair-repeat.log"
require_clean_recap "$test_tmp/metadata-repair-repeat.log"
custom_binary="$test_tmp/missing-binary-parent/seriatim"
custom_state="$test_tmp/custom-build-state"
test ! -e "$(dirname "$custom_binary")"
run_converge \
-e "seriatim_binary_path=$custom_binary" \
-e "seriatim_build_state_dir=$custom_state" |
tee "$test_tmp/custom-path.log"
require_changed_recap "$test_tmp/custom-path.log"
test "$($custom_binary --version)" = 'seriatim version v1.5.0'
test "$(stat -c '%U:%G %a' "$(dirname "$custom_binary")")" = 'root:root 755'
run_converge \
-e "seriatim_binary_path=$custom_binary" \
-e "seriatim_build_state_dir=$custom_state" |
tee "$test_tmp/custom-path-repeat.log"
require_clean_recap "$test_tmp/custom-path-repeat.log"
sudo rm -rf -- "$custom_state"
sudo install -o root -g root -m 0644 /dev/null "$custom_state"
run_converge \
-e "seriatim_binary_path=$custom_binary" \
-e "seriatim_build_state_dir=$custom_state" |
tee "$test_tmp/state-file-repair.log"
require_changed_recap "$test_tmp/state-file-repair.log"
test -d "$custom_state"
test ! -L "$custom_state"
run_converge \
-e "seriatim_binary_path=$custom_binary" \
-e "seriatim_build_state_dir=$custom_state" |
tee "$test_tmp/state-file-repair-repeat.log"
require_clean_recap "$test_tmp/state-file-repair-repeat.log"
redirected_state="$test_tmp/redirected-build-state"
sudo rm -rf -- "$custom_state"
sudo install -d -o root -g root -m 0755 "$redirected_state"
sudo ln -s "$redirected_state" "$custom_state"
run_converge \
-e "seriatim_binary_path=$custom_binary" \
-e "seriatim_build_state_dir=$custom_state" |
tee "$test_tmp/state-link-repair.log"
require_changed_recap "$test_tmp/state-link-repair.log"
test -d "$custom_state"
test ! -L "$custom_state"
test -z "$(find "$redirected_state" -mindepth 1 -maxdepth 1 -print -quit)"
run_converge \
-e "seriatim_binary_path=$custom_binary" \
-e "seriatim_build_state_dir=$custom_state" |
tee "$test_tmp/state-link-repair-repeat.log"
require_clean_recap "$test_tmp/state-link-repair-repeat.log"
baseline_binary=$(binary_digest)
baseline_state=$(state_digest)
for scenario in \
'{"test_distribution":"Debian","test_distribution_version":"26.04","test_architecture":"x86_64"}' \
'{"test_distribution":"Ubuntu","test_distribution_version":"24.04","test_architecture":"x86_64"}' \
'{"test_distribution":"Ubuntu","test_distribution_version":"26.04","test_architecture":"riscv64"}'
do
if "$playbook" -i "$inventory" tests/unsupported.yml -e "$scenario"; then
echo "unsupported platform scenario unexpectedly succeeded: $scenario" >&2
exit 1
fi
test "$(binary_digest)" = "$baseline_binary"
test "$(state_digest)" = "$baseline_state"
done
sudo touch /usr/local/src/seriatim/ansible-untracked-test
sudo sed -i '1s/^/ansible-test-change\n/' /usr/local/src/seriatim/go.mod
run_converge
"$playbook" -i "$inventory" tests/verify.yml
compiler_wrapper="$test_tmp/go-version-wrapper"
sed 's|@GO@|/snap/bin/go|' >"$compiler_wrapper" <<'WRAPPER'
#!/bin/sh
if [ "${1:-}" = version ]; then
echo 'go version go1.26.99 linux/amd64'
exit 0
fi
exec @GO@ "$@"
WRAPPER
chmod 0755 "$compiler_wrapper"
run_converge -e "seriatim_go_binary=$compiler_wrapper" |
tee "$test_tmp/compiler-change.log"
require_changed_recap "$test_tmp/compiler-change.log"
run_converge -e "seriatim_go_binary=$compiler_wrapper" |
tee "$test_tmp/compiler-repeat.log"
require_clean_recap "$test_tmp/compiler-repeat.log"
run_converge | tee "$test_tmp/compiler-restore.log"
require_changed_recap "$test_tmp/compiler-restore.log"
run_converge | tee "$test_tmp/compiler-restored-repeat.log"
require_clean_recap "$test_tmp/compiler-restored-repeat.log"
failure_wrapper="$test_tmp/go-failure-wrapper"
sed 's|@GO@|/snap/bin/go|' >"$failure_wrapper" <<'WRAPPER'
#!/bin/sh
if [ "${1:-}" = version ]; then
exec @GO@ version
fi
echo 'deliberate integration-test build failure' >&2
exit 42
WRAPPER
chmod 0755 "$failure_wrapper"
baseline_binary=$(binary_digest)
baseline_state=$(state_digest)
if run_converge -e "seriatim_go_binary=$failure_wrapper"; then
echo 'deliberate build failure unexpectedly succeeded' >&2
exit 1
fi
test "$(binary_digest)" = "$baseline_binary"
test "$(state_digest)" = "$baseline_state"
test "$(/usr/local/bin/seriatim --version)" = 'seriatim version v1.5.0'
run_converge | tee "$test_tmp/final-restore.log"
require_clean_recap "$test_tmp/final-restore.log"
"$playbook" -i "$inventory" tests/verify.yml
trap - EXIT
sudo rm -rf -- "$test_tmp"
repeat_output="$test_tmp/repeat.log"
"$workspace_venv/bin/ansible-playbook" -i "$inventory" "$converge" | tee "$repeat_output"
grep -Eq 'changed=0 .*failed=0' "$repeat_output"

View File

@@ -1,111 +1,73 @@
---
- name: Verify Seriatim installation
hosts: seriatim
gather_facts: true
- name: Verify the Seriatim installation
hosts: localhost
connection: local
gather_facts: false
become: true
vars:
seriatim_expected_commit: 0fc92f364316c874d366b00cdc884a33e1900b25
seriatim_expected_version: v1.5.0
seriatim_verify_source_dir: /usr/local/src/seriatim
seriatim_verify_binary_path: /usr/local/bin/seriatim
seriatim_verify_state_dir: /usr/local/src/seriatim.ansible-build-state
seriatim_source_dir: /usr/local/src/seriatim
seriatim_binary_path: /usr/local/bin/seriatim
tasks:
- name: Inspect Seriatim source directory
- name: Inspect the source directory
ansible.builtin.stat:
path: "{{ seriatim_verify_source_dir }}"
register: seriatim_verify_source_stat
path: "{{ seriatim_source_dir }}"
register: seriatim_source
- name: Inspect installed Seriatim binary
ansible.builtin.stat:
path: "{{ seriatim_verify_binary_path }}"
checksum_algorithm: sha256
register: seriatim_verify_binary_stat
- name: Inspect Seriatim build state directory
ansible.builtin.stat:
path: "{{ seriatim_verify_state_dir }}"
register: seriatim_verify_state_stat
- name: Inspect Seriatim build state files
ansible.builtin.stat:
path: "{{ seriatim_verify_state_dir }}/{{ item }}"
loop:
- build-inputs.json
- fingerprint.sha256
- binary.sha256
register: seriatim_verify_state_file_stats
- name: Resolve installed Seriatim source commit
- name: Find incorrectly owned source entries
ansible.builtin.command:
argv:
- /usr/bin/git
- find
- "{{ seriatim_source_dir }}"
- (
- -not
- -user
- root
- -or
- -not
- -group
- root
- )
- -print
- -quit
register: seriatim_source_ownership
changed_when: false
- name: Read the selected source commit
ansible.builtin.command:
argv:
- git
- -C
- "{{ seriatim_verify_source_dir }}"
- "{{ seriatim_source_dir }}"
- rev-parse
- HEAD
register: seriatim_verify_commit
register: seriatim_source_commit
changed_when: false
- name: Inspect installed Seriatim source cleanliness
ansible.builtin.command:
argv:
- /usr/bin/git
- -C
- "{{ seriatim_verify_source_dir }}"
- status
- --porcelain
- --untracked-files=all
- --ignored
register: seriatim_verify_source_status
changed_when: false
- name: Inspect the installed binary
ansible.builtin.stat:
path: "{{ seriatim_binary_path }}"
register: seriatim_binary
- name: Read installed Seriatim version
- name: Read the installed version
ansible.builtin.command:
argv:
- "{{ seriatim_verify_binary_path }}"
- "{{ seriatim_binary_path }}"
- --version
register: seriatim_verify_version
register: seriatim_version_output
changed_when: false
- name: Read stored Seriatim build inputs
ansible.builtin.slurp:
src: "{{ seriatim_verify_state_dir }}/build-inputs.json"
register: seriatim_verify_inputs
- name: Read stored Seriatim fingerprint
ansible.builtin.slurp:
src: "{{ seriatim_verify_state_dir }}/fingerprint.sha256"
register: seriatim_verify_fingerprint
- name: Read stored Seriatim checksum
ansible.builtin.slurp:
src: "{{ seriatim_verify_state_dir }}/binary.sha256"
register: seriatim_verify_checksum
- name: Require valid Seriatim installation state
- name: Assert the installation contract
ansible.builtin.assert:
that:
- ansible_facts.distribution == 'Ubuntu'
- ansible_facts.distribution_version is version('26.04', '==')
- ansible_facts.architecture in ['x86_64', 'aarch64']
- seriatim_verify_source_stat.stat.isdir
- seriatim_verify_source_stat.stat.pw_name == 'root'
- seriatim_verify_source_stat.stat.gr_name == 'root'
- seriatim_verify_binary_stat.stat.isreg
- seriatim_verify_binary_stat.stat.pw_name == 'root'
- seriatim_verify_binary_stat.stat.gr_name == 'root'
- seriatim_verify_binary_stat.stat.mode == '0755'
- seriatim_verify_state_stat.stat.isdir
- seriatim_verify_state_stat.stat.pw_name == 'root'
- seriatim_verify_state_stat.stat.gr_name == 'root'
- seriatim_verify_state_stat.stat.mode == '0755'
- seriatim_verify_state_file_stats.results | map(attribute='stat.isreg') | unique | list == [true]
- seriatim_verify_state_file_stats.results | map(attribute='stat.pw_name') | unique | list == ['root']
- seriatim_verify_state_file_stats.results | map(attribute='stat.gr_name') | unique | list == ['root']
- seriatim_verify_state_file_stats.results | map(attribute='stat.mode') | unique | list == ['0644']
- seriatim_verify_commit.stdout == seriatim_expected_commit
- seriatim_verify_source_status.stdout == ''
- seriatim_verify_version.stdout == 'seriatim version ' ~ seriatim_expected_version
- (seriatim_verify_inputs.content | b64decode | trim | hash('sha256')) == (seriatim_verify_fingerprint.content | b64decode | trim)
- seriatim_verify_binary_stat.stat.checksum == (seriatim_verify_checksum.content | b64decode | trim)
fail_msg: Installed Seriatim source, binary, or build state is inconsistent.
- seriatim_source.stat.isdir
- seriatim_source.stat.pw_name == "root"
- seriatim_source.stat.gr_name == "root"
- seriatim_source_ownership.stdout == ""
- seriatim_source_commit.stdout == seriatim_expected_commit
- seriatim_binary.stat.isreg
- seriatim_binary.stat.pw_name == "root"
- seriatim_binary.stat.gr_name == "root"
- seriatim_binary.stat.mode == "0755"
- seriatim_version_output.stdout == "seriatim version " ~ seriatim_expected_version