implemented most creations steps and global improvements
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
e4188e8432
commit
fb4154c7e5
44
README.md
44
README.md
@ -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
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -4,5 +4,8 @@
|
||||
"user": "root",
|
||||
"port": 22,
|
||||
"local": false
|
||||
},
|
||||
"settings": {
|
||||
"setAuthorizedHostsSSH": true
|
||||
}
|
||||
}
|
@ -25,7 +25,6 @@
|
||||
"start_on_boot": "false",
|
||||
"startup_order": 2,
|
||||
"password": "qwertz1234",
|
||||
"ssh": false,
|
||||
"tags": "2-proxy+auth"
|
||||
},
|
||||
"creation": {
|
||||
|
@ -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"])
|
||||
|
76
src/utils/lxc_commands_utils.py
Normal file
76
src/utils/lxc_commands_utils.py
Normal 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}")
|
@ -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)
|
||||
|
||||
|
@ -58,55 +58,72 @@ 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:
|
||||
# Get config
|
||||
config = get_config()
|
||||
|
||||
data = json.loads(file.read())
|
||||
# Check if PVE is local or remote
|
||||
if config['pve']['local'] or local:
|
||||
# Run command and return output (not as bytes)
|
||||
logging.debug(f"Running command on PVE (locally): \n{command}")
|
||||
|
||||
# Check if PVE is local or remote
|
||||
if data['pve']['local']:
|
||||
# Run command and return output (not as bytes)
|
||||
logging.debug(f"Running command on PVE (locally): \n{command}")
|
||||
command = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
encoding="utf-8")
|
||||
|
||||
command = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
encoding="utf-8")
|
||||
if command.returncode != 0 and warn_exit_status:
|
||||
logging.error(f"Error while running command on PVE: \n{command.stderr}")
|
||||
raise Exception(f"Error while running command on PVE: \n{command.stderr}")
|
||||
|
||||
if command.returncode != 0 and warn_exit_status:
|
||||
logging.error(f"Error while running command on PVE: \n{command.stderr}")
|
||||
raise Exception(f"Error while running command on PVE: \n{command.stderr}")
|
||||
if only_code:
|
||||
return command.returncode
|
||||
|
||||
if only_code:
|
||||
return command.returncode
|
||||
return command.stdout.decode().rstrip()
|
||||
|
||||
return command.stdout.decode().rstrip()
|
||||
else:
|
||||
host = config['pve']['host']
|
||||
username = config['pve']['user']
|
||||
port = config['pve']['port']
|
||||
|
||||
else:
|
||||
host = data['pve']['host']
|
||||
username = data['pve']['user']
|
||||
port = data['pve']['port']
|
||||
# Run command on PVE via SSH and return output
|
||||
logging.debug(f"Running command on PVE (ssh): \n{command}")
|
||||
|
||||
# Run command on PVE via SSH and return output
|
||||
logging.debug(f"Running command on PVE (ssh): \n{command}")
|
||||
# catch errors code
|
||||
command = subprocess.run(f"ssh {username}@{host} -p {port} \"{command}\"", shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8")
|
||||
|
||||
# catch errors code
|
||||
command = subprocess.run(f"ssh {username}@{host} -p {port} \"{command}\"", shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8")
|
||||
if command.returncode != 0 and warn_exit_status:
|
||||
logging.error(f"Error while running command on PVE: \n{command.stderr}")
|
||||
raise Exception(f"Error while running command on PVE: \n{command.stderr}")
|
||||
|
||||
if command.returncode != 0 and warn_exit_status:
|
||||
logging.error(f"Error while running command on PVE: \n{command.stderr}")
|
||||
raise Exception(f"Error while running command on PVE: \n{command.stderr}")
|
||||
if only_code:
|
||||
return command.returncode
|
||||
|
||||
if only_code:
|
||||
return command.returncode
|
||||
return command.stdout.rstrip()
|
||||
|
||||
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)
|
||||
|
8
src/utils/resources_utils.py
Normal file
8
src/utils/resources_utils.py
Normal 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}"
|
Loading…
Reference in New Issue
Block a user