From 6db20117fd9a3d202f428e28611ad250fdd85dc9 Mon Sep 17 00:00:00 2001 From: sam Date: Sun, 1 Mar 2026 01:06:17 -0700 Subject: [PATCH] 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 --- playbooks/find_docker_enroll_portainer.yml | 87 ++++++++-------------- 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/playbooks/find_docker_enroll_portainer.yml b/playbooks/find_docker_enroll_portainer.yml index 6415607..8638751 100644 --- a/playbooks/find_docker_enroll_portainer.yml +++ b/playbooks/find_docker_enroll_portainer.yml @@ -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