From 19986e120579643ad1c5bc304c6c48ce83816da2 Mon Sep 17 00:00:00 2001 From: Bert-Jan Fikse Date: Thu, 18 Dec 2025 11:34:09 +0100 Subject: [PATCH] feat(garage): add provisioning of and bootstraping --- roles/garage/defaults/main.yml | 22 ++++++-- roles/garage/tasks/bootstrap.yml | 51 +++++++++++++++++ roles/garage/tasks/main.yml | 31 ++++++++++- roles/garage/tasks/provision.yml | 95 ++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 roles/garage/tasks/bootstrap.yml create mode 100644 roles/garage/tasks/provision.yml diff --git a/roles/garage/defaults/main.yml b/roles/garage/defaults/main.yml index 35fced5..495317e 100644 --- a/roles/garage/defaults/main.yml +++ b/roles/garage/defaults/main.yml @@ -36,11 +36,25 @@ garage_replication_factor: 1 garage_compression_level: 1 garage_db_engine: "lmdb" garage_s3_region: "us-east-1" -garage_rpc_secret: "changeme_rpc_secret" -garage_admin_token: "changeme_admin_token" -garage_metrics_token: "changeme_metrics_token" +garage_rpc_secret: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +garage_admin_token: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +garage_metrics_token: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" # Traefik configuration garage_traefik_network: "proxy" garage_internal_network: "internal" -garage_use_ssl: true \ No newline at end of file +garage_use_ssl: true + +# Optional: Garage cluster bootstrap configuration +garage_bootstrap_enabled: false +garage_bootstrap_zone: "dc1" +garage_bootstrap_capacity: "1G" + +# Optional: S3 keys to create +# Example: +# garage_s3_keys: +# - name: "my-key" +# buckets: +# - name: "my-bucket" +# permissions: ["read", "write"] +garage_s3_keys: [] \ No newline at end of file diff --git a/roles/garage/tasks/bootstrap.yml b/roles/garage/tasks/bootstrap.yml new file mode 100644 index 0000000..6cab2cf --- /dev/null +++ b/roles/garage/tasks/bootstrap.yml @@ -0,0 +1,51 @@ +#SPDX-License-Identifier: MIT-0 +--- +# Provisioning tasks for garage (cluster bootstrap, S3 keys, and buckets) +# Cluster bootstrap tasks +- name: Get garage node ID + community.docker.docker_container_exec: + container: "{{ garage_service_name }}" + command: /garage node id -q + register: _garage_node_id + +- name: Extract short node ID + ansible.builtin.set_fact: + _garage_node_id_short: "{{ _garage_node_id.stdout.split('@')[0] }}" + +- name: Check if node layout is configured + community.docker.docker_container_exec: + container: "{{ garage_service_name }}" + command: /garage layout show + register: _garage_layout_show + failed_when: false + +- name: Check if node is in layout + ansible.builtin.set_fact: + _node_in_layout: "{{ _garage_node_id_short in _garage_layout_show.stdout }}" + +- name: Configure garage node layout + community.docker.docker_container_exec: + container: "{{ garage_service_name }}" + command: /garage layout assign -z {{ garage_bootstrap_zone }} -c {{ garage_bootstrap_capacity }} {{ _garage_node_id_short }} + register: _layout_assign_result + when: + - not _node_in_layout + +- name: Extract current layout version + ansible.builtin.set_fact: + _current_layout_version: "{{ _garage_layout_show.stdout | regex_search('Current cluster layout version: (\\d+)', '\\1') | first | default('0') }}" + when: + - _layout_assign_result is changed + +- name: Calculate next layout version + ansible.builtin.set_fact: + _next_layout_version: "{{ (_current_layout_version | int) + 1 }}" + when: + - _layout_assign_result is changed + +- name: Apply garage layout + community.docker.docker_container_exec: + container: "{{ garage_service_name }}" + command: /garage layout apply --version {{ _next_layout_version }} + when: + - _layout_assign_result is changed \ No newline at end of file diff --git a/roles/garage/tasks/main.yml b/roles/garage/tasks/main.yml index c7b553f..4aebbeb 100644 --- a/roles/garage/tasks/main.yml +++ b/roles/garage/tasks/main.yml @@ -42,4 +42,33 @@ - name: Start garage container community.docker.docker_compose_v2: project_src: "{{ garage_docker_compose_dir }}" - state: present \ No newline at end of file + state: present + +- name: Wait for garage container to be running + community.docker.docker_container_info: + name: "{{ garage_service_name }}" + register: _garage_container_info + until: _garage_container_info.container.State.Running | default(false) + retries: 30 + delay: 2 + +- name: Wait for garage to be ready (check if garage command responds) + community.docker.docker_container_exec: + container: "{{ garage_service_name }}" + command: /garage status + register: _garage_status_check + until: _garage_status_check.rc == 0 + retries: 30 + delay: 2 + changed_when: false + failed_when: false + +# Include bootstraping tasks (cluster bootstrap) +- name: Include garage bootstraping tasks + ansible.builtin.include_tasks: bootstrap.yml + when: garage_bootstrap_enabled + +# Include provisioning tasks (S3 keys and buckets) +- name: Include garage bootstraping tasks + ansible.builtin.include_tasks: provision.yml + when: garage_s3_keys | length > 0 \ No newline at end of file diff --git a/roles/garage/tasks/provision.yml b/roles/garage/tasks/provision.yml new file mode 100644 index 0000000..ba9344b --- /dev/null +++ b/roles/garage/tasks/provision.yml @@ -0,0 +1,95 @@ +# 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 + when: garage_s3_keys | length > 0 + +- name: Parse existing key names + ansible.builtin.set_fact: + _existing_keys: "{{ _existing_keys_output.stdout_lines[1:] | select('match', '^GK') | map('regex_replace', '^\\S+\\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 + 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 + when: garage_s3_keys | length > 0 + +- name: Parse existing bucket names + ansible.builtin.set_fact: + _existing_buckets: "{{ _existing_buckets_output.stdout_lines[2:] | map('split') | map('first') | 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: Set bucket permissions using key IDs + community.docker.docker_container_exec: + container: "{{ garage_service_name }}" + command: /garage bucket allow {{ item.1.name }} {% for perm in item.1.permissions %}--{{ perm }} {% endfor %}--key {{ _key_id_map[item.0.name] }} + loop: "{{ garage_s3_keys | subelements('buckets', skip_missing=True) }}" + when: garage_s3_keys | length > 0 + +# 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 }} + loop: "{{ garage_s3_keys }}" + register: _key_details_results + 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 \ No newline at end of file