Compare commits

...

5 Commits

Author SHA1 Message Date
9fe806882e
added scripts support
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-16 16:06:03 +02:00
fafa85c0e1
added local copy of folder to pve then lxc 2023-06-16 16:05:55 +02:00
cc408237d9
updated get_ipv4 2023-06-16 16:04:09 +02:00
7d124cf751
make sure bash is installed on creation of lxc 2023-06-16 16:03:39 +02:00
d5911f7fcb
updated detection in ssh and docker scripts 2023-06-16 16:03:00 +02:00
8 changed files with 161 additions and 83 deletions

View File

@ -1,10 +1,11 @@
#!/bin/bash #!/bin/bash
if which ssh >/dev/null 2>&1; then # check if ssh is installed
echo "SSH is installed" if ! command -v ssh &>/dev/null; then
exit 1 echo "SSH is not installed"
else else
echo "SSH is not installed" echo "SSH is already installed"
exit 1
fi fi
if lsb_release -a 2>/dev/null | grep -q -E "Debian|Ubuntu"; then if lsb_release -a 2>/dev/null | grep -q -E "Debian|Ubuntu"; then

View File

@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
from . import lxc_utils from . import lxc_utils
from ..utils.resources_utils import get_path from ..utils.resources_utils import get_path
from ..utils.utils import optional
if TYPE_CHECKING: if TYPE_CHECKING:
from .lxc import LXC from .lxc import LXC
@ -58,11 +59,6 @@ def are_all_conditions_met(lxc: LXC):
for condition_type in step["conditions"]: for condition_type in step["conditions"]:
result = check_conditions(c_type=condition_type, parent=step["conditions"], lxc=lxc, result=result) result = check_conditions(c_type=condition_type, parent=step["conditions"], lxc=lxc, result=result)
if all(result):
logging.info(f"All creations conditions met for LXC {lxc.lxc_id}, running deploy steps...")
else:
logging.info(f"Not all creations conditions met for LXC {lxc.lxc_id}, running creation steps...")
return all(result) return all(result)
@ -121,35 +117,13 @@ def check_conditions_for_step(step: dict, lxc: LXC):
result = [] result = []
if "conditions" in step: if "conditions" in step:
for condition in step['conditions']: for condition in step['conditions']:
result.append(check_conditions(condition, parent=step['conditions'], lxc=lxc, result=result)) result = check_conditions(condition, parent=step['conditions'], lxc=lxc, result=result)
else: else:
return True return False
return all(result) return all(result)
def optional(var: any, placeholder: any):
"""Return a placeholder if the given variable is None, otherwise return the variable
Parameters
----------
var: any
Variable to check if it is None
placeholder: any
Placeholder to return if the variable is None
Returns
-------
any[any]
The variable if it is not None, otherwise the placeholder
"""
if var is None:
return placeholder
else:
return var
def run_steps(lxc: LXC): def run_steps(lxc: LXC):
"""Run the creation steps for the given LXC """Run the creation steps for the given LXC
@ -164,43 +138,44 @@ def run_steps(lxc: LXC):
for index, step in enumerate(creation_steps): for index, step in enumerate(creation_steps):
logging.info(f"Running step {index + 1}/{len(creation_steps)} for LXC {lxc.get_id()}...")
if check_conditions_for_step(step, lxc): if check_conditions_for_step(step, lxc):
logging.info(f"Conditions already met for step {index + 1}/{len(creation_steps)} for LXC {lxc.get_id()}, " logging.info(f"Conditions already met for step {index + 1}/{len(creation_steps)} for LXC {lxc.get_id()}, "
f"skipping...") f"skipping...")
break continue
logging.info(f"Running step {index + 1}/{len(creation_steps)} for LXC {lxc.get_id()}...")
match step["type"]: match step["type"]:
# Support for scripts # Support for scripts
case "script": case "script":
lxc_utils.run_script_parser(lxc, step) lxc_utils.run_script_step_parser(lxc, step)
case "file_create": case "file_create":
lxc.create_file(step["path"], optional(step["permission"], 644)) lxc.create_file(step["path"], optional(step["permission"], 644))
case "file_copy": case "file_copy":
commands_utils.copy_local_file_to_lxc(lxc, get_path(lxc, step["path"]), step["destination"]) lxc.pve.copy_file_to_lxc(lxc, get_path(lxc, step["path"]), step["destination"])
case "folder": case "folder":
commands_utils.create_folder_in_lxc(lxc, step["path"], optional(step["permission"], 755)) lxc.create_directory(step["path"], optional(step["permission"], 755))
case "folder_copy": case "folder_copy":
commands_utils.copy_local_folder_to_lxc(lxc, get_path(lxc, step["path"]), step["destination"]) lxc.pve.copy_folder_to_lxc(lxc, get_path(lxc, step["path"]), step["destination"])
case "command": case "command":
lxc.run_command(command=step["command"], working_directory=optional(step["working_directory"], None), lxc.run_command(command=step["command"], working_directory=optional(step["working_directory"], None),
return_status_code=True) return_status_code=True)
case "docker": case "docker":
commands_utils.run_docker_command(lxc, step["container"], step["command"]) lxc.run_docker_command(step["container"], step["command"])
case "docker_compose": case "docker_compose":
commands_utils.run_docker_compose_command(lxc, step["command"], lxc.run_docker_compose_command(command=step["command"],
optional(step["working_directory"], None)) working_directory=optional(step["working_directory"], None))
case "git": case "git":
lxc.run_command(command=f"git clone {step['url']} {step['destination']}", return_status_code=True) lxc.run_command(command=f"git clone {step['url']} {step['destination']}", return_status_code=True)
case "download": case "download":
commands_utils.download_file(lxc, step["url"], step["destination"]) lxc.download_file(step["url"], step["destination"])
case "unzip": case "unzip":
commands_utils.unzip_file(lxc, step["path"], optional(step["destination"], None)) lxc.unzip_file(step["path"], optional(step["destination"], None))
case "install-package": case "install-package":
commands_utils.install_package(lxc, step["package"]) lxc.install_package(step["package"])
case "remove-package": case "remove-package":
commands_utils.remove_package(lxc, step["package"]) lxc.remove_package(step["package"])
case "start": case "start":
lxc.start() lxc.start()
case "stop": case "stop":
@ -208,4 +183,4 @@ def run_steps(lxc: LXC):
case "reboot": case "reboot":
lxc.reboot() lxc.reboot()
case "replace-in-files": case "replace-in-files":
commands_utils.replace_in_files(lxc, step["files"], step["search"], step["replace"]) lxc.replace_in_files(step["files"], step["search"], step["replace"])

