* bootstrap: `garage layout show` truncates node IDs to 16 chars, but
the membership check compared against the full hex. After the first
successful join, subsequent runs no longer found the short ID in
`layout show` and re-issued `layout assign`, marking the task
changed every time. Compare against both the truncated and the full
form so a configured node stays detected. Also tag the read-only
`garage node id` / `layout show` probes with `changed_when: false`.
* provision keys: the old parser sliced `stdout_lines[1:]` to drop the
header but missed that INFO log lines and ANSI escapes can interleave
with table rows. Replace with an explicit `^GK[0-9a-fA-F]+` filter
after stripping ANSI, so probe-output noise no longer corrupts the
existing-keys set and triggers spurious `key new` calls.
* provision buckets: same class of fix — match `^[0-9a-f]{16}\s` data
rows instead of slicing `[2:]`, which broke when the table header
wasn't exactly two lines.
* provision permissions: pre-read `bucket info` for each (key, bucket)
pair and only run `bucket allow` when the current `RWO` flag set for
that key ID doesn't already match the desired permissions. Previously
`bucket allow` ran unconditionally and reported changed every play.
* `changed_when: false` on all read-only probes (`key list`, `key info`,
`bucket list`).
138 lines
No EOL
5.2 KiB
YAML
138 lines
No EOL
5.2 KiB
YAML
# S3 keys and buckets tasks
|
|
- name: Get list of existing S3 keys
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage key list
|
|
register: _existing_keys_output
|
|
changed_when: false
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Parse existing key names
|
|
ansible.builtin.set_fact:
|
|
# `garage key list` columns: ID Created Name Expiration.
|
|
# Data rows begin with a GK<hex> key ID; header is "ID Created ..."
|
|
# and INFO log lines may interleave on stderr (kept separate by
|
|
# docker_container_exec). Strip ANSI escapes defensively, filter to
|
|
# GK-prefixed rows, then take the 3rd whitespace-separated field.
|
|
_existing_keys: "{{ _existing_keys_output.stdout_lines | map('regex_replace', '\\x1b\\[[0-9;]*m', '') | select('match', '^GK[0-9a-fA-F]+') | map('regex_replace', '^\\S+\\s+\\S+\\s+(\\S+).*$', '\\1') | list }}"
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Create S3 keys
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage key create {{ item.name }}
|
|
loop: "{{ garage_s3_keys }}"
|
|
register: _key_create_result
|
|
when:
|
|
- garage_s3_keys | length > 0
|
|
- item.name not in _existing_keys
|
|
|
|
- name: Get key IDs for all keys
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage key info {{ item.name }}
|
|
loop: "{{ garage_s3_keys }}"
|
|
register: _key_info_results
|
|
changed_when: false
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Extract key IDs from info
|
|
ansible.builtin.set_fact:
|
|
_key_id_map: "{{ _key_id_map | default({}) | combine({item.item.name: item.stdout | regex_search('Key ID:\\s+(\\S+)', '\\1') | first}) }}"
|
|
loop: "{{ _key_info_results.results }}"
|
|
when:
|
|
- garage_s3_keys | length > 0
|
|
- item.stdout is defined
|
|
|
|
- name: Get list of existing buckets
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage bucket list
|
|
register: _existing_buckets_output
|
|
changed_when: false
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Parse existing bucket names
|
|
ansible.builtin.set_fact:
|
|
# `garage bucket list` columns: ID Created Global aliases Local aliases
|
|
# Data rows start with a hex bucket ID; filter to those and take the
|
|
# third whitespace-separated field (the global alias = bucket name).
|
|
_existing_buckets: >-
|
|
{{
|
|
_existing_buckets_output.stdout_lines
|
|
| select('match', '^[0-9a-f]{16}\\s')
|
|
| map('regex_replace', '^\\S+\\s+\\S+\\s+(\\S+).*$', '\\1')
|
|
| list
|
|
}}
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Get unique bucket names
|
|
ansible.builtin.set_fact:
|
|
_unique_buckets: "{{ garage_s3_keys | subelements('buckets', skip_missing=True) | map(attribute='1.name') | unique | list }}"
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Create buckets
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage bucket create {{ item }}
|
|
loop: "{{ _unique_buckets }}"
|
|
when:
|
|
- garage_s3_keys | length > 0
|
|
- item not in _existing_buckets
|
|
failed_when: false
|
|
|
|
- name: Get current bucket permissions
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage bucket info {{ item.1.name }}
|
|
loop: "{{ garage_s3_keys | subelements('buckets', skip_missing=True) }}"
|
|
loop_control:
|
|
label: "{{ item.1.name }}"
|
|
register: _bucket_info_results
|
|
changed_when: false
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Set bucket permissions using key IDs
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage bucket allow {{ item.item.1.name }} {% for perm in item.item.1.permissions %}--{{ perm }} {% endfor %}--key {{ _key_id_map[item.item.0.name] }}
|
|
loop: "{{ _bucket_info_results.results }}"
|
|
loop_control:
|
|
label: "{{ item.item.1.name }} -> {{ item.item.0.name }}"
|
|
when:
|
|
- garage_s3_keys | length > 0
|
|
- >-
|
|
(item.stdout | regex_search(
|
|
'(?m)^\s*' ~ _wanted_flags ~ '\s+' ~ _key_id_map[item.item.0.name]
|
|
)) is none
|
|
vars:
|
|
_wanted_flags: >-
|
|
{{
|
|
('R' if 'read' in item.item.1.permissions else '-')
|
|
~ ('W' if 'write' in item.item.1.permissions else '-')
|
|
~ ('O' if 'owner' in item.item.1.permissions else '-')
|
|
}}
|
|
|
|
# Export key credentials for use by other roles
|
|
- name: Get detailed key information for all keys
|
|
community.docker.docker_container_exec:
|
|
container: "{{ garage_service_name }}"
|
|
command: /garage key info {{ item.name }} --show-secret
|
|
loop: "{{ garage_s3_keys }}"
|
|
register: _key_details_results
|
|
changed_when: false
|
|
when: garage_s3_keys | length > 0
|
|
|
|
- name: Build garage S3 credentials map
|
|
ansible.builtin.set_fact:
|
|
garage_s3_credentials: "{{ garage_s3_credentials | default({}) | combine({item.item.name: {'key_id': item.stdout | regex_search('Key ID:\\s+(\\S+)', '\\1') | first, 'secret_key': item.stdout | regex_search('Secret key:\\s+(\\S+)', '\\1') | first}}) }}"
|
|
loop: "{{ _key_details_results.results }}"
|
|
when:
|
|
- garage_s3_keys | length > 0
|
|
- item.stdout is defined
|
|
|
|
- name: Export garage S3 credentials as cacheable fact
|
|
ansible.builtin.set_fact:
|
|
garage_s3_credentials: "{{ garage_s3_credentials }}"
|
|
cacheable: true
|
|
when: garage_s3_keys | length > 0 |