Compare commits
6 Commits
3161402f77
...
44c58b91f8
| Author | SHA1 | Date |
|---|---|---|
|
|
44c58b91f8 | |
|
|
ca24d66e7a | |
|
|
3b17e5b405 | |
|
|
65b4fa9717 | |
|
|
fbbf4e2089 | |
|
|
15c9b4f1e4 |
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
rules: {}
|
||||||
|
parser:
|
||||||
|
ansible: true
|
||||||
|
warn_list: []
|
||||||
|
skip_list:
|
||||||
|
- yaml
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- run: pip install ansible ansible-lint
|
||||||
|
- run: ansible-lint -v
|
||||||
|
dryrun:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- run: pip install ansible
|
||||||
|
- run: ansible-playbook playbook/playbook.yml --check --list-tasks
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# CODEOWNERS definiert die Reviewer/Verantwortlichen für Verzeichnisse und Dateien
|
||||||
|
# Syntax: Pfad @owner1 @owner2
|
||||||
|
|
||||||
|
* @linux-admins @devops-team
|
||||||
|
playbook/roles/** @linux-admins
|
||||||
|
playbook/group_vars/** @linux-admins
|
||||||
|
docs/** @tech-writer
|
||||||
|
scripts/** @devops-team
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
# Ansible caches
|
|
||||||
.ansible
|
|
||||||
.ansible_facts_cache
|
|
||||||
|
|
||||||
# Secrets
|
|
||||||
playbook/group_vars/vault.yml
|
playbook/group_vars/vault.yml
|
||||||
|
.ansible_facts_cache/
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
.env
|
.env
|
||||||
*.retry
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
image: python:3.11
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- lint
|
||||||
|
- dryrun
|
||||||
|
|
||||||
|
variables:
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK: '1'
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: lint
|
||||||
|
script:
|
||||||
|
- pip install ansible ansible-lint
|
||||||
|
- ansible-lint -v
|
||||||
|
|
||||||
|
dryrun:
|
||||||
|
stage: dryrun
|
||||||
|
script:
|
||||||
|
- pip install ansible
|
||||||
|
- ansible-playbook playbook/playbook.yml --check --list-tasks
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- repo: https://github.com/ansible-community/ansible-lint
|
||||||
|
rev: v6.22.1
|
||||||
|
hooks:
|
||||||
|
- id: ansible-lint
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- main
|
||||||
|
|
||||||
|
steps:
|
||||||
|
lint:
|
||||||
|
image: python:3.11-slim
|
||||||
|
commands:
|
||||||
|
- pip install --no-cache-dir ansible ansible-lint
|
||||||
|
- ansible-lint -v
|
||||||
|
|
||||||
|
dryrun:
|
||||||
|
image: python:3.11-slim
|
||||||
|
commands:
|
||||||
|
- pip install --no-cache-dir ansible
|
||||||
|
- echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
|
||||||
|
- ansible-galaxy collection install -r playbook/requirements.yml --force
|
||||||
|
- ansible-playbook playbook/playbook.yml --check --list-tasks \
|
||||||
|
--vault-password-file .vault_pass -i 'localhost,' -c local -e 'gather_facts=false'
|
||||||
|
secrets: [ANSIBLE_VAULT_PASSWORD]
|
||||||
|
|
||||||
|
run_preflight:
|
||||||
|
when:
|
||||||
|
event: [manual]
|
||||||
|
image: python:3.11-slim
|
||||||
|
commands:
|
||||||
|
- pip install --no-cache-dir ansible
|
||||||
|
- echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
|
||||||
|
- ansible-galaxy collection install -r playbook/requirements.yml --force
|
||||||
|
- ansible-playbook playbook/playbook.yml --tags preflight -l pdp-portal \
|
||||||
|
--vault-password-file .vault_pass
|
||||||
|
secrets: [ANSIBLE_VAULT_PASSWORD]
|
||||||
|
|
||||||
|
run_patch:
|
||||||
|
when:
|
||||||
|
event: [manual]
|
||||||
|
image: python:3.11-slim
|
||||||
|
commands:
|
||||||
|
- pip install --no-cache-dir ansible
|
||||||
|
- echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
|
||||||
|
- ansible-galaxy collection install -r playbook/requirements.yml --force
|
||||||
|
- ansible-playbook playbook/playbook.yml -l pdp-portal -e "target_clm_version=prod-2024-06" \
|
||||||
|
--vault-password-file .vault_pass
|
||||||
|
secrets: [ANSIBLE_VAULT_PASSWORD]
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# CONTRIBUTING
|
||||||
|
|
||||||
|
Danke für deinen Beitrag! Bitte beachte folgende Richtlinien:
|
||||||
|
|
||||||
|
## Branch/Commit
|
||||||
|
- Feature-Branches: `feature/<kurzbeschreibung>`
|
||||||
|
- Fix-Branches: `fix/<kurzbeschreibung>`
|
||||||
|
- Commit Messages nach Conventional Commits (feat:, fix:, docs:, chore:, refactor:, perf:, test:)
|
||||||
|
|
||||||
|
## Code-Style
|
||||||
|
- Ansible: ansible-lint muss grün sein (`make lint`)
|
||||||
|
- YAML: 2 Spaces, keine Tabs
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Dry-Run: `ansible-playbook playbook/playbook.yml --check --list-tasks`
|
||||||
|
- App-spezifisch: `-l <app-gruppe>`
|
||||||
|
|
||||||
|
## Security
|
||||||
|
- Keine Secrets im Klartext, nutze `make vault-encrypt`
|
||||||
|
- PRs werden automatisch per CI geprüft (Linting, Dry-Run)
|
||||||
|
|
||||||
|
## Review
|
||||||
|
- CODEOWNERS regelt Reviewer
|
||||||
|
- Mindestens 1 Approval erforderlich
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
|
.PHONY: deps run lint vault-encrypt vault-decrypt
|
||||||
|
|
||||||
|
deps:
|
||||||
|
./scripts/install_collections.sh
|
||||||
|
|
||||||
|
run:
|
||||||
|
./scripts/run_patch.sh $(APP) $(CLM) "$(EXTRA)"
|
||||||
|
|
||||||
|
lint:
|
||||||
|
ansible-lint -v
|
||||||
|
|
||||||
|
vault-encrypt:
|
||||||
|
ansible-vault encrypt group_vars/vault.yml
|
||||||
|
|
||||||
|
vault-decrypt:
|
||||||
|
ansible-vault decrypt group_vars/vault.yml
|
||||||
19
ansible.cfg
19
ansible.cfg
|
|
@ -1,9 +1,18 @@
|
||||||
[defaults]
|
[defaults]
|
||||||
inventory = playbook/inventories
|
inventory = playbook/servicenow_inventory.yml
|
||||||
roles_path = playbook/roles
|
roles_path = playbook/roles
|
||||||
|
collections_path = ~/.ansible/collections:./collections
|
||||||
|
host_key_checking = False
|
||||||
retry_files_enabled = False
|
retry_files_enabled = False
|
||||||
stdout_callback = yaml
|
stdout_callback = yaml
|
||||||
host_key_checking = False
|
bin_ansible_callbacks = True
|
||||||
force_color = True
|
forks = 25
|
||||||
forks = 10
|
interpreter_python = auto_silent
|
||||||
callback_whitelist = profile_tasks
|
fact_caching = jsonfile
|
||||||
|
fact_caching_connection = .ansible_facts_cache
|
||||||
|
fact_caching_timeout = 86400
|
||||||
|
callbacks_enabled = profile_tasks, timer
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
pipelining = True
|
||||||
|
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
Alle Änderungen und Patchläufe werden hier automatisch dokumentiert.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
2024-06-01T12:00:00Z: Patch/Upgrade auf pdp-portal-server1.example.com (FQDN: pdp-portal-server1.example.com) durchgeführt. Ergebnis: OK
|
||||||
148
docs/README.md
148
docs/README.md
|
|
@ -0,0 +1,148 @@
|
||||||
|
## Enterprise Auto-Upgrade Playbook für SLES & RHEL
|
||||||
|
|
||||||
|
### Übersicht
|
||||||
|
Modulares, Enterprise-taugliches Ansible-Playbook für automatisierte Upgrades/Patching von SLES und RHEL. Enthält:
|
||||||
|
- Automatische OS-Erkennung und OS-spezifische Rollen (RHEL/SLES)
|
||||||
|
- Upgrade nach Hersteller-Best-Practices (dnf/yum, zypper)
|
||||||
|
- VMware-Snapshots für Backup/Rollback (vCenter API)
|
||||||
|
- SUSE Manager API: dynamische CLM-Zuweisung via `target_clm_version`
|
||||||
|
- Wartungsfenster-Handling, Preflight-Checks
|
||||||
|
- Smoke-Tests (HTTP/Port/DB, inkl. Oracle SID/Listener-Erkennung)
|
||||||
|
- Self-Healing (Service-Restarts, Cleanup, Netzwerk)
|
||||||
|
- Compliance-Checks (OpenSCAP, Lynis)
|
||||||
|
- Logging, Mail-Benachrichtigung (mailx), optional Slack
|
||||||
|
- CI/CD via Gitea + Woodpecker (OAuth), Collections-Lint + Dry-Run
|
||||||
|
|
||||||
|
### Verzeichnisstruktur (Auszug)
|
||||||
|
```
|
||||||
|
os-upgrade-automation/
|
||||||
|
├── ansible.cfg
|
||||||
|
├── docs/
|
||||||
|
│ ├── README.md
|
||||||
|
│ ├── CHANGELOG.md
|
||||||
|
│ └── Runbook_SelfService.md
|
||||||
|
├── playbook/
|
||||||
|
│ ├── group_vars/
|
||||||
|
│ │ ├── all.yml
|
||||||
|
│ │ └── vault.yml # Mit Ansible Vault verschlüsseln
|
||||||
|
│ ├── host_vars/
|
||||||
|
│ ├── playbook.yml
|
||||||
|
│ ├── requirements.yml
|
||||||
|
│ ├── servicenow_inventory.yml # Dynamic Inventory (ServiceNow)
|
||||||
|
│ └── roles/
|
||||||
|
│ ├── common/
|
||||||
|
│ ├── preflight_check/
|
||||||
|
│ ├── vmware_snapshot/
|
||||||
|
│ ├── suma_api_assign_clm/
|
||||||
|
│ ├── rhel_upgrade/
|
||||||
|
│ ├── sles_upgrade/
|
||||||
|
│ ├── post_upgrade/
|
||||||
|
│ ├── smoke_tests/
|
||||||
|
│ ├── self_healing/
|
||||||
|
│ └── compliance_check/
|
||||||
|
├── scripts/
|
||||||
|
│ ├── install_collections.sh
|
||||||
|
│ └── run_patch.sh
|
||||||
|
└── .woodpecker.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
- Ansible (empfohlen ≥ 2.14)
|
||||||
|
- Collections: `community.vmware`, `servicenow.servicenow`, `community.general`
|
||||||
|
- vCenter-Zugriff (API), SUSE Manager API-Zugang
|
||||||
|
- Zielsysteme: SLES oder RHEL (ggf. via SUSE Manager, venv-salt-minion)
|
||||||
|
- Ansible Vault für Secrets (zwingend empfohlen) [[Speicherpräferenz]]
|
||||||
|
|
||||||
|
Installation Collections:
|
||||||
|
```
|
||||||
|
make deps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Konfiguration
|
||||||
|
1) Secrets sicher ablegen (Vault)
|
||||||
|
- `playbook/group_vars/vault.yml` befüllen (ServiceNow, SUSE Manager, vCenter, SMTP, Slack, DB)
|
||||||
|
- Datei sofort verschlüsseln:
|
||||||
|
```
|
||||||
|
ansible-vault encrypt playbook/group_vars/vault.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
2) Globale Variablen prüfen
|
||||||
|
- `playbook/group_vars/all.yml` enthält Standardwerte (u.a. Skip-Flags, Mail-Empfänger, Wartungsfenster, Log-Verzeichnis).
|
||||||
|
|
||||||
|
3) Dynamic Inventory (ServiceNow)
|
||||||
|
- `playbook/servicenow_inventory.yml` nutzt den ServiceNow-Inventory-Plugin.
|
||||||
|
- Erwartet in Vault: `servicenow_instance`, `servicenow_user`, `servicenow_pass`.
|
||||||
|
|
||||||
|
### Nutzung (lokal)
|
||||||
|
- Lint:
|
||||||
|
```
|
||||||
|
make lint
|
||||||
|
```
|
||||||
|
|
||||||
|
- Playbook ausführen (Beispiele):
|
||||||
|
```
|
||||||
|
# App-Gruppe patchen, Ziel-CLM setzen
|
||||||
|
make run APP=pdp-portal CLM=prod-2024-06 EXTRA="debug_mode=true"
|
||||||
|
|
||||||
|
# Direkter Aufruf
|
||||||
|
./scripts/run_patch.sh pdp-portal prod-2024-06 "skip_compliance=true skip_smoke_tests=false"
|
||||||
|
```
|
||||||
|
|
||||||
|
Wichtige Extra-Variablen / Skip-Flags:
|
||||||
|
- `target_clm_version` (z.B. `prod-2024-06`)
|
||||||
|
- `debug_mode=true|false`
|
||||||
|
- `skip_vmware_snapshot`, `skip_suma_api`, `skip_post_upgrade`
|
||||||
|
- `skip_smoke_tests`, `skip_self_healing`, `skip_compliance`
|
||||||
|
- `upgrade_security_only=true|false`
|
||||||
|
|
||||||
|
### Rollenüberblick
|
||||||
|
- `preflight_check`: Diskspace, Wartungsfenster, Erreichbarkeit, Channel-Sync, pyVmomi-Check
|
||||||
|
- `vmware_snapshot`: Snapshot anlegen, optional Revert, optional Cleanup
|
||||||
|
- `suma_api_assign_clm`: System dynamisch CLM-Channel zuordnen
|
||||||
|
- `rhel_upgrade` / `sles_upgrade`: OS-spezifisches Upgrade, Kernel-Erkennung, Reboot-Flag
|
||||||
|
- `post_upgrade`: Reboot (optional), Health-Check kritischer Dienste
|
||||||
|
- `smoke_tests`: HTTP/Port/DB, Oracle (SIDs/Listener dynamisch erkannt)
|
||||||
|
- `self_healing`: Dienst-Restarts, Disk-Cleanup, Netzwerk-Remediation
|
||||||
|
- `compliance_check`: OpenSCAP, Lynis, Reporting & Mail
|
||||||
|
- `common`: Logging, mailx-Konfiguration, Admin-Mail inkl. Log-Anhänge, Slack (optional)
|
||||||
|
|
||||||
|
### VMware Snapshot & Rollback
|
||||||
|
- Vor dem Upgrade wird ein Snapshot mit Zeitstempel erstellt.
|
||||||
|
- Bei Fehlern kann automatisch ein Rollback getriggert werden (`rollback: true`).
|
||||||
|
- Optionales automatisches Löschen alter Snapshots nach erfolgreichem Lauf (`snapshot_cleanup`).
|
||||||
|
|
||||||
|
### Logging & Benachrichtigung
|
||||||
|
- Logs in `log_dir` (Standard: `/var/log/auto-upgrade`).
|
||||||
|
- Admin-Mail an `linux_admins_mail` mit Log-Summary + Anhängen.
|
||||||
|
- Failsafe-Mail an App- und Host-Kontakte bei Fehler.
|
||||||
|
- Optional Slack-Benachrichtigung (wenn `slack_enabled` und `slack_token`).
|
||||||
|
|
||||||
|
### CI/CD (Gitea + Woodpecker)
|
||||||
|
1) Repo in Woodpecker aktivieren
|
||||||
|
- In `https://ci.pp1l.de` auf “Repos” → “Sync” → `os-upgrade-automation` (oder `PurePowerPh1l/os-upgrade-automation`) “Enable”.
|
||||||
|
|
||||||
|
2) Secrets in Woodpecker setzen
|
||||||
|
- Mindestens `ANSIBLE_VAULT_PASSWORD` (für CI-Läufe ohne Interaktion).
|
||||||
|
- Optional: `SERVICENOW_*`, `SUMA_*`, `VCENTER_*`, `SLACK_TOKEN`, SMTP-Variablen, DB-Testdaten.
|
||||||
|
|
||||||
|
3) Pipelines
|
||||||
|
- `.woodpecker.yml` enthält: `lint`, `dryrun`, manuell `run_preflight`, `run_patch`.
|
||||||
|
- Hinweis: Interaktive Prompts (`--ask-vault-pass`) funktionieren in CI nicht. Nutze stattdessen eine Secret-basierte Lösung (z.B. Secret als Datei schreiben und `--vault-password-file` verwenden). Passe die Pipeline bei Bedarf an.
|
||||||
|
|
||||||
|
4) OAuth/Troubleshooting
|
||||||
|
- “Unregistered Redirect URI”: Stelle sicher, dass die Gitea-OAuth-App `https://ci.pp1l.de/authorize` als Redirect-URI nutzt.
|
||||||
|
- “PKCE is required for public clients”: Gitea-OAuth-App als Confidential Client anlegen.
|
||||||
|
- “registration_closed”: In Woodpecker `WOODPECKER_OPEN=false`, erlaubte Nutzer via `WOODPECKER_ADMIN=<user1,user2>` whitelisten.
|
||||||
|
|
||||||
|
### Sicherheit
|
||||||
|
- Keine Secrets im Klartext: Alles in `playbook/group_vars/vault.yml` ablegen und per Vault verschlüsseln [[Speicherpräferenz]].
|
||||||
|
- Zugriffsdaten in CI ausschließlich als Secrets verwalten.
|
||||||
|
|
||||||
|
### Nützliche Links
|
||||||
|
- Gitea: `https://git.pp1l.de`
|
||||||
|
- Woodpecker CI: `https://ci.pp1l.de`
|
||||||
|
- Ansible VMware Snapshot Modul: `https://docs.ansible.com/ansible/latest/collections/community/vmware/vmware_guest_snapshot_module.html`
|
||||||
|
- SUSE Manager: `https://documentation.suse.com/suma/`
|
||||||
|
|
||||||
|
—
|
||||||
|
Fragen oder Wünsche? Gerne melden.
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Self-Service Runbook für App-Owner
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Dieses Runbook beschreibt, wie App-Owner das Auto-Upgrade-Playbook für ihre Systeme selbstständig ausführen können.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
- Zugang zum Ansible-Server (SSH)
|
||||||
|
- Berechtigung für die gewünschte App-Gruppe im Inventory
|
||||||
|
- Vault-Passwort für verschlüsselte Variablen
|
||||||
|
|
||||||
|
## Schritt-für-Schritt-Anleitung
|
||||||
|
1. **Login auf dem Ansible-Server**
|
||||||
|
2. **Playbook für die eigene App-Gruppe ausführen:**
|
||||||
|
```bash
|
||||||
|
ansible-playbook -i inventory_apps playbook.yml -l <app-gruppe> --ask-vault-pass -e "target_clm_version=<channel>"
|
||||||
|
```
|
||||||
|
Beispiel für pdp-portal:
|
||||||
|
```bash
|
||||||
|
ansible-playbook -i inventory_apps playbook.yml -l pdp-portal --ask-vault-pass -e "target_clm_version=prod-2024-06"
|
||||||
|
```
|
||||||
|
3. **Ergebnis prüfen:**
|
||||||
|
- E-Mail-Benachrichtigung abwarten
|
||||||
|
- Logfiles im angegebenen Verzeichnis prüfen
|
||||||
|
|
||||||
|
## Hinweise
|
||||||
|
- Bei Fehlern wird automatisch ein Failsafe-Mail an die App- und Linux-Admins gesendet.
|
||||||
|
- Bei kritischen Fehlern erfolgt ein automatischer Rollback (VMware-Snapshot).
|
||||||
|
- Für Fragen oder Freigaben bitte an die Linux-Admins wenden.
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
upgrade_dry_run: false
|
||||||
|
reboot_after_upgrade: true
|
||||||
|
log_dir: /var/log/auto-upgrade
|
||||||
|
rollback: false
|
||||||
|
mail_to: "root@localhost"
|
||||||
|
|
||||||
|
# SMTP-Konfiguration für mailx (optional)
|
||||||
|
mail_smtp_host: "smtp.example.com"
|
||||||
|
mail_smtp_port: 587
|
||||||
|
mail_smtp_user: "user@example.com"
|
||||||
|
mail_smtp_pass: "dein_passwort"
|
||||||
|
|
||||||
|
vcenter_hostname: "vcenter.example.com"
|
||||||
|
vcenter_user: "administrator@vsphere.local"
|
||||||
|
vcenter_password: "dein_passwort"
|
||||||
|
vcenter_datacenter: "DeinDatacenter"
|
||||||
|
vcenter_folder: "/"
|
||||||
|
|
||||||
|
linux_admins_mail: "linux-admins@example.com"
|
||||||
|
|
||||||
|
maintenance_window_start: "22:00"
|
||||||
|
maintenance_window_end: "04:00"
|
||||||
|
|
||||||
|
# Upgrade-Optionen
|
||||||
|
upgrade_security_only: false # true = nur Security-Updates
|
||||||
|
|
||||||
|
# Skip-Flags für optionale Schritte
|
||||||
|
skip_smoke_tests: false
|
||||||
|
skip_compliance: false
|
||||||
|
skip_self_healing: false
|
||||||
|
skip_vmware_snapshot: false
|
||||||
|
skip_suma_api: false
|
||||||
|
skip_post_upgrade: false
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# ServiceNow API
|
||||||
|
servicenow_instance: "https://mycompany.service-now.com"
|
||||||
|
servicenow_user: "ansible_api"
|
||||||
|
servicenow_pass: "SuperSicheresServiceNowPasswort123!"
|
||||||
|
|
||||||
|
# SUSE Manager API
|
||||||
|
suma_api_url: "https://susemanager.example.com/rpc/api"
|
||||||
|
suma_api_user: "suma_admin"
|
||||||
|
suma_api_pass: "NochSichereresSumaPasswort456!"
|
||||||
|
|
||||||
|
# vCenter/VMware
|
||||||
|
vcenter_hostname: "vcenter.example.com"
|
||||||
|
vcenter_user: "administrator@vsphere.local"
|
||||||
|
vcenter_password: "MegaSicheresVcenterPasswort789!"
|
||||||
|
vcenter_datacenter: "Datacenter1"
|
||||||
|
vcenter_folder: "/"
|
||||||
|
|
||||||
|
# Mail/SMTP
|
||||||
|
mail_smtp_host: "smtp.example.com"
|
||||||
|
mail_smtp_port: 587
|
||||||
|
mail_smtp_user: "mailuser@example.com"
|
||||||
|
mail_smtp_pass: "MailPasswort123!"
|
||||||
|
|
||||||
|
# Slack
|
||||||
|
slack_token: "xoxb-1234567890-abcdefghijklmnopqrstuvwx"
|
||||||
|
slack_enabled: true
|
||||||
|
|
||||||
|
# Datenbank Smoke-Tests
|
||||||
|
smoke_test_db_host: "db.example.com"
|
||||||
|
smoke_test_db_user: "dbuser"
|
||||||
|
smoke_test_db_pass: "DBPasswort456!"
|
||||||
|
smoke_test_db_name: "appdb"
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
[pdp-portal]
|
||||||
|
pdp-portal-server1.example.com ansible_host=10.0.1.11 ansible_user=deploy host_email=admin-pdp@example.com
|
||||||
|
pdp-portal-server2.example.com ansible_host=10.0.1.12 host_email=admin-pdp2@example.com
|
||||||
|
|
||||||
|
[pdp-portal:vars]
|
||||||
|
app_mail=pdp-portal-app@example.com
|
||||||
|
|
||||||
|
[confluence]
|
||||||
|
confluence-server1.example.com ansible_host=10.0.2.21 ansible_user=confluence host_email=confluence-admin@example.com
|
||||||
|
confluence-server2.example.com ansible_host=10.0.2.22 host_email=confluence-admin2@example.com
|
||||||
|
|
||||||
|
[confluence:vars]
|
||||||
|
app_mail=confluence-app@example.com
|
||||||
|
|
||||||
|
[git]
|
||||||
|
git-server1.example.com ansible_host=10.0.3.31 ansible_user=gitadmin host_email=git-admin@example.com
|
||||||
|
git-server2.example.com ansible_host=10.0.3.32 host_email=git-admin2@example.com
|
||||||
|
|
||||||
|
[git:vars]
|
||||||
|
app_mail=git-app@example.com
|
||||||
|
|
||||||
|
# Optional: Gruppen für Umgebungen
|
||||||
|
#[dev:children]
|
||||||
|
#pdp-portal
|
||||||
|
#git
|
||||||
|
|
||||||
|
#[prod:children]
|
||||||
|
#confluence
|
||||||
|
#pdp-portal
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
- name: Enterprise Auto-Upgrade für SLES und RHEL
|
||||||
|
hosts: all
|
||||||
|
gather_facts: false
|
||||||
|
become: yes
|
||||||
|
serial: 5
|
||||||
|
vars_files:
|
||||||
|
- group_vars/all.yml
|
||||||
|
- group_vars/vault.yml
|
||||||
|
vars:
|
||||||
|
target_clm_version: "" # Kann beim Aufruf überschrieben werden
|
||||||
|
debug_mode: false # Kann beim Aufruf überschrieben werden
|
||||||
|
skip_smoke_tests: false
|
||||||
|
skip_compliance: false
|
||||||
|
skip_self_healing: false
|
||||||
|
skip_vmware_snapshot: false
|
||||||
|
skip_suma_api: false
|
||||||
|
skip_post_upgrade: false
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Sammle gezielt Netzwerk- und Hardware-Fakten
|
||||||
|
setup:
|
||||||
|
gather_subset:
|
||||||
|
- network
|
||||||
|
- hardware
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: ServiceNow Change öffnen (optional)
|
||||||
|
import_role:
|
||||||
|
name: servicenow_tickets
|
||||||
|
tags: snow
|
||||||
|
|
||||||
|
- name: Preflight-Check: Prüfe Diskspace, Erreichbarkeit, Channel, Snapshots
|
||||||
|
import_role:
|
||||||
|
name: preflight_check
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Setze Ziel-CLM-Version falls übergeben
|
||||||
|
set_fact:
|
||||||
|
target_clm_version: "{{ target_clm_version | default('') }}"
|
||||||
|
tags: always
|
||||||
|
|
||||||
|
- name: Debug: Zeige alle relevanten Variablen und Fakten
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
inventory_hostname: "{{ inventory_hostname }}"
|
||||||
|
ansible_os_family: "{{ ansible_facts['os_family'] }}"
|
||||||
|
ansible_distribution: "{{ ansible_facts['distribution'] }}"
|
||||||
|
ansible_distribution_version: "{{ ansible_facts['distribution_version'] }}"
|
||||||
|
target_clm_version: "{{ target_clm_version }}"
|
||||||
|
rollback: "{{ rollback }}"
|
||||||
|
mail_to: "{{ mail_to }}"
|
||||||
|
vcenter_hostname: "{{ vcenter_hostname }}"
|
||||||
|
suma_api_url: "{{ suma_api_url }}"
|
||||||
|
when: debug_mode | bool
|
||||||
|
tags: debug
|
||||||
|
|
||||||
|
- name: Erstelle VMware Snapshot vor Upgrade (optional)
|
||||||
|
import_role:
|
||||||
|
name: vmware_snapshot
|
||||||
|
when: not skip_vmware_snapshot
|
||||||
|
tags: snapshot
|
||||||
|
|
||||||
|
- name: Weise System per SUSE Manager API dem gewünschten CLM-Channel zu (optional)
|
||||||
|
import_role:
|
||||||
|
name: suma_api_assign_clm
|
||||||
|
when: target_clm_version != "" and not skip_suma_api
|
||||||
|
tags: suma
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: common
|
||||||
|
tags: common
|
||||||
|
- role: rhel_upgrade
|
||||||
|
when: ansible_facts['os_family'] == "RedHat"
|
||||||
|
tags: rhel
|
||||||
|
- role: sles_upgrade
|
||||||
|
when: ansible_facts['os_family'] == "Suse"
|
||||||
|
tags: sles
|
||||||
|
- role: post_upgrade
|
||||||
|
when: not skip_post_upgrade
|
||||||
|
tags: post
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
collections:
|
||||||
|
- name: community.vmware
|
||||||
|
- name: servicenow.servicenow
|
||||||
|
- name: community.general
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
---
|
||||||
|
- name: Prüfe OS-Typ und Version
|
||||||
|
debug:
|
||||||
|
msg: "OS: {{ ansible_facts['os_family'] }} Version: {{ ansible_facts['distribution_version'] }}"
|
||||||
|
|
||||||
|
- name: Erstelle Log-Verzeichnis
|
||||||
|
file:
|
||||||
|
path: "{{ log_dir }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
register: logdir_result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Breche ab, wenn Log-Verzeichnis nicht erstellt werden kann
|
||||||
|
fail:
|
||||||
|
msg: "Log-Verzeichnis konnte nicht erstellt werden: {{ logdir_result.msg | default('Unbekannter Fehler') }}"
|
||||||
|
when: logdir_result is failed
|
||||||
|
|
||||||
|
- name: Konfiguriere mailx (Absender)
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/mail.rc
|
||||||
|
line: "set from=auto-upgrade@{{ inventory_hostname }}"
|
||||||
|
create: yes
|
||||||
|
state: present
|
||||||
|
become: true
|
||||||
|
register: mailx_from_result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler bei mailx-Konfiguration (Absender)
|
||||||
|
copy:
|
||||||
|
content: "mailx-Konfigurations-Fehler: {{ mailx_from_result.msg | default('Unbekannter Fehler') }}"
|
||||||
|
dest: "{{ log_dir }}/mailx_error_{{ inventory_hostname }}.log"
|
||||||
|
when: mailx_from_result is failed
|
||||||
|
|
||||||
|
- name: Konfiguriere mailx für externen SMTP-Server (optional)
|
||||||
|
blockinfile:
|
||||||
|
path: /etc/mail.rc
|
||||||
|
block: |
|
||||||
|
set smtp=smtp://{{ mail_smtp_host }}:{{ mail_smtp_port }}
|
||||||
|
set smtp-auth=login
|
||||||
|
set smtp-auth-user={{ mail_smtp_user }}
|
||||||
|
set smtp-auth-password={{ mail_smtp_pass }}
|
||||||
|
set ssl-verify=ignore
|
||||||
|
set nss-config-dir=/etc/pki/nssdb
|
||||||
|
when: mail_smtp_host is defined and mail_smtp_user is defined and mail_smtp_pass is defined
|
||||||
|
become: true
|
||||||
|
register: mailx_smtp_result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler bei mailx-Konfiguration (SMTP)
|
||||||
|
copy:
|
||||||
|
content: "mailx-SMTP-Konfigurations-Fehler: {{ mailx_smtp_result.msg | default('Unbekannter Fehler') }}"
|
||||||
|
dest: "{{ log_dir }}/mailx_error_{{ inventory_hostname }}.log"
|
||||||
|
when: mailx_smtp_result is failed
|
||||||
|
|
||||||
|
- name: Sende Failsafe-Mail an app_mail und host_email bei Fehler
|
||||||
|
mail:
|
||||||
|
host: "localhost"
|
||||||
|
port: 25
|
||||||
|
to: |
|
||||||
|
{{ app_mail | default('') }}{{ ',' if app_mail is defined and app_mail != '' else '' }}{{ host_email | default(mail_to) }}
|
||||||
|
subject: "[FAILSAFE] Fehler beim Patch/Upgrade auf {{ inventory_hostname }}"
|
||||||
|
body: |
|
||||||
|
Es ist ein Fehler beim Patch/Upgrade auf {{ inventory_hostname }} (FQDN: {{ ansible_fqdn }}) aufgetreten.
|
||||||
|
Siehe Log-Verzeichnis: {{ log_dir }}
|
||||||
|
Zeit: {{ ansible_date_time.iso8601 }}
|
||||||
|
when: (ansible_failed_result is defined and ansible_failed_result is not none) or (rollback is defined and rollback)
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Extrahiere Log-Summary für Admin-Mail
|
||||||
|
shell: |
|
||||||
|
tail -n 20 {{ log_dir }}/rhel_upgrade_check.log 2>/dev/null; tail -n 20 {{ log_dir }}/sles_upgrade_check.log 2>/dev/null; tail -n 20 {{ log_dir }}/rhel_upgrade_error_{{ inventory_hostname }}.log 2>/dev/null; tail -n 20 {{ log_dir }}/sles_upgrade_error_{{ inventory_hostname }}.log 2>/dev/null
|
||||||
|
register: log_summary
|
||||||
|
changed_when: false
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Setze dynamische Liste der Log-Attachments
|
||||||
|
set_fact:
|
||||||
|
log_attachments: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
log_dir + '/rhel_upgrade_check.log',
|
||||||
|
log_dir + '/sles_upgrade_check.log',
|
||||||
|
log_dir + '/rhel_upgrade_error_' + inventory_hostname + '.log',
|
||||||
|
log_dir + '/sles_upgrade_error_' + inventory_hostname + '.log',
|
||||||
|
log_dir + '/snapshot_error_' + inventory_hostname + '.log',
|
||||||
|
log_dir + '/suma_api_error_' + inventory_hostname + '.log',
|
||||||
|
log_dir + '/mailx_error_' + inventory_hostname + '.log',
|
||||||
|
log_dir + '/package_report_' + inventory_hostname + '.log'
|
||||||
|
] | select('fileexists') | list
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Sende Log an Linux-Admins (immer, mit Anhang und Summary)
|
||||||
|
mail:
|
||||||
|
host: "localhost"
|
||||||
|
port: 25
|
||||||
|
to: "{{ linux_admins_mail }}"
|
||||||
|
subject: "[LOG] Patch/Upgrade-Log für {{ inventory_hostname }} am {{ ansible_date_time.iso8601 }}"
|
||||||
|
body: |
|
||||||
|
Patch/Upgrade-Log für {{ inventory_hostname }} (FQDN: {{ ansible_fqdn }})
|
||||||
|
Zeit: {{ ansible_date_time.iso8601 }}
|
||||||
|
---
|
||||||
|
Log-Summary:
|
||||||
|
{{ log_summary.stdout | default('Keine Logdaten gefunden.') }}
|
||||||
|
---
|
||||||
|
Siehe Anhang für Details.
|
||||||
|
attach: "{{ log_attachments }}"
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Slack-Benachrichtigung bei kritischen Fehlern (optional)
|
||||||
|
slack:
|
||||||
|
token: "{{ slack_token | default('xoxb-...') }}"
|
||||||
|
msg: "[CRITICAL] Fehler beim Patch/Upgrade auf {{ inventory_hostname }}: {{ ansible_failed_result.msg | default('Unbekannter Fehler') }}"
|
||||||
|
channel: "#linux-admins"
|
||||||
|
when: slack_enabled | default(false) and (ansible_failed_result is defined and ansible_failed_result is not none)
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Dokumentiere Änderung im CHANGELOG
|
||||||
|
lineinfile:
|
||||||
|
path: "{{ playbook_dir }}/../CHANGELOG.md"
|
||||||
|
line: "{{ ansible_date_time.iso8601 }}: Patch/Upgrade auf {{ inventory_hostname }} (FQDN: {{ ansible_fqdn }}) durchgeführt. Ergebnis: {{ 'OK' if (ansible_failed_result is not defined or ansible_failed_result is none) else 'FEHLER' }}"
|
||||||
|
create: yes
|
||||||
|
delegate_to: localhost
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Erfasse installierte Paketversionen (RHEL)
|
||||||
|
shell: rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n'
|
||||||
|
register: rpm_list
|
||||||
|
when: ansible_facts['os_family'] == 'RedHat'
|
||||||
|
changed_when: false
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Erfasse installierte Paketversionen (SLES)
|
||||||
|
shell: rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n'
|
||||||
|
register: rpm_list
|
||||||
|
when: ansible_facts['os_family'] == 'Suse'
|
||||||
|
changed_when: false
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Schreibe Paket-Report ins Log
|
||||||
|
copy:
|
||||||
|
content: "{{ rpm_list.stdout | default('Keine Paketdaten gefunden.') }}"
|
||||||
|
dest: "{{ log_dir }}/package_report_{{ inventory_hostname }}.log"
|
||||||
|
when: rpm_list is defined
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Sende Paket-Report an Linux-Admins
|
||||||
|
mail:
|
||||||
|
host: "localhost"
|
||||||
|
port: 25
|
||||||
|
to: "{{ linux_admins_mail }}"
|
||||||
|
subject: "[REPORT] Paketversionen nach Patch für {{ inventory_hostname }} am {{ ansible_date_time.iso8601 }}"
|
||||||
|
body: |
|
||||||
|
Paket-Report für {{ inventory_hostname }} (FQDN: {{ ansible_fqdn }})
|
||||||
|
Zeit: {{ ansible_date_time.iso8601 }}
|
||||||
|
Siehe Anhang für Details.
|
||||||
|
attach:
|
||||||
|
- "{{ log_dir }}/package_report_{{ inventory_hostname }}.log"
|
||||||
|
when: rpm_list is defined
|
||||||
|
ignore_errors: true
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
- name: "Compliance-Check: Führe OpenSCAP-Scan durch (sofern installiert)"
|
||||||
|
ansible.builtin.shell: >-
|
||||||
|
oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_standard
|
||||||
|
--results {{ log_dir }}/oscap_result_{{ inventory_hostname }}.xml
|
||||||
|
/usr/share/xml/scap/ssg/content/ssg-$(lsb_release -si | tr '[:upper:]' '[:lower:]')-ds.xml
|
||||||
|
register: oscap_result
|
||||||
|
ignore_errors: true
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: "Compliance-Check: Führe Lynis-Scan durch (sofern installiert)"
|
||||||
|
ansible.builtin.shell: lynis audit system --quiet --logfile {{ log_dir }}/lynis_{{ inventory_hostname }}.log
|
||||||
|
register: lynis_result
|
||||||
|
ignore_errors: true
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Sende Compliance-Report an Linux-Admins
|
||||||
|
community.general.mail:
|
||||||
|
host: "localhost"
|
||||||
|
port: 25
|
||||||
|
to: "{{ linux_admins_mail }}"
|
||||||
|
subject: "[COMPLIANCE] Report für {{ inventory_hostname }} am {{ ansible_date_time.iso8601 }}"
|
||||||
|
body: |
|
||||||
|
Compliance-Report für {{ inventory_hostname }} (FQDN: {{ ansible_fqdn }})
|
||||||
|
Zeit: {{ ansible_date_time.iso8601 }}
|
||||||
|
OpenSCAP-Exit: {{ oscap_result.rc | default('N/A') }}
|
||||||
|
Lynis-Exit: {{ lynis_result.rc | default('N/A') }}
|
||||||
|
Siehe Anhang für Details.
|
||||||
|
attach:
|
||||||
|
- "{{ log_dir }}/oscap_result_{{ inventory_hostname }}.xml"
|
||||||
|
- "{{ log_dir }}/lynis_{{ inventory_hostname }}.log"
|
||||||
|
ignore_errors: true
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
- name: Reboot nach Upgrade (optional)
|
||||||
|
ansible.builtin.reboot:
|
||||||
|
msg: "Reboot nach Auto-Upgrade"
|
||||||
|
pre_reboot_delay: 60
|
||||||
|
when: reboot_after_upgrade
|
||||||
|
tags: reboot
|
||||||
|
|
||||||
|
- name: "Health-Check: Prüfe, ob kritische Dienste laufen"
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
tags: health
|
||||||
|
|
||||||
|
- name: Prüfe Status der kritischen Dienste
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- "(services[item].state == 'running') or (services[item].state == 'started')"
|
||||||
|
fail_msg: "Kritischer Dienst {{ item }} läuft nicht!"
|
||||||
|
success_msg: "Dienst {{ item }} läuft."
|
||||||
|
loop: "{{ critical_services | default(['sshd','cron']) }}"
|
||||||
|
when: item in services
|
||||||
|
tags: health
|
||||||
|
|
||||||
|
- name: Führe automatisierte Smoke-Tests durch (optional)
|
||||||
|
import_role:
|
||||||
|
name: smoke_tests
|
||||||
|
when: not skip_smoke_tests
|
||||||
|
tags: smoke
|
||||||
|
|
||||||
|
- name: Führe Self-Healing/Remediation durch (optional)
|
||||||
|
import_role:
|
||||||
|
name: self_healing
|
||||||
|
when: not skip_self_healing
|
||||||
|
tags: selfheal
|
||||||
|
|
||||||
|
- name: Führe Compliance-Checks durch (optional)
|
||||||
|
import_role:
|
||||||
|
name: compliance_check
|
||||||
|
when: not skip_compliance
|
||||||
|
tags: compliance
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
---
|
||||||
|
- name: "Prüfe, ob aktueller Zeitpunkt im Maintenance-Window liegt"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
now: "{{ lookup('pipe', 'date +%H:%M') }}"
|
||||||
|
window_start: "{{ maintenance_window_start }}"
|
||||||
|
window_end: "{{ maintenance_window_end }}"
|
||||||
|
changed_when: false
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: "Maintenance-Window-Check (Abbruch, wenn außerhalb)"
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: "Aktuelle Zeit {{ now }} liegt außerhalb des Maintenance-Windows ({{ window_start }} - {{ window_end }}). Upgrade wird abgebrochen!"
|
||||||
|
when: >-
|
||||||
|
(
|
||||||
|
(window_start < window_end and (now < window_start or now > window_end))
|
||||||
|
or
|
||||||
|
(window_start > window_end and (now < window_start and now > window_end))
|
||||||
|
)
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: "Prüfe freien Speicherplatz auf / (mind. 5GB empfohlen)"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /
|
||||||
|
register: root_stat
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Warnung bei zu wenig Speicherplatz
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- root_stat.stat.avail_bytes > 5368709120
|
||||||
|
fail_msg: "Wenig freier Speicherplatz auf /: {{ root_stat.stat.avail_bytes | human_readable }} (mind. 5GB empfohlen)"
|
||||||
|
success_msg: "Genügend Speicherplatz auf /: {{ root_stat.stat.avail_bytes | human_readable }}"
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Prüfe Erreichbarkeit von SUSE Manager
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: GET
|
||||||
|
validate_certs: no
|
||||||
|
timeout: 10
|
||||||
|
register: suma_reachable
|
||||||
|
ignore_errors: true
|
||||||
|
retries: 3
|
||||||
|
delay: 5
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Warnung, wenn SUSE Manager nicht erreichbar
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- suma_reachable.status is defined and suma_reachable.status == 200
|
||||||
|
fail_msg: "SUSE Manager API nicht erreichbar!"
|
||||||
|
success_msg: "SUSE Manager API erreichbar."
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Prüfe, ob VMware-Snapshot-Modul verfügbar ist
|
||||||
|
ansible.builtin.shell: "python3 -c 'import pyVmomi'"
|
||||||
|
register: pyvmomi_check
|
||||||
|
ignore_errors: true
|
||||||
|
changed_when: false
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Warnung, wenn pyVmomi nicht installiert ist
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pyvmomi_check.rc == 0
|
||||||
|
fail_msg: "pyVmomi (VMware-Modul) nicht installiert!"
|
||||||
|
success_msg: "pyVmomi ist installiert."
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Prüfe, ob aktueller SUSE Manager Channel synchronisiert ist
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"method": "auth.login",
|
||||||
|
"params": ["{{ suma_api_user }}", "{{ suma_api_pass }}"],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
validate_certs: no
|
||||||
|
timeout: 20
|
||||||
|
register: suma_api_login
|
||||||
|
ignore_errors: true
|
||||||
|
retries: 3
|
||||||
|
delay: 10
|
||||||
|
async: 60
|
||||||
|
poll: 0
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Hole Channel-Details für Ziel-CLM-Version
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"method": "channel.software.getDetails",
|
||||||
|
"params": ["{{ suma_api_login.json.result }}", "{{ target_clm_version }}"],
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
validate_certs: no
|
||||||
|
timeout: 20
|
||||||
|
register: suma_channel_details
|
||||||
|
ignore_errors: true
|
||||||
|
retries: 3
|
||||||
|
delay: 10
|
||||||
|
async: 60
|
||||||
|
poll: 0
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Prüfe Channel-Sync-Status
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- suma_channel_details.json.result.last_sync is defined
|
||||||
|
fail_msg: "Channel {{ target_clm_version }} ist nicht synchronisiert!"
|
||||||
|
success_msg: "Channel {{ target_clm_version }} wurde zuletzt synchronisiert am {{ suma_channel_details.json.result.last_sync }}."
|
||||||
|
tags: preflight
|
||||||
|
|
||||||
|
- name: Slack-Benachrichtigung bei kritischen Fehlern (Beispiel)
|
||||||
|
community.general.slack:
|
||||||
|
token: "{{ slack_token | default('xoxb-...') }}"
|
||||||
|
msg: "[CRITICAL] Fehler beim Preflight-Check auf {{ inventory_hostname }}: {{ ansible_failed_result.msg | default('Unbekannter Fehler') }}"
|
||||||
|
channel: "#linux-admins"
|
||||||
|
when: slack_enabled | default(false) and (ansible_failed_result is defined and ansible_failed_result is not none)
|
||||||
|
ignore_errors: true
|
||||||
|
tags: preflight
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
- name: Prüfe, ob dnf verfügbar ist (RHEL 8+)
|
||||||
|
stat:
|
||||||
|
path: /usr/bin/dnf
|
||||||
|
register: dnf_exists
|
||||||
|
|
||||||
|
- name: Pre-Upgrade-Check (yum/dnf)
|
||||||
|
shell: |
|
||||||
|
if [ -x /usr/bin/dnf ]; then
|
||||||
|
dnf check-update || true
|
||||||
|
else
|
||||||
|
yum check-update || true
|
||||||
|
fi
|
||||||
|
register: rhel_check
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Kernel-Version vor Upgrade sichern
|
||||||
|
shell: uname -r
|
||||||
|
register: kernel_before
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Upgrade durchführen (dnf/yum, security-only optional)
|
||||||
|
package:
|
||||||
|
name: "*"
|
||||||
|
state: latest
|
||||||
|
register: upgrade_result
|
||||||
|
when: not upgrade_dry_run and not upgrade_security_only
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Upgrade durchführen (dnf/yum, nur Security-Updates)
|
||||||
|
dnf:
|
||||||
|
name: "*"
|
||||||
|
state: latest
|
||||||
|
security: yes
|
||||||
|
register: upgrade_result
|
||||||
|
when: not upgrade_dry_run and upgrade_security_only and dnf_exists.stat.exists
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Upgrade durchführen (yum-plugin-security Fallback)
|
||||||
|
command: yum -y --security update
|
||||||
|
register: upgrade_result
|
||||||
|
when: not upgrade_dry_run and upgrade_security_only and not dnf_exists.stat.exists
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler beim Upgrade (RHEL)
|
||||||
|
copy:
|
||||||
|
content: "Upgrade-Fehler: {{ upgrade_result.stderr | default(upgrade_result.msg | default('Unbekannter Fehler')) }}"
|
||||||
|
dest: "{{ log_dir }}/rhel_upgrade_error_{{ inventory_hostname }}.log"
|
||||||
|
when: upgrade_result is failed
|
||||||
|
|
||||||
|
- name: Setze Rollback-Flag, falls Upgrade fehlschlägt
|
||||||
|
set_fact:
|
||||||
|
rollback: true
|
||||||
|
when: upgrade_result is failed
|
||||||
|
|
||||||
|
- name: Breche Playbook ab, wenn Upgrade fehlschlägt
|
||||||
|
fail:
|
||||||
|
msg: "Upgrade fehlgeschlagen, Rollback wird empfohlen! Siehe Log: {{ log_dir }}/rhel_upgrade_error_{{ inventory_hostname }}.log"
|
||||||
|
when: upgrade_result is failed
|
||||||
|
|
||||||
|
- name: Logge Upgrade-Output (RHEL)
|
||||||
|
copy:
|
||||||
|
content: "{{ rhel_check.stdout }}"
|
||||||
|
dest: "{{ log_dir }}/rhel_upgrade_check.log"
|
||||||
|
when: upgrade_result is not failed
|
||||||
|
|
||||||
|
- name: Kernel-Version nach Upgrade sichern
|
||||||
|
shell: uname -r
|
||||||
|
register: kernel_after
|
||||||
|
changed_when: false
|
||||||
|
when: upgrade_result is not failed
|
||||||
|
|
||||||
|
- name: Prüfe, ob Kernel-Upgrade erfolgt ist und setze Reboot nötig
|
||||||
|
set_fact:
|
||||||
|
reboot_after_upgrade: true
|
||||||
|
when: upgrade_result is not failed and (kernel_before.stdout != kernel_after.stdout)
|
||||||
|
|
||||||
|
- name: Hinweis auf EUS/Leapp (nur RHEL 7/8)
|
||||||
|
debug:
|
||||||
|
msg: "Für Major Upgrades (z.B. 7->8) empfiehlt Red Hat das Tool 'leapp' oder EUS-Strategien. Siehe https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html-single/upgrading_from_rhel_7_to_rhel_8/index.html"
|
||||||
|
when: ansible_facts['distribution_major_version']|int >= 7
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
---
|
||||||
|
- name: Self-Healing: Starte kritische Dienste neu, falls sie nicht laufen
|
||||||
|
service:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: restarted
|
||||||
|
register: restart_result
|
||||||
|
loop: "{{ critical_services | default(['sshd','cron']) }}"
|
||||||
|
when: item in services and (services[item].state != 'running' and services[item].state != 'started')
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Prüfe, ob Restart erfolgreich war
|
||||||
|
service_facts:
|
||||||
|
|
||||||
|
- name: Logge Self-Healing-Resultate
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
Self-Healing-Report für {{ inventory_hostname }}
|
||||||
|
Zeit: {{ ansible_date_time.iso8601 }}
|
||||||
|
Kritische Dienste: {{ critical_services | default(['sshd','cron']) }}
|
||||||
|
Restart-Resultate: {{ restart_result.results | default(restart_result) | to_nice_json }}
|
||||||
|
Service-Status nach Restart:
|
||||||
|
{% for item in critical_services | default(['sshd','cron']) %}
|
||||||
|
- {{ item }}: {{ services[item].state | default('unbekannt') }}
|
||||||
|
{% endfor %}
|
||||||
|
dest: "{{ log_dir }}/self_healing_{{ inventory_hostname }}.log"
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Eskaliere per Mail, wenn Restart fehlschlägt
|
||||||
|
mail:
|
||||||
|
host: "localhost"
|
||||||
|
port: 25
|
||||||
|
to: "{{ linux_admins_mail }}"
|
||||||
|
subject: "[SELF-HEALING-FAIL] Dienst konnte nicht neu gestartet werden auf {{ inventory_hostname }}"
|
||||||
|
body: |
|
||||||
|
Self-Healing konnte einen oder mehrere kritische Dienste nicht erfolgreich neu starten!
|
||||||
|
Siehe Log: {{ log_dir }}/self_healing_{{ inventory_hostname }}.log
|
||||||
|
Zeit: {{ ansible_date_time.iso8601 }}
|
||||||
|
when: >-
|
||||||
|
restart_result is defined and (
|
||||||
|
(restart_result.results is defined and (restart_result.results | selectattr('state', 'ne', 'running') | list | length > 0))
|
||||||
|
or
|
||||||
|
(restart_result.state is defined and restart_result.state != 'running')
|
||||||
|
)
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Self-Healing: Bereinige /tmp, /var/tmp, /var/log/alt bei wenig Speicherplatz
|
||||||
|
shell: rm -rf /tmp/* /var/tmp/* /var/log/alt/*
|
||||||
|
when: ansible_mounts[0].size_available < 10737418240 # <10GB frei
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Self-Healing: Netzwerkdienst neu starten bei Netzwerkproblemen
|
||||||
|
service:
|
||||||
|
name: network
|
||||||
|
state: restarted
|
||||||
|
when: ansible_default_ipv4 is not defined or ansible_default_ipv4['address'] is not defined
|
||||||
|
ignore_errors: true
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
- name: Erstelle/aktualisiere Change in ServiceNow (vor Patch)
|
||||||
|
community.general.snow_record:
|
||||||
|
instance: "{{ servicenow_instance }}"
|
||||||
|
username: "{{ servicenow_user }}"
|
||||||
|
password: "{{ servicenow_pass }}"
|
||||||
|
state: present
|
||||||
|
table: change_request
|
||||||
|
data:
|
||||||
|
short_description: "OS Patch/Upgrade {{ inventory_hostname }}"
|
||||||
|
description: "Automatisiertes Upgrade via Ansible"
|
||||||
|
category: "Software"
|
||||||
|
risk: "2"
|
||||||
|
impact: "2"
|
||||||
|
priority: "3"
|
||||||
|
work_start: "{{ ansible_date_time.iso8601 }}"
|
||||||
|
requested_by: "{{ servicenow_requested_by | default('ansible_automation') }}"
|
||||||
|
register: snow_change
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Dokumentiere Change-Nummer
|
||||||
|
debug:
|
||||||
|
msg: "ServiceNow Change: {{ snow_change.record.number | default('N/A') }}"
|
||||||
|
|
||||||
|
- name: Erstelle Incident bei Fehlern (optional)
|
||||||
|
community.general.snow_record:
|
||||||
|
instance: "{{ servicenow_instance }}"
|
||||||
|
username: "{{ servicenow_user }}"
|
||||||
|
password: "{{ servicenow_pass }}"
|
||||||
|
state: present
|
||||||
|
table: incident
|
||||||
|
data:
|
||||||
|
short_description: "Patch/Upgrade FAILED auf {{ inventory_hostname }}"
|
||||||
|
description: "Siehe Logs unter {{ log_dir }}. Zeitpunkt: {{ ansible_date_time.iso8601 }}"
|
||||||
|
severity: "2"
|
||||||
|
urgency: "2"
|
||||||
|
impact: "2"
|
||||||
|
when: ansible_failed_result is defined and ansible_failed_result is not none
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Aktualisiere Change (Abschluss)
|
||||||
|
community.general.snow_record:
|
||||||
|
instance: "{{ servicenow_instance }}"
|
||||||
|
username: "{{ servicenow_user }}"
|
||||||
|
password: "{{ servicenow_pass }}"
|
||||||
|
state: present
|
||||||
|
table: change_request
|
||||||
|
number: "{{ snow_change.record.number | default(omit) }}"
|
||||||
|
data:
|
||||||
|
work_end: "{{ ansible_date_time.iso8601 }}"
|
||||||
|
close_notes: "Upgrade abgeschlossen auf {{ inventory_hostname }}"
|
||||||
|
state: "3"
|
||||||
|
when: snow_change is defined and snow_change.record is defined
|
||||||
|
ignore_errors: true
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
- name: Pre-Upgrade-Check (zypper)
|
||||||
|
shell: zypper list-updates || true
|
||||||
|
register: sles_check
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Kernel-Version vor Upgrade sichern
|
||||||
|
shell: uname -r
|
||||||
|
register: kernel_before
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Upgrade durchführen (zypper, full)
|
||||||
|
zypper:
|
||||||
|
name: '*'
|
||||||
|
state: latest
|
||||||
|
extra_args: '--non-interactive'
|
||||||
|
register: upgrade_result
|
||||||
|
when: not upgrade_dry_run and not upgrade_security_only
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Upgrade durchführen (zypper, nur Security)
|
||||||
|
command: zypper --non-interactive patch --category security
|
||||||
|
register: upgrade_result
|
||||||
|
when: not upgrade_dry_run and upgrade_security_only
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler beim Upgrade (SLES)
|
||||||
|
copy:
|
||||||
|
content: "Upgrade-Fehler: {{ upgrade_result.stderr | default(upgrade_result.msg | default('Unbekannter Fehler')) }}"
|
||||||
|
dest: "{{ log_dir }}/sles_upgrade_error_{{ inventory_hostname }}.log"
|
||||||
|
when: upgrade_result is failed
|
||||||
|
|
||||||
|
- name: Setze Rollback-Flag, falls Upgrade fehlschlägt
|
||||||
|
set_fact:
|
||||||
|
rollback: true
|
||||||
|
when: upgrade_result is failed
|
||||||
|
|
||||||
|
- name: Breche Playbook ab, wenn Upgrade fehlschlägt
|
||||||
|
fail:
|
||||||
|
msg: "Upgrade fehlgeschlagen, Rollback wird empfohlen! Siehe Log: {{ log_dir }}/sles_upgrade_error_{{ inventory_hostname }}.log"
|
||||||
|
when: upgrade_result is failed
|
||||||
|
|
||||||
|
- name: Logge Upgrade-Output (SLES)
|
||||||
|
copy:
|
||||||
|
content: "{{ sles_check.stdout }}"
|
||||||
|
dest: "{{ log_dir }}/sles_upgrade_check.log"
|
||||||
|
when: upgrade_result is not failed
|
||||||
|
|
||||||
|
- name: Kernel-Version nach Upgrade sichern
|
||||||
|
shell: uname -r
|
||||||
|
register: kernel_after
|
||||||
|
changed_when: false
|
||||||
|
when: upgrade_result is not failed
|
||||||
|
|
||||||
|
- name: Prüfe, ob Kernel-Upgrade erfolgt ist und setze Reboot nötig
|
||||||
|
set_fact:
|
||||||
|
reboot_after_upgrade: true
|
||||||
|
when: upgrade_result is not failed and (kernel_before.stdout != kernel_after.stdout)
|
||||||
|
|
||||||
|
- name: Hinweis auf SLE-Upgrade-Tool
|
||||||
|
debug:
|
||||||
|
msg: "Für Major Upgrades (z.B. SLES 12->15) empfiehlt SUSE das Tool 'SUSEConnect' und 'zypper migration'. Siehe https://documentation.suse.com/sles/15-SP4/html/SLES-all/cha-upgrade.html"
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
- name: Prüfe, ob HTTP-Service installiert ist (Apache/Nginx)
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /usr/sbin/httpd
|
||||||
|
register: apache_check
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Prüfe, ob Nginx installiert ist
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /usr/sbin/nginx
|
||||||
|
register: nginx_check
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: "Smoke-Test: Prüfe HTTP-Endpoint (nur wenn Web-Server installiert)"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ smoke_test_url | default('http://localhost') }}"
|
||||||
|
status_code: 200
|
||||||
|
return_content: false
|
||||||
|
register: http_result
|
||||||
|
ignore_errors: true
|
||||||
|
when: apache_check.stat.exists or nginx_check.stat.exists
|
||||||
|
|
||||||
|
- name: "Smoke-Test: Prüfe offenen Port (optional)"
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
port: "{{ smoke_test_port | default(80) }}"
|
||||||
|
host: "{{ smoke_test_host | default('localhost') }}"
|
||||||
|
timeout: 5
|
||||||
|
register: port_result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Prüfe, ob MySQL/MariaDB installiert ist
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /usr/bin/mysql
|
||||||
|
register: mysql_check
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: "Smoke-Test: Prüfe Datenbankverbindung (nur wenn MySQL installiert)"
|
||||||
|
ansible.builtin.shell: "echo 'select 1' | mysql -h {{ smoke_test_db_host | default('localhost') }} -u {{ smoke_test_db_user | default('root') }} --password={{ smoke_test_db_pass | default('') }} {{ smoke_test_db_name | default('') }}"
|
||||||
|
register: db_result
|
||||||
|
ignore_errors: true
|
||||||
|
when: mysql_check.stat.exists and smoke_test_db_host is defined
|
||||||
|
|
||||||
|
- name: Prüfe, ob Oracle installiert ist
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /u01/app/oracle/product
|
||||||
|
register: oracle_check
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: "Oracle DB: Finde alle Oracle SIDs (nur wenn Oracle installiert)"
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
ps -ef | grep -E "ora_pmon_[A-Z0-9_]+" | grep -v grep | awk '{print $NF}' | sed 's/ora_pmon_//'
|
||||||
|
register: oracle_sids
|
||||||
|
changed_when: false
|
||||||
|
ignore_errors: true
|
||||||
|
when: oracle_check.stat.exists
|
||||||
|
|
||||||
|
- name: "Oracle DB: Finde alle Oracle Listener (nur wenn Oracle installiert)"
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
ps -ef | grep -E "tnslsnr" | grep -v grep | awk '{print $NF}' | sed 's/tnslsnr//'
|
||||||
|
register: oracle_listeners
|
||||||
|
changed_when: false
|
||||||
|
ignore_errors: true
|
||||||
|
when: oracle_check.stat.exists
|
||||||
|
|
||||||
|
- name: "Oracle DB: Prüfe alle gefundenen SIDs (nur wenn Oracle installiert)"
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
export ORACLE_HOME={{ item.oracle_home | default('/u01/app/oracle/product/19.0.0/dbhome_1') }}
|
||||||
|
export PATH=$ORACLE_HOME/bin:$PATH
|
||||||
|
export ORACLE_SID={{ item.sid }}
|
||||||
|
sqlplus -S / as sysdba <<EOF
|
||||||
|
select 'OK' as status from dual;
|
||||||
|
exit;
|
||||||
|
EOF
|
||||||
|
register: oracle_sid_check
|
||||||
|
loop: "{{ oracle_sids.stdout_lines | map('regex_replace', '^(.+)$', '{\"sid\": \"\\1\", \"oracle_home\": \"/u01/app/oracle/product/19.0.0/dbhome_1\"}') | map('from_json') | list }}"
|
||||||
|
ignore_errors: true
|
||||||
|
when: oracle_check.stat.exists and oracle_sids.stdout_lines | length > 0
|
||||||
|
|
||||||
|
- name: "Oracle DB: Prüfe alle gefundenen Listener (nur wenn Oracle installiert)"
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
export ORACLE_HOME={{ item.oracle_home | default('/u01/app/oracle/product/19.0.0/dbhome_1') }}
|
||||||
|
export PATH=$ORACLE_HOME/bin:$PATH
|
||||||
|
lsnrctl status {{ item.listener }}
|
||||||
|
register: oracle_listener_check
|
||||||
|
loop: "{{ oracle_listeners.stdout_lines | map('regex_replace', '^(.+)$', '{\"listener\": \"\\1\", \"oracle_home\": \"/u01/app/oracle/product/19.0.0/dbhome_1\"}') | map('from_json') | list }}"
|
||||||
|
ignore_errors: true
|
||||||
|
when: oracle_check.stat.exists and oracle_listeners.stdout_lines | length > 0
|
||||||
|
|
||||||
|
- name: "Oracle DB: Logge Oracle-Check-Ergebnisse (nur wenn Oracle installiert)"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: |
|
||||||
|
Oracle DB Check für {{ inventory_hostname }}
|
||||||
|
Zeit: {{ ansible_date_time.iso8601 }}
|
||||||
|
Gefundene SIDs: {{ oracle_sids.stdout_lines | default([]) }}
|
||||||
|
Gefundene Listener: {{ oracle_listeners.stdout_lines | default([]) }}
|
||||||
|
SID-Check-Resultate: {{ oracle_sid_check.results | default([]) | to_nice_json }}
|
||||||
|
Listener-Check-Resultate: {{ oracle_listener_check.results | default([]) | to_nice_json }}
|
||||||
|
dest: "{{ log_dir }}/oracle_check_{{ inventory_hostname }}.log"
|
||||||
|
ignore_errors: true
|
||||||
|
when: oracle_check.stat.exists
|
||||||
|
|
||||||
|
- name: Smoke-Test Ergebnis zusammenfassen
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "HTTP-Test: {{ http_result.status | default('NOT INSTALLED') }}"
|
||||||
|
- "Port-Test: {{ port_result.state | default('FAILED') }}"
|
||||||
|
- "DB-Test: {{ db_result.rc | default('NOT INSTALLED') }}"
|
||||||
|
- "Oracle SIDs gefunden: {{ oracle_sids.stdout_lines | length | default(0) if oracle_check.stat.exists else 'NOT INSTALLED' }}"
|
||||||
|
- "Oracle Listener gefunden: {{ oracle_listeners.stdout_lines | length | default(0) if oracle_check.stat.exists else 'NOT INSTALLED' }}"
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
---
|
||||||
|
- name: Setze Variablen für SUSE Manager API
|
||||||
|
set_fact:
|
||||||
|
suma_api_url: "{{ suma_api_url }}"
|
||||||
|
suma_api_user: "{{ suma_api_user }}"
|
||||||
|
suma_api_pass: "{{ suma_api_pass }}"
|
||||||
|
|
||||||
|
- name: Hole System-ID aus SUSE Manager
|
||||||
|
uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"method": "auth.login",
|
||||||
|
"params": ["{{ suma_api_user }}", "{{ suma_api_pass }}"],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
validate_certs: no
|
||||||
|
register: suma_api_login
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler bei API-Login
|
||||||
|
copy:
|
||||||
|
content: "API-Login-Fehler: {{ suma_api_login.msg | default('Unbekannter Fehler') }}"
|
||||||
|
dest: "{{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_api_login.failed
|
||||||
|
|
||||||
|
- name: Breche Playbook ab, wenn API-Login fehlschlägt
|
||||||
|
fail:
|
||||||
|
msg: "SUSE Manager API-Login fehlgeschlagen! Siehe Log: {{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_api_login.failed
|
||||||
|
|
||||||
|
- name: Setze Session-ID
|
||||||
|
set_fact:
|
||||||
|
suma_session: "{{ suma_api_login.json.result }}"
|
||||||
|
|
||||||
|
- name: Suche System-ID anhand Hostname
|
||||||
|
uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"method": "system.getId",
|
||||||
|
"params": ["{{ suma_session }}", "{{ inventory_hostname }}"],
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
validate_certs: no
|
||||||
|
register: suma_system_id
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler bei System-ID-Suche
|
||||||
|
copy:
|
||||||
|
content: "System-ID-Fehler: {{ suma_system_id.msg | default('Unbekannter Fehler') }}"
|
||||||
|
dest: "{{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_system_id.failed
|
||||||
|
|
||||||
|
- name: Breche Playbook ab, wenn System-ID nicht gefunden
|
||||||
|
fail:
|
||||||
|
msg: "System-ID nicht gefunden! Siehe Log: {{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_system_id.failed
|
||||||
|
|
||||||
|
- name: Suche Channel-ID anhand Ziel-CLM-Version
|
||||||
|
uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"method": "channel.software.listAllChannels",
|
||||||
|
"params": ["{{ suma_session }}"],
|
||||||
|
"id": 3
|
||||||
|
}
|
||||||
|
validate_certs: no
|
||||||
|
register: suma_channels
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler bei Channel-Suche
|
||||||
|
copy:
|
||||||
|
content: "Channel-Such-Fehler: {{ suma_channels.msg | default('Unbekannter Fehler') }}"
|
||||||
|
dest: "{{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_channels.failed
|
||||||
|
|
||||||
|
- name: Breche Playbook ab, wenn Channel-Suche fehlschlägt
|
||||||
|
fail:
|
||||||
|
msg: "Channel-Suche fehlgeschlagen! Siehe Log: {{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_channels.failed
|
||||||
|
|
||||||
|
- name: Finde Channel-ID für Ziel-CLM-Version
|
||||||
|
set_fact:
|
||||||
|
target_channel_label: "{{ item.label }}"
|
||||||
|
loop: "{{ suma_channels.json.result }}"
|
||||||
|
when: item.name is search(target_clm_version)
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.label }}"
|
||||||
|
|
||||||
|
- name: Breche ab, wenn kein passender Channel gefunden wurde
|
||||||
|
fail:
|
||||||
|
msg: "Kein passender CLM-Channel für '{{ target_clm_version }}' gefunden!"
|
||||||
|
when: target_channel_label is not defined
|
||||||
|
|
||||||
|
- name: Weise System dem Channel zu
|
||||||
|
uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"method": "system.setBaseChannel",
|
||||||
|
"params": ["{{ suma_session }}", {{ suma_system_id.json.result[0].id }}, "{{ target_channel_label }}"],
|
||||||
|
"id": 4
|
||||||
|
}
|
||||||
|
validate_certs: no
|
||||||
|
register: suma_assign_result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Logge Fehler bei Channel-Zuweisung
|
||||||
|
copy:
|
||||||
|
content: "Channel-Zuweisungs-Fehler: {{ suma_assign_result.msg | default('Unbekannter Fehler') }}"
|
||||||
|
dest: "{{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_assign_result.failed
|
||||||
|
|
||||||
|
- name: Breche Playbook ab, wenn Channel-Zuweisung fehlschlägt
|
||||||
|
fail:
|
||||||
|
msg: "Channel-Zuweisung fehlgeschlagen! Siehe Log: {{ log_dir }}/suma_api_error_{{ inventory_hostname }}.log"
|
||||||
|
when: suma_assign_result.failed
|
||||||
|
|
||||||
|
- name: Logout von der SUSE Manager API
|
||||||
|
uri:
|
||||||
|
url: "{{ suma_api_url }}"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"method": "auth.logout",
|
||||||
|
"params": ["{{ suma_session }}"],
|
||||||
|
"id": 5
|
||||||
|
}
|
||||||
|
validate_certs: no
|
||||||
|
when: suma_session is defined
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
- name: Erstelle VMware Snapshot vor Upgrade
|
||||||
|
community.vmware.vmware_guest_snapshot:
|
||||||
|
hostname: "{{ vcenter_hostname }}"
|
||||||
|
username: "{{ vcenter_user }}"
|
||||||
|
password: "{{ vcenter_password }}"
|
||||||
|
validate_certs: no
|
||||||
|
datacenter: "{{ vcenter_datacenter }}"
|
||||||
|
folder: "{{ vcenter_folder | default('/') }}"
|
||||||
|
name: "{{ inventory_hostname }}"
|
||||||
|
state: present
|
||||||
|
snapshot_name: "pre-upgrade-{{ inventory_hostname }}-{{ ansible_date_time.iso8601_basic }}"
|
||||||
|
description: "Snapshot vor Auto-Upgrade"
|
||||||
|
memory: yes
|
||||||
|
quiesce: yes
|
||||||
|
delegate_to: localhost
|
||||||
|
register: snapshot_result
|
||||||
|
failed_when: snapshot_result.failed is defined and snapshot_result.failed
|
||||||
|
retries: 3
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Logge Fehler bei Snapshot-Erstellung
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "Snapshot-Fehler: {{ snapshot_result.msg | default('Unbekannter Fehler') }}"
|
||||||
|
dest: "{{ log_dir }}/snapshot_error_{{ inventory_hostname }}.log"
|
||||||
|
when: snapshot_result is failed
|
||||||
|
|
||||||
|
- name: Setze Rollback-Flag, falls Snapshot-Erstellung fehlschlägt
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
rollback: true
|
||||||
|
when: snapshot_result is failed
|
||||||
|
|
||||||
|
- name: Breche Playbook ab, wenn Snapshot-Erstellung fehlschlägt
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: "Snapshot-Erstellung fehlgeschlagen, Upgrade wird abgebrochen! Siehe Log: {{ log_dir }}/snapshot_error_{{ inventory_hostname }}.log"
|
||||||
|
when: snapshot_result is failed
|
||||||
|
|
||||||
|
- name: "Rollback: Setze VM auf Snapshot zurück (nur bei Fehler und wenn aktiviert)"
|
||||||
|
community.vmware.vmware_guest_snapshot:
|
||||||
|
hostname: "{{ vcenter_hostname }}"
|
||||||
|
username: "{{ vcenter_user }}"
|
||||||
|
password: "{{ vcenter_password }}"
|
||||||
|
validate_certs: no
|
||||||
|
datacenter: "{{ vcenter_datacenter }}"
|
||||||
|
folder: "{{ vcenter_folder | default('/') }}"
|
||||||
|
name: "{{ inventory_hostname }}"
|
||||||
|
state: revert
|
||||||
|
snapshot_name: "pre-upgrade-{{ inventory_hostname }}-{{ ansible_date_time.iso8601_basic }}"
|
||||||
|
when: rollback is defined and rollback
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Lösche VMware Snapshot nach erfolgreichem Patchlauf (optional)
|
||||||
|
community.vmware.vmware_guest_snapshot:
|
||||||
|
hostname: "{{ vcenter_hostname }}"
|
||||||
|
username: "{{ vcenter_user }}"
|
||||||
|
password: "{{ vcenter_password }}"
|
||||||
|
validate_certs: no
|
||||||
|
datacenter: "{{ vcenter_datacenter }}"
|
||||||
|
folder: "{{ vcenter_folder | default('/') }}"
|
||||||
|
name: "{{ inventory_hostname }}"
|
||||||
|
state: absent
|
||||||
|
snapshot_name: "pre-upgrade-{{ inventory_hostname }}-{{ ansible_date_time.iso8601_basic }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
when: (upgrade_result is defined and upgrade_result is not failed) and (snapshot_cleanup | default(true))
|
||||||
|
ignore_errors: true
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
plugin: servicenow.servicenow.now
|
||||||
|
instance: "{{ servicenow_instance }}"
|
||||||
|
username: "{{ servicenow_user }}"
|
||||||
|
password: "{{ servicenow_pass }}"
|
||||||
|
table: 'cmdb_ci_server'
|
||||||
|
fields:
|
||||||
|
- fqdn
|
||||||
|
- name
|
||||||
|
- u_app_group
|
||||||
|
- u_app_mail
|
||||||
|
- u_host_email
|
||||||
|
keyed_groups:
|
||||||
|
- key: u_app_group
|
||||||
|
prefix: ''
|
||||||
|
separator: ''
|
||||||
|
compose:
|
||||||
|
ansible_host: fqdn
|
||||||
|
app_mail: u_app_mail
|
||||||
|
host_email: u_host_email
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
ansible-galaxy collection install -r playbook/requirements.yml --force
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP_GROUP=${1:-}
|
||||||
|
TARGET_CLM=${2:-}
|
||||||
|
EXTRA_VARS=${3:-}
|
||||||
|
|
||||||
|
if [[ -z "$APP_GROUP" ]]; then
|
||||||
|
echo "Usage: $0 <app-group> [target_clm_version] [extra_vars]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CMD=(ansible-playbook playbook/playbook.yml -l "$APP_GROUP" --ask-vault-pass)
|
||||||
|
|
||||||
|
if [[ -n "$TARGET_CLM" ]]; then
|
||||||
|
CMD+=( -e "target_clm_version=$TARGET_CLM" )
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$EXTRA_VARS" ]]; then
|
||||||
|
CMD+=( -e "$EXTRA_VARS" )
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tags können bei Bedarf angepasst werden, z.B. nur preflight+upgrade
|
||||||
|
# CMD+=( --tags preflight,common,rhel,sles,post )
|
||||||
|
|
||||||
|
exec "${CMD[@]}"
|
||||||
Loading…
Reference in New Issue