Eliminate localhost tasks to fix sudo issue on Semaphore runner

- Play 3: Run Portainer API calls from remote hosts directly (no
  delegate_to: localhost). Add validate_certs: false for self-signed cert.
- Play 4: Replace localhost file report with debug output using run_once.
  No filesystem writes = no privilege escalation needed on the runner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sam 2026-03-01 01:06:17 -07:00
parent b8dde7f2ca
commit 6db20117fd

View File

@ -140,6 +140,9 @@
# ---------------------------------------------------------------------------
# Play 3: Register Docker hosts in Portainer
# ---------------------------------------------------------------------------
# Runs the Portainer API calls FROM each remote host (not delegate_to localhost)
# to avoid privilege escalation issues on the Semaphore runner.
# validate_certs: false needed for Portainer's self-signed cert.
- name: Register Docker hosts in Portainer
hosts: all
@ -161,10 +164,8 @@
X-API-Key: "{{ portainer_api_token }}"
return_content: true
status_code: 200
validate_certs: false
register: existing_endpoints
delegate_to: localhost
become: false
run_once: false
- name: Determine if this host is already enrolled
ansible.builtin.set_fact:
@ -188,9 +189,8 @@
URL: "tcp://{{ ansible_host }}:{{ portainer_agent_port }}"
status_code: [200, 201]
return_content: true
validate_certs: false
register: portainer_enroll
delegate_to: localhost
become: false
when: not already_enrolled
- name: Store enrollment result
@ -209,24 +209,17 @@
# ---------------------------------------------------------------------------
# Play 4: Generate local report
# Play 4: Print discovery report to Semaphore output
# ---------------------------------------------------------------------------
# Runs on all hosts but only executes once (run_once: true per task).
# Uses debug instead of file I/O to avoid any privilege issues on the runner.
- name: Generate Docker host discovery report
hosts: localhost
- name: Print Docker host discovery report
hosts: all
gather_facts: false
become: false
vars:
report_path: "{{ playbook_dir }}/../semaphore/reports/docker_hosts_{{ ansible_date_time.date }}.txt"
tasks:
- name: Ensure reports directory exists
ansible.builtin.file:
path: "{{ playbook_dir }}/../semaphore/reports"
state: directory
mode: "0755"
- name: Collect results from all hosts
- name: Collect discovery results
ansible.builtin.set_fact:
docker_hosts_found: >-
{{
@ -250,44 +243,30 @@
| rejectattr('value.ssh_reachable', 'equalto', false)
| map(attribute='key') | list | sort
}}
run_once: true
- name: Write report to file
ansible.builtin.copy:
dest: "{{ report_path }}"
mode: "0644"
content: |
Docker Host Discovery Report
============================
Generated: {{ ansible_date_time.iso8601 }}
Portainer: {{ portainer_url }}
DOCKER HOSTS FOUND ({{ docker_hosts_found | length }})
{% for h in docker_hosts_found %}
- {{ h }}
IP: {{ hostvars[h].ansible_host | default('unknown') }}
Docker version: {{ hostvars[h].docker_version | default('unknown') }}
Portainer ID: {{ hostvars[h].portainer_endpoint_id | default('not enrolled') }}
Enrolled now: {{ hostvars[h].portainer_enrolled_now | default(false) }}
{% endfor %}
NO DOCKER ({{ no_docker_hosts | length }})
{% for h in no_docker_hosts %}
- {{ h }} ({{ hostvars[h].ansible_host | default('?') }})
{% endfor %}
UNREACHABLE ({{ unreachable_hosts | length }})
{% for h in unreachable_hosts %}
- {{ h }} ({{ hostvars[h].ansible_host | default('?') }})
{% endfor %}
- name: Print report path
ansible.builtin.debug:
msg: "Report written to {{ report_path }}"
- name: Print Docker hosts summary
- name: Print discovery report
ansible.builtin.debug:
msg: |
Docker hosts found ({{ docker_hosts_found | length }}):
============================================================
Docker Host Discovery Report — {{ portainer_url }}
============================================================
DOCKER FOUND ({{ docker_hosts_found | length }}):
{% for h in docker_hosts_found %}
- {{ h }} ({{ hostvars[h].ansible_host | default('?') }}) Docker {{ hostvars[h].docker_version | default('?') }}
{{ h }} ({{ hostvars[h].ansible_host | default('?') }})
Docker: {{ hostvars[h].docker_version | default('?') }}
Portainer: ID={{ hostvars[h].portainer_endpoint_id | default('n/a') }} enrolled_now={{ hostvars[h].portainer_enrolled_now | default(false) }}
{% endfor %}
NO DOCKER ({{ no_docker_hosts | length }}):
{% for h in no_docker_hosts %}
{{ h }} ({{ hostvars[h].ansible_host | default('?') }})
{% endfor %}
UNREACHABLE ({{ unreachable_hosts | length }}):
{% for h in unreachable_hosts %}
{{ h }} ({{ hostvars[h].ansible_host | default('?') }})
{% endfor %}
============================================================
run_once: true