Running local scripts now works correctly and improved the varions way to run commands on both the PVE and the LXCs
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
2e70f64269
commit
eb191a7a55
@ -105,7 +105,7 @@ class LXC:
|
|||||||
|
|
||||||
if is_template_downloaded == "":
|
if is_template_downloaded == "":
|
||||||
logging.info(f"Template {template_name} not found, downloading it...")
|
logging.info(f"Template {template_name} not found, downloading it...")
|
||||||
proxmox_utils.run_command_on_pve(command=f"pveam download local {template_name}", warn_exit_status=True)
|
proxmox_utils.run_command_on_pve(command=f"pveam download local {template_name}")
|
||||||
|
|
||||||
return f"local:vztmpl/{template_name}"
|
return f"local:vztmpl/{template_name}"
|
||||||
|
|
||||||
@ -284,21 +284,21 @@ class LXC:
|
|||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logging.info(f"Starting LXC {self.lxc_id}")
|
logging.info(f"Starting LXC {self.lxc_id}")
|
||||||
proxmox_utils.run_command_on_pve(command=f"pct start {self.lxc_id}", warn_exit_status=True)
|
proxmox_utils.run_command_on_pve(command=f"pct start {self.lxc_id}")
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
Stop LXC
|
Stop LXC
|
||||||
"""
|
"""
|
||||||
logging.info(f"Stopping LXC {self.lxc_id}")
|
logging.info(f"Stopping LXC {self.lxc_id}")
|
||||||
proxmox_utils.run_command_on_pve(command=f"pct stop {self.lxc_id}", warn_exit_status=True)
|
proxmox_utils.run_command_on_pve(command=f"pct stop {self.lxc_id}")
|
||||||
|
|
||||||
def reboot(self):
|
def reboot(self):
|
||||||
"""
|
"""
|
||||||
Reboot LXC
|
Reboot LXC
|
||||||
"""
|
"""
|
||||||
logging.info(f"Rebooting LXC {self.lxc_id}")
|
logging.info(f"Rebooting LXC {self.lxc_id}")
|
||||||
proxmox_utils.run_command_on_pve(command=f"pct reboot {self.lxc_id}", warn_exit_status=True)
|
proxmox_utils.run_command_on_pve(command=f"pct reboot {self.lxc_id}")
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
"""
|
"""
|
||||||
@ -306,10 +306,10 @@ class LXC:
|
|||||||
"""
|
"""
|
||||||
if self.does_exist():
|
if self.does_exist():
|
||||||
logging.info(f"LXC {self.lxc_id} already exists, skipping creation")
|
logging.info(f"LXC {self.lxc_id} already exists, skipping creation")
|
||||||
proxmox_utils.run_command_on_pve(command=self.get_pct_command(create=False), warn_exit_status=True)
|
proxmox_utils.run_command_on_pve(command=self.get_pct_command(create=False))
|
||||||
else:
|
else:
|
||||||
logging.info(f"Creating LXC {self.lxc_id}")
|
logging.info(f"Creating LXC {self.lxc_id}")
|
||||||
proxmox_utils.run_command_on_pve(command=self.get_pct_command(create=True), warn_exit_status=True)
|
proxmox_utils.run_command_on_pve(command=self.get_pct_command(create=True))
|
||||||
|
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
@ -343,9 +343,9 @@ class LXC:
|
|||||||
:return: boolean
|
:return: boolean
|
||||||
"""
|
"""
|
||||||
if type(program) == str:
|
if type(program) == str:
|
||||||
return self.run_command("which " + program, only_code=True) == 0
|
return self.run_command("which " + program, return_status_code=True) == 0
|
||||||
elif type(program) == list:
|
elif type(program) == list:
|
||||||
return all((self.run_command("which " + p, only_code=True) == 0) for p in program)
|
return all((self.run_command("which " + p, return_status_code=True) == 0) for p in program)
|
||||||
|
|
||||||
def run_script(self, script_path):
|
def run_script(self, script_path):
|
||||||
"""
|
"""
|
||||||
@ -356,7 +356,10 @@ class LXC:
|
|||||||
"""
|
"""
|
||||||
return commands_utils.run_script(self, {"lxc_path": script_path})
|
return commands_utils.run_script(self, {"lxc_path": script_path})
|
||||||
|
|
||||||
def run_command(self, command, ssh=False, warn_exit_status=False, only_code=False, working_directory=None):
|
def run_command(self, command: str, ssh: bool = False, return_status_code: bool = False,
|
||||||
|
exception_on_exit: bool = False,
|
||||||
|
exception_on_empty_stdout: bool = False,
|
||||||
|
working_directory: str = None, shell: bool = False):
|
||||||
"""
|
"""
|
||||||
Run command on LXC
|
Run command on LXC
|
||||||
:param command: command to run
|
:param command: command to run
|
||||||
@ -370,10 +373,16 @@ class LXC:
|
|||||||
|
|
||||||
if ssh:
|
if ssh:
|
||||||
return proxmox_utils.run_command_ssh(command=command, host=self.get_ipv4(), username="root", port=22,
|
return proxmox_utils.run_command_ssh(command=command, host=self.get_ipv4(), username="root", port=22,
|
||||||
warn_exit_status=warn_exit_status, only_code=only_code)
|
return_status_code=return_status_code,
|
||||||
|
exception_on_exit=exception_on_exit,
|
||||||
|
exception_on_empty_stdout=exception_on_empty_stdout,
|
||||||
|
shell=shell)
|
||||||
else:
|
else:
|
||||||
return proxmox_utils.run_command_on_pve(command=f"pct exec {self.lxc_id} -- {command}",
|
return proxmox_utils.run_command_on_pve(command=f"pct exec {self.lxc_id} -- {command}",
|
||||||
warn_exit_status=warn_exit_status, only_code=only_code)
|
return_status_code=return_status_code,
|
||||||
|
exception_on_exit=exception_on_exit,
|
||||||
|
exception_on_empty_stdout=exception_on_empty_stdout,
|
||||||
|
shell=shell)
|
||||||
|
|
||||||
def get_pct_command(self, create=True):
|
def get_pct_command(self, create=True):
|
||||||
"""
|
"""
|
||||||
|
@ -54,11 +54,11 @@ def _run_script_on_lxc(lxc: LXC, path: Path):
|
|||||||
Path to the script inside the LXC storage
|
Path to the script inside the LXC storage
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lxc.run_command(f"bash {str(path)}")
|
lxc.run_command(f"bash {str(path.as_posix())}", shell=True)
|
||||||
|
|
||||||
|
|
||||||
def _run_local_script_on_lxc(lxc: LXC, path: Path):
|
def _run_local_script_on_lxc(lxc: LXC, path: Path):
|
||||||
"""Runs a script present on the machine running this program, inside the LXC
|
"""Runs a script present on the local machine running this program, inside the LXC
|
||||||
It works by copying the script from the local machine to the PVE using SCP and
|
It works by copying the script from the local machine to the PVE using SCP and
|
||||||
then using "pct push" to send it to the LXC.
|
then using "pct push" to send it to the LXC.
|
||||||
|
|
||||||
@ -70,9 +70,17 @@ def _run_local_script_on_lxc(lxc: LXC, path: Path):
|
|||||||
Path object to the script on the local machine (machine running this program)
|
Path object to the script on the local machine (machine running this program)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Copy the script to /tmp and run it from there
|
# define the path of the script inside the LXC
|
||||||
proxmox_utils.copy_file_to_lxc(lxc.get_id(), path, "/tmp/pdj-scripts/")
|
script_path_in_lxc = Path("/tmp/pdj-scripts/").joinpath(path.name)
|
||||||
lxc.run_command(f"bash /tmp/pdj-scripts/{path.name}")
|
|
||||||
|
# Copy the script to the LXC
|
||||||
|
proxmox_utils.copy_file_to_lxc(lxc, path, f"{str(script_path_in_lxc.parent.as_posix())}/")
|
||||||
|
|
||||||
|
# and run it from there
|
||||||
|
_run_script_on_lxc(lxc, script_path_in_lxc)
|
||||||
|
|
||||||
|
# once it has been run, remove the script from the LXC
|
||||||
|
remove_file_in_lxc(lxc, script_path_in_lxc)
|
||||||
|
|
||||||
# with open(path, "r") as file:
|
# with open(path, "r") as file:
|
||||||
# script = file.read()
|
# script = file.read()
|
||||||
@ -95,10 +103,10 @@ def _run_remote_script_on_lxc(lxc: LXC, url: str):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if lxc.has_program("curl"):
|
if lxc.has_program("curl"):
|
||||||
lxc.run_command(f"curl -sSL {url} | bash")
|
lxc.run_command(f"curl -sSL {url} | bash", shell=True)
|
||||||
|
|
||||||
|
|
||||||
def create_folder_in_lxc(lxc: LXC, path: str, permission=755):
|
def create_folder_in_lxc(lxc: LXC, path: str or Path, permission=755):
|
||||||
"""Create a folder in the LXC
|
"""Create a folder in the LXC
|
||||||
It's possible to specify the permission of the folder, by default it uses 755
|
It's possible to specify the permission of the folder, by default it uses 755
|
||||||
|
|
||||||
@ -106,19 +114,39 @@ def create_folder_in_lxc(lxc: LXC, path: str, permission=755):
|
|||||||
----------
|
----------
|
||||||
lxc: LXC
|
lxc: LXC
|
||||||
The LXC object of the LXC to create the folder in
|
The LXC object of the LXC to create the folder in
|
||||||
path: str
|
path: str or pathlib.Path
|
||||||
Path to the folder to create in the LXC
|
Path to the folder to create in the LXC
|
||||||
permission: int, optional
|
permission: int, optional
|
||||||
Permission of the folder to create in the LXC (default is 755)
|
Permission of the folder to create in the LXC (default is 755)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lxc.run_command(f"mkdir -p {path}")
|
if isinstance(path, Path):
|
||||||
|
path = str(path.as_posix())
|
||||||
|
|
||||||
|
lxc.run_command(f"mkdir -p {path}", return_status_code=True)
|
||||||
|
|
||||||
if permission != 755:
|
if permission != 755:
|
||||||
lxc.run_command(f"chmod -R {permission} {path}")
|
lxc.run_command(f"chmod -R {permission} {path}", return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def create_file_in_lxc(lxc: LXC, path: str, permission=644):
|
def remove_folder_in_lxc(lxc: LXC, path: str or Path):
|
||||||
|
"""Remove a folder in the LXC
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
lxc: LXC
|
||||||
|
The LXC object of the LXC to remove the folder in
|
||||||
|
path: str or pathlib.Path
|
||||||
|
Path to the folder to remove in the LXC
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(path, Path):
|
||||||
|
path = str(path.as_posix())
|
||||||
|
|
||||||
|
lxc.run_command(f"rm -rf {path}", return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_file_in_lxc(lxc: LXC, path: str or Path, permission=644):
|
||||||
"""Create a file in the LXC
|
"""Create a file in the LXC
|
||||||
It's possible to specify the permission of the file, by default it uses 644
|
It's possible to specify the permission of the file, by default it uses 644
|
||||||
|
|
||||||
@ -126,29 +154,35 @@ def create_file_in_lxc(lxc: LXC, path: str, permission=644):
|
|||||||
----------
|
----------
|
||||||
lxc: LXC
|
lxc: LXC
|
||||||
The LXC object of the LXC to create the file in
|
The LXC object of the LXC to create the file in
|
||||||
path: str
|
path: str or pathlib.Path
|
||||||
Path to the file to create in the LXC
|
Path to the file to create in the LXC
|
||||||
permission: int, optional
|
permission: int, optional
|
||||||
Permission of the file to create in the LXC (default is 644)
|
Permission of the file to create in the LXC (default is 644)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lxc.run_command(f"touch {path}")
|
if isinstance(path, Path):
|
||||||
|
path = str(path.as_posix())
|
||||||
|
|
||||||
|
lxc.run_command(f"touch {path}", return_status_code=True)
|
||||||
if permission != 644:
|
if permission != 644:
|
||||||
lxc.run_command(f"chmod {permission} {path}")
|
lxc.run_command(f"chmod {permission} {path}", return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def remove_file_in_lxc(lxc: LXC, path: str):
|
def remove_file_in_lxc(lxc: LXC, path: str or Path):
|
||||||
"""Remove a file in the LXC
|
"""Remove a file in the LXC
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
lxc: LXC
|
lxc: LXC
|
||||||
The LXC object of the LXC to remove the file in
|
The LXC object of the LXC to remove the file in
|
||||||
path: str
|
path: str or pathlib.Path
|
||||||
Path to the file to remove in the LXC
|
Path to the file to remove in the LXC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lxc.run_command(f"rm {path}")
|
if isinstance(path, Path):
|
||||||
|
path = str(path.as_posix())
|
||||||
|
|
||||||
|
lxc.run_command(f"rm {path}", return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def copy_local_file_to_lxc(lxc: LXC, path: Path, destination: str):
|
def copy_local_file_to_lxc(lxc: LXC, path: Path, destination: str):
|
||||||
@ -164,7 +198,8 @@ def copy_local_file_to_lxc(lxc: LXC, path: Path, destination: str):
|
|||||||
Path to the destination in the LXC
|
Path to the destination in the LXC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proxmox_utils.run_command_locally(f"scp {str(path)} {lxc.get_ssh_string()}:{destination}")
|
proxmox_utils.run_command_locally(command=f"scp -B {str(path)} {lxc.get_ssh_string()}:{destination}",
|
||||||
|
return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def copy_local_folder_to_lxc(lxc: LXC, path: Path, destination: str):
|
def copy_local_folder_to_lxc(lxc: LXC, path: Path, destination: str):
|
||||||
@ -180,7 +215,8 @@ def copy_local_folder_to_lxc(lxc: LXC, path: Path, destination: str):
|
|||||||
Path to the destination in the LXC
|
Path to the destination in the LXC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proxmox_utils.run_command_locally(f"scp -r {str(path)} {lxc.get_ssh_string()}:{destination}")
|
proxmox_utils.run_command_locally(command=f"scp -B -r {str(path)} {lxc.get_ssh_string()}:{destination}",
|
||||||
|
return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def run_docker_command(lxc: LXC, container: str, command: str):
|
def run_docker_command(lxc: LXC, container: str, command: str):
|
||||||
@ -200,7 +236,7 @@ def run_docker_command(lxc: LXC, container: str, command: str):
|
|||||||
>>> run_docker_command(lxc, "<container-name>", "<command>")
|
>>> run_docker_command(lxc, "<container-name>", "<command>")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lxc.run_command(f"docker exec -it {container} {command}")
|
lxc.run_command(f"docker exec -it {container} {command}", exception_on_empty_stdout=False)
|
||||||
|
|
||||||
|
|
||||||
def run_docker_compose_command(lxc: LXC, command: str, working_directory: str = None):
|
def run_docker_compose_command(lxc: LXC, command: str, working_directory: str = None):
|
||||||
@ -225,9 +261,9 @@ def run_docker_compose_command(lxc: LXC, command: str, working_directory: str =
|
|||||||
docker_compose_exec = "docker compose"
|
docker_compose_exec = "docker compose"
|
||||||
|
|
||||||
if working_directory is not None:
|
if working_directory is not None:
|
||||||
lxc.run_command(f"cd {working_directory} && {docker_compose_exec} {command}")
|
lxc.run_command(f"cd {working_directory} && {docker_compose_exec} {command}", return_status_code=True)
|
||||||
else:
|
else:
|
||||||
lxc.run_command(f"{docker_compose_exec} {command}")
|
lxc.run_command(f"{docker_compose_exec} {command}", return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def download_file(lxc: LXC, url: str, destination: str):
|
def download_file(lxc: LXC, url: str, destination: str):
|
||||||
@ -251,9 +287,9 @@ def download_file(lxc: LXC, url: str, destination: str):
|
|||||||
"""Download a file from a URL to the LXC"""
|
"""Download a file from a URL to the LXC"""
|
||||||
if type(url) is list:
|
if type(url) is list:
|
||||||
for u in url:
|
for u in url:
|
||||||
lxc.run_command(f"wget {u} --directory-prefix={destination}")
|
lxc.run_command(f"wget {u} --directory-prefix={destination}", return_status_code=True)
|
||||||
else:
|
else:
|
||||||
lxc.run_command(f"wget {url} --directory-prefix={destination}")
|
lxc.run_command(f"wget {url} --directory-prefix={destination}", return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def unzip_file(lxc: LXC, path: str, destination: str = None):
|
def unzip_file(lxc: LXC, path: str, destination: str = None):
|
||||||
@ -280,9 +316,9 @@ def unzip_file(lxc: LXC, path: str, destination: str = None):
|
|||||||
destination = Path(path).parent
|
destination = Path(path).parent
|
||||||
|
|
||||||
if ".zip" in path:
|
if ".zip" in path:
|
||||||
lxc.run_command(f"unzip {path} -d {destination}")
|
lxc.run_command(f"unzip {path} -d {destination}", return_status_code=True)
|
||||||
elif ".tar.gz" in path:
|
elif ".tar.gz" in path:
|
||||||
lxc.run_command(f"mkdir -p {destination} && tar -xzf {path} --directory {destination}")
|
lxc.run_command(f"mkdir -p {destination} && tar -xzf {path} --directory {destination}", return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def install_package(lxc: LXC, package: str or list):
|
def install_package(lxc: LXC, package: str or list):
|
||||||
@ -303,9 +339,11 @@ def install_package(lxc: LXC, package: str or list):
|
|||||||
|
|
||||||
if type(package) is list:
|
if type(package) is list:
|
||||||
for p in package:
|
for p in package:
|
||||||
lxc.run_command(f"{proxmox_utils.get_install_package_command(lxc.get_os_name())} {p}")
|
lxc.run_command(f"{proxmox_utils.get_install_package_command(lxc.get_os_name())} {p}",
|
||||||
|
return_status_code=True)
|
||||||
else:
|
else:
|
||||||
lxc.run_command(f"{proxmox_utils.get_install_package_command(lxc.get_os_name())} {package}")
|
lxc.run_command(f"{proxmox_utils.get_install_package_command(lxc.get_os_name())} {package}",
|
||||||
|
return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def remove_package(lxc: LXC, package: str or list):
|
def remove_package(lxc: LXC, package: str or list):
|
||||||
@ -328,10 +366,12 @@ def remove_package(lxc: LXC, package: str or list):
|
|||||||
packages = []
|
packages = []
|
||||||
for p in package:
|
for p in package:
|
||||||
packages.append(p)
|
packages.append(p)
|
||||||
lxc.run_command(f"{proxmox_utils.get_remove_package_command(lxc.get_os_name())} {' '.join(packages)}")
|
lxc.run_command(f"{proxmox_utils.get_remove_package_command(lxc.get_os_name())} {' '.join(packages)}",
|
||||||
|
return_status_code=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
lxc.run_command(f"{proxmox_utils.get_remove_package_command(lxc.get_os_name())} {package}")
|
lxc.run_command(f"{proxmox_utils.get_remove_package_command(lxc.get_os_name())} {package}",
|
||||||
|
return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def replace_in_files(lxc: LXC, path: str or list, search: str, replace: str, case_sensitive: bool = False):
|
def replace_in_files(lxc: LXC, path: str or list, search: str, replace: str, case_sensitive: bool = False):
|
||||||
@ -360,6 +400,8 @@ def replace_in_files(lxc: LXC, path: str or list, search: str, replace: str, cas
|
|||||||
|
|
||||||
if type(path) is list:
|
if type(path) is list:
|
||||||
for p in path:
|
for p in path:
|
||||||
lxc.run_command(f"sed {'-i' if case_sensitive else ''} 's/{search}/{replace}/g' {p}")
|
lxc.run_command(f"sed {'-i' if case_sensitive else ''} 's/{search}/{replace}/g' {p}",
|
||||||
|
return_status_code=True)
|
||||||
else:
|
else:
|
||||||
lxc.run_command(f"sed {'-i' if case_sensitive else ''} 's/{search}/{replace}/g' {path}")
|
lxc.run_command(f"sed {'-i' if case_sensitive else ''} 's/{search}/{replace}/g' {path}",
|
||||||
|
return_status_code=True)
|
||||||
|
@ -40,7 +40,7 @@ def are_all_conditions_met(lxc: LXC):
|
|||||||
for folder in conditions['folders']:
|
for folder in conditions['folders']:
|
||||||
result.append(Path(folder).is_dir())
|
result.append(Path(folder).is_dir())
|
||||||
for program in conditions['programs']:
|
for program in conditions['programs']:
|
||||||
result.append(lxc.run_command("which " + program, only_code=True) == 0)
|
result.append(lxc.run_command("which " + program, return_status_code=True) == 0)
|
||||||
|
|
||||||
if all(result):
|
if all(result):
|
||||||
logging.info(f"All creations conditions met for LXC {lxc.lxc_id}, running deploy steps...")
|
logging.info(f"All creations conditions met for LXC {lxc.lxc_id}, running deploy steps...")
|
||||||
@ -99,14 +99,15 @@ def run_steps(lxc: LXC):
|
|||||||
case "folder_copy":
|
case "folder_copy":
|
||||||
commands_utils.copy_local_folder_to_lxc(lxc, get_path(lxc, step["path"]), step["destination"])
|
commands_utils.copy_local_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)
|
||||||
case "docker":
|
case "docker":
|
||||||
commands_utils.run_docker_command(lxc, step["container"], step["command"])
|
commands_utils.run_docker_command(lxc, step["container"], step["command"])
|
||||||
case "docker_compose":
|
case "docker_compose":
|
||||||
commands_utils.run_docker_compose_command(lxc, step["command"],
|
commands_utils.run_docker_compose_command(lxc, step["command"],
|
||||||
optional(step["working_directory"], None))
|
optional(step["working_directory"], None))
|
||||||
case "git":
|
case "git":
|
||||||
lxc.run_command(command=f"git clone {step['url']} {step['destination']}")
|
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"])
|
commands_utils.download_file(lxc, step["url"], step["destination"])
|
||||||
case "unzip":
|
case "unzip":
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .. import project_path
|
from .. import project_path
|
||||||
|
from ..lxc import commands_utils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from . import LXC
|
||||||
|
|
||||||
|
|
||||||
def get_pve_version():
|
def get_pve_version():
|
||||||
@ -16,7 +23,7 @@ def get_pve_version():
|
|||||||
str
|
str
|
||||||
PVE version
|
PVE version
|
||||||
"""
|
"""
|
||||||
return run_command_on_pve(command="pveversion", warn_exit_status=True)
|
return run_command_on_pve(command="pveversion")
|
||||||
|
|
||||||
|
|
||||||
def get_pve_hostname():
|
def get_pve_hostname():
|
||||||
@ -28,7 +35,7 @@ def get_pve_hostname():
|
|||||||
PVE hostname
|
PVE hostname
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return run_command_on_pve(command="hostname", warn_exit_status=True)
|
return run_command_on_pve(command="hostname")
|
||||||
|
|
||||||
|
|
||||||
def does_lxc_exist(lxc_id: int):
|
def does_lxc_exist(lxc_id: int):
|
||||||
@ -45,7 +52,7 @@ def does_lxc_exist(lxc_id: int):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: only check in VMID column
|
# TODO: only check in VMID column
|
||||||
return str(lxc_id) in run_command_on_pve(command=f"pct list | awk '/{lxc_id}/'")
|
return str(lxc_id) in run_command_on_pve(command=f"pct list | awk '/{lxc_id}/'", exception_on_empty_stdout=False)
|
||||||
|
|
||||||
|
|
||||||
def does_qemu_vm_exist(vm_id: int):
|
def does_qemu_vm_exist(vm_id: int):
|
||||||
@ -62,28 +69,28 @@ def does_qemu_vm_exist(vm_id: int):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: only check in VMID column
|
# TODO: only check in VMID column
|
||||||
return str(vm_id) in run_command_on_pve(command=f"qm list {vm_id}")
|
return str(vm_id) in run_command_on_pve(command=f"qm list {vm_id}", exception_on_empty_stdout=False)
|
||||||
|
|
||||||
|
|
||||||
def execute_tteck_script(script_url: str, env_variables: list):
|
# def execute_tteck_script(script_url: str, env_variables: list):
|
||||||
"""Execute TTECK script with already filled environment variables to run silently (non-interactive)
|
# """Execute TTECK script with already filled environment variables to run silently (non-interactive)
|
||||||
|
#
|
||||||
Parameters
|
# Parameters
|
||||||
----------
|
# ----------
|
||||||
script_url: str
|
# script_url: str
|
||||||
script url (github or other)
|
# script url (github or other)
|
||||||
env_variables: list
|
# env_variables: list
|
||||||
list of environment variables
|
# list of environment variables
|
||||||
|
#
|
||||||
Returns
|
# Returns
|
||||||
-------
|
# -------
|
||||||
int
|
# int
|
||||||
status code
|
# status code
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
env_variables = " ".join(env_variables)
|
# env_variables = " ".join(env_variables)
|
||||||
|
#
|
||||||
run_command_on_pve(command=f"{env_variables} && bash -c \"$(wget -qLO - {script_url}\"", warn_exit_status=True)
|
# run_command_on_pve(command=f"{env_variables} && bash -c \"$(wget -qLO - {script_url}\"")
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
@ -100,54 +107,62 @@ def get_config():
|
|||||||
return json.loads(file.read())
|
return json.loads(file.read())
|
||||||
|
|
||||||
|
|
||||||
def run_command_on_pve(command: str, warn_exit_status: bool = False, only_code: bool = False, local: bool = False):
|
def run_command_on_pve(command: str, return_status_code: bool = False, exception_on_exit: bool = True,
|
||||||
"""Run command on the Proxmox VE host
|
exception_on_empty_stdout: bool = False,
|
||||||
|
force_local: bool = False, shell: bool = False):
|
||||||
|
"""Run a command on the Proxmox VE host
|
||||||
|
The default behavior is as follows :
|
||||||
|
- runs the command on the Proxmox VE host
|
||||||
|
- throws an exception if the exit status is not 0
|
||||||
|
- returns the stdout of the command
|
||||||
|
-- if the stdout is None or empty, throws an exception
|
||||||
|
|
||||||
|
If you don't except an output or depend on the status code returned by the command, you can set return_status_code to True:
|
||||||
|
- runs the command on the Proxmox VE host
|
||||||
|
- returns the status code of the command
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
command: str
|
command: str
|
||||||
command to run
|
command to run
|
||||||
warn_exit_status: bool, optional
|
return_status_code: bool, optional
|
||||||
should an exception be thrown if the exit status is not 0
|
should it return the exit code and not the stdout, disables exception_on_exit
|
||||||
only_code: bool, optional
|
exception_on_exit: bool, optional
|
||||||
should it only return the exit code and not the stdout
|
should an exception be thrown if the exit status code is not 0
|
||||||
local: bool, optional
|
even though it's True by default, if you use return_status_code, it is disabled
|
||||||
|
exception_on_empty_stdout: bool, optional
|
||||||
|
should an exception be thrown if the stdout is None or empty
|
||||||
|
force_local: bool, optional
|
||||||
should it be run locally not via ssh even with local mode off in the pve section of config.json
|
should it be run locally not via ssh even with local mode off in the pve section of config.json
|
||||||
|
shell: bool, optional
|
||||||
|
should the command be run in a shell or not, see python subprocess documentation for more informations
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
str
|
str
|
||||||
command output by default
|
command output by default
|
||||||
int
|
int
|
||||||
command exit code if only_code is True
|
command exit code if return_status_code is True
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
Exception
|
Exception
|
||||||
if the exit status is not 0 and warn_exit_status is True
|
if the exit status is not 0 and exception_on_exit is True
|
||||||
|
Exception
|
||||||
|
if the stdout is None and return_status_code is False
|
||||||
|
Exception
|
||||||
|
if the stdout is empty and exception_on_empty_stdout is True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get config
|
# Get config
|
||||||
config = get_config()
|
config = get_config()
|
||||||
|
|
||||||
# Check if PVE is local or remote
|
# Check if PVE is local or remote
|
||||||
if config['pve']['local'] or local:
|
if config['pve']['local'] or force_local:
|
||||||
# Run command and return output (not as bytes)
|
# Run command and return output (not as bytes)
|
||||||
logging.debug(f"Running command on PVE/Machine running this script (locally): {command}")
|
logging.debug(f"Running command on PVE/Machine running this script (locally): {command}")
|
||||||
|
|
||||||
command = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
command = subprocess.run(command, shell=shell, capture_output=True, encoding="utf-8")
|
||||||
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 only_code:
|
|
||||||
return command.returncode
|
|
||||||
|
|
||||||
return command.stdout.decode().rstrip()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
host = config['pve']['host']
|
host = config['pve']['host']
|
||||||
@ -158,20 +173,28 @@ def run_command_on_pve(command: str, warn_exit_status: bool = False, only_code:
|
|||||||
logging.debug(f"Running command on PVE (ssh): {command}")
|
logging.debug(f"Running command on PVE (ssh): {command}")
|
||||||
|
|
||||||
# catch errors code
|
# catch errors code
|
||||||
command = subprocess.run(f'ssh {username}@{host} -p {port} "{command}"', shell=True,
|
command = subprocess.run(f'ssh {username}@{host} -p {port} "{command}"', shell=shell, capture_output=True,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8")
|
encoding="utf-8")
|
||||||
|
|
||||||
if command.returncode != 0 and warn_exit_status:
|
if command.returncode != 0 and exception_on_exit and not return_status_code:
|
||||||
logging.error(f"Error while running command on PVE: \n{command.stderr}")
|
raise Exception(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:
|
if return_status_code:
|
||||||
return command.returncode
|
return command.returncode
|
||||||
|
|
||||||
return command.stdout.rstrip()
|
if (command.stdout is None or command.stdout == "") and exception_on_empty_stdout:
|
||||||
|
raise Exception(
|
||||||
|
f"Error, no output from command on PVE, try using the command with return_status_code instead: \n{command.stderr}")
|
||||||
|
|
||||||
|
if command.stdout is None or command.stdout == "":
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if type(command.stdout) == bytes:
|
||||||
|
return command.stdout.decode().rstrip()
|
||||||
|
return command.stdout.rstrip()
|
||||||
|
|
||||||
|
|
||||||
def run_command_locally(command: str, warn_exit_status: bool = False, only_code: bool = False):
|
def run_command_locally(command: str, return_status_code: bool = False, exception_on_exit: bool = False):
|
||||||
"""Force running command locally even if local mode is off in config.json
|
"""Force running command locally even if local mode is off in config.json
|
||||||
|
|
||||||
See Also
|
See Also
|
||||||
@ -179,11 +202,12 @@ def run_command_locally(command: str, warn_exit_status: bool = False, only_code:
|
|||||||
run_command_on_pve
|
run_command_on_pve
|
||||||
"""
|
"""
|
||||||
|
|
||||||
run_command_on_pve(command=command, warn_exit_status=warn_exit_status, only_code=only_code, local=True)
|
run_command_on_pve(command=command, exception_on_exit=exception_on_exit, return_status_code=return_status_code,
|
||||||
|
force_local=True)
|
||||||
|
|
||||||
|
|
||||||
def run_command_ssh(command: str, host: str, username: str, port: int = 22, warn_exit_status: bool = False,
|
def run_command_ssh(command: str, host: str, username: str, port: int = 22, return_status_code: bool = False,
|
||||||
only_code: bool = False):
|
exception_on_exit: bool = False, exception_on_empty_stdout: bool = False, shell: bool = False):
|
||||||
"""Run command on a remote host via SSH
|
"""Run command on a remote host via SSH
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -196,17 +220,21 @@ def run_command_ssh(command: str, host: str, username: str, port: int = 22, warn
|
|||||||
username to use to connect to the host (usually root for lxc and vm)
|
username to use to connect to the host (usually root for lxc and vm)
|
||||||
port: int, optional
|
port: int, optional
|
||||||
port to use to connect to the host (default: 22)
|
port to use to connect to the host (default: 22)
|
||||||
warn_exit_status: bool, optional
|
exception_on_exit: bool, optional
|
||||||
should an exception be thrown if the exit status is not 0
|
should an exception be thrown if the exit status is not 0
|
||||||
only_code: bool, optional
|
exception_on_empty_stdout: bool, optional
|
||||||
|
should an exception be thrown if the stdout is empty or none
|
||||||
|
return_status_code: bool, optional
|
||||||
should it only return the exit code and not the stdout
|
should it only return the exit code and not the stdout
|
||||||
|
shell: bool, optional
|
||||||
|
should the command be run in a shell (default: True)
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
str
|
str
|
||||||
command output by default
|
command output by default
|
||||||
int
|
int
|
||||||
command exit code if only_code is True
|
command exit code if return_status_code is True
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -214,13 +242,12 @@ def run_command_ssh(command: str, host: str, username: str, port: int = 22, warn
|
|||||||
|
|
||||||
# catch errors code
|
# catch errors code
|
||||||
command = subprocess.run(f'ssh {username}@{host} -p {port} "{command}"', shell=True,
|
command = subprocess.run(f'ssh {username}@{host} -p {port} "{command}"', shell=True,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8")
|
capture_output=True, encoding="utf-8")
|
||||||
|
|
||||||
if command.returncode != 0 and warn_exit_status:
|
if command.returncode != 0 and exception_on_exit:
|
||||||
logging.error(f"Error while running command on PVE: \n{command.stderr}")
|
|
||||||
raise Exception(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:
|
if return_status_code:
|
||||||
return command.returncode
|
return command.returncode
|
||||||
|
|
||||||
return command.stdout.rstrip()
|
return command.stdout.rstrip()
|
||||||
@ -328,7 +355,7 @@ def get_ssh_public_key():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: maybe implement custom path for the key
|
# TODO: maybe implement custom path for the key
|
||||||
return run_command_on_pve(command="cat ~/.ssh/id_rsa.pub", warn_exit_status=True)
|
return run_command_on_pve(command="cat ~/.ssh/id_rsa.pub")
|
||||||
|
|
||||||
|
|
||||||
def copy_file_to_pve(path: Path, destination: str):
|
def copy_file_to_pve(path: Path, destination: str):
|
||||||
@ -346,23 +373,29 @@ def copy_file_to_pve(path: Path, destination: str):
|
|||||||
>>> copy_file_to_pve(Path("/home/user/file.txt"), "/root/file.txt")
|
>>> copy_file_to_pve(Path("/home/user/file.txt"), "/root/file.txt")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# create the destination directory if it does not exist
|
||||||
|
run_command_on_pve(command=f"mkdir -p {destination.rsplit('/', 1)[0]}", return_status_code=True)
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
run_command_locally(f"scp {str(path)} {config['pve']['user']}@{config['pve']['host']}:{destination}")
|
|
||||||
|
# copy the file to the PVE from the local machine
|
||||||
|
run_command_locally(command=f"scp {str(path)} {config['pve']['user']}@{config['pve']['host']}:{destination}",
|
||||||
|
return_status_code=True)
|
||||||
|
|
||||||
|
|
||||||
def copy_file_to_lxc(lxc_id: int, path: Path, destination: str):
|
def copy_file_to_lxc(lxc: LXC, path: Path, destination: str or Path):
|
||||||
"""Copy a local file (on the machine running this script) to the PVE and finally to the LXC
|
"""Copy a local file (on the machine running this program) to the PVE and finally to the LXC
|
||||||
It works by copying the script from the local machine to the PVE using SCP and
|
It works by copying the file from the local machine to the PVE using SCP and
|
||||||
then using "pct push" to send it to the LXC.
|
then using "pct push" to send it to the LXC.
|
||||||
Sometimes SSH is not installed on freshs LXC, so by using pct push we are sure that it will work.
|
Sometimes SSH is not installed on freshs LXC, so by using pct push we are sure that it will work.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
lxc_id: int
|
lxc: LXC
|
||||||
LXC ID
|
LXC object to copy the file to
|
||||||
path: pathlib.Path
|
path: pathlib.Path
|
||||||
Path object to file on local machine
|
Path object to file on local machine
|
||||||
destination: str
|
destination: str or pathlib.Path
|
||||||
Destination on LXC
|
Destination on LXC
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
@ -370,8 +403,19 @@ def copy_file_to_lxc(lxc_id: int, path: Path, destination: str):
|
|||||||
>>> copy_file_to_lxc(100, Path("/home/user/file.txt"), "/root/")
|
>>> copy_file_to_lxc(100, Path("/home/user/file.txt"), "/root/")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# define path for temp file on pve
|
||||||
file = path.name
|
file = path.name
|
||||||
temp_path = f"/tmp/proxmoxdeployscripts/{file}"
|
temp_path = f"/tmp/pdj-temp/{file}"
|
||||||
|
|
||||||
|
# convert destination to string if it is a Path object
|
||||||
|
if isinstance(destination, Path):
|
||||||
|
destination = str(destination.as_posix())
|
||||||
|
|
||||||
|
# copy file to pve from local machine
|
||||||
copy_file_to_pve(path, temp_path)
|
copy_file_to_pve(path, temp_path)
|
||||||
run_command_on_pve(f"pct push {lxc_id} {temp_path} {destination}")
|
|
||||||
|
# make sure to create destination folder on lxc
|
||||||
|
commands_utils.create_folder_in_lxc(lxc, destination)
|
||||||
|
|
||||||
|
# copy/push file from pve to lxc
|
||||||
|
run_command_on_pve(f"pct push {lxc.get_id()} {temp_path} {destination}{file}", return_status_code=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user