feat(semaphore): add config-as-code via Semaphore REST API
Adds an idempotent configuration pass that drives a freshly-deployed
Semaphore instance into its desired state via the REST API. Declared
in group_vars/all/semaphore.yml, applied by tasks/configure.yml,
toggled by semaphore_configure feature flag (default off).
Object types managed:
- Project (mk-labs)
- Keys (ansible-vault-pass, gitea-deploy, jarvis-ssh)
- Repositories (homelab on gitea)
- Inventories (production -> ansible/inventory.yml in homelab repo)
- Environments (default with ANSIBLE_HOST_KEY_CHECKING=False)
- Templates (day0_linux_baseline + variants, day1_deploy_semaphore)
with survey vars for runtime parameters
Each object found-or-created by name; existing ones never modified.
no_log on token-bearing calls to keep secrets out of stdout.
Inputs (already in vault):
vault_semaphore_api_token
vault_jarvis_ssh_private_key
vault_gitea_deploy_key
vault_ansible_vault_password
This commit is contained in:
132
ansible/group_vars/all/semaphore.yml
Normal file
132
ansible/group_vars/all/semaphore.yml
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
# ============================================================================
|
||||||
|
# Semaphore configuration-as-code
|
||||||
|
# ============================================================================
|
||||||
|
# Drives a freshly-deployed Semaphore instance into its desired state via
|
||||||
|
# the Semaphore REST API. Idempotent: every object is checked first; only
|
||||||
|
# missing ones are created. Existing objects are left alone.
|
||||||
|
#
|
||||||
|
# Loaded from group_vars/all/semaphore.yml so that the configuration is
|
||||||
|
# version-controlled in the homelab repo and survives a wipe-and-redeploy
|
||||||
|
# of the Semaphore VM.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# API connection (defaults to the local Traefik-fronted service-name URL).
|
||||||
|
# Override semaphore_api_url to point at a specific instance if needed.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
semaphore_api_url: "https://semaphore.local.mk-labs.cloud/api"
|
||||||
|
semaphore_api_validate_certs: true
|
||||||
|
semaphore_api_token: "{{ vault_semaphore_api_token }}"
|
||||||
|
|
||||||
|
# Feature flag — keeps day1_deploy_semaphore.yml deploy-only by default.
|
||||||
|
# Set true to also run the configuration pass.
|
||||||
|
semaphore_configure: false
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Declarative configuration of the Semaphore instance.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Top-level shape:
|
||||||
|
#
|
||||||
|
# semaphore_config:
|
||||||
|
# project: single dict — the lab uses one project ("mk-labs")
|
||||||
|
# keys: list of credentials Semaphore stores
|
||||||
|
# repositories: git repos Semaphore can clone
|
||||||
|
# inventories: Ansible inventories from those repos
|
||||||
|
# environments: env-var bundles
|
||||||
|
# templates: task templates that tie everything together
|
||||||
|
#
|
||||||
|
# Each list element has a unique "name" used as the natural identity key.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
semaphore_config:
|
||||||
|
project:
|
||||||
|
name: mk-labs
|
||||||
|
alert: false
|
||||||
|
max_parallel_tasks: 0 # 0 = unlimited
|
||||||
|
|
||||||
|
keys:
|
||||||
|
# The ansible-vault password. login_password type with empty login
|
||||||
|
# — only the password field is consumed by Semaphore at runtime.
|
||||||
|
- name: ansible-vault-pass
|
||||||
|
type: login_password
|
||||||
|
login: ""
|
||||||
|
password: "{{ vault_ansible_vault_password }}"
|
||||||
|
|
||||||
|
# SSH key for the gitea deploy access (clone the homelab repo).
|
||||||
|
- name: gitea-deploy
|
||||||
|
type: ssh
|
||||||
|
ssh_login: git
|
||||||
|
ssh_private_key: "{{ vault_gitea_deploy_key }}"
|
||||||
|
|
||||||
|
# SSH key Semaphore uses to reach the fleet as jarvis.
|
||||||
|
- name: jarvis-ssh
|
||||||
|
type: ssh
|
||||||
|
ssh_login: jarvis
|
||||||
|
ssh_private_key: "{{ vault_jarvis_ssh_private_key }}"
|
||||||
|
|
||||||
|
repositories:
|
||||||
|
- name: homelab
|
||||||
|
git_url: "ssh://git@gitea.mk-labs.cloud:2221/rblundon/homelab.git"
|
||||||
|
git_branch: main
|
||||||
|
ssh_key: gitea-deploy
|
||||||
|
|
||||||
|
inventories:
|
||||||
|
- name: production
|
||||||
|
type: file
|
||||||
|
inventory_file: ansible/inventory.yml
|
||||||
|
repository: homelab
|
||||||
|
ssh_key: jarvis-ssh
|
||||||
|
become_key: jarvis-ssh
|
||||||
|
|
||||||
|
environments:
|
||||||
|
- name: default
|
||||||
|
env:
|
||||||
|
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||||
|
ANSIBLE_FORCE_COLOR: "True"
|
||||||
|
|
||||||
|
templates:
|
||||||
|
- name: "day0_linux_baseline"
|
||||||
|
description: "Apply the mk-labs Linux baseline to one or more hosts."
|
||||||
|
app: ansible
|
||||||
|
playbook: ansible/playbooks/day0_linux_baseline.yml
|
||||||
|
inventory: production
|
||||||
|
repository: homelab
|
||||||
|
environment: default
|
||||||
|
vault_password: ansible-vault-pass
|
||||||
|
arguments: '["--diff"]'
|
||||||
|
survey_vars:
|
||||||
|
- name: target
|
||||||
|
title: "Target host or group"
|
||||||
|
description: "Inventory target (e.g. figment, semaphore_server, all)"
|
||||||
|
required: true
|
||||||
|
type: TextVar
|
||||||
|
default_value: "all"
|
||||||
|
|
||||||
|
- name: "day1_deploy_semaphore"
|
||||||
|
description: "Re-deploy Semaphore + PostgreSQL on figment."
|
||||||
|
app: ansible
|
||||||
|
playbook: ansible/playbooks/day1_deploy_semaphore.yml
|
||||||
|
inventory: production
|
||||||
|
repository: homelab
|
||||||
|
environment: default
|
||||||
|
vault_password: ansible-vault-pass
|
||||||
|
arguments: '["--diff"]'
|
||||||
|
|
||||||
|
- name: "day0_linux_baseline_check"
|
||||||
|
description: "Dry-run the baseline — shows diffs, applies nothing."
|
||||||
|
app: ansible
|
||||||
|
playbook: ansible/playbooks/day0_linux_baseline.yml
|
||||||
|
inventory: production
|
||||||
|
repository: homelab
|
||||||
|
environment: default
|
||||||
|
vault_password: ansible-vault-pass
|
||||||
|
arguments: '["--check","--diff"]'
|
||||||
|
survey_vars:
|
||||||
|
- name: target
|
||||||
|
title: "Target host or group"
|
||||||
|
description: "Inventory target (e.g. figment, semaphore_server, all)"
|
||||||
|
required: true
|
||||||
|
type: TextVar
|
||||||
|
default_value: "all"
|
||||||
309
ansible/roles/semaphore/tasks/configure.yml
Normal file
309
ansible/roles/semaphore/tasks/configure.yml
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
---
|
||||||
|
# ============================================================================
|
||||||
|
# Semaphore configuration via REST API (idempotent)
|
||||||
|
# ============================================================================
|
||||||
|
# Each section: GET the existing list, build a name→id lookup, then POST
|
||||||
|
# any declared objects whose names are missing. Existing objects are
|
||||||
|
# never modified — change-by-recreate is the operator's job through the
|
||||||
|
# UI or by hand-editing semaphore_config and deleting the old object.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
- name: Set Semaphore API auth header
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_semaphore_headers:
|
||||||
|
Authorization: "Bearer {{ semaphore_api_token }}"
|
||||||
|
Content-Type: "application/json"
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Project — find-or-create
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: List Semaphore projects
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/projects"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_projects
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Create Semaphore project
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/projects"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: "{{ semaphore_config.project.name }}"
|
||||||
|
alert: "{{ semaphore_config.project.alert | default(false) }}"
|
||||||
|
max_parallel_tasks: "{{ semaphore_config.project.max_parallel_tasks | default(0) }}"
|
||||||
|
status_code: [201, 204]
|
||||||
|
when: semaphore_config.project.name not in (_semaphore_projects.json | map(attribute='name') | list)
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Resolve project id
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/projects"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_projects_after
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Set project id fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_semaphore_project_id: >-
|
||||||
|
{{ (_semaphore_projects_after.json
|
||||||
|
| selectattr('name', 'equalto', semaphore_config.project.name)
|
||||||
|
| list | first).id }}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Keys — find-or-create
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: List existing keys
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/keys"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_keys
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Create missing keys
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/keys"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
body_format: json
|
||||||
|
body: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
'name': item.name,
|
||||||
|
'type': item.type,
|
||||||
|
'project_id': _semaphore_project_id | int
|
||||||
|
}
|
||||||
|
| combine(
|
||||||
|
{'login_password': {'login': item.login | default(''), 'password': item.password | default('')}}
|
||||||
|
if item.type == 'login_password' else {}
|
||||||
|
)
|
||||||
|
| combine(
|
||||||
|
{'ssh': {'login': item.ssh_login | default(''),
|
||||||
|
'passphrase': item.ssh_passphrase | default(''),
|
||||||
|
'private_key': item.ssh_private_key | default('')}}
|
||||||
|
if item.type == 'ssh' else {}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
status_code: [201, 204]
|
||||||
|
loop: "{{ semaphore_config.keys }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: item.name not in (_semaphore_keys.json | map(attribute='name') | list)
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Re-list keys with assigned ids
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/keys"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_keys_after
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Build key name→id lookup
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_semaphore_key_ids: "{{ dict(_semaphore_keys_after.json | map(attribute='name') | zip(_semaphore_keys_after.json | map(attribute='id'))) }}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Repositories — find-or-create
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: List existing repositories
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/repositories"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_repos
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Create missing repositories
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/repositories"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
project_id: "{{ _semaphore_project_id | int }}"
|
||||||
|
git_url: "{{ item.git_url }}"
|
||||||
|
git_branch: "{{ item.git_branch | default('main') }}"
|
||||||
|
ssh_key_id: "{{ _semaphore_key_ids[item.ssh_key] | int }}"
|
||||||
|
status_code: [201, 204]
|
||||||
|
loop: "{{ semaphore_config.repositories }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: item.name not in (_semaphore_repos.json | map(attribute='name') | list)
|
||||||
|
|
||||||
|
- name: Re-list repositories with assigned ids
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/repositories"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_repos_after
|
||||||
|
|
||||||
|
- name: Build repo name→id lookup
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_semaphore_repo_ids: "{{ dict(_semaphore_repos_after.json | map(attribute='name') | zip(_semaphore_repos_after.json | map(attribute='id'))) }}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Inventories — find-or-create
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: List existing inventories
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/inventory"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_inv
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Create missing inventories
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/inventory"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
project_id: "{{ _semaphore_project_id | int }}"
|
||||||
|
type: "{{ item.type | default('file') }}"
|
||||||
|
inventory: "{{ item.inventory_file }}"
|
||||||
|
repository_id: "{{ _semaphore_repo_ids[item.repository] | int }}"
|
||||||
|
ssh_key_id: "{{ _semaphore_key_ids[item.ssh_key] | int }}"
|
||||||
|
become_key_id: "{{ _semaphore_key_ids[item.become_key | default(item.ssh_key)] | int }}"
|
||||||
|
status_code: [201, 204]
|
||||||
|
loop: "{{ semaphore_config.inventories }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: item.name not in (_semaphore_inv.json | map(attribute='name') | list)
|
||||||
|
|
||||||
|
- name: Re-list inventories with assigned ids
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/inventory"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_inv_after
|
||||||
|
|
||||||
|
- name: Build inventory name→id lookup
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_semaphore_inv_ids: "{{ dict(_semaphore_inv_after.json | map(attribute='name') | zip(_semaphore_inv_after.json | map(attribute='id'))) }}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Environments — find-or-create
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: List existing environments
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/environment"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_envs
|
||||||
|
|
||||||
|
- name: Create missing environments
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/environment"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
project_id: "{{ _semaphore_project_id | int }}"
|
||||||
|
# Semaphore stores both env (process env vars) and json (extra
|
||||||
|
# variables). Pass an empty object for json unless someone
|
||||||
|
# extends the schema later.
|
||||||
|
env: "{{ item.env | default({}) | to_json }}"
|
||||||
|
json: "{{ item.extra_vars | default({}) | to_json }}"
|
||||||
|
status_code: [201, 204]
|
||||||
|
loop: "{{ semaphore_config.environments }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: item.name not in (_semaphore_envs.json | map(attribute='name') | list)
|
||||||
|
|
||||||
|
- name: Re-list environments with assigned ids
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/environment"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_envs_after
|
||||||
|
|
||||||
|
- name: Build environment name→id lookup
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_semaphore_env_ids: "{{ dict(_semaphore_envs_after.json | map(attribute='name') | zip(_semaphore_envs_after.json | map(attribute='id'))) }}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Templates — find-or-create
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: List existing templates
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/templates"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
return_content: true
|
||||||
|
register: _semaphore_templates
|
||||||
|
|
||||||
|
- name: Create missing templates
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ semaphore_api_url }}/project/{{ _semaphore_project_id }}/templates"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ _semaphore_headers }}"
|
||||||
|
validate_certs: "{{ semaphore_api_validate_certs }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
project_id: "{{ _semaphore_project_id | int }}"
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
description: "{{ item.description | default('') }}"
|
||||||
|
app: "{{ item.app | default('ansible') }}"
|
||||||
|
playbook: "{{ item.playbook }}"
|
||||||
|
arguments: "{{ item.arguments | default('[]') }}"
|
||||||
|
allow_override_args_in_task: "{{ item.allow_override_args | default(false) }}"
|
||||||
|
inventory_id: "{{ _semaphore_inv_ids[item.inventory] | int }}"
|
||||||
|
repository_id: "{{ _semaphore_repo_ids[item.repository] | int }}"
|
||||||
|
environment_id: "{{ _semaphore_env_ids[item.environment] | int }}"
|
||||||
|
vault_key_id: "{{ _semaphore_key_ids[item.vault_password] | int if item.vault_password is defined else omit }}"
|
||||||
|
survey_vars: "{{ item.survey_vars | default([]) }}"
|
||||||
|
status_code: [201, 204]
|
||||||
|
loop: "{{ semaphore_config.templates }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: item.name not in (_semaphore_templates.json | map(attribute='name') | list)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: Report configuration status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Semaphore project: {{ semaphore_config.project.name }} (id={{ _semaphore_project_id }})"
|
||||||
|
- "Keys: {{ _semaphore_key_ids | length }}"
|
||||||
|
- "Repositories: {{ _semaphore_repo_ids | length }}"
|
||||||
|
- "Inventories: {{ _semaphore_inv_ids | length }}"
|
||||||
|
- "Environments: {{ _semaphore_env_ids | length }}"
|
||||||
|
- "Configuration applied successfully."
|
||||||
@@ -34,3 +34,8 @@
|
|||||||
- name: Verify Semaphore is reachable
|
- name: Verify Semaphore is reachable
|
||||||
ansible.builtin.import_tasks: verify.yml
|
ansible.builtin.import_tasks: verify.yml
|
||||||
tags: [semaphore, verify]
|
tags: [semaphore, verify]
|
||||||
|
|
||||||
|
- name: Apply Semaphore configuration (projects, keys, repos, templates)
|
||||||
|
ansible.builtin.import_tasks: configure.yml
|
||||||
|
when: semaphore_configure | default(false) | bool
|
||||||
|
tags: [semaphore, configure]
|
||||||
|
|||||||
Reference in New Issue
Block a user