Skip to content

Commit

Permalink
update permission settings when installing
Browse files Browse the repository at this point in the history
  • Loading branch information
weaverba137 committed May 23, 2023
1 parent a15f4f9 commit 2580024
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 100 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ MANIFEST
# Other
.*.swp
*~
.vscode
.env

# Mac OSX
.DS_Store
10 changes: 8 additions & 2 deletions doc/desiInstall.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ Configure Module File
:command:`desiInstall` will scan :envvar:`WORKING_DIR` to determine the details that need
to be added to the module file. The final module file will then be written
into the DESI module directory at NERSC. If ``--default`` is specified
on the command line, an appropriate .version file will be created.
on the command line, an appropriate .version file will be created. Module
files are always installed with world-read permissions.

Load Module
-----------
Expand Down Expand Up @@ -342,7 +343,12 @@ The script itself is intended to be a thin wrapper on *e.g.*::
Fix Permissions
---------------

The script :command:`fix_permissions.sh` will be run on :envvar:`INSTALL_DIR`.
The permissions of :envvar:`INSTALL_DIR` will be recursively set to standard
values under these circumstances:

1. World-read, unless ``--no-world`` is specified on the command line.
2. Unwriteable to all, unless a branch install is being performed, in which
case user-write is set.

Clean Up
--------
Expand Down
4 changes: 4 additions & 0 deletions py/desiutil/iers.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ def _check_interpolate_indices(self, indices_orig, indices_clipped,
astropy.utils.iers.conf.auto_max_age = None
astropy.utils.iers.conf.iers_auto_url = 'frozen'
astropy.utils.iers.conf.iers_auto_url_mirror = 'frozen'
if ignore_warnings:
astropy.utils.iers.conf.iers_degraded_accuracy = 'ignore'
else:
astropy.utils.iers.conf.iers_degraded_accuracy = 'warn'
# Sanity check.
auto_class = astropy.utils.iers.IERS_Auto.open()
if auto_class is not iers:
Expand Down
69 changes: 37 additions & 32 deletions py/desiutil/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import os
import sys
import tarfile
import re
import stat
import shutil
import requests
from io import BytesIO
Expand Down Expand Up @@ -239,6 +239,9 @@ def get_options(self, test_args=None):
help='Print extra information.')
parser.add_argument('-V', '--version', action='version',
version='%(prog)s ' + desiutilVersion)
parser.add_argument('-W', '--no-world', action='store_false',
dest='world',
help='Disable world-readable installation.')
parser.add_argument('product', nargs='?',
default='NO PACKAGE',
help='Name of product to install.')
Expand Down Expand Up @@ -966,40 +969,42 @@ def verify_bootstrap(self):
return True

