feat(ansible): add semaphore docker splash demo with dagster config

This commit is contained in:
Matteo Basile
2026-05-04 11:51:13 +02:00
commit 63aa0658ef
13 changed files with 565 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# SemaphoreUI Docker Splash Demo
Demo Ansible molto semplice, pensata per essere eseguita da SemaphoreUI:
- `test.yml`: verifica connettivita e prerequisiti
- `deploy.yml`: deploy di un container Nginx Docker con pagina HTML custom
- `undeploy.yml`: rimozione container e cleanup
## Struttura
```text
ansible-semaphore-docker-demo/
ansible.cfg
inventory
requirements.yml
deploy.yml
test.yml
undeploy.yml
group_vars/
all.yml
secrets.yml
roles/
splash/
tasks/main.yml
templates/index.html.j2
templates/nginx.conf.j2
semaphore_dagster_config.example.yml
```
## Prerequisiti host target
- Linux con accesso SSH
- Privilegi sudo/root
- Debian/Ubuntu (nel playbook installo `docker.io` e `python3-docker`)
## Uso locale rapido
```bash
cd ansible-semaphore-docker-demo
ansible-galaxy collection install -r requirements.yml
ansible-playbook -i inventory test.yml
ansible-playbook -i inventory deploy.yml
ansible-playbook -i inventory undeploy.yml
```
## Accesso app
Dopo il deploy:
- `http://<IP_SERVER>:8090`
- healthcheck: `http://<IP_SERVER>:8090/health`
## Variabili principali
In `group_vars/all.yml`:
- `app_port`
- `greeting_message`
- `container_name`
- `nginx_version`
- `accent_color`
- `secondary_color`
In `group_vars/secrets.yml`:
- `app_secret`
- `release_id`
Per produzione: cifra `group_vars/secrets.yml` con Ansible Vault.
## SemaphoreUI
Per bootstrap automatico con il tuo DAG, usa il file:
- `semaphore_dagster_config.example.yml`
Basta aggiornare URL Semaphore, token API, project ID, repo URL e inventory content.

View File

@@ -0,0 +1,20 @@
[defaults]
inventory = ./inventory
host_key_checking = False
stdout_callback = default
callback_result_format = yaml
force_color = True
retry_files_enabled = False
gathering = smart
roles_path = ./roles
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
timeout = 30
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

View File

@@ -0,0 +1,44 @@
---
- name: Deploy Docker splash app via Nginx
hosts: webservers
become: yes
vars_files:
- group_vars/all.yml
- group_vars/secrets.yml
pre_tasks:
- name: Installa pacchetti necessari (Debian/Ubuntu)
ansible.builtin.apt:
name:
- docker.io
- python3-docker
state: present
update_cache: yes
when: ansible_os_family == 'Debian'
- name: Avvia e abilita Docker
ansible.builtin.service:
name: docker
state: started
enabled: yes
roles:
- role: splash
post_tasks:
- name: Verifica endpoint health
ansible.builtin.uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: healthcheck
retries: 10
delay: 2
until: healthcheck.status == 200
- name: Output finale
ansible.builtin.debug:
msg:
- "Deploy completato"
- "URL: http://{{ ansible_host }}:{{ app_port }}"
- "Container: {{ container_name }}"

View File

@@ -0,0 +1,17 @@
---
# Docker image settings
nginx_image: nginx
nginx_version: "1.27-alpine"
container_name: semaphore-splash
# App settings
app_environment: production
app_port: 8090
greeting_message: "Ciao dal Demo SemaphoreUI"
accent_color: "#0ea5a4"
secondary_color: "#f97316"
remove_image: false
# Nginx tuning
nginx_worker_processes: auto
nginx_worker_connections: 1024

View File

@@ -0,0 +1,5 @@
---
# Per demo: valori in chiaro.
# In produzione cifra questo file con ansible-vault.
app_secret: "demo-secret-change-me"
release_id: "v1"

View File

@@ -0,0 +1,11 @@
[webservers]
# Sostituisci con il tuo host reale
server1 ansible_host=XXX.XXX.XXX.XXX ansible_user=root
[webservers:vars]
ansible_python_interpreter=/usr/bin/python3
# In SemaphoreUI configura la chiave SSH nella sezione Key,
# quindi normalmente non serve impostare ansible_ssh_private_key_file qui.
[all:vars]
ansible_connection=ssh

View File

@@ -0,0 +1,4 @@
---
collections:
- name: community.docker
version: ">=3.0.0"

View File

