Просмотр исходного кода

Add a Molecule scenario for hookshot, and the openssl host command to the shared context

Hookshot is the widest HTTP surface of the bridges here - an appservice port
plus a `listeners` list whose entries appear or vanish with independent
switches - so the scenario asserts on that surface rather than on "it started".

The scenario configures NO third-party service. GitHub, GitLab, Jira and Figma
each need an account and a credential somewhere else, which is where a scenario
stops testing this repository (docs/molecule-testing.md). GitLab, which the role
turns on by default, is switched off and its absence from both config.yml and
the registration's namespaces is asserted. The generic webhooks listener needs
no credential from anyone, and is what gets exercised live: a POST to an unknown
hook id comes back as `{"ok":false,"error":"Webhook not found"}`, which only
Hookshot's own generic-webhook handler produces - an Express "Cannot POST" page
there would mean the port is Hookshot's but the service was never mounted on it.

Every port the scenario sets differs from both the role's default and Hookshot's
own, and the run asserts both halves: the webhooks, metrics and appservice ports
it chose all answer, and 9000/9001/9003/9993 - what the role would have used
instead - all refuse. Metrics are off in the role's defaults, so that listener
exists only because the scenario asked for it, and /metrics answers with
`hookshot_*` series that nothing else could have produced.

config.yml and registration.yml are parsed as YAML and asserted structurally, so
a value landing under the wrong key cannot pass as a substring match.

Two things the role made necessary:

- `tasks/main.yml` opens with a block tagged `reset-hookshot-encryption`, and
  Ansible runs tagged tasks unless tags are actually selected - so any tagless
  play hits it, and it stops `matrix-hookshot.service` before that service
  exists. The scenario skips the tag, which is what the playbook effectively
  does by always running with `--tags=setup-all,start`.

- The role reads `matrix_host_command_openssl`, which matrix-base defines.
  matrix-bridge-appservice-irc reads it too, so it goes in the shared playbook
  context rather than into this scenario.

Falsified by pointing the webhooks listener at 127.0.0.1 in the role's config
template - a change no file-level assertion can see. Every config and
registration assertion still passed, and the run failed at "Assert the generic
webhooks service is mounted on that listener" with "Port 9741 did not answer as
Hookshot's generic webhooks service". Reverted afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT
pull/5575/head
Slavi Pantaleev 13 часов назад
Родитель
Сommit
7e8b3466b6
6 измененных файлов: 627 добавлений и 0 удалений
  1. +10
    -0
      molecule-shared/playbook-context.yml
  2. +36
    -0
      roles/custom/matrix-bridge-hookshot/molecule/default/converge.yml
  3. +123
    -0
      roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml
  4. +84
    -0
      roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml
  5. +1
    -0
      roles/custom/matrix-bridge-hookshot/molecule/default/requirements.yml
  6. +373
    -0
      roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml

+ 10
- 0
molecule-shared/playbook-context.yml Просмотреть файл

@@ -38,6 +38,16 @@ matrix_user_gid: 1234
# scenario wants.
matrix_admin: ''

# --- Host commands (matrix-base) -------------------------------------------
#
# Some roles shell out to a host binary through this indirection instead of
# naming it directly (matrix-bridge-hookshot and matrix-bridge-appservice-irc
# both generate a key with it). matrix-base's defaults are what supplies the
# value in a real run; those roles install the binary themselves, by including
# matrix-base's `ensure_openssl_installed` tasks.

matrix_host_command_openssl: "/usr/bin/env openssl"

# --- Bridge-wide switches (matrix-base) ------------------------------------
#
# Every bridge role reads these, so they live here rather than in each bridge's


+ 36
- 0
roles/custom/matrix-bridge-hookshot/molecule/default/converge.yml Просмотреть файл

@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
- name: Include roles for hookshot Molecule tests
hosts: all
become: true
vars_files:
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
gather_facts: true
tasks:
- name: Include roles for hookshot Molecule tests
ansible.builtin.include_role:
name: "{{ role_name }}"
public: true
loop:
- com.devture.ansible.role.playbook_help
- com.devture.ansible.role.systemd_docker_base
- "custom/{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
loop_control:
loop_var: role_name

- name: Ensure hookshot is started
hosts: all
become: true
gather_facts: false
tasks:
- name: Ensure systemd daemon is reloaded
ansible.builtin.systemd_service:
daemon_reload: true