View File

@ -1,7 +1,6 @@
import logging import logging
from . import creation_utils, lxc_utils from . import creation_utils, lxc_utils
from ..utils import commands_utils
from ..utils.machine import LinuxMachine from ..utils.machine import LinuxMachine
from ..utils.proxmox import ProxmoxHost from ..utils.proxmox import ProxmoxHost
@ -172,14 +171,25 @@ class LXC(LinuxMachine):
""" """
return self.bridge return self.bridge
def get_ipv4(self): def get_ipv4(self, netmask: bool = False):
""" """
Get IPv4 Get IPv4
:return: ipv4 :return: ipv4
""" """
if self.ipv4 == "dhcp": if self.ipv4 == "dhcp":
if self.is_running(): if self.is_running():
return super().get_ipv4() if self.has_program("ip"):
if netmask:
ip = self.run_command(
"""ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 """)
return ip
ip = self.run_command(
"""ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/'""")
return ip
elif self.has_program("ifconfig"):
return self.run_command(command="ifconfig eth0 | awk '/inet addr/{print substr($2,6)}'")
return self.ipv4 return self.ipv4
@ -317,9 +327,12 @@ class LXC(LinuxMachine):
self.start() self.start()
# Make sure bash is installed for later use
if not self.has_program("bash"):
self.install_package("bash")
logging.info("Setting up SSH for LXC") logging.info("Setting up SSH for LXC")
# self.run_script(script_path="protected/scripts/install-config-ssh.sh") lxc_utils.run_protected_script(lxc=self, script_path="protected/scripts/install-config-ssh.sh")
commands_utils.run_script(lxc=self, step={"local_path": "protected/scripts/install-config-ssh.sh"})
def run_creation(self): def run_creation(self):
""" """
@ -349,7 +362,7 @@ class LXC(LinuxMachine):
:param script_path: :param script_path:
:return: :return:
""" """
return commands_utils.run_script(self, {"lxc_path": script_path}) return self.run_command(command=f"chmod +x {script_path} && bash {script_path}")
def run_command(self, command: str, return_status_code: bool = False, def run_command(self, command: str, return_status_code: bool = False,
exception_on_exit: bool = False, exception_on_exit: bool = False,
@ -359,6 +372,11 @@ class LXC(LinuxMachine):
Run command on LXC Run command on LXC
:param command: command to run :param command: command to run
:return: command output :return: command output
return_status_code: bool = False,
exception_on_exit: bool = False,
exception_on_empty_stdout: bool = False,
working_directory: str = None
""" """
# logging.debug(f"Running command {command} on LXC {self.lxc_id}") # logging.debug(f"Running command {command} on LXC {self.lxc_id}")
@ -366,6 +384,7 @@ class LXC(LinuxMachine):
if working_directory: if working_directory:
command = f"cd {working_directory} && {command}" command = f"cd {working_directory} && {command}"
self.pve.run_command(command=f"pct exec {self.lxc_id} -- {command}", return_status_code=return_status_code, return self.pve.run_command(command=f"pct exec {self.lxc_id} -- {command}",
exception_on_exit=exception_on_exit, return_status_code=return_status_code,
exception_on_empty_stdout=exception_on_empty_stdout) exception_on_exit=exception_on_exit,
exception_on_empty_stdout=exception_on_empty_stdout)

View File

@ -1,7 +1,9 @@
import json import json
from .lxc import LXC from .lxc import LXC
from ..utils import utils
from ..utils.proxmox import ProxmoxHost from ..utils.proxmox import ProxmoxHost
from ..utils.resources_utils import get_path
lxcs = [] lxcs = []
@ -94,7 +96,7 @@ def generate_pct_command_for_lxc(lxc: LXC, create: bool = True):
f"--cores {lxc.get_cpu()} " \ f"--cores {lxc.get_cpu()} " \
f"--memory {lxc.get_memory()} " \ f"--memory {lxc.get_memory()} " \
f"--swap {lxc.get_swap()} " \ f"--swap {lxc.get_swap()} " \
f"--net0 name=eth0,bridge={lxc.get_bridge()},ip={lxc.get_ipv4()},hwaddr={lxc.get_mac()},type=veth " \ f"--net0 name=eth0,bridge={lxc.get_bridge()},ip={lxc.get_ipv4(netmask=True)},hwaddr={lxc.get_mac()},type=veth " \
f"--onboot {int(lxc.is_start_on_boot())} " \ f"--onboot {int(lxc.is_start_on_boot())} " \
f"--ostype {lxc.get_os_name()} " \ f"--ostype {lxc.get_os_name()} " \
f"--password {lxc.get_password()} " \ f"--password {lxc.get_password()} " \
@ -110,7 +112,7 @@ def generate_pct_command_for_lxc(lxc: LXC, create: bool = True):
f"--cores {lxc.get_cpu()} " \ f"--cores {lxc.get_cpu()} " \
f"--memory {lxc.get_memory()} " \ f"--memory {lxc.get_memory()} " \
f"--swap {lxc.get_memory()} " \ f"--swap {lxc.get_memory()} " \
f"--net0 name=eth0,bridge={lxc.get_bridge()},ip={lxc.get_ipv4()},hwaddr={lxc.get_mac()},type=veth " \ f"--net0 name=eth0,bridge={lxc.get_bridge()},ip={lxc.get_ipv4(netmask=True)},hwaddr={lxc.get_mac()},type=veth " \
f"--onboot {int(lxc.is_start_on_boot())} " \ f"--onboot {int(lxc.is_start_on_boot())} " \
f"--ostype {lxc.get_os_name()} " f"--ostype {lxc.get_os_name()} "
@ -119,22 +121,51 @@ def generate_pct_command_for_lxc(lxc: LXC, create: bool = True):
return pct_command return pct_command
def run_script_parser(lxc: LXC, step: dict): def run_script_step_parser(lxc: LXC, step: dict):
# Install bash if not installed # Install bash if not installed
# Sometimes only ash or sh are installed, which doesn't work for some scripts # Sometimes only ash or sh are installed, which doesn't work for some scripts
if not lxc.has_program("bash"): if not lxc.has_program("bash"):
lxc.install_package("bash") lxc.install_package("bash")
# # Run local script # Run local script
# if "path" in step: if "path" in step:
# path = get_path(lxc, step["local_path"]) if "protected/" in step["path"]:
# _run_local_script_on_lxc(lxc, path) run_protected_script(lxc, step["path"])
# else:
# # Run remote script run_repo_script(lxc, step["path"])
# elif "url" in step:
# _run_remote_script_on_lxc(lxc, step["url"]) # Run remote script
# elif "url" in step:
# # Run script in LXC run_remote_script(lxc, step["url"])
# elif "lxc_path" in step:
# path = get_path(lxc, step["lxc_path"]) # Run script in LXC
# _run_script_on_lxc(lxc, path) elif "lxc_path" in step:
lxc.run_script(step["lxc_path"])
def run_repo_script(lxc: LXC, script_path: str):
# Run local script
script_path = get_path(lxc, script_path)
lxc.pve.copy_file_to_lxc(lxc, script_path, f"/tmp/pdj-temp/{script_path.name}")
lxc.run_script(f"/tmp/pdj-temp/{script_path.name}")
lxc.delete_file(f"/tmp/pdj-temp/{script_path.name}")
def run_protected_script(lxc: LXC, script_path: str):
script_path = get_path(lxc, script_path)
utils.copy_local_file_to_pve(lxc.pve, script_path, f"/tmp/pdj-temp/{script_path.name}")
lxc.pve.copy_file_to_lxc(lxc, f"/tmp/pdj-temp/{script_path.name}", f"/tmp/pdj-temp/{script_path.name}")
lxc.run_script(f"/tmp/pdj-temp/{script_path.name}")
lxc.delete_file(f"/tmp/pdj-temp/{script_path.name}")
lxc.pve.delete_file(f"/tmp/pdj-temp/{script_path.name}")
def run_remote_script(lxc: LXC, url: str):
# Install curl if not installed
if not lxc.has_program("curl"):
lxc.install_package("curl")
# Run remote script
lxc.run_command(f"curl -sSL {url} | bash")

View File

@ -29,6 +29,7 @@ class LinuxMachine():
return self.run_command("free -m | grep Mem | awk '{print $2}'") return self.run_command("free -m | grep Mem | awk '{print $2}'")
def get_ipv4(self): def get_ipv4(self):
"""Get IPv4 address"""
if self.has_program("ip"): if self.has_program("ip"):
return self.run_command("""ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/'""") return self.run_command("""ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/'""")
elif self.has_program("ifconfig"): elif self.has_program("ifconfig"):
@ -88,7 +89,7 @@ class LinuxMachine():
pass pass
def run_script(self, script: str or Path): def run_script(self, script: str or Path):
pass return self.run_command(f"bash {script}", return_status_code=True)
def list_dir(self, directory: str or Path): def list_dir(self, directory: str or Path):
pass pass

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import fnmatch import fnmatch
import subprocess import subprocess
from pathlib import Path, PosixPath from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from fabric import Connection from fabric import Connection
@ -134,7 +134,7 @@ class ProxmoxHost(LinuxMachine):
"""Get all the LXCs on the Proxmox host.""" """Get all the LXCs on the Proxmox host."""
pct_list_output = self.run_command("pct list", exception_on_exit=False) pct_list_output = self.run_command("pct list", exception_on_exit=False)
pct_list_output = pct_list_output.split("\n")[1:] pct_list_output = pct_list_output.split("\n")[1:]
ids = [line.split()[0] for line in pct_list_output] ids = [int(line.split()[0]) for line in pct_list_output]
if not ids: if not ids:
return [] return []
@ -212,10 +212,11 @@ class ProxmoxHost(LinuxMachine):
"""Copy the given file to the given LXC.""" """Copy the given file to the given LXC."""
if isinstance(source, Path): if isinstance(source, Path):
source = str(source.as_posix()) source = str(source.as_posix())
if isinstance(destination, Path): if isinstance(destination, str):
destination = str(destination.as_posix()) destination = Path(destination)
self.run_command(f"pct push {lxc.get_id()} {source} {destination}") lxc.create_directory(destination.parent.as_posix())
self.run_command(f"pct push {lxc.get_id()} {source} {destination.as_posix()}")
def copy_folder_to_lxc(self, lxc: LXC, source: str or Path, destination: str or Path): def copy_folder_to_lxc(self, lxc: LXC, source: str or Path, destination: str or Path):
"""Copy the given folder to the given LXC.""" """Copy the given folder to the given LXC."""
@ -224,7 +225,7 @@ class ProxmoxHost(LinuxMachine):
if isinstance(destination, Path): if isinstance(destination, Path):
destination = str(destination.as_posix()) destination = str(destination.as_posix())
self.run_command(f"pct push {lxc.get_id()} {source} {destination} -r") self.run_command(f"scp -B -r {source} {lxc.get_ssh_string()}:{destination}")
def list_dir(self, directory: str or Path, glob_filter: str = '*'): def list_dir(self, directory: str or Path, glob_filter: str = '*'):
"""List the given directory.""" """List the given directory."""

View File

@ -1,10 +1,9 @@
from __future__ import annotations from __future__ import annotations
import os
import pathlib import pathlib
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .. import project_path
if TYPE_CHECKING: if TYPE_CHECKING:
from ..lxc.lxc import LXC from ..lxc.lxc import LXC
@ -35,14 +34,15 @@ def get_path(lxc: LXC, path: str):
Complete path to the resource file Complete path to the resource file
""" """
parent = pathlib.Path(project_path).joinpath("resources") parent = pathlib.Path(lxc.pve.repo_path)
if path.startswith("global/"): if path.startswith("global/"):
# Use global folder # Use global folder
return parent.joinpath(path[len("global/"):]) return parent.joinpath(path[len("global/"):])
elif path.startswith("protected/"): elif path.startswith("protected/"):
# Use protected folder # Use protected folder
return parent.parent.joinpath("protected_resources", path[len("protected/"):]) app_path = pathlib.Path(os.path.dirname(__file__)).parent.parent
return app_path.joinpath("protected_resources", path[len("protected/"):])
else: else:
# Use VM/LXC folder # Use VM/LXC folder
lxc_id = lxc.get_id() lxc_id = lxc.get_id()

View File

@ -1,3 +1,8 @@
from pathlib import Path
from src.utils.proxmox import ProxmoxHost
def get_install_package_command(distribution: str): def get_install_package_command(distribution: str):
"""Retrieve the correct command to install a package based on the distribution specified """Retrieve the correct command to install a package based on the distribution specified
It supports all the distribution supported by Proxmox VE (from the pct command documentation). It supports all the distribution supported by Proxmox VE (from the pct command documentation).
@ -88,3 +93,48 @@ def get_remove_package_command(distribution: str):
return "zypper remove -y" return "zypper remove -y"
else: else:
raise Exception(f"Unsupported distribution: {distribution}") raise Exception(f"Unsupported distribution: {distribution}")
def optional(var: any, placeholder: any):
"""Return a placeholder if the given variable is None, otherwise return the variable
Parameters
----------
var: any
Variable to check if it is None
placeholder: any
Placeholder to return if the variable is None
Returns
-------
any[any]
The variable if it is not None, otherwise the placeholder
"""
if var is None:
return placeholder
else:
return var
def copy_local_file_to_pve(pve: ProxmoxHost, local_source: str or Path, destination: str or Path):
"""Copy a local file (in the compiled app for users) to a Proxmox VE host
Parameters
----------
pve: ProxmoxHost
Proxmox VE host to copy the file to
local_source: str or Path
Path to the local file to copy
destination: str or Path
Destination path on the Proxmox VE host
"""
if isinstance(local_source, Path):
local_source = str(local_source.as_posix())
if isinstance(destination, str):
destination = Path(destination)
pve.run_command(f"mkdir -p {destination.parent.as_posix()}")
pve.get_connection().put(local_source, destination.as_posix())