@@ -0,0 +1,48 @@
---
- name: Crea cartella app
ansible.builtin.file:
path: /opt/semaphore-splash
state: directory
mode: '0755'
- name: Render index HTML
ansible.builtin.template:
src: index.html.j2
dest: /opt/semaphore-splash/index.html
mode: '0644'
- name: Render config Nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /opt/semaphore-splash/nginx.conf
mode: '0644'
- name: Ferma eventuale container precedente
community.docker.docker_container:
name: "{{ container_name }}"
state: absent
- name: Scarica immagine Nginx
community.docker.docker_image:
name: "{{ nginx_image }}"
tag: "{{ nginx_version }}"
source: pull
- name: Avvia container Nginx demo
community.docker.docker_container:
name: "{{ container_name }}"
image: "{{ nginx_image }}:{{ nginx_version }}"
state: started
restart_policy: unless-stopped
ports:
- "{{ app_port }}:80"
volumes:
- /opt/semaphore-splash/index.html:/usr/share/nginx/html/index.html:ro
- /opt/semaphore-splash/nginx.conf:/etc/nginx/nginx.conf:ro
env:
APP_ENV: "{{ app_environment }}"
GREETING_MESSAGE: "{{ greeting_message }}"
APP_SECRET: "{{ app_secret }}"
RELEASE_ID: "{{ release_id }}"
ACCENT_COLOR: "{{ accent_color }}"
SECONDARY_COLOR: "{{ secondary_color }}"

View File

