implemented most creations steps and global improvements
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Mathieu Broillet 2023-06-12 15:15:51 +02:00
parent e4188e8432
commit fb4154c7e5
No known key found for this signature in database
GPG Key ID: 7D4F25BC50A0AA32
8 changed files with 240 additions and 105 deletions

View File

@ -167,28 +167,50 @@ The script step will execute a script.
```
### File
The file step will copy a file to the VM/LXC.
The file step will create / copy a file to the VM/LXC.
```json
"steps": [
{
"type": "file",
"path": "traefik.toml", // local path (here: resources/lxc/<id>/traefik.toml)
"destination": "/var/data/traefikv2/traefik.toml" // lxc/vm path
"type": "file_create",
"path": "/var/data/traefikv2/traefik.toml", // lxc/vm path
"permissions": "644" // (optional) permissions of the file
}
]
```
```json
"steps": [
{
"type": "file_copy",
"path": "traefik.toml", // local path (here: resources/lxc/<id>/traefik.toml)
"destination": "/var/data/traefikv2/traefik.toml", // lxc/vm path
"permissions": "644" // (optional) permissions of the file
}
]
```
*Note: `file_copy` creates the folder if it doesn't exist*
### Folder
The folder step will copy a folder to the VM/LXC.
```json
"steps": [
{
"type": "folder",
"path": "/data/", // local path (here: resources/lxc/<id>/data/)
"destination": "/var/data/traefikv2" // lxc/vm path
"type": "folder_create",
"path": "/var/data/traefikv2", // lxc/vm path
"permissions": "755" // (optional) permissions of the folder
}
]
```
```json
"steps": [
{
"type": "folder_copy",
"path": "/data/", // local path (here: resources/lxc/<id>/data/)
"destination": "/var/data/traefikv2", // lxc/vm path
"permissions": "755" // (optional) permissions of the folder
}
]
```
*Note: `folder_copy` creates the folder if it doesn't exist*
### Command
The command step will execute a command on the VM/LXC.
@ -197,19 +219,19 @@ The command step will execute a command on the VM/LXC.
{
"type": "command",
"command": "whoami", // command to execute
"working_directory": "/var/data/config/traefikv2" // lxc/vm path
"working_directory": "/var/data/config/traefikv2" // (optional) lxc/vm path
}
]
```
### Docker
The docker step will execute a docker command on the VM/LXC.
The docker step will execute a command inside a docker container running on the VM/LXC.
```json
"steps": [
{
"type": "docker",
"command": "run -d --name=traefikv2 --restart=always -p 80:80 -p 443:443 -p 8080:8080 -v /var/data/traefikv2:/etc/traefik -v /var/data/config/traefikv2:/config -v /var/run/docker.sock:/var/run/docker.sock traefik:latest", // docker command to execute
"working_directory": "/var/data/config/traefikv2" // lxc/vm path
"container": "<container-name>", // docker container name
"command": "<command-to-run-inside>" // docker command to execute
}
]
```

View File

@ -4,5 +4,8 @@
"user": "root",
"port": 22,
"local": false
},
"settings": {
"setAuthorizedHostsSSH": true
}
}

View File

