2022-03-10 14:26:22 +00:00
|
|
|
import os
|
2022-12-07 18:13:01 +00:00
|
|
|
import stat
|
2024-12-06 18:02:59 +00:00
|
|
|
from typing import Any, Optional, Tuple
|
|
|
|
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
2022-05-24 19:51:38 +01:00
|
|
|
|
|
|
|
import jinja2
|
2022-03-10 14:26:22 +00:00
|
|
|
|
|
|
|
|
2022-12-07 18:13:01 +00:00
|
|
|
class DeterministicZip:
|
|
|
|
"""
|
|
|
|
Create a zip file deterministically.
|
|
|
|
|
|
|
|
Heavily inspired by https://github.com/bboe/deterministic_zip.
|
|
|
|
"""
|
2024-12-06 18:15:47 +00:00
|
|
|
|
2022-12-07 18:13:01 +00:00
|
|
|
zipfile: ZipFile
|
|
|
|
|
|
|
|
def __init__(self, filename: str):
|
|
|
|
self.zipfile = ZipFile(filename, "w")
|
|
|
|
|
|
|
|
def __enter__(self) -> "DeterministicZip":
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
|
|
self.zipfile.close()
|
|
|
|
|
|
|
|
def add_file(self, path: str, contents: bytes) -> None:
|
|
|
|
permission = 0o555 if os.access(path, os.X_OK) else 0o444
|
|
|
|
zip_info = ZipInfo()
|
|
|
|
zip_info.filename = path
|
|
|
|
zip_info.date_time = (2022, 1, 1, 0, 0, 0)
|
|
|
|
zip_info.external_attr = (stat.S_IFREG | permission) << 16
|
|
|
|
self.zipfile.writestr(
|
|
|
|
zip_info,
|
|
|
|
contents,
|
|
|
|
compress_type=ZIP_DEFLATED,
|
|
|
|
compresslevel=9,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-11-28 21:18:56 +00:00
|
|
|
class BaseAutomation:
|
2022-05-15 18:47:46 +01:00
|
|
|
short_name: str = "base"
|
|
|
|
description: str = "Abstract base automation."
|
|
|
|
frequency: int
|
2022-11-28 18:55:10 +00:00
|
|
|
working_dir: Optional[str]
|
2022-05-15 18:47:46 +01:00
|
|
|
|
2022-05-08 13:01:15 +01:00
|
|
|
"""
|
|
|
|
The short name of the automation provider. This is used as an opaque token throughout
|
|
|
|
the portal system.
|
|
|
|
"""
|
|
|
|
|
2022-11-28 18:55:10 +00:00
|
|
|
def __init__(self, working_dir: Optional[str] = None):
|
|
|
|
super().__init__()
|
|
|
|
self.working_dir = working_dir
|
|
|
|
|
|
|
|
def automate(self, full: bool = False) -> Tuple[bool, str]:
|
2022-05-08 13:01:15 +01:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
2022-11-28 18:55:10 +00:00
|
|
|
def tmpl_write(self, filename: str, template: str, **kwargs: Any) -> None:
|
2022-05-24 19:51:38 +01:00
|
|
|
"""
|
|
|
|
Write a Jinja2 template to the working directory for use by an automation module.
|
|
|
|
|
|
|
|
:param filename: filename to write to
|
|
|
|
:param template: Jinja2 template
|
|
|
|
:param kwargs: variables for use with the template
|
|
|
|
:return: None
|
|
|
|
"""
|
2022-11-28 18:55:10 +00:00
|
|
|
if not self.working_dir:
|
|
|
|
raise RuntimeError("No working directory specified.")
|
2022-05-24 19:51:38 +01:00
|
|
|
tmpl = jinja2.Template(template)
|
2024-12-06 18:15:47 +00:00
|
|
|
with open(
|
|
|
|
os.path.join(self.working_dir, filename), "w", encoding="utf-8"
|
|
|
|
) as tfconf:
|
2022-06-23 13:42:45 +01:00
|
|
|
tfconf.write(tmpl.render(**kwargs))
|
2022-12-07 18:13:01 +00:00
|
|
|
|
2024-12-06 18:15:47 +00:00
|
|
|
def bin_write(
|
|
|
|
self, filename: str, data: bytes, group_id: Optional[int] = None
|
|
|
|
) -> None:
|
2022-12-07 18:13:01 +00:00
|
|
|
if not self.working_dir:
|
|
|
|
raise RuntimeError("No working directory specified.")
|
|
|
|
try:
|
|
|
|
os.mkdir(os.path.join(self.working_dir, str(group_id)))
|
|
|
|
except FileExistsError:
|
|
|
|
pass
|
2024-12-06 18:15:47 +00:00
|
|
|
with open(
|
|
|
|
os.path.join(self.working_dir, str(group_id) if group_id else "", filename),
|
|
|
|
"wb",
|
|
|
|
) as binfile:
|
2022-12-07 18:13:01 +00:00
|
|
|
binfile.write(data)
|