diff --git a/.github/workflows/check_eol.yml b/.github/workflows/check_eol.yml index b2289976..fb371f27 100644 --- a/.github/workflows/check_eol.yml +++ b/.github/workflows/check_eol.yml @@ -7,6 +7,7 @@ on: permissions: contents: read + issues: write jobs: check-eol: @@ -61,14 +62,13 @@ jobs: } echo "=== Checking OS distributions ===" - check_eol "rocky-linux" "8" "Rocky Linux 8" check_eol "rocky-linux" "9" "Rocky Linux 9" check_eol "rocky-linux" "10" "Rocky Linux 10" - check_eol "debian" "11" "Debian 11 (Bullseye)" check_eol "debian" "12" "Debian 12 (Bookworm)" check_eol "debian" "13" "Debian 13 (Trixie)" check_eol "ubuntu" "22.04" "Ubuntu 22.04 (Jammy)" check_eol "ubuntu" "24.04" "Ubuntu 24.04 (Noble)" + check_eol "ubuntu" "26.04" "Ubuntu 26.04 (Resolute Raccoon)" echo "" echo "=== Checking Python versions ===" diff --git a/.github/workflows/test_elasticsearch_custom_certs.yml b/.github/workflows/test_elasticsearch_custom_certs.yml index 7dcc9e42..30c9f729 100644 --- a/.github/workflows/test_elasticsearch_custom_certs.yml +++ b/.github/workflows/test_elasticsearch_custom_certs.yml @@ -42,5 +42,5 @@ jobs: uses: ./.github/workflows/molecule.yml with: scenarios: '["elasticsearch_custom_certs","elasticsearch_custom_certs_minimal"]' - distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }} + distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }} secrets: inherit diff --git a/.github/workflows/test_elasticsearch_modules.yml b/.github/workflows/test_elasticsearch_modules.yml index 4bf49846..bc844792 100644 --- a/.github/workflows/test_elasticsearch_modules.yml +++ b/.github/workflows/test_elasticsearch_modules.yml @@ -42,6 +42,6 @@ jobs: uses: ./.github/workflows/molecule.yml with: scenarios: '["elasticsearch_test_modules"]' - distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }} + distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }} timeout: 20 secrets: inherit diff --git a/.github/workflows/test_full_stack.yml b/.github/workflows/test_full_stack.yml index 0fcae443..f64d1d5b 100644 --- a/.github/workflows/test_full_stack.yml +++ b/.github/workflows/test_full_stack.yml @@ -12,6 +12,10 @@ on: - info - warning - debug + distros: + description: 'JSON array of distros (empty = all)' + required: false + type: string pull_request: merge_group: schedule: @@ -80,7 +84,7 @@ jobs: strategy: fail-fast: false matrix: - distro: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && fromJSON('["rockylinux9","debian13"]') || fromJSON('["rockylinux9","ubuntu2204","ubuntu2404","debian12","debian13"]') }} + distro: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && fromJSON('["rockylinux9","debian13"]') || (inputs.distros != '' && fromJSON(inputs.distros)) || fromJSON('["rockylinux9","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]') }} scenario: - elasticstack_default - es_kibana diff --git a/.github/workflows/test_role_beats.yml b/.github/workflows/test_role_beats.yml index c2ffa9f9..0c09230b 100644 --- a/.github/workflows/test_role_beats.yml +++ b/.github/workflows/test_role_beats.yml @@ -42,5 +42,5 @@ jobs: uses: ./.github/workflows/molecule.yml with: scenarios: '["beats_default","beats_peculiar","beats_advanced","beats_security"]' - distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }} + distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }} secrets: inherit diff --git a/.github/workflows/test_role_elasticsearch.yml b/.github/workflows/test_role_elasticsearch.yml index 7f4655e4..48b2e087 100644 --- a/.github/workflows/test_role_elasticsearch.yml +++ b/.github/workflows/test_role_elasticsearch.yml @@ -42,7 +42,7 @@ jobs: uses: ./.github/workflows/molecule.yml with: scenarios: '["elasticsearch_default","elasticsearch_roles_calculation","elasticsearch_custom","elasticsearch_no-security","elasticsearch_cert_content"]' - distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }} + distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }} secrets: inherit molecule_elasticsearch_diagnostics: diff --git a/.github/workflows/test_role_kibana.yml b/.github/workflows/test_role_kibana.yml index 825b60ba..bd064b85 100644 --- a/.github/workflows/test_role_kibana.yml +++ b/.github/workflows/test_role_kibana.yml @@ -42,5 +42,5 @@ jobs: uses: ./.github/workflows/molecule.yml with: scenarios: '["kibana_default","kibana_custom","kibana_custom_certs"]' - distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }} + distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }} secrets: inherit diff --git a/.github/workflows/test_role_logstash.yml b/.github/workflows/test_role_logstash.yml index 77697285..bf6c31ef 100644 --- a/.github/workflows/test_role_logstash.yml +++ b/.github/workflows/test_role_logstash.yml @@ -42,5 +42,5 @@ jobs: uses: ./.github/workflows/molecule.yml with: scenarios: '["logstash_default","logstash_ssl","logstash_custom_pipeline","logstash_advanced","logstash_standalone_certs","logstash_centralized_pipelines"]' - distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }} + distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }} secrets: inherit diff --git a/.github/workflows/test_role_repos.yml b/.github/workflows/test_role_repos.yml index 6e447566..cf0c3a47 100644 --- a/.github/workflows/test_role_repos.yml +++ b/.github/workflows/test_role_repos.yml @@ -42,6 +42,6 @@ jobs: uses: ./.github/workflows/molecule.yml with: scenarios: '["repos_default"]' - distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }} + distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }} timeout: 30 secrets: inherit diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..be546595 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,52 @@ +# Changelog + +## 0.1.0 (unreleased) + +First release of the `oddly.elasticstack` collection, forked from +[NETWAYS/ansible-collection-elasticstack](https://github.com/NETWAYS/ansible-collection-elasticstack). + +### Highlights + +- Full Elastic Stack 9.x support alongside 8.x, with version-aware templates + that switch configuration syntax automatically. +- Rolling upgrades from 8.x to 9.x with upgrade path validation, per-node + orchestration, and shard allocation management. +- Complete TLS PKI: CA generation, per-node certificates, automatic renewal, + and support for external certificates (PEM and PKCS12) with format + auto-detection. +- Inline PEM content variables for Vault / secrets manager integration + (Elasticsearch, Kibana, Beats). +- Separate transport and HTTP layer certificates on Elasticsearch. +- Certificate expiry warnings and tag-driven renewal (`--tags certificates`, + `--tags renew_es_cert`, `--tags renew_ca`). +- Logstash standalone certificate mode for independent deployments. +- Beats Filebeat `filestream` input type for 9.x (replacing deprecated `log`). +- Logstash `elastic_agent` input plugin support. +- Elasticsearch `cluster_settings` for runtime cluster configuration via API. +- LogsDB support with automatic enablement on fresh 9.x installs. +- Elasticsearch filesystem snapshot repository configuration. +- Cgroup-aware JVM heap auto-calculation for containerised deployments. +- Container workarounds: systemd `Type=exec` override, lenient disk watermarks. +- `elasticsearch_extra_config`, `kibana_extra_config`, `logstash_extra_config` + for arbitrary YAML settings without dedicated variables. +- `elasticstack_cert_pass` shortcut to set all TLS key passphrases at once. +- MkDocs Material documentation site with task flow diagrams, TLS guide, and + recipes. + +### Supported platforms + +- Debian 12 (bookworm), 13 (trixie) +- Ubuntu 22.04 (jammy), 24.04 (noble), 26.04 (resolute) +- Rocky Linux / RHEL 9, 10 +- Elastic Stack 8.x and 9.x +- Ansible 2.18+ + +### Breaking changes from netways.elasticstack + +- Namespace changed from `netways` to `oddly`. +- Minimum Ansible version raised to 2.18 (was 2.9). +- Debian 10/11 and Ubuntu 20.04 dropped from supported platforms. +- `elasticsearch_security: false` no longer permitted on ES 8.x+ (Elastic + upstream requirement). +- Logstash `logstash_beats_tls` renamed to `logstash_input_beats_ssl` (old name + still accepted for backwards compatibility). diff --git a/README.md b/README.md index 00b80099..ce8e3f81 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,9 @@ See the **[getting started guide](https://oddly.github.io/elasticstack/getting-s | | Versions | |-|----------| -| Debian | 11, 12, 13 | -| Ubuntu | 22.04, 24.04 | -| Rocky Linux / RHEL | 8, 9, 10 | +| Debian | 12, 13 | +| Ubuntu | 22.04, 24.04, 26.04 | +| Rocky Linux / RHEL | 9, 10 | | Elastic Stack | 8.x, 9.x | | Ansible | 2.18+ | diff --git a/docs/index.md b/docs/index.md index d8120a71..a82d83df 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,7 +23,7 @@ ansible-galaxy collection install oddly.elasticstack - **Automatic TLS** — generates and distributes certificates for inter-node and HTTP encryption - **Security bootstrapping** — creates users, roles, and passwords on first run - **Rolling upgrades** — 8.x to 9.x with zero-downtime node-by-node upgrades -- **Multi-platform** — Debian 11-13, Ubuntu 22.04/24.04, Rocky Linux/RHEL 8-10 +- **Multi-platform** — Debian 12-13, Ubuntu 22.04/24.04/26.04, Rocky Linux/RHEL 9-10
diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index ba0277b2..9a52f473 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -3,7 +3,7 @@ ## Prerequisites - Ansible 2.18 or later -- Target hosts running Debian 11/12/13, Ubuntu 22.04/24.04, or Rocky Linux/RHEL 8/9/10 +- Target hosts running Debian 12/13, Ubuntu 22.04/24.04/26.04, or Rocky Linux/RHEL 9/10 - SSH access to target hosts with root or sudo privileges - Minimum 4 GB RAM per Elasticsearch node (8 GB recommended) - Python 3 on all target hosts (with `python3-apt` on Debian/Ubuntu) diff --git a/molecule/shared/create.yml b/molecule/shared/create.yml index 1f826b68..9a5ff360 100644 --- a/molecule/shared/create.yml +++ b/molecule/shared/create.yml @@ -52,6 +52,7 @@ rockylinux10: "molecule-rockylinux-10" ubuntu2204: "molecule-ubuntu-22.04" ubuntu2404: "molecule-ubuntu-24.04" + ubuntu2604: "molecule-ubuntu-26.04" tasks: - name: Delete any existing containers with same names diff --git a/roles/beats/defaults/main.yml b/roles/beats/defaults/main.yml index 57d28622..73d1275f 100644 --- a/roles/beats/defaults/main.yml +++ b/roles/beats/defaults/main.yml @@ -200,9 +200,6 @@ beats_metricbeat_loadbalance: true beats_cert_validity_period: 1095 # @var beats_cert_expiration_buffer:description: Days before certificate expiry to trigger renewal beats_cert_expiration_buffer: 30 -# @var beats_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually -beats_cert_will_expire_soon: false - # @var beats_cert_source:description: Where to get TLS certificates. 'elasticsearch_ca' (default) auto-generates from the built-in CA; 'external' uses user-provided files beats_cert_source: elasticsearch_ca diff --git a/roles/beats/meta/main.yml b/roles/beats/meta/main.yml index 02e51807..b44eab8d 100644 --- a/roles/beats/meta/main.yml +++ b/roles/beats/meta/main.yml @@ -10,7 +10,6 @@ galaxy_info: platforms: - name: EL versions: - - "8" - "9" - "10" - name: Debian @@ -21,6 +20,7 @@ galaxy_info: versions: - jammy - noble + - resolute galaxy_tags: - elk - beats diff --git a/roles/beats/tasks/beats-security.yml b/roles/beats/tasks/beats-security.yml index 4ecf94ad..bc4ed2b4 100644 --- a/roles/beats/tasks/beats-security.yml +++ b/roles/beats/tasks/beats-security.yml @@ -10,42 +10,20 @@ # -- Detect content mode vs file mode -- - name: beats-security | Detect content mode for Beats - ansible.builtin.set_fact: - _beats_content_mode: "{{ beats_tls_certificate_content | default('', true) | length > 0 }}" - - - name: beats-security | Validate certificate is provided - ansible.builtin.assert: - that: - - (beats_tls_certificate_file | length > 0) or - (beats_tls_certificate_content | default('', true) | length > 0) - fail_msg: >- - External cert mode requires at minimum: - beats_tls_certificate_file or beats_tls_certificate_content - - # -- Content mode: set format to PEM -- - - name: beats-security | Set format facts for content mode - ansible.builtin.set_fact: - _beats_cert_format: pem - _beats_ca_extracted: false - when: _beats_content_mode | bool + ansible.builtin.include_tasks: + file: "{{ role_path }}/../elasticstack/tasks/certs/cert_detect_content_mode.yml" + vars: + _detect_cert_content_var: "{{ beats_tls_certificate_content | default('', true) }}" + _detect_key_content_var: "{{ beats_tls_key_content | default('', true) }}" + _detect_cert_file_var: "{{ beats_tls_certificate_file }}" + _detect_service_name: Beats + _detect_key_var_name: beats_tls_key_content - - name: beats-security | Check for CA chain in certificate content + - name: beats-security | Map detection results to Beats facts ansible.builtin.set_fact: - _beats_ca_extracted: true - when: - - _beats_content_mode | bool - - >- - beats_tls_certificate_content | default('') - | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1 - - - name: beats-security | Validate key content is provided (content mode) - ansible.builtin.assert: - that: - - beats_tls_key_content | default('', true) | length > 0 - fail_msg: >- - Content mode requires both certificate and key content. - Set beats_tls_key_content. - when: _beats_content_mode | bool + _beats_content_mode: "{{ _detect_content_mode }}" + _beats_cert_format: "{{ _detect_cert_format | default(omit) }}" + _beats_ca_extracted: "{{ _detect_ca_extracted | default(false) }}" # -- File mode: validate and detect format -- - name: beats-security | Validate Beats certificate (file mode) diff --git a/roles/beats/templates/_beats_logging.j2 b/roles/beats/templates/_beats_logging.j2 new file mode 100644 index 00000000..2bf0fa81 --- /dev/null +++ b/roles/beats/templates/_beats_logging.j2 @@ -0,0 +1,9 @@ +{% if beats_logging == "file" %} +logging.level: {{ beats_loglevel }} +logging.to_files: true +logging.files: + path: {{ beats_logpath }} + name: {{ _beat_name }} + keepfiles: {{ beats_logging_keepfiles }} + permissions: {{ beats_logging_permissions }} +{% endif %} diff --git a/roles/beats/templates/_beats_output.j2 b/roles/beats/templates/_beats_output.j2 new file mode 100644 index 00000000..04b93e74 --- /dev/null +++ b/roles/beats/templates/_beats_output.j2 @@ -0,0 +1,38 @@ +{% if _beat_output == "elasticsearch" %} +output.elasticsearch: +{% if beats_security | bool %} +{% if elasticstack_full_stack | bool %} + hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] +{% else %} + hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] +{% endif %} + username: "elastic" + password: "{{ beats_writer_password.stdout }}" + ssl.enabled: true + ssl.verification_mode: {{ beats_ssl_verification_mode }} + ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"] +{% else %} +{% if elasticstack_full_stack | bool %} + hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] +{% else %} + hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] +{% endif %} +{% endif %} +{% endif %} +{% if _beat_output == "logstash" %} +output.logstash: +{% if elasticstack_full_stack | bool %} + hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] +{% else %} + hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] +{% endif %} + loadbalance: {{ _beat_loadbalance }} +{% if beats_security | bool %} + ssl.enabled: true + ssl.certificate_authorities: ["{{ beats_tls_cacert }}"] + ssl.verification_mode: full + ssl.key: {{ beats_tls_key }} + ssl.key_passphrase: {{ beats_tls_key_passphrase }} + ssl.certificate: {{ beats_tls_cert }} +{% endif %} +{% endif %} diff --git a/roles/beats/templates/auditbeat.yml.j2 b/roles/beats/templates/auditbeat.yml.j2 index ed995d0c..44cfcaed 100644 --- a/roles/beats/templates/auditbeat.yml.j2 +++ b/roles/beats/templates/auditbeat.yml.j2 @@ -34,54 +34,12 @@ setup.kibana: {% if elasticstack_full_stack | default(false) | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %} host: "http://{{ groups[elasticstack_kibana_group_name][0] }}:{{ elasticstack_kibana_port }}" {% endif %} -{% if beats_auditbeat_output == "elasticsearch" %} -output.elasticsearch: -{% if beats_security | bool %} -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} - username: "elastic" - password: "{{ beats_writer_password.stdout }}" - ssl.enabled: true - ssl.verification_mode: {{ beats_ssl_verification_mode }} - ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"] -{% else %} -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} -{% endif %} -{% endif %} -{% if beats_auditbeat_output == "logstash" %} -output.logstash: -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} - loadbalance: {{ beats_auditbeat_loadbalance }} -{% if beats_security | bool %} - ssl.enabled: true - ssl.certificate_authorities: ["{{ beats_tls_cacert }}"] - ssl.verification_mode: full - ssl.key: {{ beats_tls_key }} - ssl.key_passphrase: {{ beats_tls_key_passphrase }} - ssl.certificate: {{ beats_tls_cert }} -{% endif %} -{% endif %} +{% set _beat_output = beats_auditbeat_output %} +{% set _beat_loadbalance = beats_auditbeat_loadbalance %} +{% set _beat_name = "auditbeat" %} +{% include '_beats_output.j2' %} -{% if beats_logging == "file" %} -logging.level: {{ beats_loglevel }} -logging.to_files: true -logging.files: - path: {{ beats_logpath }} - name: auditbeat - keepfiles: {{ beats_logging_keepfiles }} - permissions: {{ beats_logging_permissions }} -{% endif %} +{% include '_beats_logging.j2' %} processors: - add_host_metadata: ~ diff --git a/roles/beats/templates/filebeat.yml.j2 b/roles/beats/templates/filebeat.yml.j2 index 1320f911..e05be379 100644 --- a/roles/beats/templates/filebeat.yml.j2 +++ b/roles/beats/templates/filebeat.yml.j2 @@ -145,59 +145,17 @@ setup.kibana: {% if elasticstack_full_stack | default(false) | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %} host: "http://{{ groups[elasticstack_kibana_group_name][0] }}:{{ elasticstack_kibana_port }}" {% endif %} -{% if beats_filebeat_output == "elasticsearch" %} -output.elasticsearch: -{% if beats_security | bool %} -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} - username: "elastic" - password: "{{ beats_writer_password.stdout }}" - ssl.enabled: true - ssl.verification_mode: {{ beats_ssl_verification_mode }} - ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"] -{% else %} -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} -{% endif %} -{% endif %} -{% if beats_filebeat_output == "logstash" %} -output.logstash: -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} - loadbalance: {{ beats_filebeat_loadbalance }} -{% if beats_security | bool %} - ssl.enabled: true - ssl.certificate_authorities: ["{{ beats_tls_cacert }}"] - ssl.verification_mode: full - ssl.key: {{ beats_tls_key }} - ssl.key_passphrase: {{ beats_tls_key_passphrase }} - ssl.certificate: {{ beats_tls_cert }} -{% endif %} -{% endif %} +{% set _beat_output = beats_filebeat_output %} +{% set _beat_loadbalance = beats_filebeat_loadbalance %} +{% set _beat_name = "filebeat" %} +{% include '_beats_output.j2' %} {% if beats_filebeat_output == "file" %} output.file: path: {{ beats_filebeat_output_file_path | default("/tmp/filebeat-output") }} filename: filebeat {% endif %} -{% if beats_logging == "file" %} -logging.level: {{ beats_loglevel }} -logging.to_files: true -logging.files: - path: {{ beats_logpath }} - name: filebeat - keepfiles: {{ beats_logging_keepfiles }} - permissions: {{ beats_logging_permissions }} -{% endif %} +{% include '_beats_logging.j2' %} queue.{{ beats_queue_type }}: {% if beats_queue_type == "disk" %} diff --git a/roles/beats/templates/metricbeat.yml.j2 b/roles/beats/templates/metricbeat.yml.j2 index 49984c6a..3cf548c8 100644 --- a/roles/beats/templates/metricbeat.yml.j2 +++ b/roles/beats/templates/metricbeat.yml.j2 @@ -11,54 +11,12 @@ setup.kibana: {% if elasticstack_full_stack | default(false) | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %} host: "http://{{ groups[elasticstack_kibana_group_name][0] }}:{{ elasticstack_kibana_port }}" {% endif %} -{% if beats_metricbeat_output == "elasticsearch" %} -output.elasticsearch: -{% if beats_security | bool %} -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} - username: "elastic" - password: "{{ beats_writer_password.stdout }}" - ssl.enabled: true - ssl.verification_mode: {{ beats_ssl_verification_mode }} - ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"] -{% else %} -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} -{% endif %} -{% endif %} -{% if beats_metricbeat_output == "logstash" %} -output.logstash: -{% if elasticstack_full_stack | bool %} - hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% else %} - hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}] -{% endif %} - loadbalance: {{ beats_metricbeat_loadbalance }} -{% if beats_security | bool %} - ssl.enabled: true - ssl.certificate_authorities: ["{{ beats_tls_cacert }}"] - ssl.verification_mode: full - ssl.key: {{ beats_tls_key }} - ssl.key_passphrase: {{ beats_tls_key_passphrase }} - ssl.certificate: {{ beats_tls_cert }} -{% endif %} -{% endif %} +{% set _beat_output = beats_metricbeat_output %} +{% set _beat_loadbalance = beats_metricbeat_loadbalance %} +{% set _beat_name = "metricbeat" %} +{% include '_beats_output.j2' %} -{% if beats_logging == "file" %} -logging.level: {{ beats_loglevel }} -logging.to_files: true -logging.files: - path: {{ beats_logpath }} - name: metricbeat - keepfiles: {{ beats_logging_keepfiles }} - permissions: {{ beats_logging_permissions }} -{% endif %} +{% include '_beats_logging.j2' %} processors: - add_host_metadata: ~ diff --git a/roles/beats/vars/main.yml b/roles/beats/vars/main.yml new file mode 100644 index 00000000..fa515fb3 --- /dev/null +++ b/roles/beats/vars/main.yml @@ -0,0 +1,3 @@ +--- +# Internal state — do not set manually +beats_cert_will_expire_soon: false diff --git a/roles/elasticsearch/defaults/main.yml b/roles/elasticsearch/defaults/main.yml index 67b9af64..a86fcda0 100644 --- a/roles/elasticsearch/defaults/main.yml +++ b/roles/elasticsearch/defaults/main.yml @@ -116,8 +116,6 @@ elasticsearch_tls_key_passphrase: PleaseChangeMeIndividually elasticsearch_cert_validity_period: 1095 # @var elasticsearch_cert_expiration_buffer:description: Days before certificate expiry to trigger renewal elasticsearch_cert_expiration_buffer: 30 -# @var elasticsearch_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually -elasticsearch_cert_will_expire_soon: false # @var elasticsearch_ssl_verification_mode:description: TLS verification mode for inter-node communication (full, certificate, or none) elasticsearch_ssl_verification_mode: full diff --git a/roles/elasticsearch/meta/main.yml b/roles/elasticsearch/meta/main.yml index 1eaa741c..b2c4409e 100644 --- a/roles/elasticsearch/meta/main.yml +++ b/roles/elasticsearch/meta/main.yml @@ -9,7 +9,6 @@ galaxy_info: platforms: - name: EL versions: - - "8" - "9" - "10" - name: Debian @@ -20,5 +19,6 @@ galaxy_info: versions: - jammy - noble + - resolute galaxy_tags: [] dependencies: [] diff --git a/roles/elasticsearch/tasks/main.yml b/roles/elasticsearch/tasks/main.yml index 0960d76a..78df6188 100644 --- a/roles/elasticsearch/tasks/main.yml +++ b/roles/elasticsearch/tasks/main.yml @@ -44,7 +44,7 @@ name: oddly.elasticstack.elasticstack when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) -- name: main | Propagate stack-level security setting +- name: Main | Propagate stack-level security setting ansible.builtin.set_fact: elasticsearch_security: "{{ elasticstack_security | bool }}" when: diff --git a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml index 5b7e42d9..564a639b 100644 --- a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml +++ b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml @@ -1,38 +1,8 @@ --- - name: restart_and_verify_elasticsearch | Restart and verify Elasticsearch - block: - - name: restart_and_verify_elasticsearch | Reset Elasticsearch failed state before restart - ansible.builtin.command: - cmd: systemctl reset-failed elasticsearch.service - changed_when: false - failed_when: false - - - name: restart_and_verify_elasticsearch | Restart Elasticsearch service - ansible.builtin.service: - name: elasticsearch - state: restarted - daemon_reload: true - - - name: restart_and_verify_elasticsearch | Verify Elasticsearch is running - ansible.builtin.systemd: - name: elasticsearch - register: _elasticsearch_service_state - until: _elasticsearch_service_state.status.ActiveState == 'active' - retries: 5 - delay: 3 - - rescue: - - name: restart_and_verify_elasticsearch | Get recent Elasticsearch journal output - ansible.builtin.command: - cmd: journalctl -u elasticsearch --no-pager -n 50 - register: _elasticsearch_journal - changed_when: false - - - name: restart_and_verify_elasticsearch | Fail with Elasticsearch startup diagnostics - ansible.builtin.fail: - msg: | - Elasticsearch failed to start. - - Recent log output: - {{ _elasticsearch_journal.stdout }} + ansible.builtin.include_tasks: + file: "{{ role_path }}/../elasticstack/tasks/restart_and_verify_service.yml" + vars: + _service_name: elasticsearch + _daemon_reload: true diff --git a/roles/elasticsearch/vars/main.yml b/roles/elasticsearch/vars/main.yml index af01504e..01b0b90a 100644 --- a/roles/elasticsearch/vars/main.yml +++ b/roles/elasticsearch/vars/main.yml @@ -39,6 +39,9 @@ _elasticsearch_managed_keys: # Internal sentinel values — prevent handler errors on first run. # These are overwritten by task register output; users must never set them. +# Internal state — do not set manually +elasticsearch_cert_will_expire_soon: false + _elasticsearch_freshstart: changed: false _elasticsearch_freshstart_security: diff --git a/roles/elasticstack/defaults/main.yml b/roles/elasticstack/defaults/main.yml index 1a2d9f02..5889811a 100644 --- a/roles/elasticstack/defaults/main.yml +++ b/roles/elasticstack/defaults/main.yml @@ -45,9 +45,6 @@ elasticstack_ca_pass: PleaseChangeMe # elasticstack_cert_pass: # @var elasticstack_ca_validity_period:description: Validity period in days for the CA certificate elasticstack_ca_validity_period: 1095 -# @var elasticstack_ca_will_expire_soon:description: Internal flag set when the CA certificate is within the expiration buffer. Do not set manually -elasticstack_ca_will_expire_soon: false - # === General Settings === # @var elasticstack_enable_repos:description: Let the repos role manage Elastic APT/YUM repositories diff --git a/roles/elasticstack/meta/main.yml b/roles/elasticstack/meta/main.yml new file mode 100644 index 00000000..37aa4a13 --- /dev/null +++ b/roles/elasticstack/meta/main.yml @@ -0,0 +1,29 @@ +--- +galaxy_info: + role_name: elasticstack + namespace: oddly + author: Netways GmbH + description: Shared defaults for the oddly.elasticstack collection + company: Netways GmbH + license: GPL-3.0-or-later + min_ansible_version: "2.18" + platforms: + - name: EL + versions: + - "9" + - "10" + - name: Debian + versions: + - bookworm + - trixie + - name: Ubuntu + versions: + - jammy + - noble + - resolute + galaxy_tags: + - elk + - elastic + - logging + - system +dependencies: [] diff --git a/roles/elasticstack/tasks/certs/cert_detect_content_mode.yml b/roles/elasticstack/tasks/certs/cert_detect_content_mode.yml new file mode 100644 index 00000000..3f68616a --- /dev/null +++ b/roles/elasticstack/tasks/certs/cert_detect_content_mode.yml @@ -0,0 +1,56 @@ +--- +# Detect whether external certificates are provided as inline PEM content +# or as file paths, and set format/CA-extraction facts accordingly. +# +# Required variables: +# _detect_cert_content_var – the certificate content variable value +# (e.g. beats_tls_certificate_content) +# _detect_key_content_var – the key content variable value +# (e.g. beats_tls_key_content) +# _detect_cert_file_var – the certificate file path variable value +# (e.g. beats_tls_certificate_file) +# _detect_service_name – human-readable service name for error messages +# _detect_key_var_name – variable name string for the fail_msg hint +# (e.g. "beats_tls_key_content") +# +# Output facts (set on the host): +# _detect_content_mode – "True"/"False" string (use with | bool) +# _detect_cert_format – "pem" when content mode, undefined otherwise +# _detect_ca_extracted – whether the PEM bundle contains a CA chain + +- name: "cert_detect_content_mode | Detect content mode for {{ _detect_service_name }}" # noqa: name[template] var-naming[no-role-prefix] + ansible.builtin.set_fact: + _detect_content_mode: "{{ _detect_cert_content_var | default('', true) | length > 0 }}" + +- name: "cert_detect_content_mode | Validate certificate is provided for {{ _detect_service_name }}" # noqa: name[template] + ansible.builtin.assert: + that: + - (_detect_cert_file_var | length > 0) or + (_detect_cert_content_var | default('', true) | length > 0) + fail_msg: >- + External cert mode requires at minimum a certificate file or content + for {{ _detect_service_name }}. + +- name: cert_detect_content_mode | Set format facts for content mode # noqa: var-naming[no-role-prefix] + ansible.builtin.set_fact: + _detect_cert_format: pem + _detect_ca_extracted: false + when: _detect_content_mode | bool + +- name: cert_detect_content_mode | Check for CA chain in certificate content # noqa: var-naming[no-role-prefix] + ansible.builtin.set_fact: + _detect_ca_extracted: true + when: + - _detect_content_mode | bool + - >- + _detect_cert_content_var | default('') + | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1 + +- name: "cert_detect_content_mode | Validate key content is provided for {{ _detect_service_name }}" # noqa: name[template] + ansible.builtin.assert: + that: + - _detect_key_content_var | default('', true) | length > 0 + fail_msg: >- + Content mode requires both certificate and key content. + Set {{ _detect_key_var_name }}. + when: _detect_content_mode | bool diff --git a/roles/elasticstack/tasks/restart_and_verify_service.yml b/roles/elasticstack/tasks/restart_and_verify_service.yml new file mode 100644 index 00000000..b7e95c68 --- /dev/null +++ b/roles/elasticstack/tasks/restart_and_verify_service.yml @@ -0,0 +1,45 @@ +--- +# Shared restart-and-verify pattern for systemd services. +# +# Required variables: +# _service_name – systemd unit name (without .service suffix) +# +# Optional variables: +# _daemon_reload – whether to daemon-reload before restarting (default: false) + +- name: "restart_and_verify_service | Restart and verify {{ _service_name }}" # noqa: name[template] + block: + - name: "restart_and_verify_service | Reset {{ _service_name }} failed state before restart" # noqa: name[template] + ansible.builtin.command: + cmd: "systemctl reset-failed {{ _service_name }}.service" + changed_when: false + failed_when: false + + - name: "restart_and_verify_service | Restart {{ _service_name }} service" # noqa: name[template] + ansible.builtin.service: + name: "{{ _service_name }}" + state: restarted + daemon_reload: "{{ _daemon_reload | default(false) | bool }}" + + - name: "restart_and_verify_service | Verify {{ _service_name }} is running" # noqa: name[template] var-naming[no-role-prefix] + ansible.builtin.systemd: + name: "{{ _service_name }}" + register: _service_state + until: _service_state.status.ActiveState == 'active' + retries: 5 + delay: 3 + + rescue: + - name: "restart_and_verify_service | Get recent {{ _service_name }} journal output" # noqa: name[template] var-naming[no-role-prefix] + ansible.builtin.command: + cmd: "journalctl -u {{ _service_name }} --no-pager -n 50" + register: _service_journal + changed_when: false + + - name: "restart_and_verify_service | Fail with {{ _service_name }} startup diagnostics" # noqa: name[template] + ansible.builtin.fail: + msg: | + {{ _service_name }} failed to start. + + Recent log output: + {{ _service_journal.stdout }} diff --git a/roles/elasticstack/vars/main.yml b/roles/elasticstack/vars/main.yml index ed456934..20998a99 100644 --- a/roles/elasticstack/vars/main.yml +++ b/roles/elasticstack/vars/main.yml @@ -1,2 +1,3 @@ --- -# vars file for logstash +# Internal state — do not set manually +elasticstack_ca_will_expire_soon: false diff --git a/roles/kibana/defaults/main.yml b/roles/kibana/defaults/main.yml index 92255046..12ad5d86 100644 --- a/roles/kibana/defaults/main.yml +++ b/roles/kibana/defaults/main.yml @@ -41,9 +41,6 @@ kibana_keystore_password: "" kibana_cert_expiration_buffer: 30 # @var kibana_cert_validity_period:description: Validity period in days for generated TLS certificates kibana_cert_validity_period: 1095 -# @var kibana_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually -kibana_cert_will_expire_soon: false - # @var kibana_cert_source:description: Where to get TLS certificates. 'elasticsearch_ca' (default) auto-generates from the built-in CA; 'external' uses user-provided files kibana_cert_source: elasticsearch_ca diff --git a/roles/kibana/meta/main.yml b/roles/kibana/meta/main.yml index 1432001d..600c3d11 100644 --- a/roles/kibana/meta/main.yml +++ b/roles/kibana/meta/main.yml @@ -9,7 +9,6 @@ galaxy_info: platforms: - name: EL versions: - - "8" - "9" - "10" - name: Debian @@ -20,6 +19,7 @@ galaxy_info: versions: - jammy - noble + - resolute galaxy_tags: [] dependencies: [] # List your role dependencies here, one per line. Be sure to remove the '[]' above, diff --git a/roles/kibana/tasks/kibana-security.yml b/roles/kibana/tasks/kibana-security.yml index f050ca83..1cb18f31 100644 --- a/roles/kibana/tasks/kibana-security.yml +++ b/roles/kibana/tasks/kibana-security.yml @@ -15,42 +15,20 @@ block: # -- Detect content mode vs file mode -- - name: kibana-security | Detect content mode for Kibana - ansible.builtin.set_fact: - _kibana_content_mode: "{{ kibana_tls_certificate_content | default('', true) | length > 0 }}" - - - name: kibana-security | Validate certificate is provided - ansible.builtin.assert: - that: - - (kibana_tls_certificate_file | length > 0) or - (kibana_tls_certificate_content | default('', true) | length > 0) - fail_msg: >- - External cert mode with kibana_tls requires at minimum: - kibana_tls_certificate_file or kibana_tls_certificate_content - - # -- Content mode: set format to PEM -- - - name: kibana-security | Set format facts for content mode - ansible.builtin.set_fact: - _kibana_cert_format: pem - _kibana_ca_extracted: false - when: _kibana_content_mode | bool + ansible.builtin.include_tasks: + file: "{{ role_path }}/../elasticstack/tasks/certs/cert_detect_content_mode.yml" + vars: + _detect_cert_content_var: "{{ kibana_tls_certificate_content | default('', true) }}" + _detect_key_content_var: "{{ kibana_tls_key_content | default('', true) }}" + _detect_cert_file_var: "{{ kibana_tls_certificate_file }}" + _detect_service_name: Kibana + _detect_key_var_name: kibana_tls_key_content - - name: kibana-security | Check for CA chain in certificate content + - name: kibana-security | Map detection results to Kibana facts ansible.builtin.set_fact: - _kibana_ca_extracted: true - when: - - _kibana_content_mode | bool - - >- - kibana_tls_certificate_content | default('') - | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1 - - - name: kibana-security | Validate key content is provided (content mode) - ansible.builtin.assert: - that: - - kibana_tls_key_content | default('', true) | length > 0 - fail_msg: >- - Content mode requires both certificate and key content. - Set kibana_tls_key_content. - when: _kibana_content_mode | bool + _kibana_content_mode: "{{ _detect_content_mode }}" + _kibana_cert_format: "{{ _detect_cert_format | default(omit) }}" + _kibana_ca_extracted: "{{ _detect_ca_extracted | default(false) }}" # -- File mode: validate and detect format -- - name: kibana-security | Validate Kibana certificate (file mode) diff --git a/roles/kibana/tasks/main.yml b/roles/kibana/tasks/main.yml index 92e7f536..d5ad60da 100644 --- a/roles/kibana/tasks/main.yml +++ b/roles/kibana/tasks/main.yml @@ -5,7 +5,7 @@ name: oddly.elasticstack.elasticstack when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) -- name: main | Propagate stack-level security setting +- name: Main | Propagate stack-level security setting ansible.builtin.set_fact: kibana_security: "{{ elasticstack_security | bool }}" when: diff --git a/roles/kibana/vars/main.yml b/roles/kibana/vars/main.yml index cad325d0..914722fe 100644 --- a/roles/kibana/vars/main.yml +++ b/roles/kibana/vars/main.yml @@ -1,4 +1,7 @@ --- +# Internal state — do not set manually +kibana_cert_will_expire_soon: false + # Internal sentinel values — prevent handler errors on first run. # These are overwritten by task register output; users must never set them. _kibana_freshstart: diff --git a/roles/logstash/defaults/main.yml b/roles/logstash/defaults/main.yml index 27ce0426..b6a2c253 100644 --- a/roles/logstash/defaults/main.yml +++ b/roles/logstash/defaults/main.yml @@ -207,8 +207,6 @@ logstash_certs_dir: /etc/logstash/certs logstash_cert_validity_period: 1095 # @var logstash_cert_expiration_buffer:description: Days before certificate expiry to trigger renewal logstash_cert_expiration_buffer: 30 -# @var logstash_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually -logstash_cert_will_expire_soon: false # @var logstash_cert_force_regenerate:description: Force TLS certificate regeneration even if current certificates are valid logstash_cert_force_regenerate: false # @var logstash_tls_key_passphrase:description: Passphrase for the Logstash TLS private key diff --git a/roles/logstash/meta/main.yml b/roles/logstash/meta/main.yml index e3a2f610..93c24f85 100644 --- a/roles/logstash/meta/main.yml +++ b/roles/logstash/meta/main.yml @@ -9,7 +9,6 @@ galaxy_info: platforms: - name: EL versions: - - "8" - "9" - "10" - name: Debian @@ -20,6 +19,7 @@ galaxy_info: versions: - jammy - noble + - resolute galaxy_tags: - elk - logstash diff --git a/roles/logstash/tasks/restart_and_verify_logstash.yml b/roles/logstash/tasks/restart_and_verify_logstash.yml index 65b41ccf..8ba03d43 100644 --- a/roles/logstash/tasks/restart_and_verify_logstash.yml +++ b/roles/logstash/tasks/restart_and_verify_logstash.yml @@ -1,38 +1,8 @@ --- - name: restart_and_verify_logstash | Restart and verify Logstash - block: - - name: restart_and_verify_logstash | Reset Logstash failed state before restart - ansible.builtin.command: - cmd: systemctl reset-failed logstash.service - changed_when: false - failed_when: false - - - name: restart_and_verify_logstash | Restart Logstash service - ansible.builtin.service: - name: logstash - state: restarted - daemon_reload: true - - - name: restart_and_verify_logstash | Verify Logstash is running - ansible.builtin.systemd: - name: logstash - register: _logstash_service_state - until: _logstash_service_state.status.ActiveState == 'active' - retries: 5 - delay: 3 - - rescue: - - name: restart_and_verify_logstash | Get recent Logstash journal output - ansible.builtin.command: - cmd: journalctl -u logstash --no-pager -n 50 - register: _logstash_journal - changed_when: false - - - name: restart_and_verify_logstash | Fail with Logstash startup diagnostics - ansible.builtin.fail: - msg: | - Logstash failed to start. - - Recent log output: - {{ _logstash_journal.stdout }} + ansible.builtin.include_tasks: + file: "{{ role_path }}/../elasticstack/tasks/restart_and_verify_service.yml" + vars: + _service_name: logstash + _daemon_reload: true diff --git a/roles/logstash/vars/main.yml b/roles/logstash/vars/main.yml index 2031f6a3..c5aa6191 100644 --- a/roles/logstash/vars/main.yml +++ b/roles/logstash/vars/main.yml @@ -1,4 +1,7 @@ --- +# Internal state — do not set manually +logstash_cert_will_expire_soon: false + # Internal sentinel values — prevent handler errors on first run. # These are overwritten by task register output; users must never set them. _logstash_freshstart: diff --git a/roles/repos/meta/main.yml b/roles/repos/meta/main.yml index 3693d9c5..c7c41f21 100644 --- a/roles/repos/meta/main.yml +++ b/roles/repos/meta/main.yml @@ -10,7 +10,6 @@ galaxy_info: platforms: - name: EL versions: - - "8" - "9" - "10" - name: Debian @@ -21,6 +20,7 @@ galaxy_info: versions: - jammy - noble + - resolute galaxy_tags: - logmanagement - elasticstack