@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SemaphoreUI Docker Demo</title>
<style>
:root {
--accent: {{ accent_color }};
--secondary: {{ secondary_color }};
--ink: #1f2937;
--paper: #fefcf8;
--card: #ffffff;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Trebuchet MS", "Segoe UI", sans-serif;
color: var(--ink);
min-height: 100vh;
background:
radial-gradient(circle at 15% 20%, color-mix(in oklab, var(--accent), white 65%) 0%, transparent 40%),
radial-gradient(circle at 85% 80%, color-mix(in oklab, var(--secondary), white 70%) 0%, transparent 35%),
linear-gradient(160deg, #fff7ed 0%, var(--paper) 50%, #ecfeff 100%);
display: grid;
place-items: center;
padding: 24px;
}
.card {
width: min(860px, 100%);
background: var(--card);
border-radius: 24px;
border: 1px solid #e5e7eb;
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.12);
overflow: hidden;
animation: lift 550ms ease-out;
}
.hero {
padding: 34px 30px;
background: linear-gradient(135deg, color-mix(in oklab, var(--accent), black 6%), color-mix(in oklab, var(--secondary), black 10%));
color: #fff;
}
.hero h1 {
font-size: clamp(1.7rem, 3.5vw, 2.5rem);
letter-spacing: 0.2px;
margin-bottom: 8px;
}
.hero p {
opacity: 0.95;
line-height: 1.5;
}
.body {
padding: 26px 30px 30px;
display: grid;
gap: 14px;
}
.pill {
display: inline-flex;
align-items: center;
padding: 7px 12px;
border-radius: 999px;
background: #f3f4f6;
font-size: 0.85rem;
margin-right: 8px;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 8px;
}
.item {
padding: 14px;
border-radius: 14px;
background: #f9fafb;
border: 1px solid #edf2f7;
}
.item .k {
font-size: 0.8rem;
color: #6b7280;
margin-bottom: 6px;
}
.item .v {
font-family: "Courier New", monospace;
font-size: 0.95rem;
color: #111827;
word-break: break-word;
}
.footer {
padding: 14px 30px 26px;
font-size: 0.85rem;
color: #6b7280;
}
@keyframes lift {
from { transform: translateY(10px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@media (max-width: 640px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<section class="card">
<header class="hero">
<h1>{{ greeting_message }}</h1>
<p>Demo Docker deployata con Ansible e orchestrata tramite SemaphoreUI.</p>
</header>
<div class="body">
<div>
<span class="pill">Environment: {{ app_environment | upper }}</span>
<span class="pill">Release: {{ release_id }}</span>
</div>
<div class="grid">
<div class="item">
<div class="k">Container</div>
<div class="v">{{ container_name }}</div>
</div>
<div class="item">
<div class="k">Image</div>
<div class="v">{{ nginx_image }}:{{ nginx_version }}</div>
</div>
<div class="item">
<div class="k">Porta esposta</div>
<div class="v">{{ app_port }}</div>
</div>
<div class="item">
<div class="k">Secret hash hint</div>
<div class="v">{{ app_secret | hash('sha1') }}</div>
</div>
</div>
</div>
<footer class="footer">
Gestito da SemaphoreUI + Ansible Collections community.docker
</footer>
</section>
</body>
</html>

View File

@@ -0,0 +1,35 @@
user nginx;
worker_processes {{ nginx_worker_processes }};
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
server {
listen 80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /health {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
}
}

View File

@@ -0,0 +1,78 @@
# Configuration for SemaphoreUI + Ansible Docker Splash Demo
# Aggiorna i campi in base al tuo ambiente
# SemaphoreUI connection settings
semaphore_url: "https://your-semaphore-instance.com"
semaphore_token: "your-semaphore-api-token"
project_id: 1
# Git repository configuration
git_provider: "gitea" # github, gitlab, gitea, ...
git_base_url: "https://your-gitea-instance.com"
git_repo_url: "https://your-gitea-instance.com/your-user/ansible-semaphore-docker-demo.git"
git_branch: "main"
# Git authentication (optional for private repos)
git_username: "your-username"
git_access_token: "your-personal-access-token"
# Environment configuration
environment_name: "Semaphore Docker Splash Environment"
environment_variables:
nginx_image: "nginx"
nginx_version: "1.27-alpine"
container_name: "semaphore-splash"
app_port: "8090"
app_environment: "production"
greeting_message: "Ciao dal deploy SemaphoreUI"
accent_color: "#0ea5a4"
secondary_color: "#f97316"
remove_image: "false"
nginx_worker_processes: "auto"
nginx_worker_connections: "1024"
# Optional encrypted secrets in SemaphoreUI Environment
secrets:
app_secret: "demo-secret-from-semaphore"
release_id: "v1-test"
demo_api_key: "demo-api-key-123"
demo_db_password: "demo-db-pass-123"
# Inventory configuration
inventory_name: "Docker Splash Servers"
inventory_type: "static"
inventory_content: |
[webservers]
server1 ansible_host=XXX.XXX.XXX.XXX ansible_user=root
[webservers:vars]
ansible_python_interpreter=/usr/bin/python3
[all:vars]
ansible_connection=ssh
# Ansible Vault configuration
# Se non usi file vault, puo restare vuoto
vault_password: ""
# Playbooks configuration
playbooks:
- name: "Test Connectivity"
description: "Check host reachability and Docker prerequisites"
playbook_filename: "test.yml"
- name: "Deploy Splash App"
description: "Deploy Nginx container with custom splash page"
playbook_filename: "deploy.yml"
- name: "Undeploy Splash App"
description: "Remove container and clean up resources"
playbook_filename: "undeploy.yml"
# Optional additional settings
timeout_seconds: 30
enable_health_check: true

View File

@@ -0,0 +1,41 @@
---
- name: Test host e prerequisiti Docker
hosts: webservers
become: yes
gather_facts: yes
vars_files:
- group_vars/all.yml
tasks:
- name: Test SSH
ansible.builtin.ping:
- name: Mostra info host
ansible.builtin.debug:
msg:
- "Host: {{ ansible_hostname }}"
- "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Python: {{ ansible_python_version }}"
- name: Controlla Docker CLI
ansible.builtin.command: docker --version
register: docker_version
changed_when: false
failed_when: false
- name: Stato Docker CLI
ansible.builtin.debug:
msg: "{{ docker_version.stdout if docker_version.rc == 0 else 'Docker non trovato' }}"
- name: Verifica porta target disponibile
ansible.builtin.wait_for:
port: "{{ app_port }}"
state: stopped
timeout: 1
register: port_check
failed_when: false
- name: Risultato porta
ansible.builtin.debug:
msg: "Porta {{ app_port }} {{ 'libera' if port_check is not failed else 'gia in uso' }}"

View File

@@ -0,0 +1,29 @@
---
- name: Undeploy demo container
hosts: webservers
become: yes
vars_files:
- group_vars/all.yml
tasks:
- name: Rimuovi container demo
community.docker.docker_container:
name: "{{ container_name }}"
state: absent
- name: Rimuovi immagine (opzionale)
community.docker.docker_image:
name: "{{ nginx_image }}"
tag: "{{ nginx_version }}"
state: absent
when: remove_image | bool
- name: Rimuovi directory applicazione
ansible.builtin.file:
path: /opt/semaphore-splash
state: absent
- name: Conferma undeploy
ansible.builtin.debug:
msg: "Container {{ container_name }} rimosso con successo"