Initial commit: playbooks and inventory for Semaphore automation
- find_docker_enroll_portainer.yml: discover Docker hosts across all VLANs, deploy Portainer Agent, register in Portainer, write discovery report - inventory/hosts.yml: auto-generated from NetBox (31 hosts) + UniFi clients (135 unmanaged hosts not in NetBox) across vlan1/vlan40/vlan20 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
017a3a00ee
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Ansible run reports (generated per-run)
|
||||
reports/*.txt
|
||||
1343
inventory/hosts.yml
Normal file
1343
inventory/hosts.yml
Normal file
File diff suppressed because it is too large
Load Diff
288
playbooks/find_docker_enroll_portainer.yml
Normal file
288
playbooks/find_docker_enroll_portainer.yml
Normal file
@ -0,0 +1,288 @@
|
||||
---
|
||||
# find_docker_enroll_portainer.yml
|
||||
#
|
||||
# Discovers which hosts in the inventory are running the Docker engine,
|
||||
# deploys the Portainer Agent container on each Docker host, registers the
|
||||
# host as a new environment in Portainer, and writes a local report.
|
||||
#
|
||||
# Required variables (set in group_vars/all.yml or Semaphore extra-vars):
|
||||
# portainer_url - e.g. http://10.40.40.2:9000
|
||||
# portainer_api_token - Portainer API token (Settings → Users → API key)
|
||||
# portainer_agent_port - defaults to 9001
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i semaphore/inventory/hosts.yml \
|
||||
# playbooks/find_docker_enroll_portainer.yml
|
||||
#
|
||||
# From Semaphore: point at this playbook, select the hosts.yml inventory,
|
||||
# and add portainer_api_token as an extra-var or in group_vars/all.yml.
|
||||
|
||||
- name: Discover Docker hosts and collect facts
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
ignore_unreachable: true
|
||||
tasks:
|
||||
|
||||
- name: Test SSH connectivity
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 10
|
||||
register: ssh_check
|
||||
ignore_errors: true
|
||||
|
||||
- name: Gather minimal facts for reachable hosts
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- network
|
||||
- distribution
|
||||
when: ssh_check is succeeded
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check if Docker binary is present
|
||||
ansible.builtin.command: which docker
|
||||
register: docker_which
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: ssh_check is succeeded
|
||||
|
||||
- name: Check if Docker daemon is responding
|
||||
ansible.builtin.command: docker info --format '{% raw %}{{json .ServerVersion}}{% endraw %}'
|
||||
register: docker_info
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
become: true
|
||||
when:
|
||||
- ssh_check is succeeded
|
||||
- docker_which.rc is defined
|
||||
- docker_which.rc == 0
|
||||
|
||||
- name: Record Docker status as host fact
|
||||
ansible.builtin.set_fact:
|
||||
docker_running: >-
|
||||
{{
|
||||
docker_which.rc is defined and docker_which.rc == 0 and
|
||||
docker_info.rc is defined and docker_info.rc == 0
|
||||
}}
|
||||
docker_version: >-
|
||||
{{
|
||||
(docker_info.stdout | default('""') | from_json)
|
||||
if (docker_info.rc is defined and docker_info.rc == 0)
|
||||
else 'not running'
|
||||
}}
|
||||
when: ssh_check is succeeded
|
||||
|
||||
- name: Mark unreachable hosts
|
||||
ansible.builtin.set_fact:
|
||||
docker_running: false
|
||||
ssh_reachable: false
|
||||
when: ssh_check is failed or ssh_check is skipped
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Play 2: Deploy Portainer Agent on Docker hosts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Deploy Portainer Agent on Docker hosts
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
ignore_unreachable: true
|
||||
become: true
|
||||
vars:
|
||||
portainer_agent_port: "{{ portainer_agent_port | default(9001) }}"
|
||||
tasks:
|
||||
|
||||
- name: Skip hosts without Docker
|
||||
ansible.builtin.meta: end_host
|
||||
when: not (docker_running | default(false))
|
||||
|
||||
- name: Check if portainer_agent container already exists
|
||||
ansible.builtin.command: >
|
||||
docker ps -a --filter name=portainer_agent --format "{% raw %}{{.Status}}{% endraw %}"
|
||||
register: agent_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Pull Portainer Agent image
|
||||
community.docker.docker_image:
|
||||
name: portainer/agent
|
||||
tag: latest
|
||||
source: pull
|
||||
when: "'Up' not in (agent_status.stdout | default(''))"
|
||||
|
||||
- name: Deploy Portainer Agent container
|
||||
community.docker.docker_container:
|
||||
name: portainer_agent
|
||||
image: portainer/agent:latest
|
||||
state: started
|
||||
restart_policy: always
|
||||
ports:
|
||||
- "{{ portainer_agent_port }}:9001"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/lib/docker/volumes:/var/lib/docker/volumes
|
||||
env:
|
||||
AGENT_PORT: "9001"
|
||||
when: "'Up' not in (agent_status.stdout | default(''))"
|
||||
register: agent_deployed
|
||||
|
||||
- name: Wait for Portainer Agent to be ready
|
||||
ansible.builtin.wait_for:
|
||||
port: "{{ portainer_agent_port }}"
|
||||
host: "{{ ansible_host }}"
|
||||
delay: 3
|
||||
timeout: 30
|
||||
delegate_to: localhost
|
||||
when: agent_deployed is changed
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Play 3: Register Docker hosts in Portainer
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Register Docker hosts in Portainer
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
ignore_unreachable: true
|
||||
vars:
|
||||
portainer_agent_port: "{{ portainer_agent_port | default(9001) }}"
|
||||
tasks:
|
||||
|
||||
- name: Skip hosts without Docker
|
||||
ansible.builtin.meta: end_host
|
||||
when: not (docker_running | default(false))
|
||||
|
||||
- name: Check if endpoint already exists in Portainer
|
||||
ansible.builtin.uri:
|
||||
url: "{{ portainer_url }}/api/endpoints"
|
||||
method: GET
|
||||
headers:
|
||||
X-API-Key: "{{ portainer_api_token }}"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: existing_endpoints
|
||||
delegate_to: localhost
|
||||
run_once: false
|
||||
|
||||
- name: Determine if this host is already enrolled
|
||||
ansible.builtin.set_fact:
|
||||
already_enrolled: >-
|
||||
{{
|
||||
existing_endpoints.json
|
||||
| selectattr('Name', 'equalto', inventory_hostname)
|
||||
| list | length > 0
|
||||
}}
|
||||
|
||||
- name: Register host as Portainer Agent endpoint
|
||||
ansible.builtin.uri:
|
||||
url: "{{ portainer_url }}/api/endpoints"
|
||||
method: POST
|
||||
headers:
|
||||
X-API-Key: "{{ portainer_api_token }}"
|
||||
body_format: form-multipart
|
||||
body:
|
||||
Name: "{{ inventory_hostname }}"
|
||||
EndpointCreationType: "2"
|
||||
URL: "tcp://{{ ansible_host }}:{{ portainer_agent_port }}"
|
||||
status_code: [200, 201]
|
||||
return_content: true
|
||||
register: portainer_enroll
|
||||
delegate_to: localhost
|
||||
when: not already_enrolled
|
||||
|
||||
- name: Store enrollment result
|
||||
ansible.builtin.set_fact:
|
||||
portainer_endpoint_id: >-
|
||||
{{
|
||||
(portainer_enroll.json.Id | string)
|
||||
if (portainer_enroll is not skipped and portainer_enroll.json is defined)
|
||||
else (
|
||||
existing_endpoints.json
|
||||
| selectattr('Name', 'equalto', inventory_hostname)
|
||||
| map(attribute='Id') | list | first | string
|
||||
)
|
||||
}}
|
||||
portainer_enrolled_now: "{{ portainer_enroll is changed }}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Play 4: Generate local report
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Generate Docker host discovery report
|
||||
hosts: localhost
|
||||
gather_facts: 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
|
||||
ansible.builtin.set_fact:
|
||||
docker_hosts_found: >-
|
||||
{{
|
||||
hostvars | dict2items
|
||||
| selectattr('value.docker_running', 'defined')
|
||||
| selectattr('value.docker_running', 'equalto', true)
|
||||
| map(attribute='key') | list | sort
|
||||
}}
|
||||
unreachable_hosts: >-
|
||||
{{
|
||||
hostvars | dict2items
|
||||
| selectattr('value.ssh_reachable', 'defined')
|
||||
| selectattr('value.ssh_reachable', 'equalto', false)
|
||||
| map(attribute='key') | list | sort
|
||||
}}
|
||||
no_docker_hosts: >-
|
||||
{{
|
||||
hostvars | dict2items
|
||||
| selectattr('value.docker_running', 'defined')
|
||||
| selectattr('value.docker_running', 'equalto', false)
|
||||
| rejectattr('value.ssh_reachable', 'equalto', false)
|
||||
| map(attribute='key') | list | sort
|
||||
}}
|
||||
|
||||
- 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
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Docker hosts found ({{ docker_hosts_found | length }}):
|
||||
{% for h in docker_hosts_found %}
|
||||
- {{ h }} ({{ hostvars[h].ansible_host | default('?') }}) Docker {{ hostvars[h].docker_version | default('?') }}
|
||||
{% endfor %}
|
||||
32
playbooks/group_vars/all.yml
Normal file
32
playbooks/group_vars/all.yml
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
# playbooks/group_vars/all.yml
|
||||
#
|
||||
# Defaults for all Ansible playbooks in this repo.
|
||||
# Override sensitive values (portainer_api_token) via Semaphore's
|
||||
# "Extra Variables" or a Vault-encrypted file, not here in plaintext.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Portainer
|
||||
# ---------------------------------------------------------------------------
|
||||
portainer_url: "http://10.40.40.2:9000"
|
||||
|
||||
# API token: generate in Portainer → User settings → Access tokens
|
||||
# Set this in Semaphore "Extra Variables" as: portainer_api_token=<token>
|
||||
# Or export as an env var and reference with: "{{ lookup('env', 'PORTAINER_API_TOKEN') }}"
|
||||
portainer_api_token: "{{ lookup('env', 'PORTAINER_API_TOKEN') }}"
|
||||
|
||||
# Port the Portainer Agent listens on (default 9001)
|
||||
portainer_agent_port: 9001
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SSH defaults (override per group in host_vars/<name>.yml or Semaphore)
|
||||
# ---------------------------------------------------------------------------
|
||||
ansible_user: ubuntu
|
||||
ansible_become: true
|
||||
ansible_become_method: sudo
|
||||
|
||||
# SSH connection settings
|
||||
ansible_ssh_common_args: >-
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o ConnectTimeout=10
|
||||
0
reports/.gitkeep
Normal file
0
reports/.gitkeep
Normal file
Loading…
x
Reference in New Issue
Block a user