feat(ansible): add semaphore docker splash demo with dagster config
This commit is contained in:
72
ansible-semaphore-docker-demo/README.md
Normal file
72
ansible-semaphore-docker-demo/README.md
Normal 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.
|
||||
20
ansible-semaphore-docker-demo/ansible.cfg
Normal file
20
ansible-semaphore-docker-demo/ansible.cfg
Normal 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
|
||||
44
ansible-semaphore-docker-demo/deploy.yml
Normal file
44
ansible-semaphore-docker-demo/deploy.yml
Normal 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 }}"
|
||||
17
ansible-semaphore-docker-demo/group_vars/all.yml
Normal file
17
ansible-semaphore-docker-demo/group_vars/all.yml
Normal 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
|
||||
5
ansible-semaphore-docker-demo/group_vars/secrets.yml
Normal file
5
ansible-semaphore-docker-demo/group_vars/secrets.yml
Normal 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"
|
||||
11
ansible-semaphore-docker-demo/inventory
Normal file
11
ansible-semaphore-docker-demo/inventory
Normal 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
|
||||
4
ansible-semaphore-docker-demo/requirements.yml
Normal file
4
ansible-semaphore-docker-demo/requirements.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
collections:
|
||||
- name: community.docker
|
||||
version: ">=3.0.0"
|
||||
48
ansible-semaphore-docker-demo/roles/splash/tasks/main.yml
Normal file
48
ansible-semaphore-docker-demo/roles/splash/tasks/main.yml
Normal 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 }}"
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
41
ansible-semaphore-docker-demo/test.yml
Normal file
41
ansible-semaphore-docker-demo/test.yml
Normal 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' }}"
|
||||
29
ansible-semaphore-docker-demo/undeploy.yml
Normal file
29
ansible-semaphore-docker-demo/undeploy.yml
Normal 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"
|
||||
Reference in New Issue
Block a user