- name: Ensure the hookshot systemd service is started
ansible.builtin.systemd_service:
name: matrix-hookshot.service
state: started

+ 123
- 0
roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml Просмотреть файл

@@ -0,0 +1,123 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
dependency:
name: galaxy
options:
requirements-file: requirements.yml
force: true
driver:
name: docker
platforms:
- name: hookshot-${MOLECULE_DISTRO:-ubuntu2604}-default
image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2604}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
provisioner:
name: ansible
config_options:
defaults:
callback_result_format: yaml
options:
# The role's `tasks/main.yml` opens with a block tagged
# `reset-hookshot-encryption`, and Ansible runs tagged tasks unless tags are
# actually selected - so a tagless play (which is what Molecule runs) hits
# it. It stops `matrix-hookshot.service` before that service exists, which
# fails on a fresh host. Skipping the tag is what the playbook effectively
# does by always running with `--tags=setup-all,start`.
#
# `molecule-notest,notest` are Molecule's own defaults, repeated here
# because this key replaces them rather than adding to them.
skip-tags: molecule-notest,notest,reset-hookshot-encryption
inventory:
group_vars:
all:
matrix_bridge_hookshot_container_network: hookshot-molecule

# The stub prepare.yml stands up. Hookshot contacts it while starting;
# it is not a real homeserver and nothing is asserted about it.
matrix_bridge_hookshot_homeserver_address: http://matrix.molecule.local:8008

# Appservice tokens. These are what Hookshot and the homeserver would
# authenticate to each other with; here they only have to reach the
# rendered configuration and the registration file.
matrix_bridge_hookshot_appservice_token: molecule_as_token_4f2a91
matrix_bridge_hookshot_homeserver_token: molecule_hs_token_9b3e77

# Deliberately different from the role's default (`hookshot`), so the
# registration's sender_localpart can only have come from the role.
# prepare.yml tells the homeserver stub to claim this same user id.
matrix_bridge_hookshot_bot_localpart: molecule-hookshot

# Hookshot's HTTP surface is the point of this scenario. Every port
# below differs from BOTH the role's default and Hookshot's own, so an
# answer on one of them can only mean the role's configuration reached
# the process. verify.yml also asserts that nothing answers on the
# defaults these replace (9993 appservice, 9000 webhooks, 9001 metrics).
matrix_bridge_hookshot_appservice_port: 9772
matrix_bridge_hookshot_webhook_port: 9741

# Off in the role's defaults. Enabling it makes the role render a second
# entry in Hookshot's `listeners` list, which is the cheapest listener to
# assert *content* on: /metrics answers in a format nothing else would.
matrix_bridge_hookshot_metrics_enabled: true
matrix_bridge_hookshot_metrics_port: 9752

# On in the role's defaults. Turned off here so that the absence of a
# listener can be asserted too - the widgets port staying closed is what
# tells "the role rendered the listener list" apart from "Hookshot binds
# everything anyway".
matrix_bridge_hookshot_widgets_enabled: false

# No third-party service is configured in this scenario - see the header
# of verify.yml. GitLab is the one the role enables by default, so it is
# explicitly switched off and its absence from the rendered config and
# registration is asserted.
matrix_bridge_hookshot_gitlab_enabled: false

# The generic webhooks listener is the only part of Hookshot that needs
# no account anywhere, so it is what this scenario exercises live. The
# prefix differs from the role's default (`_webhooks_`).
matrix_bridge_hookshot_generic_userIdPrefix: _molecule_hook_ # noqa var-naming

# Neither the role's default (600) nor Hookshot's own (600).
matrix_bridge_hookshot_feeds_pollIntervalSeconds: 907 # noqa var-naming

# The role defaults to `warn`; Hookshot itself defaults to `info`. This
# is a third value, so finding it in config.yml cannot be a coincidence.
matrix_bridge_hookshot_logging_level: debug

# Traefik is not deployed here, so the labels the role would render for
# it are switched off and their absence is asserted instead.
matrix_bridge_hookshot_container_labels_traefik_enabled: false

