Ansible deployed via boilerplates and playbooks
This commit is contained in:
23
ansible/playbooks/day1_deploy_semaphore.yml
Normal file
23
ansible/playbooks/day1_deploy_semaphore.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: playbooks/day1_deploy_semaphore.yml
|
||||
# DESCRIPTION: Deploys Semaphore on imagineering
|
||||
# Runs: common → docker-host → semaphore
|
||||
#
|
||||
# USAGE:
|
||||
# ansible-playbook -i inventory.yml playbooks/day1_deploy_semaphore.yml
|
||||
#
|
||||
# SECRETS REQUIRED IN VAULT (group_vars/all/vault):
|
||||
# vault_semaphore_database_password
|
||||
# vault_semaphore_admin_password
|
||||
# vault_semaphore_access_key_encryption
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Deploy Semaphore
|
||||
hosts: semaphore_server
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- common
|
||||
- docker-host
|
||||
- semaphore
|
||||
4
ansible/playbooks/roles/semaphore/defaults/main.yml
Normal file
4
ansible/playbooks/roles/semaphore/defaults/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
# file: roles/semaphore/defaults/main.yml
|
||||
|
||||
semaphore_compose_dir: /opt/docker/semaphore
|
||||
7
ansible/playbooks/roles/semaphore/handlers/main.yml
Normal file
7
ansible/playbooks/roles/semaphore/handlers/main.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
# file: roles/semaphore/handlers/main.yml
|
||||
|
||||
- name: Restart Semaphore
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ semaphore_compose_dir }}"
|
||||
state: restarted
|
||||
212
ansible/playbooks/roles/semaphore/tasks/main.yml
Normal file
212
ansible/playbooks/roles/semaphore/tasks/main.yml
Normal file
@@ -0,0 +1,212 @@
|
||||
---
|
||||
# file: roles/semaphore/tasks/main.yml
|
||||
|
||||
- name: Create Semaphore directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ semaphore_compose_dir }}"
|
||||
state: directory
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy Compose file from boilerplate
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/semaphore/compose.yaml"
|
||||
dest: "{{ semaphore_compose_dir }}/compose.yaml"
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0644'
|
||||
|
||||
- name: Deploy Semaphore .env file
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
dest: "{{ semaphore_compose_dir }}/.env"
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0600'
|
||||
|
||||
- name: Start Semaphore containers
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ semaphore_compose_dir }}"
|
||||
state: present
|
||||
register: semaphore_compose
|
||||
|
||||
- name: Wait for Semaphore to be ready
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/ping"
|
||||
method: GET
|
||||
status_code: 200
|
||||
register: semaphore_health
|
||||
retries: 24
|
||||
delay: 5
|
||||
until: semaphore_health.status == 200
|
||||
|
||||
# ── Post-deploy: Restore project config via API ──
|
||||
|
||||
- name: Authenticate to Semaphore API
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/auth/login"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
auth: "admin"
|
||||
password: "{{ vault_semaphore_admin_password }}"
|
||||
status_code: 204
|
||||
return_content: false
|
||||
register: semaphore_auth
|
||||
no_log: true
|
||||
|
||||
- name: Set auth cookie fact
|
||||
ansible.builtin.set_fact:
|
||||
semaphore_cookie: "{{ semaphore_auth.cookies_string }}"
|
||||
no_log: true
|
||||
|
||||
- name: Check if mk-labs project already exists
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/projects"
|
||||
method: GET
|
||||
headers:
|
||||
Cookie: "{{ semaphore_cookie }}"
|
||||
return_content: true
|
||||
register: semaphore_projects
|
||||
|
||||
- name: Set project exists flag
|
||||
ansible.builtin.set_fact:
|
||||
project_exists: "{{ semaphore_projects.json | selectattr('name', 'equalto', 'mk-labs') | list | length > 0 }}"
|
||||
|
||||
- name: Read project config from boilerplate
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/semaphore/project-config.json"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: project_config_raw
|
||||
when: not project_exists
|
||||
|
||||
- name: Restore project via API
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/projects/restore"
|
||||
method: POST
|
||||
headers:
|
||||
Cookie: "{{ semaphore_cookie }}"
|
||||
Content-Type: "application/json"
|
||||
body: "{{ project_config_raw.content | b64decode | string }}"
|
||||
body_format: raw
|
||||
status_code: [200, 201]
|
||||
return_content: true
|
||||
register: restore_result
|
||||
when: not project_exists
|
||||
|
||||
- name: Display restore result
|
||||
ansible.builtin.debug:
|
||||
msg: "Project '{{ restore_result.json.name | default('mk-labs') }}' restored successfully (ID: {{ restore_result.json.id | default('unknown') }})"
|
||||
when: not project_exists and restore_result is defined
|
||||
|
||||
- name: Skip restore — project already exists
|
||||
ansible.builtin.debug:
|
||||
msg: "Project 'mk-labs' already exists, skipping restore"
|
||||
when: project_exists
|
||||
|
||||
# ── Push secrets: DELETE empty shells, POST with actual values ──
|
||||
|
||||
- name: Get project ID
|
||||
ansible.builtin.set_fact:
|
||||
semaphore_project_id: >-
|
||||
{{ (semaphore_projects.json | selectattr('name', 'equalto', 'mk-labs') | first).id
|
||||
if project_exists else restore_result.json.id }}
|
||||
|
||||
- name: Get existing keys
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/project/{{ semaphore_project_id }}/keys"
|
||||
method: GET
|
||||
headers:
|
||||
Cookie: "{{ semaphore_cookie }}"
|
||||
return_content: true
|
||||
register: semaphore_keys
|
||||
|
||||
- name: Find wed-ssh-key ID (if exists)
|
||||
ansible.builtin.set_fact:
|
||||
wed_ssh_key_id: "{{ (semaphore_keys.json | selectattr('name', 'equalto', 'wed-ssh-key') | first).id | default(omit) }}"
|
||||
ignore_errors: true
|
||||
|
||||
- name: Find ansible-vault-password ID (if exists)
|
||||
ansible.builtin.set_fact:
|
||||
vault_password_key_id: "{{ (semaphore_keys.json | selectattr('name', 'equalto', 'ansible-vault-password') | first).id | default(omit) }}"
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check if wed-ssh-key is empty
|
||||
ansible.builtin.set_fact:
|
||||
wed_ssh_key_empty: "{{ (semaphore_keys.json | selectattr('name', 'equalto', 'wed-ssh-key') | first).empty | default(true) }}"
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check if ansible-vault-password is empty
|
||||
ansible.builtin.set_fact:
|
||||
vault_password_empty: "{{ (semaphore_keys.json | selectattr('name', 'equalto', 'ansible-vault-password') | first).empty | default(true) }}"
|
||||
ignore_errors: true
|
||||
|
||||
# -- wed-ssh-key: delete empty shell if needed, then create with value --
|
||||
|
||||
- name: Delete empty wed-ssh-key shell
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/project/{{ semaphore_project_id }}/keys/{{ wed_ssh_key_id }}"
|
||||
method: DELETE
|
||||
headers:
|
||||
Cookie: "{{ semaphore_cookie }}"
|
||||
status_code: [200, 204]
|
||||
when: wed_ssh_key_id is defined and wed_ssh_key_empty | bool
|
||||
|
||||
- name: Build wed-ssh-key payload
|
||||
ansible.builtin.set_fact:
|
||||
wed_key_payload: >-
|
||||
{{ {"name": "wed-ssh-key",
|
||||
"type": "ssh",
|
||||
"project_id": semaphore_project_id | int,
|
||||
"ssh": {"login": "", "passphrase": "", "private_key": vault_wed_ssh_private_key}} | to_json }}
|
||||
no_log: true
|
||||
when: wed_ssh_key_empty | bool
|
||||
|
||||
- name: Create wed-ssh-key with private key
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/project/{{ semaphore_project_id }}/keys"
|
||||
method: POST
|
||||
headers:
|
||||
Cookie: "{{ semaphore_cookie }}"
|
||||
Content-Type: "application/json"
|
||||
body: "{{ wed_key_payload }}"
|
||||
body_format: raw
|
||||
status_code: [200, 201]
|
||||
no_log: true
|
||||
when: wed_ssh_key_empty | bool
|
||||
|
||||
# -- ansible-vault-password: delete empty shell if needed, then create with value --
|
||||
|
||||
- name: Delete empty ansible-vault-password shell
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/project/{{ semaphore_project_id }}/keys/{{ vault_password_key_id }}"
|
||||
method: DELETE
|
||||
headers:
|
||||
Cookie: "{{ semaphore_cookie }}"
|
||||
status_code: [200, 204]
|
||||
when: vault_password_key_id is defined and vault_password_empty | bool
|
||||
|
||||
- name: Build ansible-vault-password payload
|
||||
ansible.builtin.set_fact:
|
||||
vault_key_payload: >-
|
||||
{{ {"name": "ansible-vault-password",
|
||||
"type": "login_password",
|
||||
"project_id": semaphore_project_id | int,
|
||||
"login_password": {"login": "", "password": vault_ansible_vault_password}} | to_json }}
|
||||
no_log: true
|
||||
when: vault_password_empty | bool
|
||||
|
||||
- name: Create ansible-vault-password with value
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/project/{{ semaphore_project_id }}/keys"
|
||||
method: POST
|
||||
headers:
|
||||
Cookie: "{{ semaphore_cookie }}"
|
||||
Content-Type: "application/json"
|
||||
body: "{{ vault_key_payload }}"
|
||||
body_format: raw
|
||||
status_code: [200, 201]
|
||||
no_log: true
|
||||
when: vault_password_empty | bool
|
||||
3
ansible/playbooks/roles/semaphore/templates/env.j2
Normal file
3
ansible/playbooks/roles/semaphore/templates/env.j2
Normal file
@@ -0,0 +1,3 @@
|
||||
DATABASE_PASSWORD={{ vault_semaphore_database_password }}
|
||||
SEMAPHORE_ADMIN_PASSWORD={{ vault_semaphore_admin_password }}
|
||||
SEMAPHORE_ACCESS_KEY_ENCRYPTION={{ vault_semaphore_access_key_encryption }}
|
||||
Reference in New Issue
Block a user