mirror of
https://github.com/MAGICGrants/truenas-apps.git
synced 2026-01-09 20:47:58 -05:00
update lib and hashes
This commit is contained in:
@@ -1,50 +1,51 @@
|
||||
app_version: "1.40.2.8395"
|
||||
app_version: 1.40.2.8395
|
||||
capabilities:
|
||||
- name: CHOWN
|
||||
description: Plex is able to chown files.
|
||||
- name: FOWNER
|
||||
description: Plex is able to bypass permission checks for it's sub-processes.
|
||||
- name: DAC_OVERRIDE
|
||||
description: Plex is able to bypass permission checks.
|
||||
- name: SETGID
|
||||
description: Plex is able to set group ID for it's sub-processes.
|
||||
- name: SETUID
|
||||
description: Plex is able to set user ID for it's sub-processes.
|
||||
- name: KILL
|
||||
description: Plex is able to kill processes.
|
||||
- description: Plex is able to chown files.
|
||||
name: CHOWN
|
||||
- description: Plex is able to bypass permission checks for it's sub-processes.
|
||||
name: FOWNER
|
||||
- description: Plex is able to bypass permission checks.
|
||||
name: DAC_OVERRIDE
|
||||
- description: Plex is able to set group ID for it's sub-processes.
|
||||
name: SETGID
|
||||
- description: Plex is able to set user ID for it's sub-processes.
|
||||
name: SETUID
|
||||
- description: Plex is able to kill processes.
|
||||
name: KILL
|
||||
categories:
|
||||
- media
|
||||
description: Plex is a media server that allows you to stream your media to any Plex client.
|
||||
- media
|
||||
description: Plex is a media server that allows you to stream your media to any Plex
|
||||
client.
|
||||
home: https://plex.tv
|
||||
host_mounts: []
|
||||
icon: https://media.sys.truenas.net/apps/plex/icons/icon.png
|
||||
keywords:
|
||||
- plex
|
||||
- media
|
||||
- entertainment
|
||||
- movies
|
||||
- series
|
||||
- tv
|
||||
- streaming
|
||||
- plex
|
||||
- media
|
||||
- entertainment
|
||||
- movies
|
||||
- series
|
||||
- tv
|
||||
- streaming
|
||||
lib_version: 1.0.0
|
||||
lib_version_hash: ""
|
||||
lib_version_hash: 7275a78ff384fab4e8a7c41791d2e0b8e244cb71ed7956ccffffa9455d840da0
|
||||
maintainers:
|
||||
- email: dev@ixsystems.com
|
||||
name: truenas
|
||||
url: https://www.truenas.com/
|
||||
- email: dev@ixsystems.com
|
||||
name: truenas
|
||||
url: https://www.truenas.com/
|
||||
name: plex
|
||||
run_as_context:
|
||||
- description: Plex runs as root user.
|
||||
user_name: root
|
||||
uid: 0
|
||||
group_name: root
|
||||
gid: 0
|
||||
- description: Plex runs as root user.
|
||||
gid: 0
|
||||
group_name: root
|
||||
uid: 0
|
||||
user_name: root
|
||||
screenshots:
|
||||
- https://media.sys.truenas.net/apps/plex/screenshots/screenshot1.png
|
||||
- https://media.sys.truenas.net/apps/plex/screenshots/screenshot2.png
|
||||
- https://media.sys.truenas.net/apps/plex/screenshots/screenshot1.png
|
||||
- https://media.sys.truenas.net/apps/plex/screenshots/screenshot2.png
|
||||
sources:
|
||||
- https://plex.tv
|
||||
- https://hub.docker.com/r/plexinc/pms-docker
|
||||
- https://plex.tv
|
||||
- https://hub.docker.com/r/plexinc/pms-docker
|
||||
title: Plex
|
||||
train: charts
|
||||
version: 1.0.0
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from . import utils
|
||||
|
||||
|
||||
def envs(app={}, user=[]):
|
||||
track_env = {**app}
|
||||
result = {**app}
|
||||
|
||||
if not user:
|
||||
user = []
|
||||
elif isinstance(user, dict):
|
||||
user = [{"name": k, "value": v} for k, v in user.items()]
|
||||
else:
|
||||
utils.throw_error(f"Unsupported type for user environment variables [{type(user)}]")
|
||||
|
||||
for k in app.keys():
|
||||
if not k:
|
||||
utils.throw_error("Environment variable name cannot be empty.")
|
||||
|
||||
for item in user:
|
||||
if not item.get("name", None):
|
||||
utils.throw_error("Environment variable name cannot be empty.")
|
||||
if item.get("name", None) in track_env:
|
||||
utils.throw_error(f"Environment variable [{k}] is already defined from the application developer.")
|
||||
track_env[item["name"]] = item.get("value", None)
|
||||
result[item["name"]] = item.get("value", None)
|
||||
return result
|
||||
@@ -0,0 +1,39 @@
|
||||
from . import utils
|
||||
|
||||
|
||||
def check_health(test, interval=10, timeout=10, retries=5, start_period=30):
|
||||
if not test:
|
||||
utils.throw_error("Expected [test] to be set")
|
||||
|
||||
return {
|
||||
"test": test,
|
||||
"interval": f"{interval}s",
|
||||
"timeout": f"{timeout}s",
|
||||
"retries": retries,
|
||||
"start_period": f"{start_period}s",
|
||||
}
|
||||
|
||||
|
||||
def pg_test(user, db, host="127.0.0.1", port=5432):
|
||||
if not user or not db:
|
||||
utils.throw_error("Postgres container: [user] and [db] must be set")
|
||||
return f"pg_isready -h {host} -p {port} -d {db} -U {user}"
|
||||
|
||||
|
||||
def curl_test(url):
|
||||
if not url:
|
||||
utils.throw_error("Curl test: [url] must be set")
|
||||
return f"curl --silent --output /dev/null --show-error --fail {url}"
|
||||
|
||||
|
||||
def wget_test(url):
|
||||
if not url:
|
||||
utils.throw_error("Wget test: [url] must be set")
|
||||
return f"wget --spider --quiet {url}"
|
||||
|
||||
|
||||
def http_test(port, path, host="127.0.0.1"):
|
||||
if not port or not path:
|
||||
utils.throw_error("Expected [port] and [path] to be set")
|
||||
|
||||
return f"/bin/bash -c 'exec {{health_check_fd}}<>/dev/tcp/{host}/{port} && echo -e \"GET {path} HTTP/1.1\\r\\nHost: {host}\\r\\nConnection: close\\r\\n\\r\\n\" >&$${{health_check_fd}} && cat <&$${{health_check_fd}}'"
|
||||
18
ix-dev/charts/plex/templates/library/base_v1_0_0/network.py
Normal file
18
ix-dev/charts/plex/templates/library/base_v1_0_0/network.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from . import utils
|
||||
|
||||
|
||||
def dns_opts(dns_opts=[]):
|
||||
if not dns_opts:
|
||||
return []
|
||||
|
||||
tracked = {}
|
||||
disallowed_opts = []
|
||||
for opt in dns_opts:
|
||||
key = opt.split(":")[0]
|
||||
if key in tracked:
|
||||
utils.throw_error(f"Expected [dns_opts] to be unique, got [{', '.join([d.split(':')[0] for d in tracked])}]")
|
||||
if key in disallowed_opts:
|
||||
utils.throw_error(f"Expected [dns_opts] to not contain [{key}] key.")
|
||||
tracked[key] = opt
|
||||
|
||||
return dns_opts
|
||||
40
ix-dev/charts/plex/templates/library/base_v1_0_0/ports.py
Normal file
40
ix-dev/charts/plex/templates/library/base_v1_0_0/ports.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from . import utils
|
||||
import ipaddress
|
||||
|
||||
|
||||
def must_valid_port(num: int):
|
||||
if num < 1 or num > 65535:
|
||||
utils.throw_error(f"Expected a valid port number, got [{num}]")
|
||||
|
||||
|
||||
def must_valid_ip(ip: str):
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
except ValueError:
|
||||
utils.throw_error(f"Expected a valid IP address, got [{ip}]")
|
||||
|
||||
|
||||
def must_valid_protocol(protocol: str):
|
||||
if protocol not in ["tcp", "udp"]:
|
||||
utils.throw_error(f"Expected a valid protocol, got [{protocol}]")
|
||||
|
||||
|
||||
def must_valid_mode(mode: str):
|
||||
if mode not in ["ingress", "host"]:
|
||||
utils.throw_error(f"Expected a valid mode, got [{mode}]")
|
||||
|
||||
|
||||
def get_port(port={}):
|
||||
must_valid_port(port["published"])
|
||||
must_valid_port(port["target"])
|
||||
must_valid_ip(port.get("host_ip", "0.0.0.0"))
|
||||
must_valid_protocol(port.get("protocol", "tcp"))
|
||||
must_valid_mode(port.get("mode", "ingress"))
|
||||
|
||||
return {
|
||||
"target": port["target"],
|
||||
"published": port["published"],
|
||||
"protocol": port.get("protocol", "tcp"),
|
||||
"mode": port.get("mode", "ingress"),
|
||||
"host_ip": port.get("host_ip", "0.0.0.0"),
|
||||
}
|
||||
34
ix-dev/charts/plex/templates/library/base_v1_0_0/postgres.py
Normal file
34
ix-dev/charts/plex/templates/library/base_v1_0_0/postgres.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from . import utils
|
||||
|
||||
|
||||
def pg_url(variant, host, user, password, dbname, port=5432):
|
||||
if not host:
|
||||
utils.throw_error("Expected [host] to be set")
|
||||
if not user:
|
||||
utils.throw_error("Expected [user] to be set")
|
||||
if not password:
|
||||
utils.throw_error("Expected [password] to be set")
|
||||
if not dbname:
|
||||
utils.throw_error("Expected [dbname] to be set")
|
||||
|
||||
if variant == "postgresql":
|
||||
return f"postgresql://{user}:{password}@{host}:{port}/{dbname}?sslmode=disable"
|
||||
elif variant == "postgres":
|
||||
return f"postgres://{user}:{password}@{host}:{port}/{dbname}?sslmode=disable"
|
||||
else:
|
||||
utils.throw_error(f"Expected [variant] to be one of [postgresql, postgres], got [{variant}]")
|
||||
|
||||
|
||||
def pg_env(user, password, dbname, port=5432):
|
||||
if not user:
|
||||
utils.throw_error("Expected [user] to be set for postgres")
|
||||
if not password:
|
||||
utils.throw_error("Expected [password] to be set for postgres")
|
||||
if not dbname:
|
||||
utils.throw_error("Expected [dbname] to be set for postgres")
|
||||
return {
|
||||
"POSTGRES_USER": user,
|
||||
"POSTGRES_PASSWORD": password,
|
||||
"POSTGRES_DB": dbname,
|
||||
"POSTGRES_PORT": port,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
from . import utils
|
||||
import re
|
||||
|
||||
|
||||
def resources(data):
|
||||
cpus = str(data.get("limits", {}).get("cpus", "2.0"))
|
||||
memory = str(data.get("limits", {}).get("memory", "4gb"))
|
||||
if not re.match(r"^[1-9][0-9]*(\.[0-9]+)?$", cpus):
|
||||
utils.throw_error(f"Expected cpus to be a number or a float, got [{cpus}]")
|
||||
if not re.match(r"^[1-9][0-9]*(([GMK]B?)|([gmk]b?))$", memory):
|
||||
raise ValueError(f"Expected memory to be a number with unit, got [{memory}]")
|
||||
|
||||
return {
|
||||
"limits": {
|
||||
"cpus": cpus,
|
||||
"memory": memory,
|
||||
},
|
||||
}
|
||||
16
ix-dev/charts/plex/templates/library/base_v1_0_0/security.py
Normal file
16
ix-dev/charts/plex/templates/library/base_v1_0_0/security.py
Normal file
@@ -0,0 +1,16 @@
|
||||
def get_caps(add=[], drop=[]):
|
||||
result = {"drop": drop or ["ALL"]}
|
||||
if add:
|
||||
result["add"] = add
|
||||
return result
|
||||
|
||||
|
||||
def get_sec_opts(add=[], remove=[]):
|
||||
result = ["no-new-privileges"]
|
||||
for opt in add:
|
||||
if opt not in result:
|
||||
result.append(opt)
|
||||
for opt in remove:
|
||||
if opt in result:
|
||||
result.remove(opt)
|
||||
return result
|
||||
303
ix-dev/charts/plex/templates/library/base_v1_0_0/storage.py
Normal file
303
ix-dev/charts/plex/templates/library/base_v1_0_0/storage.py
Normal file
@@ -0,0 +1,303 @@
|
||||
from . import utils
|
||||
import re
|
||||
|
||||
BIND_TYPES = ["host_path", "ix_volume"]
|
||||
VOL_TYPES = ["volume", "nfs", "cifs"]
|
||||
ALL_TYPES = BIND_TYPES + VOL_TYPES + ["tmpfs", "anonymous"]
|
||||
PROPAGATION_TYPES = ["shared", "slave", "private", "rshared", "rslave", "rprivate"]
|
||||
|
||||
|
||||
# Basic validation for a path (Expand later)
|
||||
def valid_path(path=""):
|
||||
if not path.startswith("/"):
|
||||
utils.throw_error(f"Expected path [{path}] to start with /")
|
||||
|
||||
# There is no reason to allow / as a path, either on host or in a container
|
||||
if path == "/":
|
||||
utils.throw_error(f"Expected path [{path}] to not be /")
|
||||
|
||||
return path
|
||||
|
||||
|
||||
# Returns a volume mount object (Used in container's "volumes" level)
|
||||
def vol_mount(data, ix_volumes=[]):
|
||||
vol_type = _get_docker_vol_type(data)
|
||||
|
||||
volume = {
|
||||
"type": vol_type,
|
||||
"target": valid_path(data.get("mount_path", "")),
|
||||
"read_only": data.get("read_only", False),
|
||||
}
|
||||
if vol_type == "bind": # Default create_host_path is true in short-syntax
|
||||
volume.update(_get_bind_vol_config(data, ix_volumes))
|
||||
elif vol_type == "volume":
|
||||
volume.update(_get_volume_vol_config(data))
|
||||
elif vol_type == "tmpfs":
|
||||
volume.update(_get_tmpfs_vol_config(data))
|
||||
elif vol_type == "anonymous":
|
||||
volume["type"] = "volume"
|
||||
volume.update(_get_anonymous_vol_config(data))
|
||||
|
||||
return volume
|
||||
|
||||
|
||||
def storage_item(data, ix_volumes=[], perm_opts={}):
|
||||
return {
|
||||
"vol_mount": vol_mount(data, ix_volumes),
|
||||
"vol": vol(data),
|
||||
"perms_item": perms_item(data, ix_volumes, perm_opts) if perm_opts else {},
|
||||
}
|
||||
|
||||
|
||||
def perms_item(data, ix_volumes, opts={}):
|
||||
if not data.get("auto_permissions"):
|
||||
return {}
|
||||
|
||||
if data.get("type") == "host_path":
|
||||
if data.get("host_path_config", {}).get("aclEnable", False):
|
||||
return {}
|
||||
if data.get("type") == "ix_volume":
|
||||
if data.get("ix_volume_config", {}).get("aclEnable", False):
|
||||
return {}
|
||||
|
||||
if not ix_volumes:
|
||||
ix_volumes = []
|
||||
|
||||
req_keys = ["mount_path", "mode", "uid", "gid"]
|
||||
for key in req_keys:
|
||||
if not opts.get(key):
|
||||
utils.throw_error(f"Expected opts passed to [perms_item] to have [{key}] key")
|
||||
|
||||
data.update({"mount_path": opts["mount_path"]})
|
||||
volume_mount = vol_mount(data, ix_volumes)
|
||||
|
||||
return {
|
||||
"vol_mount": volume_mount,
|
||||
"perm_dir": {
|
||||
"dir": volume_mount["target"],
|
||||
"mode": opts["mode"],
|
||||
"uid": opts["uid"],
|
||||
"gid": opts["gid"],
|
||||
"chmod": opts.get("chmod", ""),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _get_bind_vol_config(data, ix_volumes=[]):
|
||||
path = host_path(data, ix_volumes)
|
||||
if data.get("propagation", "rprivate") not in PROPAGATION_TYPES:
|
||||
utils.throw_error(f"Expected [propagation] to be one of [{', '.join(PROPAGATION_TYPES)}], got [{data['propagation']}]")
|
||||
|
||||
# https://docs.docker.com/storage/bind-mounts/#configure-bind-propagation
|
||||
return {"source": path, "bind": {"create_host_path": data.get("host_path_config", {}).get("create_host_path", True), "propagation": _get_valid_propagation(data)}}
|
||||
|
||||
|
||||
def _get_volume_vol_config(data):
|
||||
if not data.get("volume_name"):
|
||||
utils.throw_error("Expected [volume_name] to be set for [volume] type")
|
||||
|
||||
return {"source": data["volume_name"], "volume": _process_volume_config(data)}
|
||||
|
||||
|
||||
def _get_anonymous_vol_config(data):
|
||||
return {"volume": _process_volume_config(data)}
|
||||
|
||||
|
||||
mode_regex = re.compile(r"^0[0-7]{3}$")
|
||||
|
||||
|
||||
def _get_tmpfs_vol_config(data):
|
||||
tmpfs = {}
|
||||
config = data.get("tmpfs_config", {})
|
||||
|
||||
if config.get("size"):
|
||||
if not isinstance(config["size"], int):
|
||||
utils.throw_error("Expected [size] to be an integer for [tmpfs] type")
|
||||
if not config["size"] > 0:
|
||||
utils.throw_error("Expected [size] to be greater than 0 for [tmpfs] type")
|
||||
# Convert Mebibytes to Bytes
|
||||
tmpfs.update({"size": config["size"] * 1024 * 1024})
|
||||
|
||||
if config.get("mode"):
|
||||
if not mode_regex.match(str(config["mode"])):
|
||||
utils.throw_error(f"Expected [mode] to be a octal string for [tmpfs] type, got [{config['mode']}]")
|
||||
tmpfs.update({"mode": int(config["mode"], 8)})
|
||||
|
||||
return {"tmpfs": tmpfs}
|
||||
|
||||
|
||||
# Returns a volume object (Used in top "volumes" level)
|
||||
def vol(data):
|
||||
if not data or _get_docker_vol_type(data) != "volume":
|
||||
return {}
|
||||
|
||||
if not data.get("volume_name"):
|
||||
utils.throw_error("Expected [volume_name] to be set for [volume] type")
|
||||
|
||||
if data["type"] == "nfs":
|
||||
return {data["volume_name"]: _process_nfs(data)}
|
||||
elif data["type"] == "cifs":
|
||||
return {data["volume_name"]: _process_cifs(data)}
|
||||
else:
|
||||
return {data["volume_name"]: {}}
|
||||
|
||||
|
||||
def _is_host_path(data):
|
||||
return data.get("type") == "host_path"
|
||||
|
||||
|
||||
def _get_valid_propagation(data):
|
||||
if not data.get("propagation"):
|
||||
return "rprivate"
|
||||
if not data["propagation"] in PROPAGATION_TYPES:
|
||||
utils.throw_error(f"Expected [propagation] to be one of [{', '.join(PROPAGATION_TYPES)}], got [{data['propagation']}]")
|
||||
return data["propagation"]
|
||||
|
||||
|
||||
def _is_ix_volume(data):
|
||||
return data.get("type") == "ix_volume"
|
||||
|
||||
|
||||
# Returns the host path for a for either a host_path or ix_volume
|
||||
def host_path(data, ix_volumes=[]):
|
||||
path = ""
|
||||
if _is_host_path(data):
|
||||
path = _process_host_path_config(data)
|
||||
elif _is_ix_volume(data):
|
||||
path = _process_ix_volume_config(data, ix_volumes)
|
||||
else:
|
||||
utils.throw_error(f"Expected [_host_path] to be called only for types [host_path, ix_volume], got [{data['type']}]")
|
||||
|
||||
return valid_path(path)
|
||||
|
||||
|
||||
# Returns the type of storage as used in docker-compose
|
||||
def _get_docker_vol_type(data):
|
||||
if not data.get("type"):
|
||||
utils.throw_error("Expected [type] to be set for storage")
|
||||
|
||||
if data["type"] not in ALL_TYPES:
|
||||
utils.throw_error(f"Expected storage [type] to be one of {ALL_TYPES}, got [{data['type']}]")
|
||||
|
||||
if data["type"] in BIND_TYPES:
|
||||
return "bind"
|
||||
elif data["type"] in VOL_TYPES:
|
||||
return "volume"
|
||||
else:
|
||||
return data["type"]
|
||||
|
||||
|
||||
def _process_host_path_config(data):
|
||||
if data.get("host_path_config", {}).get("aclEnable", False):
|
||||
if not data["host_path_config"].get("acl", {}).get("path"):
|
||||
utils.throw_error("Expected [host_path_config.acl.path] to be set for [host_path] type with ACL enabled")
|
||||
return data["host_path_config"]["acl"]["path"]
|
||||
|
||||
if not data.get("host_path_config", {}).get("path"):
|
||||
utils.throw_error("Expected [host_path_config.path] to be set for [host_path] type")
|
||||
|
||||
return data["host_path_config"]["path"]
|
||||
|
||||
|
||||
def _process_volume_config(data):
|
||||
return {"nocopy": data.get("volume_config", {}).get("nocopy", False)}
|
||||
|
||||
|
||||
def _process_ix_volume_config(data, ix_volumes):
|
||||
path = ""
|
||||
if not data.get("ix_volume_config", {}).get("dataset_name"):
|
||||
utils.throw_error("Expected [ix_volume_config.dataset_name] to be set for [ix_volume] type")
|
||||
|
||||
if not ix_volumes:
|
||||
utils.throw_error("Expected [ix_volumes] to be set for [ix_volume] type")
|
||||
|
||||
ds = data["ix_volume_config"]["dataset_name"]
|
||||
for item in ix_volumes:
|
||||
# TODO: verify the "hostPath" key is the correct from middleware side
|
||||
# Ideally we would want to have the "dataset_name" in the dict, instead of doing this check below
|
||||
if item.get("hostPath", "").split("/")[-1] == ds:
|
||||
path = item["hostPath"]
|
||||
break
|
||||
|
||||
if not path:
|
||||
utils.throw_error(f"Expected [ix_volumes] to contain path for dataset with name [{ds}]")
|
||||
|
||||
return path
|
||||
|
||||
|
||||
# Constructs a volume object for a cifs type
|
||||
def _process_cifs(data):
|
||||
if not data.get("cifs_config"):
|
||||
utils.throw_error("Expected [cifs_config] to be set for [cifs] type")
|
||||
|
||||
required_keys = ["server", "path", "username", "password"]
|
||||
for key in required_keys:
|
||||
if not data["cifs_config"].get(key):
|
||||
utils.throw_error(f"Expected [{key}] to be set for [cifs] type")
|
||||
|
||||
opts = [f"user={data['cifs_config']['username']}", f"password={data['cifs_config']['password']}"]
|
||||
if data["cifs_config"].get("options"):
|
||||
if not isinstance(data["cifs_config"]["options"], list):
|
||||
utils.throw_error("Expected [cifs_config.options] to be a list for [cifs] type")
|
||||
|
||||
disallowed_opts = ["user", "password"]
|
||||
for opt in data["cifs_config"]["options"]:
|
||||
if not isinstance(opt, str):
|
||||
utils.throw_error("Expected [cifs_config.options] to be a list of strings for [cifs] type")
|
||||
|
||||
key = opt.split("=")[0]
|
||||
for disallowed in disallowed_opts:
|
||||
if key == disallowed:
|
||||
utils.throw_error(f"Expected [cifs_config.options] to not start with [{disallowed}] for [cifs] type")
|
||||
|
||||
opts.append(opt)
|
||||
|
||||
server = data["cifs_config"]["server"].lstrip("/")
|
||||
path = data["cifs_config"]["path"]
|
||||
volume = {
|
||||
"driver_opts": {
|
||||
"type": "cifs",
|
||||
"device": f"//{server}/{path}",
|
||||
"o": f"{','.join(opts)}",
|
||||
},
|
||||
}
|
||||
|
||||
return volume
|
||||
|
||||
|
||||
# Constructs a volume object for a nfs type
|
||||
def _process_nfs(data):
|
||||
if not data.get("nfs_config"):
|
||||
utils.throw_error("Expected [nfs_config] to be set for [nfs] type")
|
||||
|
||||
required_keys = ["server", "path"]
|
||||
for key in required_keys:
|
||||
if not data["nfs_config"].get(key):
|
||||
utils.throw_error(f"Expected [{key}] to be set for [nfs] type")
|
||||
|
||||
opts = [f"addr={data['nfs_config']['server']}"]
|
||||
if data["nfs_config"].get("options"):
|
||||
if not isinstance(data["nfs_config"]["options"], list):
|
||||
utils.throw_error("Expected [nfs_config.options] to be a list for [nfs] type")
|
||||
|
||||
disallowed_opts = ["addr"]
|
||||
for opt in data["nfs_config"]["options"]:
|
||||
if not isinstance(opt, str):
|
||||
utils.throw_error("Expected [nfs_config.options] to be a list of strings for [nfs] type")
|
||||
|
||||
key = opt.split("=")[0]
|
||||
for disallowed in disallowed_opts:
|
||||
if key == disallowed:
|
||||
utils.throw_error(f"Expected [nfs_config.options] to not start with [{disallowed}] for [nfs] type")
|
||||
|
||||
opts.append(opt)
|
||||
|
||||
volume = {
|
||||
"driver_opts": {
|
||||
"type": "nfs",
|
||||
"device": f":{data['nfs_config']['path']}",
|
||||
"o": f"{','.join(opts)}",
|
||||
},
|
||||
}
|
||||
|
||||
return volume
|
||||
18
ix-dev/charts/plex/templates/library/base_v1_0_0/utils.py
Normal file
18
ix-dev/charts/plex/templates/library/base_v1_0_0/utils.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import secrets
|
||||
import sys
|
||||
|
||||
|
||||
class TemplateException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def throw_error(message):
|
||||
# When throwing a known error, hide the traceback
|
||||
# This is because the error is also shown in the UI
|
||||
# and having a traceback makes it hard for user to read
|
||||
sys.tracebacklimit = 0
|
||||
raise TemplateException(message)
|
||||
|
||||
|
||||
def secure_string(length):
|
||||
return secrets.token_urlsafe(length)
|
||||
@@ -0,0 +1,48 @@
|
||||
{% from "macros/global/perms/script.sh.jinja" import process_dir_func %}
|
||||
|
||||
{# Takes a list of items to process #}
|
||||
{# Each item is a dictionary with the following keys: #}
|
||||
{# - dir: directory to process #}
|
||||
{# - mode: always, check. (
|
||||
always: Always changes ownership and permissions,
|
||||
check: Checks the top level dir, and only applies if there is a mismatch.
|
||||
) #}
|
||||
{# - uid: uid to change to #}
|
||||
{# - gid: gid to change to #}
|
||||
{# - chmod: chmod to change to (Optional, default is no change) #}
|
||||
{% macro perms_container(items=[]) %}
|
||||
image: bash
|
||||
user: root
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512m
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
command:
|
||||
- |
|
||||
{{- process_dir_func() | indent(4) }}
|
||||
{%- for item in items %}
|
||||
process_dir {{ item.dir }} {{ item.mode }} {{ item.uid }} {{ item.gid }} {{ item.chmod }}
|
||||
{%- endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{# Examples #}
|
||||
{# perms_container([
|
||||
{
|
||||
"dir": "/mnt/directories/dir1",
|
||||
"mode": "always",
|
||||
"uid": 500,
|
||||
"gid": 500,
|
||||
"chmod": "755",
|
||||
},
|
||||
{
|
||||
"dir": "/mnt/directories/dir2",
|
||||
"mode": "check",
|
||||
"uid": 500,
|
||||
"gid": 500,
|
||||
"chmod": "755",
|
||||
},
|
||||
]) #}
|
||||
@@ -0,0 +1,66 @@
|
||||
{#
|
||||
Don't forget to use double $ for shell variables,
|
||||
otherwise docker-compose will try to expand them
|
||||
#}
|
||||
|
||||
{% macro process_dir_func() %}
|
||||
function process_dir() {
|
||||
local dir=$$1
|
||||
local mode=$$2
|
||||
local uid=$$3
|
||||
local gid=$$4
|
||||
local chmod=$$5
|
||||
|
||||
local fix_owner="false"
|
||||
local fix_perms="false"
|
||||
|
||||
if [ ! -d "$$dir" ]; then
|
||||
echo "Path [$$dir] does is not a directory, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Current Ownership and Permissions on [$$dir]:"
|
||||
echo "chown: $$(stat -c "%u %g" "$$dir")"
|
||||
echo "chmod: $$(stat -c "%a" "$$dir")"
|
||||
|
||||
if [ "$$mode" = "always" ]; then
|
||||
fix_owner="true"
|
||||
fix_perms="true"
|
||||
fi
|
||||
|
||||
if [ "$$mode" = "check" ]; then
|
||||
if [ $$(stat -c %u "$$dir") -eq $$uid ] && [ $$(stat -c %g "$$dir") -eq $$gid ]; then
|
||||
echo "Ownership is correct. Skipping..."
|
||||
fix_owner="false"
|
||||
else
|
||||
echo "Ownership is incorrect. Fixing..."
|
||||
fix_owner="true"
|
||||
fi
|
||||
|
||||
if [ -n "$$chmod" ]; then
|
||||
if [ $$(stat -c %a "$$dir") -eq $$chmod ]; then
|
||||
echo "Permissions are correct. Skipping..."
|
||||
fix_perms="false"
|
||||
else
|
||||
echo "Permissions are incorrect. Fixing..."
|
||||
fix_perms="true"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$$fix_owner" = "true" ]; then
|
||||
echo "Changing ownership to $$uid:$$gid on: [$$dir]"
|
||||
chown -R "$$uid:$$gid" "$$dir"
|
||||
echo "Finished changing ownership"
|
||||
echo "Ownership after changes:"
|
||||
stat -c "%u %g" "$$dir"
|
||||
fi
|
||||
|
||||
if [ -n "$$chmod" ] && [ "$$fix_perms" = "true" ]; then
|
||||
echo "Changing permissions to $$chmod on: [$$dir]"
|
||||
chmod -R "$$chmod" "$$dir"
|
||||
echo "Finished changing permissions"
|
||||
echo "Permissions after changes:"
|
||||
stat -c "%a" "$$dir"
|
||||
fi
|
||||
}
|
||||
{% endmacro %}
|
||||
Reference in New Issue
Block a user