# verify.yml runs as its own play, where role defaults are out of scope,
# so what it reads is pinned here. These two match the role's own
# defaults on purpose - they name things (a path, a container) rather
# than configure them, and nothing is asserted *about* them.
matrix_bridge_hookshot_base_path: /matrix/hookshot
matrix_bridge_hookshot_identifier: matrix-hookshot
env:
# Workaround for https://github.com/ansible/molecule/issues/4391
ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/../..:/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:~/.ansible/roles
scenario:
test_sequence:
- dependency
- cleanup
- destroy
- syntax
- create
- prepare
- converge
- idempotence
- verify
- cleanup
- destroy
verifier:
name: ansible

+ 84
- 0
roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml Просмотреть файл

@@ -0,0 +1,84 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
- name: Prepare hookshot Molecule tests
hosts: all
become: true
vars_files:
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
gather_facts: true
tasks:
- name: Ensure apt cache is updated
ansible.builtin.apt:
update_cache: true
cache_valid_time: 600
when: ansible_os_family == 'Debian'

- name: Ensure required packages are installed
ansible.builtin.package:
name:
- python3-requests
- fuse-overlayfs
state: present

- name: Ensure Docker is installed
ansible.builtin.include_role:
name: ansible-role-docker
vars:
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name and Ansible resolves those
# through the passwd database, so they have to exist first. matrix-base
# creates them in a real deployment.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
gid: "{{ matrix_user_gid }}"
state: present

- name: Ensure the matrix user exists
ansible.builtin.user:
name: "{{ matrix_user_name }}"
uid: "{{ matrix_user_uid }}"
group: "{{ matrix_group_name }}"
create_home: false
system: true
state: present

- name: Ensure the base data path exists
ansible.builtin.file:
path: "{{ matrix_base_data_path }}"
state: directory
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
mode: "0750"

- name: Ensure the container network the role attaches to exists
ansible.builtin.command:
argv:
- docker
- network
- create
- "{{ matrix_bridge_hookshot_container_network }}"
register: hookshot_molecule_network
changed_when: hookshot_molecule_network.rc == 0
failed_when:
- hookshot_molecule_network.rc != 0
- "'already exists' not in hookshot_molecule_network.stderr"

# Hookshot contacts the homeserver as it starts, and - being an appservice -
# refuses to run if /whoami answers with a user other than the bot it was
# configured as, so the stub is told to claim the localpart the scenario set.
# It is not being asked to bridge anything, and nothing is asserted about it;
# see molecule-shared/homeserver-stub.py.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"
vars:
molecule_shared_stub_network: "{{ matrix_bridge_hookshot_container_network }}"
molecule_shared_stub_server_name: molecule.local
molecule_shared_stub_user_id: "@{{ matrix_bridge_hookshot_bot_localpart }}:molecule.local"

+ 1
- 0
roles/custom/matrix-bridge-hookshot/molecule/default/requirements.yml Просмотреть файл

@@ -0,0 +1 @@
../../../../../molecule-shared/requirements.yml

+ 373
- 0
roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml Просмотреть файл

@@ -0,0 +1,373 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# What this proves: Hookshot starts, accepts the config.yml and registration.yml
# the role rendered, and opens exactly the HTTP listeners that configuration
# described - on the ports the role put there, and not on the ones it did not.
#
# What it deliberately does NOT do: configure GitHub, GitLab, Jira or Figma.
# Every one of those needs an account and a credential on a third-party service,
# which is the line where a scenario stops testing this repository and starts
# testing a fake (see docs/molecule-testing.md). The generic webhooks listener
# needs no credential from anyone, so it is the one that gets exercised live.
- name: Verify hookshot
hosts: all
become: true
vars_files:
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
gather_facts: false

tasks:
# Read from the role's own defaults rather than pinned in molecule.yml, so
# the version assertion below compares the running image against what the
# role ships instead of against the scenario itself. The default ports come
# from here for the same reason: the "these ports stay closed" assertion is
# only meaningful against the ports the role would otherwise have used.
- name: Load the role's defaults under a separate name
ansible.builtin.include_vars:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
name: hookshot_role_defaults

- name: Wait for the hookshot service to become active
ansible.builtin.systemd_service:
name: matrix-hookshot.service
register: hookshot_service
until: hookshot_service.status.ActiveState == 'active'
retries: 30
delay: 5
failed_when: false

