--- # Portainer Stack Management via API # Manages GitOps stacks across all Portainer endpoints # Run with: ansible-playbook -i hosts.ini playbooks/portainer_stack_management.yml - name: Portainer Stack Management hosts: localhost gather_facts: no vars: portainer_url: "https://192.168.0.200:9443" portainer_username: "admin" # portainer_password: "{{ vault_portainer_password }}" # Use ansible-vault git_repo_url: "https://git.vish.gg/Vish/homelab.git" # Portainer endpoints mapping endpoints: atlantis: id: 1 name: "Atlantis" stacks_path: "Atlantis" calypso: id: 2 name: "Calypso" stacks_path: "Calypso" concord_nuc: id: 3 name: "Concord NUC" stacks_path: "concord_nuc" homelab_vm: id: 4 name: "Homelab VM" stacks_path: "homelab_vm" rpi5: id: 5 name: "RPi 5" stacks_path: "raspberry-pi-5-vish" tasks: - name: Authenticate with Portainer uri: url: "{{ portainer_url }}/api/auth" method: POST body_format: json body: Username: "{{ portainer_username }}" Password: "{{ portainer_password | default('admin') }}" validate_certs: no register: auth_response no_log: true - name: Set authentication token set_fact: portainer_token: "{{ auth_response.json.jwt }}" - name: Get all endpoints uri: url: "{{ portainer_url }}/api/endpoints" method: GET headers: Authorization: "Bearer {{ portainer_token }}" validate_certs: no register: endpoints_response - name: Display available endpoints debug: msg: | Available Portainer Endpoints: {% for endpoint in endpoints_response.json %} - ID: {{ endpoint.Id }}, Name: {{ endpoint.Name }}, Status: {{ endpoint.Status }} {% endfor %} - name: Get stacks for each endpoint uri: url: "{{ portainer_url }}/api/stacks" method: GET headers: Authorization: "Bearer {{ portainer_token }}" validate_certs: no register: stacks_response - name: Analyze GitOps stacks set_fact: gitops_stacks: "{{ stacks_response.json | selectattr('GitConfig', 'defined') | list }}" non_gitops_stacks: "{{ stacks_response.json | rejectattr('GitConfig', 'defined') | list }}" - name: Display GitOps status debug: msg: | GitOps Stack Analysis: - Total Stacks: {{ stacks_response.json | length }} - GitOps Managed: {{ gitops_stacks | length }} - Non-GitOps: {{ non_gitops_stacks | length }} GitOps Stacks: {% for stack in gitops_stacks %} - {{ stack.Name }} (Endpoint: {{ stack.EndpointId }}) {% endfor %} Non-GitOps Stacks: {% for stack in non_gitops_stacks %} - {{ stack.Name }} (Endpoint: {{ stack.EndpointId }}) {% endfor %} - name: Check stack health uri: url: "{{ portainer_url }}/api/stacks/{{ item.Id }}/file" method: GET headers: Authorization: "Bearer {{ portainer_token }}" validate_certs: no register: stack_files loop: "{{ gitops_stacks }}" failed_when: false - name: Trigger GitOps sync for all stacks uri: url: "{{ portainer_url }}/api/stacks/{{ item.Id }}/git/redeploy" method: PUT headers: Authorization: "Bearer {{ portainer_token }}" body_format: json body: RepositoryReferenceName: "refs/heads/main" PullImage: true validate_certs: no register: sync_results loop: "{{ gitops_stacks }}" when: sync_stacks | default(false) | bool failed_when: false - name: Display sync results debug: msg: | GitOps Sync Results: {% for result in sync_results.results %} {% if result.skipped is not defined %} - Stack: {{ gitops_stacks[loop.index0].Name }} - Status: {{ result.status | default('Failed') }} {% endif %} {% endfor %} when: sync_stacks | default(false) | bool - name: Generate stack health report copy: content: | # Portainer Stack Health Report Generated: {{ ansible_date_time.iso8601 }} ## Summary - Total Stacks: {{ stacks_response.json | length }} - GitOps Managed: {{ gitops_stacks | length }} - Non-GitOps: {{ non_gitops_stacks | length }} ## GitOps Stacks {% for stack in gitops_stacks %} ### {{ stack.Name }} - Endpoint: {{ stack.EndpointId }} - Status: {{ stack.Status }} - Git Repository: {{ stack.GitConfig.URL if stack.GitConfig is defined else 'N/A' }} - Git Reference: {{ stack.GitConfig.ReferenceName if stack.GitConfig is defined else 'N/A' }} - Last Update: {{ stack.UpdatedAt }} {% endfor %} ## Non-GitOps Stacks (Manual Management Required) {% for stack in non_gitops_stacks %} - {{ stack.Name }} (Endpoint: {{ stack.EndpointId }}) {% endfor %} dest: "/tmp/portainer_stack_report_{{ ansible_date_time.epoch }}.md" delegate_to: localhost - name: Display report location debug: msg: "Stack health report saved to: /tmp/portainer_stack_report_{{ ansible_date_time.epoch }}.md"