test: verify Seriatim source installation
This commit is contained in:
23
README.md
23
README.md
@@ -82,6 +82,29 @@ ANSIBLE_COLLECTIONS_PATH="$PWD/.ansible/collections" \
|
|||||||
../.venv/bin/ansible-lint .
|
../.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:
|
||||||
|
|
||||||
|
```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
|
## License
|
||||||
|
|
||||||
BSD-3-Clause
|
BSD-3-Clause
|
||||||
|
|||||||
129
tests/integration
Executable file
129
tests/integration
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
workspace_venv=$(cd "$repository_root/../.venv" && pwd)
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
|
||||||
|
export ANSIBLE_COLLECTIONS_PATH="$repository_root/.ansible/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
|
||||||
|
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}'
|
||||||
|
}
|
||||||
|
|
||||||
|
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" .
|
||||||
|
|
||||||
|
run_converge
|
||||||
|
"$playbook" -i "$inventory" tests/verify.yml
|
||||||
|
run_converge | tee "$test_tmp/default-repeat.log"
|
||||||
|
require_clean_recap "$test_tmp/default-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
|
||||||
|
rm -rf -- "$test_tmp"
|
||||||
12
tests/unsupported.yml
Normal file
12
tests/unsupported.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
- name: Exercise Seriatim platform mutation guard
|
||||||
|
hosts: seriatim
|
||||||
|
gather_facts: false
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
ansible_facts:
|
||||||
|
distribution: "{{ test_distribution }}"
|
||||||
|
distribution_version: "{{ test_distribution_version }}"
|
||||||
|
architecture: "{{ test_architecture }}"
|
||||||
|
roles:
|
||||||
|
- role: ansible-role-seriatim
|
||||||
97
tests/verify.yml
Normal file
97
tests/verify.yml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
- name: Verify Seriatim installation
|
||||||
|
hosts: seriatim
|
||||||
|
gather_facts: true
|
||||||
|
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
|
||||||
|
tasks:
|
||||||
|
- name: Inspect Seriatim source directory
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ seriatim_verify_source_dir }}"
|
||||||
|
register: seriatim_verify_source_stat
|
||||||
|
|
||||||
|
- 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: Resolve installed Seriatim source commit
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /usr/bin/git
|
||||||
|
- -C
|
||||||
|
- "{{ seriatim_verify_source_dir }}"
|
||||||
|
- rev-parse
|
||||||
|
- HEAD
|
||||||
|
register: seriatim_verify_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: Read installed Seriatim version
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- "{{ seriatim_verify_binary_path }}"
|
||||||
|
- --version
|
||||||
|
register: seriatim_verify_version
|
||||||
|
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
|
||||||
|
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_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.
|
||||||
Reference in New Issue
Block a user