# `Restart=always` means a bridge crash-looping on a configuration it cannot
# read still reports `active`, so the restart counter is checked too. It is
# asserted `is defined` because `| int` turns a missing property into 0 and
# would pass vacuously.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
- hookshot_service.status.ActiveState == 'active'
- hookshot_service.status.NRestarts is defined
- hookshot_service.status.NRestarts | int == 0
fail_msg: >-
matrix-hookshot.service is
{{ hookshot_service.status.ActiveState | default('unknown') }}
after {{ hookshot_service.status.NRestarts | default('?') }}
automatic restart(s)
success_msg: "matrix-hookshot.service is active and has not restarted"

# ------------------------------------------------------------------
# The rendered configuration
# ------------------------------------------------------------------

- name: Read the configuration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_hookshot_base_path }}/config.yml"
register: hookshot_config_file

- name: Parse the rendered configuration
ansible.builtin.set_fact:
hookshot_config: "{{ hookshot_config_file.content | b64decode | from_yaml }}"

# Each of these differs from what Hookshot would use on its own AND from
# what the role defaults to, so finding them here means the role's
# configuration is what Hookshot is running on rather than a coincidence.
- name: Assert the rendered configuration carries this scenario's values
ansible.builtin.assert:
that:
- hookshot_config.bridge.domain == matrix_domain
- hookshot_config.bridge.url == matrix_bridge_hookshot_homeserver_address
- hookshot_config.bridge.port | int == matrix_bridge_hookshot_appservice_port | int
- hookshot_config.generic.userIdPrefix == matrix_bridge_hookshot_generic_userIdPrefix
- hookshot_config.feeds.pollIntervalSeconds | int == matrix_bridge_hookshot_feeds_pollIntervalSeconds | int
- hookshot_config.logging.level == matrix_bridge_hookshot_logging_level
- hookshot_config.metrics.enabled | bool
fail_msg: "The rendered configuration does not carry the scenario's values"
success_msg: "The rendered configuration carries the scenario's values"

# Hookshot's `listeners` list is the role's own construction: it decides
# which resources get a port at all, from a handful of independent switches.
# Getting this wrong is invisible in a "did it start" test, which is why it
# is asserted as a whole rather than key by key.
- name: Assert the role rendered exactly the listeners the scenario asked for
ansible.builtin.assert:
that:
- hookshot_config.listeners | length == 2
- hookshot_listener_ports == [matrix_bridge_hookshot_webhook_port | int, matrix_bridge_hookshot_metrics_port | int]
- hookshot_config.listeners | map(attribute='resources') | flatten == ['webhooks', 'metrics']
fail_msg: >-
The rendered listeners are {{ hookshot_config.listeners }}, not the
webhooks and metrics listeners this scenario configured
success_msg: "The role rendered exactly the webhooks and metrics listeners"
vars:
hookshot_listener_ports: "{{ hookshot_config.listeners | map(attribute='port') | map('int') | list }}"

# No third-party service is configured here, so none of their sections may
# appear. GitLab is the interesting one: the role turns it ON by default, so
# its absence is what proves the scenario's switch reached the template.
- name: Assert no third-party service section was rendered
ansible.builtin.assert:
that:
- "'gitlab' not in hookshot_config"
- "'github' not in hookshot_config"
- "'jira' not in hookshot_config"
- "'figma' not in hookshot_config"
fail_msg: >-
The rendered configuration contains a third-party service section
({{ hookshot_config.keys() | list }}); this scenario configures none
success_msg: "No third-party service section was rendered"

- name: Assert no widgets section was rendered while widgets are disabled
ansible.builtin.assert:
that:
- "'widgets' not in hookshot_config"
fail_msg: >-
A widgets section was rendered even though
matrix_bridge_hookshot_widgets_enabled is false
success_msg: "No widgets section was rendered while widgets are disabled"

# ------------------------------------------------------------------
# The rendered registration
# ------------------------------------------------------------------

# The registration file is the half of the appservice handshake the
# homeserver reads. The role generates it, Hookshot only consumes it, so it
# is worth checking on its own.
- name: Read the appservice registration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_hookshot_base_path }}/registration.yml"
register: hookshot_registration_file

- name: Parse the rendered registration
ansible.builtin.set_fact:
hookshot_registration: "{{ hookshot_registration_file.content | b64decode | from_yaml }}"

