Files
chart/library/ix-dev/charts/minio/migrations/migrate

121 lines
4.0 KiB
Python
Executable File

#!/usr/bin/python3
import json
import os
import sys
import subprocess
from pathlib import Path
from middlewared.client import Client
from middlewared.service import ValidationErrors, CallError
def path_in_locked_datasets(path: str) -> bool:
with Client() as c:
return c.call('pool.dataset.path_in_locked_datasets', path)
def get_configured_user_group(path: str) -> dict:
with Client() as c:
return c.call('filesystem.stat', path)
def get_host_path_attachments(path: str) -> set:
with Client() as c:
return {
attachment['type']
for attachment in c.call('pool.dataset.attachments_with_path', path)
if attachment['type'].lower() not in ['kubernetes', 'chart releases']
}
def get_kubernetes_config() -> dict:
with Client() as c:
return c.call('kubernetes.config')
def validate_host_path(path: str, schema_name: str, verrors: ValidationErrors) -> None:
"""
These validations are taken from `FilesystemService._common_perm_path_validate`.
Including an additional validation that makes sure all the children under
a path are on same device.
"""
schema_name += ".migration.chown"
p = Path(path)
if not p.is_absolute():
verrors.add(schema_name, f"Must be an absolute path: {path}")
if p.is_file():
verrors.add(schema_name, f"Recursive operations on a file are invalid: {path}")
if not p.absolute().as_posix().startswith("/mnt/"):
verrors.add(
schema_name,
f"Changes to permissions on paths that are not beneath the directory /mnt are not permitted: {path}"
)
elif len(p.resolve().parents) == 2:
verrors.add(schema_name, f"The specified path is a ZFS pool mountpoint: {path}")
# Make sure that dataset is not locked
if path_in_locked_datasets(path):
verrors.add(schema_name, f"Dataset is locked at path: {path}.")
# Validate attachments
if attachments := get_host_path_attachments(path):
verrors.add(schema_name, f"The path '{path}' is already attached to service(s): {', '.join(attachments)}.")
# Make sure all the minio's data directory children are on same device.
device_id = os.stat(path).st_dev
for root, dirs, files in os.walk(path):
for child in dirs + files:
abs_path = os.path.join(root, child)
if os.stat(abs_path).st_dev != device_id:
verrors.add(
schema_name,
(f"All the children of MinIO data directory should be on "
f"same device as root: path={abs_path} device={os.stat(abs_path).st_dev}")
)
break
def migrate(values: dict) -> dict:
# minio user / group ID
uid = gid = 473
verrors = ValidationErrors()
k8s_config = get_kubernetes_config()
if values["appVolumeMounts"]["export"]["hostPathEnabled"]:
host_path = values["appVolumeMounts"]["export"]["hostPath"]
else:
app_dataset = values["appVolumeMounts"]["export"]["datasetName"]
host_path = os.path.join(
"/mnt", k8s_config['dataset'], "releases", values["release_name"], "volumes/ix_volumes", app_dataset
)
current_config = get_configured_user_group(host_path)
if current_config["uid"] == uid and current_config["gid"] == gid:
return values
validate_host_path(host_path, values['release_name'], verrors)
verrors.check()
# chown the host path
acltool = subprocess.run([
"/usr/bin/nfs4xdr_winacl",
"-a", "chown",
"-O", str(uid), "-G", str(gid),
"-r",
"-c", host_path,
"-p", host_path], check=False, capture_output=True
)
if acltool.returncode != 0:
raise CallError(f"acltool [chown] on path {host_path} failed with error: [{acltool.stderr.decode().strip()}]")
return values
if __name__ == "__main__":
if len(sys.argv) != 2:
exit(1)
if os.path.exists(sys.argv[1]):
with open(sys.argv[1], "r") as f:
print(json.dumps(migrate(json.loads(f.read()))))