Skip to content

Commit

Permalink
[pyoutline] Standardize config env vars and paths. (#1074)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcipriano committed Feb 27, 2022
1 parent bdbd8c9 commit 6da47ba
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 17 deletions.
104 changes: 87 additions & 17 deletions pyoutline/outline/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
# pylint: enable=wrong-import-position

import getpass
import logging
import os
import pathlib
import platform
import tempfile

import six
Expand All @@ -40,27 +42,95 @@
else:
ConfigParser = configparser.ConfigParser


__all__ = ["config"]
__all__ = ['config', 'read_config_from_disk']
__file_path__ = pathlib.Path(__file__)

PYOUTLINE_ROOT_DIR = __file_path__.parent.parent
DEFAULT_USER_DIR = pathlib.Path(tempfile.gettempdir()) / 'opencue' / 'outline' / getpass.getuser()
# Environment variables which can be used to define a custom config file.
__CONFIG_FILE_ENV_VARS = [
# OUTLINE_CONFIG_FILE is the preferred setting to use.
'OUTLINE_CONFIG_FILE',
# OL_CONFIG is deprecated, but kept for now for backwards compatibility.
'OL_CONFIG',
]


logger = logging.getLogger("outline.config")


def config_base_directory():
"""Returns the OpenCue config base directory.
This platform-dependent directory, stored within your user profile, is used by
OpenCue components as the default location for various configuration files. Typically
if you store your config files in this location, there is no need to set environment
variables to indicate where your config files are located -- OpenCue should recognize
them automatically.
NOTE: This work is ongoing. Over time more OpenCue components will start using this
base directory. See https://github.com/AcademySoftwareFoundation/OpenCue/issues/785.
:rtype: str
:return: config file base directory
"""
if platform.system() == 'Windows':
return os.path.join(os.path.expandvars('%APPDATA%'), 'opencue')
return os.path.join(os.path.expanduser('~'), '.config', 'opencue')


def read_config_from_disk():
"""Loads configuration settings from config file on the local system.
The configuration file used is, in order of preference:
- Path defined by the OUTLINE_CONFIG_FILE environment variable.
- Path defined by the OL_CONFIG environment variable.
- Path within the config base directory (i.e. ~/.config/opencue/outline.cfg)
- The default outline.cfg file which is distributed with the outline library.
:rtype: ConfigParser
:return: config settings
"""
pyoutline_root_dir = __file_path__.parent.parent
default_user_dir = pathlib.Path(
tempfile.gettempdir()) / 'opencue' / 'outline' / getpass.getuser()

_config = ConfigParser()
config_file = None

for config_file_env_var in __CONFIG_FILE_ENV_VARS:
logger.debug('Checking for outline config file path in %s', config_file_env_var)
config_file_from_env = os.environ.get(config_file_env_var)
if config_file_from_env and os.path.exists(config_file_from_env):
config_file = config_file_from_env
break

if not config_file:
config_from_user_profile = os.path.join(config_base_directory(), 'outline.cfg')
logger.debug('Checking for outline config at %s', config_from_user_profile)
if os.path.exists(config_from_user_profile):
config_file = config_from_user_profile

if not config_file:
default_config_paths = [__file_path__.parent.parent.parent / 'etc' / 'outline.cfg',
__file_path__.parent.parent / 'etc' / 'outline.cfg']
for default_config_path in default_config_paths:
logger.info('Loading default outline config from %s', default_config_path)
if default_config_path.exists():
config_file = default_config_path
break

if not config_file:
raise FileNotFoundError('outline config file was not found')

_config.read(str(config_file))

config = ConfigParser()
# Add defaults to the config,if they were not specified.
if not _config.get('outline', 'home'):
_config.set('outline', 'home', str(pyoutline_root_dir))

default_config_paths = [__file_path__.parent.parent.parent / 'etc' / 'outline.cfg',
__file_path__.parent.parent / 'etc' / 'outline.cfg']
default_config_path = None
for default_config_path in default_config_paths:
if default_config_path.exists():
break
if not _config.get('outline', 'user_dir'):
_config.set('outline', 'user_dir', str(default_user_dir))

config.read(os.environ.get("OL_CONFIG", str(default_config_path)))
return _config

# Add defaults to the config,if they were not specified.
if not config.get('outline', 'home'):
config.set('outline', 'home', str(PYOUTLINE_ROOT_DIR))

if not config.get('outline', 'user_dir'):
config.set('outline', 'user_dir', str(DEFAULT_USER_DIR))
config = read_config_from_disk()
139 changes: 139 additions & 0 deletions pyoutline/tests/config_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env python

# Copyright Contributors to the OpenCue Project
#
# 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
#
# http://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.

"""Tests for the outline.config module."""

import getpass
import os.path
import unittest

import mock
import pyfakefs.fake_filesystem_unittest

import opencue
import outline
# The local import is necessary as `outline.config` will point to the ConfigParser after the
# first import.
from outline.config import read_config_from_disk


USER_CONFIG = '''
[outline]
home = /some/users/home/dir
session_dir = {HOME}/.opencue/sessions
wrapper_dir = %(home)s/wrappers
user_dir = /arbitrary/user/dir
spec_version = 1.9
facility = cloud
[plugin:local]
module=outline.plugins.local
enable=1
'''


class ConfigTest(pyfakefs.fake_filesystem_unittest.TestCase):
def setUp(self):
self.setUpPyfakefs()
self.fs.add_real_file(
os.path.join(os.path.dirname(opencue.__file__), 'default.yaml'), read_only=True)
if 'OL_CONFIG' in os.environ:
del os.environ['OL_CONFIG']
if 'OUTLINE_CONFIG_FILE' in os.environ:
del os.environ['OUTLINE_CONFIG_FILE']

@mock.patch('tempfile.gettempdir', new=mock.Mock(return_value='/path/to/tmp/dir'))
def test__should_load_default_values(self):
self.assertIsNone(os.environ.get('OL_CONF'))
self.assertIsNone(os.environ.get('OUTLINE_CONFIG_FILE'))
self.fs.add_real_file(
os.path.join(os.path.dirname(os.path.dirname(outline.__file__)), 'etc', 'outline.cfg'),
read_only=True)

config = read_config_from_disk()

default_home = os.path.dirname(os.path.dirname(__file__))
self.assertEqual(default_home, config.get('outline', 'home'))
self.assertEqual('{HOME}/.opencue/sessions', config.get('outline', 'session_dir'))
self.assertEqual(
os.path.join(default_home, 'wrappers'), config.get('outline', 'wrapper_dir'))
self.assertEqual(
'/path/to/tmp/dir/opencue/outline/%s' % getpass.getuser(),
config.get('outline', 'user_dir'))
self.assertEqual(
os.path.join(default_home, 'bin'), config.get('outline', 'bin_dir'))
self.assertEqual('cue', config.get('outline', 'backend'))
self.assertEqual('local', config.get('outline', 'facility'))
self.assertEqual('example.com', config.get('outline', 'domain'))
self.assertEqual('2', config.get('outline', 'maxretries'))
self.assertEqual('testing', config.get('outline', 'default_show'))
self.assertEqual('default', config.get('outline', 'default_shot'))
self.assertEqual('outline.plugins.local', config.get('plugin:local', 'module'))
self.assertEqual('1', config.get('plugin:local', 'enable'))

def test__should_load_user_config_from_env_var(self):
config_file_path = '/path/to/outline.cfg'
self.fs.create_file(config_file_path, contents=USER_CONFIG)
os.environ['OUTLINE_CONFIG_FILE'] = config_file_path

config = read_config_from_disk()

custom_home = '/some/users/home/dir'
self.assertEqual(custom_home, config.get('outline', 'home'))
self.assertEqual('{HOME}/.opencue/sessions', config.get('outline', 'session_dir'))
self.assertEqual(
os.path.join(custom_home, 'wrappers'), config.get('outline', 'wrapper_dir'))
self.assertEqual('/arbitrary/user/dir', config.get('outline', 'user_dir'))
self.assertEqual('1.9', config.get('outline', 'spec_version'))
self.assertEqual('cloud', config.get('outline', 'facility'))

def test__should_load_user_config_from_legacy_env_var(self):
config_file_path = '/path/to/outline.cfg'
self.fs.create_file(config_file_path, contents=USER_CONFIG)
os.environ['OL_CONFIG'] = config_file_path

config = read_config_from_disk()

custom_home = '/some/users/home/dir'
self.assertEqual(custom_home, config.get('outline', 'home'))
self.assertEqual('{HOME}/.opencue/sessions', config.get('outline', 'session_dir'))
self.assertEqual(
os.path.join(custom_home, 'wrappers'), config.get('outline', 'wrapper_dir'))
self.assertEqual('/arbitrary/user/dir', config.get('outline', 'user_dir'))
self.assertEqual('1.9', config.get('outline', 'spec_version'))
self.assertEqual('cloud', config.get('outline', 'facility'))

@mock.patch('platform.system', new=mock.Mock(return_value='Linux'))
@mock.patch('os.path.expanduser', new=mock.Mock(return_value='/home/username'))
def test__should_load_user_config_from_user_profile(self):
config_file_path = '/home/username/.config/opencue/outline.cfg'
self.fs.create_file(config_file_path, contents=USER_CONFIG)
os.environ['OL_CONFIG'] = config_file_path

config = read_config_from_disk()

custom_home = '/some/users/home/dir'
self.assertEqual(custom_home, config.get('outline', 'home'))
self.assertEqual('{HOME}/.opencue/sessions', config.get('outline', 'session_dir'))
self.assertEqual(
os.path.join(custom_home, 'wrappers'), config.get('outline', 'wrapper_dir'))
self.assertEqual('/arbitrary/user/dir', config.get('outline', 'user_dir'))
self.assertEqual('1.9', config.get('outline', 'spec_version'))
self.assertEqual('cloud', config.get('outline', 'facility'))


if __name__ == '__main__':
unittest.main()

0 comments on commit 6da47ba

Please sign in to comment.