first
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.csv
|
||||||
84
README.md
Normal file
84
README.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Anleitung: Automatisierte Erstellung mehrerer LXC-Container
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Dieses Projekt automatisiert die Erstellung und Konfiguration von LXC-Containern mit verschiedenen Linux-Distributionen.
|
||||||
|
|
||||||
|
Es besteht aus zwei Python-Skripten:
|
||||||
|
|
||||||
|
1. `setup_host.py`: Das **Haupt-Skript**, das Sie ausführen. Es orchestriert die Erstellung und Konfiguration von einem oder mehreren Containern.
|
||||||
|
2. `setup_container.py`: Ein **Hilfs-Skript**, das vom Haupt-Skript automatisch in jeden neuen Container kopiert und dort ausgeführt wird, um die interne Konfiguration (Benutzer, SSH, etc.) vorzunehmen.
|
||||||
|
|
||||||
|
Sie interagieren nur mit `setup_host.py`.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- **LXD installiert:** Sie müssen LXD auf Ihrem Host-System installiert und konfiguriert haben. Führen Sie bei Bedarf `lxd init` aus, um LXD zu initialisieren.
|
||||||
|
- **Benutzerrechte:** Der Benutzer, der das Skript ausführt, muss Mitglied der `lxd`-Gruppe sein, um Container erstellen und verwalten zu dürfen.
|
||||||
|
|
||||||
|
## Benutzung
|
||||||
|
|
||||||
|
### Schritt 1: Skripte ausführbar machen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x setup_host.py setup_container.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 2: Haupt-Skript ausführen
|
||||||
|
|
||||||
|
Führen Sie `setup_host.py` aus. Das Skript akzeptiert verschiedene Argumente, um den Erstellungsprozess anzupassen.
|
||||||
|
|
||||||
|
#### Wichtige Argumente
|
||||||
|
|
||||||
|
* `-c`, `--count`: Die Gesamtanzahl der Container. (Standard: `1`)
|
||||||
|
* `-p`, `--root-password`: **(Erforderlich)** Das Passwort für `root` und `jonnybravo`.
|
||||||
|
* `--image`: Das zu verwendende Image. (Standard: `images:archlinux`)
|
||||||
|
* `--hostname-template`: Vorlage für die Namen der Container. Unterstützt `{basename}` und `{i}`. **Wichtig:** Muss `{i}` enthalten, wenn `--count > 1`.
|
||||||
|
* `-b`, `--basename`: Der Basisname für die Container, falls die Vorlage `{basename}` enthält. (Standard: `vm`)
|
||||||
|
* `--no-port-forward`: Deaktiviert die SSH-Port-Weiterleitung.
|
||||||
|
* `--port-start`: Der Start-Port für die SSH-Weiterleitung, falls diese aktiv ist. (Standard: `2201`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Anwendungsbeispiele
|
||||||
|
|
||||||
|
#### Beispiel 1: Standard-Container
|
||||||
|
|
||||||
|
Erstellt einen einzelnen Arch-Linux-Container, der nur über den Host per Port-Weiterleitung erreichbar ist.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 setup_host.py -p "IhrSicheresPasswort"
|
||||||
|
```
|
||||||
|
*Ergebnis: Container ist über `ssh root@localhost -p 2201` erreichbar. *
|
||||||
|
|
||||||
|
#### Beispiel 2: Mehrere Container ohne Port-Weiterleitung
|
||||||
|
|
||||||
|
Erstellt zwei Ubuntu-Container. Der Zugriff erfolgt hier nur über `lxc exec` oder durch manuelle Konfiguration im Nachhinein, da keine Ports weitergeleitet werden.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 setup_host.py \
|
||||||
|
-c 2 \
|
||||||
|
-p "EinAnderesSicheresPasswort" \
|
||||||
|
--image ubuntu:22.04 \
|
||||||
|
--hostname-template "backend-{i}" \
|
||||||
|
--no-port-forward
|
||||||
|
```
|
||||||
|
*Ergebnis: Container sind nur über die vom Host zugewiesene interne IP oder `lxc exec` erreichbar.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Ausgabe & Verwaltung
|
||||||
|
|
||||||
|
- **CSV-Datei:** Alle erstellten Container werden mit Name und IP in `out/clients.csv` geloggt.
|
||||||
|
- **Container-Verwaltung:** Nutzen Sie Standard-Befehle wie `lxc list`, `lxc stop <name>`, `lxc delete <name>`.
|
||||||
|
- **Massen-Löschung:** `lxc list projekt-x --format csv -c n | xargs -I {} lxc delete {} --force` löscht alle Container, die mit `projekt-x` beginnen.
|
||||||
|
- **Image-Verwaltung:** `lxc image list` und `lxc image delete <alias>`.
|
||||||
|
|
||||||
|
### Port-Verwaltung (falls genutzt)
|
||||||
|
|
||||||
|
Das Skript richtet eine Port-Weiterleitung namens `ssh-proxy` ein.
|
||||||
|
|
||||||
|
* **Anzeigen:** `lxc config show <container-name>`
|
||||||
|
* **Entfernen:** `lxc config device remove <container-name> ssh-proxy`
|
||||||
|
|
||||||
|
```
|
||||||
145
setup_container.py
Executable file
145
setup_container.py
Executable file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# HINWEIS: Dieses Skript ist für die Ausführung INNERHALB des Containers gedacht.
|
||||||
|
|
||||||
|
def run_command(command_str, description, input_str=None):
|
||||||
|
"""Führt einen Shell-Befehl als String aus und prüft auf Fehler."""
|
||||||
|
print(f"\n--- {description} ---")
|
||||||
|
try:
|
||||||
|
process = subprocess.run(
|
||||||
|
command_str,
|
||||||
|
check=True,
|
||||||
|
shell=True,
|
||||||
|
text=True,
|
||||||
|
input=input_str,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
print(f"--- ERFOLG: {description} ---")
|
||||||
|
if process.stdout:
|
||||||
|
print(f"STDOUT:\n{process.stdout}")
|
||||||
|
if process.stderr:
|
||||||
|
print(f"STDERR:\n{process.stderr}", file=sys.stderr)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"FEHLER bei '{description}': Befehl '{command_str}' gab den Exit-Code {e.returncode} zurück", file=sys.stderr)
|
||||||
|
if e.stdout:
|
||||||
|
print(f"STDOUT:\n{e.stdout}", file=sys.stderr)
|
||||||
|
if e.stderr:
|
||||||
|
print(f"STDERR:\n{e.stderr}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Hauptfunktion des Skripts."""
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
print("FEHLER: Dieses Skript muss als root im Container ausgeführt werden.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Konfiguriert einen neuen Container.")
|
||||||
|
parser.add_argument("password", help="Das gewünschte root-Passwort.")
|
||||||
|
parser.add_argument("hostname", help="Der Hostname für diesen Container.")
|
||||||
|
parser.add_argument("distro", help="Die Distribution des Containers (z.B. 'arch', 'ubuntu').")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
root_password = args.password
|
||||||
|
hostname = args.hostname
|
||||||
|
distro = args.distro
|
||||||
|
password_input = f"{root_password}\n{root_password}\n"
|
||||||
|
|
||||||
|
# --- Distributionsspezifische Konfiguration ---
|
||||||
|
distro_configs = {
|
||||||
|
"arch": {
|
||||||
|
"update": "pacman -Syu --noconfirm",
|
||||||
|
"install": "pacman -S --noconfirm",
|
||||||
|
"ssh_package": "openssh",
|
||||||
|
"ssh_service": "sshd.service",
|
||||||
|
"pre_install_cmds": [
|
||||||
|
("Pacman-Schlüsselbund initialisieren", "pacman-key --init"),
|
||||||
|
("Pacman-Schlüsselbund füllen", "pacman-key --populate archlinux")
|
||||||
|
],
|
||||||
|
"user_groups": "users,wheel"
|
||||||
|
},
|
||||||
|
"ubuntu": {
|
||||||
|
"update": "apt-get update && apt-get upgrade -y",
|
||||||
|
"install": "apt-get install -y",
|
||||||
|
"ssh_package": "openssh-server",
|
||||||
|
"ssh_service": "ssh.service",
|
||||||
|
"pre_install_cmds": [
|
||||||
|
("Locales-Paket installieren", "apt-get install -y locales"),
|
||||||
|
("Locales generieren", "echo 'de_DE.UTF-8 UTF-8' > /etc/locale.gen && echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen")
|
||||||
|
],
|
||||||
|
"user_groups": "users,sudo"
|
||||||
|
},
|
||||||
|
"debian": {
|
||||||
|
"update": "apt-get update && apt-get upgrade -y",
|
||||||
|
"install": "apt-get install -y",
|
||||||
|
"ssh_package": "openssh-server",
|
||||||
|
"ssh_service": "ssh.service",
|
||||||
|
"pre_install_cmds": [
|
||||||
|
("Locales-Paket installieren", "apt-get install -y locales"),
|
||||||
|
("Locales generieren", "echo 'de_DE.UTF-8 UTF-8' > /etc/locale.gen && echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen")
|
||||||
|
],
|
||||||
|
"user_groups": "users,sudo"
|
||||||
|
},
|
||||||
|
"fedora": {
|
||||||
|
"update": "dnf upgrade -y",
|
||||||
|
"install": "dnf install -y",
|
||||||
|
"ssh_package": "openssh-server",
|
||||||
|
"ssh_service": "sshd.service",
|
||||||
|
"pre_install_cmds": [],
|
||||||
|
"user_groups": "users,wheel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if distro not in distro_configs:
|
||||||
|
print(f"FEHLER: Nicht unterstützte Distribution: {distro}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
config = distro_configs[distro]
|
||||||
|
print(f"Starte die Konfiguration für Container '{hostname}' mit Distribution '{distro}'...")
|
||||||
|
|
||||||
|
# --- Allgemeine Konfiguration ---
|
||||||
|
run_command("passwd", "Root-Passwort setzen", input_str=password_input)
|
||||||
|
run_command(f"echo '{hostname}' > /etc/hostname", "Hostname setzen")
|
||||||
|
run_command("ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime", "Zeitzone setzen")
|
||||||
|
run_command("echo 'LANG=de_DE.UTF-8' > /etc/locale.conf", "Standardsprache setzen")
|
||||||
|
if distro != "fedora": # Fedora hat andere Keyboard-Settings
|
||||||
|
run_command("echo 'KEYMAP=de-latin1' > /etc/vconsole.conf", "Tastaturlayout setzen")
|
||||||
|
|
||||||
|
# --- Distributionsspezifische Befehle ausführen ---
|
||||||
|
for description, command in config["pre_install_cmds"]:
|
||||||
|
run_command(command, description)
|
||||||
|
|
||||||
|
run_command(config["update"], "System aktualisieren")
|
||||||
|
run_command(f"{config['install']} {config['ssh_package']}", "OpenSSH installieren")
|
||||||
|
|
||||||
|
# Störende Cloud-Init-Konfiguration entfernen, die Passwort-Auth blockiert
|
||||||
|
run_command("rm -f /etc/ssh/sshd_config.d/60-cloudimg-settings.conf", "Entferne Cloud-Image-SSH-Einstellungen", input_str=None)
|
||||||
|
|
||||||
|
# --- SSH und Benutzerkonfiguration ---
|
||||||
|
run_command(f"systemctl enable {config['ssh_service']}", "SSH-Dienst aktivieren")
|
||||||
|
run_command("sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config", "Root-Login in sshd_config erlauben")
|
||||||
|
run_command("sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config", "Passwort-Authentifizierung in sshd_config erlauben")
|
||||||
|
run_command("sed -i 's/^#*LogLevel.*/LogLevel DEBUG3/' /etc/ssh/sshd_config", "SSH LogLevel DEBUG3 setzen")
|
||||||
|
run_command(f"systemctl restart {config['ssh_service']}", "SSH-Dienst neustarten")
|
||||||
|
|
||||||
|
# Benutzer 'jonnybravo' erstellen und sudo-Rechte geben
|
||||||
|
wheel_group_equivalent = "wheel" if distro in ["arch", "fedora"] else "sudo"
|
||||||
|
run_command(f"useradd -m -G {config['user_groups']} -s /bin/bash jonnybravo", "Benutzer 'jonnybravo' hinzufügen")
|
||||||
|
run_command("passwd jonnybravo", "Passwort für jonnybravo setzen", input_str=password_input)
|
||||||
|
|
||||||
|
if wheel_group_equivalent == "wheel":
|
||||||
|
run_command("echo '%wheel ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/wheel_nopasswd", "Passwortloses sudo für Gruppe 'wheel' aktivieren")
|
||||||
|
else: # sudo group
|
||||||
|
run_command("echo '%sudo ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/sudo_nopasswd", "Passwortloses sudo für Gruppe 'sudo' aktivieren")
|
||||||
|
|
||||||
|
|
||||||
|
print(f"\n=== Container '{hostname}' erfolgreich konfiguriert! ===")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
153
setup_host.py
Executable file
153
setup_host.py
Executable file
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
def run_command(command_str, description, ignore_errors=False):
|
||||||
|
print(f"\n--- {description} ---")
|
||||||
|
try:
|
||||||
|
process = subprocess.run(
|
||||||
|
command_str, shell=True, text=True, check=not ignore_errors, capture_output=True)
|
||||||
|
if process.returncode == 0:
|
||||||
|
print(f"--- ERFOLG: {description} ---")
|
||||||
|
return process
|
||||||
|
else:
|
||||||
|
raise subprocess.CalledProcessError(
|
||||||
|
process.returncode, command_str, output=process.stdout, stderr=process.stderr)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"FEHLER bei '{description}': Befehl '{
|
||||||
|
e.cmd}' gab den Exit-Code {e.returncode} zurück", file=sys.stderr)
|
||||||
|
if e.stdout:
|
||||||
|
print(f"STDOUT:\n{e.stdout}", file=sys.stderr)
|
||||||
|
if e.stderr:
|
||||||
|
print(f"STDERR:\n{e.stderr}", file=sys.stderr)
|
||||||
|
if not ignore_errors:
|
||||||
|
sys.exit(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def wait_for_network(container_name):
|
||||||
|
print(f"\n--- Warte auf Netzwerk für Container '{container_name}' ---")
|
||||||
|
for i in range(30):
|
||||||
|
print(f"Versuch {i+1}/30...")
|
||||||
|
try:
|
||||||
|
result = run_command(f"lxc list {container_name} --format json", "Frage Container-Status ab", ignore_errors=True)
|
||||||
|
if result and result.returncode == 0:
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
if data and data[0]["state"] and data[0]["state"]["network"] and "eth0" in data[0]["state"]["network"]:
|
||||||
|
addresses = data[0]["state"]["network"]["eth0"]["addresses"]
|
||||||
|
for addr in addresses:
|
||||||
|
if addr["family"] == "inet":
|
||||||
|
print(f"Container '{container_name}' hat IP-Adresse {addr['address']} erhalten.")
|
||||||
|
return
|
||||||
|
except (json.JSONDecodeError, IndexError, KeyError):
|
||||||
|
pass
|
||||||
|
time.sleep(2)
|
||||||
|
print(f"FEHLER: Container '{container_name}' hat nach 60 Sekunden keine IP-Adresse erhalten.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def get_container_ip(container_name):
|
||||||
|
for _ in range(15):
|
||||||
|
try:
|
||||||
|
result = run_command(f"lxc list {container_name} --format json", "Frage Container-IP ab", ignore_errors=True)
|
||||||
|
if result and result.returncode == 0:
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
if data and data[0]["state"] and data[0]["state"]["network"] and "eth0" in data[0]["state"]["network"]:
|
||||||
|
addresses = data[0]["state"]["network"]["eth0"]["addresses"]
|
||||||
|
for addr in addresses:
|
||||||
|
if addr["family"] == "inet":
|
||||||
|
return addr['address']
|
||||||
|
except (json.JSONDecodeError, IndexError, KeyError):
|
||||||
|
pass
|
||||||
|
time.sleep(2)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Erstellt und konfiguriert mehrere LXD-Container.")
|
||||||
|
parser.add_argument("-c", "--count", type=int, default=1, help="Anzahl der zu erstellenden Container.")
|
||||||
|
parser.add_argument("-b", "--basename", default="vm", help="Basisname für die Container.")
|
||||||
|
parser.add_argument("--hostname-template", default="{basename}-{i:02d}", help="Vorlage für den Hostnamen, z.B. 'webserver-{i}'. Unterstützt {basename} und {i}.")
|
||||||
|
parser.add_argument("-p", "--root-password", required=True, help="Root-Passwort für alle Container.")
|
||||||
|
parser.add_argument("--port-start", type=int, default=2201, help="Start-Port für die SSH-Weiterleitung.")
|
||||||
|
parser.add_argument("--image", default="images:archlinux", help="Zu verwendendes Image (z.B. images:archlinux, ubuntu:22.04).")
|
||||||
|
parser.add_argument("--no-port-forward", action="store_true", help="Überspringt die Einrichtung der SSH-Port-Weiterleitung.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.count > 1 and '{i}' not in args.hostname_template:
|
||||||
|
print("FEHLER: Bei --count > 1 muss die --hostname-template den Platzhalter '{i}' enthalten, um eindeutige Namen zu gewährleisten.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
run_command("command -v lxc >/dev/null", "Prüfe, ob LXD installiert ist")
|
||||||
|
print("LXD ist verfügbar. WICHTIG: Es wird davon ausgegangen, dass 'lxd init' bereits ausgeführt wurde.")
|
||||||
|
|
||||||
|
if not os.path.exists("out"):
|
||||||
|
os.makedirs("out")
|
||||||
|
elif not os.path.isdir("out"):
|
||||||
|
os.remove("out")
|
||||||
|
os.makedirs("out")
|
||||||
|
|
||||||
|
clients = []
|
||||||
|
|
||||||
|
for i in range(1, args.count + 1):
|
||||||
|
vm_name = args.hostname_template.format(basename=args.basename, i=i)
|
||||||
|
ssh_port = args.port_start + i - 1
|
||||||
|
|
||||||
|
print(f"{ '=' * 20} ERSTELLE CONTAINER: {vm_name} {'=' * 20}")
|
||||||
|
|
||||||
|
run_command(f"lxc delete {vm_name} --force", f"{vm_name} vorsorglich löschen", ignore_errors=True)
|
||||||
|
run_command(f"lxc launch {args.image} {vm_name}", f"Starte {vm_name} von Image")
|
||||||
|
wait_for_network(vm_name)
|
||||||
|
|
||||||
|
distro_id_command = "lxc exec {} -- cat /etc/os-release | grep '^ID=' | cut -d'=' -f2"
|
||||||
|
distro_id_result = run_command(distro_id_command.format(vm_name), f"Ermittle Distributions-ID für {vm_name}")
|
||||||
|
distro_id = distro_id_result.stdout.strip().replace('"', '')
|
||||||
|
|
||||||
|
install_cmd = ""
|
||||||
|
if distro_id == "arch":
|
||||||
|
install_cmd = "pacman -Sy --noconfirm python"
|
||||||
|
elif distro_id in ["ubuntu", "debian"]:
|
||||||
|
install_cmd = "sh -c 'export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y python3'"
|
||||||
|
elif distro_id == "fedora":
|
||||||
|
install_cmd = "dnf install -y python3"
|
||||||
|
else:
|
||||||
|
print(f"FEHLER: Nicht unterstützte Distribution: {distro_id}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
run_command(f"lxc exec {vm_name} -- {install_cmd}", f"Installiere Python in {vm_name}")
|
||||||
|
|
||||||
|
run_command(f"lxc file push setup_container.py {vm_name}/root/", f"Kopiere Setup-Skript nach {vm_name}")
|
||||||
|
run_command(f"lxc exec {vm_name} -- python3 /root/setup_container.py '{args.root_password}' '{vm_name}' '{distro_id}'", f"Führe Konfiguration in {vm_name} aus")
|
||||||
|
|
||||||
|
ssh_info = ""
|
||||||
|
if not args.no_port_forward:
|
||||||
|
run_command(f"lxc config device add {vm_name} ssh-proxy proxy listen=tcp:0.0.0.0:{ssh_port} connect=tcp:127.0.0.1:22", f"Richte Port-Weiterleitung für {vm_name} ein")
|
||||||
|
ssh_info = f"SSH-Zugang: ssh root@localhost -p {ssh_port}"
|
||||||
|
else:
|
||||||
|
print("\n--- Überspringe Port-Weiterleitung ---")
|
||||||
|
|
||||||
|
ip_address = get_container_ip(vm_name)
|
||||||
|
if ip_address:
|
||||||
|
clients.append({"name": vm_name, "ip": ip_address})
|
||||||
|
print(f"\n>>> Container {vm_name} erfolgreich erstellt und konfiguriert! <<<")
|
||||||
|
if ssh_info:
|
||||||
|
print(ssh_info)
|
||||||
|
print(f"IP-Adresse: {ip_address}")
|
||||||
|
else:
|
||||||
|
print(f"\nFEHLER: IP-Adresse für Container {vm_name} konnte nicht abgerufen werden.", file=sys.stderr)
|
||||||
|
|
||||||
|
if clients:
|
||||||
|
csv_path = "out/clients.csv"
|
||||||
|
with open(csv_path, "w") as f:
|
||||||
|
f.write("name,ip\n")
|
||||||
|
for client in clients:
|
||||||
|
f.write(f"{client['name']},{client['ip']}\n")
|
||||||
|
print(f"\nAlle Container wurden erstellt und in {csv_path} gespeichert.")
|
||||||
|
else:
|
||||||
|
print("\nKeine Container wurden erstellt.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user