123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- #!/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()))))
|