# `url` is where the homeserver would push transactions, and the role builds
# it out of the container name and the appservice port. It has to agree with
# `bridge.port` in config.yml or the two halves would silently disagree.
- name: Assert the registration carries the scenario's tokens, bot and callback URL
ansible.builtin.assert:
that:
- hookshot_registration.as_token == matrix_bridge_hookshot_appservice_token
- hookshot_registration.hs_token == matrix_bridge_hookshot_homeserver_token
- hookshot_registration.sender_localpart == matrix_bridge_hookshot_bot_localpart
- hookshot_registration.url == 'http://' + matrix_bridge_hookshot_identifier + ':' + (matrix_bridge_hookshot_appservice_port | string)
fail_msg: "The appservice registration does not carry the scenario's tokens, bot and callback URL"
success_msg: "The appservice registration carries the scenario's tokens, bot and callback URL"

# The user namespace is derived from the generic webhook prefix, and the
# GitLab namespace is conditional on the service being enabled - so this
# checks that the two switches reach the registration, not just config.yml.
- name: Assert the registration namespaces follow the enabled services
ansible.builtin.assert:
that:
- hookshot_registration_user_regexes | select('search', matrix_bridge_hookshot_generic_userIdPrefix) | list | length == 1
- hookshot_registration_user_regexes | select('search', '_gitlab_') | list | length == 0
fail_msg: >-
The registration's user namespaces are
{{ hookshot_registration_user_regexes }}, which do not follow the
services this scenario enabled
success_msg: "The registration's user namespaces follow the enabled services"
vars:
hookshot_registration_user_regexes: "{{ hookshot_registration.namespaces.users | map(attribute='regex') | list }}"

# ------------------------------------------------------------------
# The listeners, live
# ------------------------------------------------------------------

# A helper container is used because the role publishes no host port, exactly
# as in a real deployment; see docs/molecule-testing.md.
- name: Wait for the webhooks listener to answer on the port the role configured
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bridge_hookshot_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --max-time
- "5"
- --request
- POST
- --header
- "Content-Type: application/json"
- --data
- "{}"
- --write-out
- "|HTTP_STATUS=%{http_code}"
- "http://{{ matrix_bridge_hookshot_identifier }}:{{ matrix_bridge_hookshot_webhook_port }}/webhook/molecule-no-such-hook"
register: hookshot_webhooks_probe
changed_when: false
until: "'HTTP_STATUS=000' not in hookshot_webhooks_probe.stdout"
retries: 24
delay: 5
failed_when: false

# Hookshot answers an unknown webhook id from its generic-webhook handler,
# with a JSON body no other component would produce. An Express "Cannot POST"
# page here would mean the port is Hookshot's but the generic webhooks
# service was never mounted on it; a refused connection would mean the
# listener the role described was never opened at all.
- name: Assert the generic webhooks service is mounted on that listener
ansible.builtin.assert:
that:
- "'\"ok\":false' in hookshot_webhooks_probe.stdout"
- "'Webhook not found' in hookshot_webhooks_probe.stdout"
- "'HTTP_STATUS=404' in hookshot_webhooks_probe.stdout"
fail_msg: >-
Port {{ matrix_bridge_hookshot_webhook_port }} did not answer as
Hookshot's generic webhooks service
({{ hookshot_webhooks_probe.stdout | default('no output') }})
success_msg: "The generic webhooks service answers on the port the role configured"

# Metrics are OFF in the role's defaults, so this listener exists only
# because the scenario asked for it - and /metrics answers in a format
# nothing else on that port could have produced.
- name: Probe the metrics listener on the port the role configured
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bridge_hookshot_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --max-time
- "5"
- --write-out
- "|HTTP_STATUS=%{http_code}"
- "http://{{ matrix_bridge_hookshot_identifier }}:{{ matrix_bridge_hookshot_metrics_port }}/metrics"
register: hookshot_metrics_probe
changed_when: false
failed_when: false

- name: Assert the metrics listener serves Hookshot's own metrics
ansible.builtin.assert:
that:
- "'HTTP_STATUS=200' in hookshot_metrics_probe.stdout"
- "'hookshot_webhooks_http_request' in hookshot_metrics_probe.stdout"
fail_msg: >-
Port {{ matrix_bridge_hookshot_metrics_port }} did not serve Hookshot's
metrics ({{ hookshot_metrics_probe.stdout | default('no output') | truncate(200) }})
success_msg: "The metrics listener serves Hookshot's own metrics"