@ -25,7 +25,6 @@
"start_on_boot": "false",
"startup_order": 2,
"password": "qwertz1234",
"ssh": false,
"tags": "2-proxy+auth"
},
"creation": {

View File

@ -1,7 +1,8 @@
import logging
import os
from pathlib import Path
from src.utils import lxc_commands_utils
def are_all_conditions_met(lxc):
"""
@ -10,7 +11,7 @@ def are_all_conditions_met(lxc):
If all conditions are met, the deploy/updates steps are run
Otherwise, we run the creation steps
:param lxc_id: lxc id
:param lxc:
:return: are conditions met
"""
@ -34,6 +35,13 @@ def are_all_conditions_met(lxc):
return all(result)
def optional(var, placeholder):
if var is None:
return placeholder
else:
return var
def run_steps(lxc):
"""
Run creation steps for an LXC
@ -49,48 +57,23 @@ def run_steps(lxc):
# Support for scripts
case "script":
run_script(step, lxc)
def run_script(step, lxc):
# Run local script
if "path" in step:
path = step["path"]
with open(path, "r") as file:
script = file.read()
lxc.run_command("'bash -s' <<'ENDSSH'\n" + script)
# Run remote script
elif "url" in step:
url = step["url"]
lxc.has
pass
# Run script in LXC
elif "lxc_path" in step:
lxc_path = step["lxc_path"]
pass
path = step["path"]
if path.startswith("/global/"):
# Use global folder
full_path = "resources/scripts" + path[len("/global/"):]
else:
# Use VM/LXC folder
lxc_id = lxc.get_id()
full_path = f"resources/lxc/{lxc_id}/{path}"
if os.path.isfile(full_path):
lxc.run_command(f"bash {full_path}")
def has_program(lxc, program):
"""
Check if program is installed in LXC
:param lxc: lxc
:param program: program
:return: is program installed
"""
return lxc.run_command("which " + program, only_code=True) == 0
lxc_commands_utils.run_script(step, lxc)
case "file_create":
lxc_commands_utils.create_file_in_lxc(lxc, step["path"], optional(step["permission"], 644))
case "file_copy":
lxc_commands_utils.copy_local_file_to_lxc(lxc, step["path"], step["destination"])
case "folder":
lxc_commands_utils.create_folder_in_lxc(lxc, step["path"], optional(step["permission"], 755))
case "folder_copy":
lxc_commands_utils.copy_local_folder_to_lxc(lxc, step["path"], step["destination"])
case "command":
lxc.run_command(step["command"], optional(step["working_directory"], None))
case "docker":
lxc_commands_utils.run_docker_command(lxc, step["container"], step["command"])
case "docker_compose":
lxc_commands_utils.run_docker_compose_command(lxc, step["command"],
optional(step["working_directory"], None))
case "git":
lxc.run_command(f"git clone {step['url']} {step['destination']}")
case "download":
lxc_commands_utils.download_file(lxc, step["url"], step["destination"])

View File

@ -0,0 +1,76 @@
from src.utils import proxmox_utils
from src.utils.resources_utils import get_path
def run_script(lxc, step):
# Run local script
if "path" in step:
_run_local_script_on_lxc(lxc, step["path"])
# Run remote script
elif "url" in step:
_run_remote_script_on_lxc(lxc, step["url"])
# Run script in LXC
elif "lxc_path" in step:
_run_script_on_lxc(lxc, step["lxc_path"])
def _run_script_on_lxc(lxc, path):
lxc.run_command(f"bash {path}")
def _run_local_script_on_lxc(lxc, path):
path = get_path(lxc, path)
with open(path, "r") as file:
script = file.read()
lxc.run_command("'bash -s' <<'ENDSSH'\n" + script)
def _run_remote_script_on_lxc(lxc, url):
if lxc.has_program("curl"):
lxc.run_command(f"curl -sSL {url} | bash")
def create_folder_in_lxc(lxc, path, permission=755):
lxc.run_command(f"mkdir -p {path}")
if permission != 755:
lxc.run_command(f"chmod -R {permission} {path}")
def create_file_in_lxc(lxc, path, permission=644):
lxc.run_command(f"touch {path}")
if permission != 644:
lxc.run_command(f"chmod {permission} {path}")
def copy_local_file_to_lxc(lxc, path, destination):
proxmox_utils.run_command_locally(f"scp {path} {lxc.get_ssh_string()}:{destination}")
def copy_local_folder_to_lxc(lxc, path, destination):
proxmox_utils.run_command_locally(f"scp -r {path} {lxc.get_ssh_string()}:{destination}")
def run_docker_command(lxc, container, command):
lxc.run_command(f"docker exec -it {container} {command}")
def run_docker_compose_command(lxc, command, working_directory=None):
docker_compose_exec = "docker-compose"
if not lxc.has_program(docker_compose_exec):
docker_compose_exec = "docker compose"
if working_directory is not None:
lxc.run_command(f"cd {working_directory} && {docker_compose_exec} {command}")
else:
lxc.run_command(f"{docker_compose_exec} {command}")
def download_file(lxc, url, destination):
if type(url) is list:
for u in url:
lxc.run_command(f"wget {u} -O {destination}")
else:
lxc.run_command(f"wget {url} -O {destination}")

View File

@ -1,7 +1,7 @@
import json
import logging
from src.utils import proxmox_utils, creation_utils
from src.utils import proxmox_utils, creation_utils, lxc_commands_utils
lxcs = []
@ -86,7 +86,6 @@ class LXC:
self.privileged = options["privileged"]
self.start_on_boot = options["start_on_boot"]
self.password = options["password"]
self.ssh = options["ssh"]
self.creation = creation
self.deploy = deploy
@ -283,12 +282,9 @@ class LXC:
"""
return self.password
def is_ssh_enabled(self):
"""
Is SSH enabled
:return: ssh
"""
return self.ssh
def get_ssh_string(self):
# TODO: implement hostname and maybe ipv6?
return "root@" + self.get_ipv4()
def get_deploy(self):
"""
@ -311,6 +307,13 @@ class LXC:
"""
return proxmox_utils.run_command_on_pve(f"pct list | awk '/running/ && /{self.lxc_id}/'") != ""
def does_exist(self):
"""
Does exist
:return: does lxc exist? (boolean)
"""
return proxmox_utils.does_lxc_exist(self.lxc_id)
def start(self):
"""
Start LXC
@ -326,13 +329,17 @@ class LXC:
"""
Create LXC
"""
if proxmox_utils.does_lxc_exist(self.lxc_id):
if self.does_exist():
logging.info(f"LXC {self.lxc_id} already exists, skipping creation")
proxmox_utils.run_command_on_pve(self.get_pct_command(create=False), True)
else:
logging.info(f"Creating LXC {self.lxc_id}")
proxmox_utils.run_command_on_pve(self.get_pct_command(create=True), True)
if proxmox_utils.get_config()['settings']['setAuthorizedHostsSSH']:
pve_public_key = proxmox_utils.get_ssh_public_key()
self.run_command(f"echo '{pve_public_key}' >> /root/.ssh/authorized_keys")
def creation(self):
"""
Run the creations checks and steps
@ -343,21 +350,37 @@ class LXC:
self.start()
# Check if all creation conditions are met
if not creation_utils.creation_conditions_met(self):
if not creation_utils.are_all_conditions_met(self):
logging.info(f"Not all creation conditions met for LXC {self.lxc_id}, running creation steps...")
# Run creation steps
creation_utils.run_creation_steps(self)
creation_utils.run_steps(self)
def deploy(self):
pass
def has_program(self, program):
return creation_utils.has_program(self, program)
"""
Check if program is installed on LXC
:param program: program executable name
def run_command(self, command, warn_exit_status=False, only_code=False):
:return: boolean
"""
if type(program) == str:
return self.run_command("which " + program, only_code=True) == 0
elif type(program) == list:
return all((self.run_command("which " + p, only_code=True) == 0) for p in program)
def run_script(self, script_path):
"""
Run script on LXC filesystem using bash
:param script_path:
:return:
"""
return lxc_commands_utils.run_script(self, {"lxc_path": script_path})
def run_command(self, command, warn_exit_status=False, only_code=False, working_directory=None):
"""
Run command on LXC
:param command: command to run
@ -365,6 +388,10 @@ class LXC:
"""
logging.debug(f"Running command {command} on LXC {self.lxc_id}")
if working_directory:
command = f"cd {working_directory} && {command}"
if only_code:
return proxmox_utils.run_command_on_pve(f"pct exec {self.lxc_id} -- {command}", warn_exit_status, only_code)

View File

@ -58,23 +58,28 @@ def execute_tteck_script(script_url, env_variables):
run_command_on_pve(f"{env_variables} && bash -c \"$(wget -qLO - {script_url}\"", True)
def run_command_on_pve(command, warn_exit_status=False, only_code=False):
def get_config():
config_file = os.path.join(project_path / "resources" / "config.json")
with open(config_file, "r") as file:
return json.loads(file.read())
def run_command_on_pve(command, warn_exit_status=False, only_code=False, local=False):
"""
Run command on PVE
:param local: should be run locally not via ssh even with local mode off
:param only_code: should we only return the exit code and not the stdout
:param warn_exit_status: should an exception be thrown if the exit status is not 0
:param command: command
:return: command
"""
config_file = os.path.join(project_path / "resources" / "config.json")
with open(config_file, "r") as file:
data = json.loads(file.read())
# Get config
config = get_config()
# Check if PVE is local or remote
if data['pve']['local']:
if config['pve']['local'] or local:
# Run command and return output (not as bytes)
logging.debug(f"Running command on PVE (locally): \n{command}")
@ -91,9 +96,9 @@ def run_command_on_pve(command, warn_exit_status=False, only_code=False):
return command.stdout.decode().rstrip()
else:
host = data['pve']['host']
username = data['pve']['user']
port = data['pve']['port']
host = config['pve']['host']
username = config['pve']['user']
port = config['pve']['port']
# Run command on PVE via SSH and return output
logging.debug(f"Running command on PVE (ssh): \n{command}")
@ -110,3 +115,15 @@ def run_command_on_pve(command, warn_exit_status=False, only_code=False):
return command.returncode
return command.stdout.rstrip()
def run_command_locally(command, warn_exit_status=False, only_code=False):
run_command_on_pve(command, warn_exit_status, only_code, local=True)
def get_ssh_public_key():
"""
Get SSH public key
:return: ssh public key
"""
return run_command_on_pve("cat ~/.ssh/id_rsa.pub", True)

View File

@ -0,0 +1,8 @@
def get_path(lxc, path):
if path.startswith("/global/"):
# Use global folder
return "resources/scripts" + path[len("/global/"):]
else:
# Use VM/LXC folder
lxc_id = lxc.get_id()
return f"resources/lxc/{lxc_id}/{path}"