def permissions(self):
"""Fix possible install permission errors.
Returns
-------
:class:`int`
Status code returned by fix_permissions.sh script.
"""Set permissions on installed software.
"""
command = ['fix_permissions.sh']
if self.options.verbose:
command.append('-v')
if self.options.test:
command.append('-t')
command.append(self.install_dir)
self.log.debug(' '.join(command))
proc = Popen(command, universal_newlines=True,
stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
status = proc.returncode
self.log.debug(out)
read_file = stat.S_IRUSR | stat.S_IRGRP
if self.options.world:
read_file |= stat.S_IROTH
read_exec = read_file | stat.S_IXUSR | stat.S_IXGRP
if self.options.world:
read_exec |= stat.S_IXOTH
read_dir = read_exec | stat.S_ISGID
if self.is_branch:
read_file |= stat.S_IWUSR
read_exec |= stat.S_IWUSR
read_dir |= stat.S_IWUSR
#
# Remove write permission to avoid accidental changes
# Recursively set permissions from the bottom up.
#
if self.is_branch:
chmod_mode = 'g-w,o-w'
else:
chmod_mode = 'a-w'
command = ['chmod', '-R', chmod_mode, self.install_dir]
self.log.debug(' '.join(command))
proc = Popen(command, universal_newlines=True,
stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
chmod_status = proc.returncode
self.log.debug(out)
return status
for dirpath, dirnames, filenames in os.walk(self.install_dir, topdown=False):
for f in filenames:
fname = os.path.join(dirpath, f)
if os.path.islink(fname):
continue
executable = (stat.S_IMODE(os.stat(fname).st_mode) & stat.S_IXUSR) != 0
if executable:
self.log.debug("os.chmod('%s', %s)", fname, read_exec)
os.chmod(fname, read_exec)
else:
self.log.debug("os.chmod('%s', %s)", fname, read_exec)
os.chmod(fname, read_file)
for d in dirnames:
self.log.debug("os.chmod('%s', %s)", os.path.join(dirpath, d), read_dir)
os.chmod(os.path.join(dirpath, d), read_dir)
#
# Finally set permissions on the top directory.
#
self.log.debug("os.chmod('%s', %s)", self.install_dir, read_dir)
os.chmod(self.install_dir, read_dir)

def unlock_permissions(self):
"""Unlock installed directories to allow their removal.
Expand Down
50 changes: 23 additions & 27 deletions py/desiutil/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
from argparse import ArgumentParser
from shutil import which
from stat import S_IRUSR, S_IRGRP, S_IROTH
try:
from ConfigParser import SafeConfigParser
except ImportError:
from configparser import ConfigParser as SafeConfigParser
from configparser import ConfigParser
from pkg_resources import resource_filename
from . import __version__ as desiutilVersion
from .io import unlock_file
Expand Down Expand Up @@ -224,14 +221,14 @@ def configure_module(product, version, product_root, working_dir=None, dev=False
else:
module_keywords['needs_python'] = ''
if os.path.exists(os.path.join(working_dir, 'setup.cfg')):
conf = SafeConfigParser()
conf = ConfigParser()
conf.read([os.path.join(working_dir, 'setup.cfg')])
if conf.has_section('entry_points') or conf.has_section('options.entry_points'):
module_keywords['needs_bin'] = ''
return module_keywords


def process_module(module_file, module_keywords, module_dir, world=True):
def process_module(module_file, module_keywords, module_dir):
"""Process a Module file.
Parameters
Expand All @@ -242,25 +239,27 @@ def process_module(module_file, module_keywords, module_dir, world=True):
The parameters to use for Module file processing.
module_dir : :class:`str`
The directory where the Module file should be installed.
world : :class:`bool`, optional
Make module files world-readable.
Returns
-------
:class:`str`
The text of the processed Module file.
Note
----
Module files are always installed with world-read permissions.
"""
if not os.path.isdir(os.path.join(module_dir, module_keywords['name'])):
os.makedirs(os.path.join(module_dir, module_keywords['name']))
install_module_file = os.path.join(module_dir, module_keywords['name'],
module_keywords['version'])
with open(module_file) as m:
mod = m.read().format(**module_keywords)
_write_module_data(install_module_file, mod, world=world)
_write_module_data(install_module_file, mod)
return mod


def default_module(module_keywords, module_dir, world=True):
def default_module(module_keywords, module_dir):
"""Install or update a .version file to set the default Module.
Parameters
Expand All @@ -269,23 +268,25 @@ def default_module(module_keywords, module_dir, world=True):
The parameters to use for Module file processing.
module_dir : :class:`str`
The directory where the Module file should be installed.
world : :class:`bool`, optional
Make .version files world-readable.
Returns
-------
:class:`str`
The text of the processed .version file.
Note
----
.version files are always installed with world-read permissions.
"""
dot_template = '#%Module1.0\nset ModulesVersion "{version}"\n'
install_version_file = os.path.join(module_dir, module_keywords['name'],
'.version')
dot_version = dot_template.format(**module_keywords)
_write_module_data(install_version_file, dot_version, world=world)
_write_module_data(install_version_file, dot_version)
return dot_version


def _write_module_data(filename, data, world=True):
def _write_module_data(filename, data):
"""Write and permission-lock Module file data. This is intended
to consolidate some duplicated code.
Expand All @@ -295,14 +296,10 @@ def _write_module_data(filename, data, world=True):
The module file to write.
data : :class:`str`
The data to be written to `filename`.
world : :class:`bool`, optional
Make `filename` world-readable.
"""
with unlock_file(filename, 'w') as f:
f.write(data)
p = S_IRUSR | S_IRGRP
if world:
p |= S_IROTH
p = S_IRUSR | S_IRGRP | S_IROTH
os.chmod(filename, p)
return

Expand All @@ -319,19 +316,18 @@ def main():
prog=os.path.basename(sys.argv[0]))
parser.add_argument('-d', '--default', dest='default', action='store_true', help='Mark this Module as default.')
parser.add_argument('-m', '--modules', dest='modules', help='Set the Module install directory.')
parser.add_argument('-p', '--private', dest='world', action='store_false', help='Do not make module files world-readable.')
parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + desiutilVersion)
parser.add_argument('product', help='Name of product.')
parser.add_argument('product_version', help='Version of product.')
options = parser.parse_args()

if options.modules is None:
try:
self.modules = os.path.join('/global/common/software/desi',
os.environ['NERSC_HOST'],
'desiconda',
'current',
'modulefiles')
options.modules = os.path.join('/global/common/software/desi',
os.environ['NERSC_HOST'],
'desiconda',
'current',
'modulefiles')
except KeyError:
try:
options.modules = os.path.join(os.environ['DESI_PRODUCT_ROOT'],
Expand All @@ -353,9 +349,9 @@ def main():
log.warning("Could not find Module file: %s; using default.", module_file)
module_file = resource_filename('desiutil', 'data/desiutil.module')

process_module(module_file, module_keywords, options.modules, world=options.world)
process_module(module_file, module_keywords, options.modules)

if options.default:
default_module(module_keywords, options.modules, world=options.world)
default_module(module_keywords, options.modules)

return 0
15 changes: 15 additions & 0 deletions py/desiutil/test/test_iers.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@ def test_freeze_iers(self, mock_logger):
self.assertIsNone(astropy.utils.iers.conf.auto_max_age)
self.assertEqual(astropy.utils.iers.conf.iers_auto_url, 'frozen')
self.assertEqual(astropy.utils.iers.conf.iers_auto_url_mirror, 'frozen')
self.assertEqual(astropy.utils.iers.conf.iers_degraded_accuracy, 'ignore')
mock_logger().info.assert_has_calls([call('Freezing IERS table used by astropy time, coordinates.')])

@patch('desiutil.iers.get_logger')
def test_freeze_iers_ignore_warnings(self, mock_logger):
"""Test freezing from package data/, but allow warnings.
"""
i.freeze_iers(ignore_warnings=False)
future = Time('2024-01-01', location=self.location)
lst = future.sidereal_time('apparent')
self.assertFalse(astropy.utils.iers.conf.auto_download)
self.assertIsNone(astropy.utils.iers.conf.auto_max_age)
self.assertEqual(astropy.utils.iers.conf.iers_auto_url, 'frozen')
self.assertEqual(astropy.utils.iers.conf.iers_auto_url_mirror, 'frozen')
self.assertEqual(astropy.utils.iers.conf.iers_degraded_accuracy, 'warn')
mock_logger().info.assert_has_calls([call('Freezing IERS table used by astropy time, coordinates.')])

@patch('desiutil.iers.get_logger')
Expand Down
Loading

0 comments on commit 2580024

Please sign in to comment.