# The appservice port is not in `listeners` - it comes from `bridge.port` -
# so it is a separate socket, opened by a separate part of the config.
- name: Probe the appservice port the role configured
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bridge_hookshot_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --max-time
- "5"
- --write-out
- "|HTTP_STATUS=%{http_code}"
- "http://{{ matrix_bridge_hookshot_identifier }}:{{ matrix_bridge_hookshot_appservice_port }}/_matrix/app/v1/ping"
register: hookshot_appservice_probe
changed_when: false
failed_when: false

- name: Assert the appservice API answers on the port the role configured
ansible.builtin.assert:
that:
- "'HTTP_STATUS=000' not in hookshot_appservice_probe.stdout"
- "'errcode' in hookshot_appservice_probe.stdout"
fail_msg: >-
Port {{ matrix_bridge_hookshot_appservice_port }} did not answer as a
Matrix appservice
({{ hookshot_appservice_probe.stdout | default('no output') }})
success_msg: "The appservice API answers on the port the role configured"

# The other half of the story. Every port above is one the scenario chose;
# these are the ones the role and Hookshot would have used if the scenario's
# configuration had never reached the process. If any of them answers, then
# a passing probe above proves much less than it looks like it does.
- name: Probe the ports the role's defaults would have used
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bridge_hookshot_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --max-time
- "5"
- --output
- /dev/null
- --write-out
- "HTTP_STATUS=%{http_code}"
- "http://{{ matrix_bridge_hookshot_identifier }}:{{ item.port }}/"
register: hookshot_closed_probes
changed_when: false
failed_when: false
loop:
- port: "{{ hookshot_role_defaults.matrix_bridge_hookshot_appservice_port }}"
what: the appservice port the role defaults to
- port: "{{ hookshot_role_defaults.matrix_bridge_hookshot_webhook_port }}"
what: the webhooks port the role defaults to
- port: "{{ hookshot_role_defaults.matrix_bridge_hookshot_metrics_port }}"
what: the metrics port the role defaults to
- port: "{{ hookshot_role_defaults.matrix_bridge_hookshot_widgets_port }}"
what: the widgets port, whose listener this scenario disabled
loop_control:
label: "{{ item.port }} - {{ item.what }}"

- name: Assert nothing listens on the ports the role's defaults would have used
ansible.builtin.assert:
that:
- hookshot_closed_probes.results | rejectattr('stdout', 'search', 'HTTP_STATUS=000') | list | length == 0
fail_msg: >-
Something answered on
{{ hookshot_closed_probes.results | rejectattr('stdout', 'search', 'HTTP_STATUS=000') | map(attribute='item') | list }},
so the ports this scenario configured are not the only ones Hookshot
is listening on
success_msg: >-
Nothing listens on the ports the role's defaults would have used
({{ hookshot_closed_probes.results | map(attribute='item.port') | list | join(', ') }})

# ------------------------------------------------------------------
# The container the role started
# ------------------------------------------------------------------

- name: Read the image of the running container
ansible.builtin.command:
argv:
- docker
- container
- inspect
- "{{ matrix_bridge_hookshot_identifier }}"
- --format
- "{{ '{{' }} .Config.Image {{ '}}' }}"
register: hookshot_image
changed_when: false

- name: Assert the running container is the version defaults/main.yml pins
ansible.builtin.assert:
that:
- hookshot_role_defaults.matrix_bridge_hookshot_version | string in hookshot_image.stdout
fail_msg: >-
The running container is {{ hookshot_image.stdout }}, which does not
carry the pinned version
{{ hookshot_role_defaults.matrix_bridge_hookshot_version }}
success_msg: "The running container is the version defaults/main.yml pins"

- name: Read the labels the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_hookshot_base_path }}/labels"
register: hookshot_labels

- name: Assert no Traefik labels are emitted while Traefik support is disabled
ansible.builtin.assert:
that:
- "'traefik.' not in (hookshot_labels.content | b64decode)"
fail_msg: >-
Traefik labels were emitted even though
matrix_bridge_hookshot_container_labels_traefik_enabled is false
success_msg: "No Traefik labels are emitted while Traefik support is disabled"

Загрузка…
Отмена
Сохранить