# Copyright 2013 – present by the SalishSeaCast Project contributors
# and The University of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# SPDX-License-Identifier: Apache-2.0
"""SalishSeaCast utility functions for use by workers.
"""
import grp
import logging
import logging.handlers
import os
import subprocess
from nemo_nowcast import WorkerError
from nemo_nowcast.fileutils import FilePerms
[docs]
def fix_perms(
path, mode=FilePerms(user="rw", group="rw", other="r").__int__(), grp_name=None
):
"""Try to set the permissions and group ownership of the file
or directory at path.
The desired permissions are given by mode.
If grp_name is given,
set the directory's gid to that associated with the grp_name.
In the event that the file or directory at path is owned by another
user the gid or permissions changes fail silently because they are
probably correct already.
:arg path: Path to fix the permissions of.
:type path: :py:class:`pathlib.Path` or str
:arg mode: Permissions to set for the path.
:type mode: int
:arg grp_name: Group name to change the path ownership to.
Defaults to None meaning do nothing.
:type grp_name: str
"""
try:
if grp_name is not None:
gid = grp.getgrnam(grp_name).gr_gid
os.chown(path, -1, gid)
os.chmod(path, mode)
except OSError:
# Can't change gid or mode of a directory we don't own
# but we just accept that
pass
[docs]
def mkdir(
path,
logger,
mode=FilePerms(user="rwx", group="rwx", other="rx").__int__(),
grp_name=None,
exist_ok=True,
):
"""Create a directory at path with its permissions set to mode.
If grp_name is given,
set the directory's gid to that associated with the grp_name.
If path already exists and exist_ok is False,
log an error messages and raise an exception.
In the event that the directory already exists at path but is owned by
another user the gid or permissions changes fail silently because they
are probably correct already.
:arg path: Path to create the directory at.
:type path: :py:class:`pathlib.Path` or str
:arg logger: Logger object.
:type logger: :class:`logging.Logger`
:arg mode: Permissions to set for the directory.
:type mode: int
:arg grp_name: Group name to change the directory's ownership to.
Defaults to None meaning that the directory's group
will be the same as its parent's.
:type grp_name: str
:arg exist_ok: Indicate whether to log and error message and
raise an exception if path already exists.
Defaults to True meaning that an existing path is
accepted silently.
:type exist_ok: boolean
:raises: :py:exc:`lib.WorkerError`
if path already exists and exist_ok is False
"""
try:
os.mkdir(path)
except OSError:
if not exist_ok:
msg = f"{path} directory already exists; not overwriting"
logger.error(msg)
raise WorkerError
fix_perms(path, mode, grp_name)
[docs]
def run_in_subprocess(cmd, output_logger, error_logger):
"""Run cmd in a subprocess and log its stdout to output_logger.
Catch errors from the subprocess, log them to error_logger,
and raise the exception for handling somewhere higher in the call stack.
:arg cmd: Command and its arguments/options to run in subprocess.
:type cmd: list
:arg output_logger: Logger object to send command output to when
command is successful.
:type output_logger: :meth:`logging.Logger` method
:arg error_logger: Logger object to send error message(s) to when
command returns non-zero status code.
:type error_logger: :meth:`logging.Logger` method
:raises: :py:exc:`nowcast.lib.WorkerError`
"""
output_logger(f"running command in subprocess: {cmd}")
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
for line in output.splitlines():
if line:
output_logger(line)
except subprocess.CalledProcessError as e:
error_logger(f"subprocess {cmd} failed with return code {e.returncode}")
for line in e.output.splitlines():
if line:
error_logger(line)
raise WorkerError