Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f5a6aa8
Initial changes to support pack override of enable parameter
amanda11 Dec 10, 2021
af455f6
Add UTs and make more generic
amanda11 Dec 10, 2021
6592a7c
Add global override
amanda11 Dec 16, 2021
8f4f4f8
Add changelog and black formatting
amanda11 Dec 16, 2021
d7a1435
Update test_content_loader.py
amanda11 Dec 19, 2021
e3c8352
Add review comments
amanda11 Jan 7, 2022
20fe84f
Merge branch 'add_override_support' of https://github.com/StackStorm/…
amanda11 Jan 7, 2022
6200478
Change level of logging so doesn't appear in output when do a st2ctl …
amanda11 Jan 31, 2022
2415056
Add output to reload to indicate number of resources where metadata i…
amanda11 Feb 7, 2022
9ce0662
Merge branch 'master' into add_override_support
amanda11 Feb 11, 2022
df920ab
Fix black error
amanda11 Feb 14, 2022
e8ea7a4
Fix flake8
amanda11 Feb 14, 2022
7938dce
Fix UT
amanda11 Feb 14, 2022
27da722
Fix UT
amanda11 Feb 14, 2022
3635f4c
Fix UT
amanda11 Feb 14, 2022
70035fc
Fix Black UT
amanda11 Feb 14, 2022
3810862
Apply suggestions from code review
amanda11 Feb 17, 2022
0f1d233
Merge branch 'master' into add_override_support
amanda11 Feb 28, 2022
455f890
Add reserved pack list name - currently just global
amanda11 Mar 4, 2022
ef1b2c2
Merge branch 'master' into add_override_support
amanda11 Mar 4, 2022
03671cf
Fix flake8 UT error
amanda11 Mar 4, 2022
d485fc4
Merge branch 'add_override_support' of https://github.com/StackStorm/…
amanda11 Mar 4, 2022
45c6bc9
Rename overrides global to _global
amanda11 Mar 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Added

Contributed by @khushboobhatia01

* Added support to override enabled parameter of resources. #5506

Contributed by Amanda McGuinness (@amanda11 Intive)

Fixed
~~~~~

Expand Down
3 changes: 3 additions & 0 deletions st2common/st2common/bootstrap/actionsregistrar.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ def _register_action(self, pack, action):
)
content["metadata_file"] = metadata_file

# Pass override information
self._override_loader.override(pack, "actions", content)

action_api = ActionAPI(**content)

try:
Expand Down
3 changes: 3 additions & 0 deletions st2common/st2common/bootstrap/aliasesregistrar.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ def _get_action_alias_db(
else:
content["metadata_file"] = metadata_file

# Pass override information
self._override_loader.override(pack, "aliases", content)

action_alias_api = ActionAliasAPI(**content)
action_alias_api.validate()
action_alias_db = ActionAliasAPI.to_model(action_alias_api)
Expand Down
2 changes: 2 additions & 0 deletions st2common/st2common/bootstrap/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from st2common import log as logging
from st2common.constants.pack import CONFIG_SCHEMA_FILE_NAME
from st2common.content.loader import MetaLoader
from st2common.content.loader import OverrideLoader
from st2common.content.loader import ContentPackLoader
from st2common.models.api.pack import PackAPI
from st2common.models.api.pack import ConfigSchemaAPI
Expand Down Expand Up @@ -68,6 +69,7 @@ def __init__(
self._fail_on_failure = fail_on_failure

self._meta_loader = MetaLoader()
self._override_loader = OverrideLoader()
self._pack_loader = ContentPackLoader()

# Maps runner name -> RunnerTypeDB
Expand Down
3 changes: 3 additions & 0 deletions st2common/st2common/bootstrap/rulesregistrar.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ def _register_rules_from_pack(self, pack, rules):
)
content["metadata_file"] = metadata_file

# Pass override information
self._override_loader.override(pack, "rules", content)

rule_api = RuleAPI(**content)
rule_api.validate()
rule_db = RuleAPI.to_model(rule_api)
Expand Down
3 changes: 3 additions & 0 deletions st2common/st2common/bootstrap/sensorsregistrar.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ def _register_sensor_from_pack(self, pack, sensor):
)
content["metadata_file"] = metadata_file

# Pass override information
self._override_loader.override(pack, "sensors", content)

sensors_dir = os.path.dirname(sensor_metadata_file_path)
sensor_file_path = os.path.join(sensors_dir, entry_point)
artifact_uri = "file://%s" % (sensor_file_path)
Expand Down
124 changes: 123 additions & 1 deletion st2common/st2common/content/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from yaml.parser import ParserError
import six

from oslo_config import cfg
from st2common import log as logging
from st2common.constants.meta import ALLOWED_EXTS
from st2common.constants.meta import PARSER_FUNCS
Expand All @@ -28,7 +29,7 @@
if six.PY2:
from io import open

__all__ = ["ContentPackLoader", "MetaLoader"]
__all__ = ["ContentPackLoader", "MetaLoader", "OverrideLoader"]

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -268,3 +269,124 @@ def _load(self, parser_func, file_path):
except ParserError:
LOG.exception("Failed loading content from %s.", file_path)
raise


class OverrideLoader(object):
"""
Class for loading pack override data
"""

# Mapping of permitted override types to resource name
ALLOWED_OVERRIDE_TYPES = {
"sensors": "class_name",
"actions": "name",
"rules": "name",
"aliases": "name",
}

ALLOWED_OVERRIDE_NAMES = [
"enabled",
]

def override(self, pack_name, type, content):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to avoid hijacking the type keyword and use resource_type instead?


"""
Loads override content for pack, and updates content

:param pack_name: Name of pack
:type pack_name: ``str``
:param type: Type of resource loading
:type type: ``str``
:param content: Content as loaded from meta information
:type content: ``object``

"""

if type not in self.ALLOWED_OVERRIDE_TYPES.keys():
raise ValueError(
f"Invalid override type of {type} attempted for pack {pack_name}"
)

override_dir = os.path.join(cfg.CONF.system.base_path, "configs/overrides")
# Apply global overrides
global_file = os.path.join(override_dir, "global.yaml")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should do something during pack install to make sure the pack name is not global so people don't shoot themselves in the foot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 Nice find of the corner case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cognifloyd I'll take a look at how best to do that, the alternative is to make the global something we can't use as a packname...

self._apply_override_file(global_file, pack_name, type, content, True)

# Apply pack overrides
override_file = os.path.join(override_dir, f"{pack_name}.yaml")
self._apply_override_file(override_file, pack_name, type, content, False)

return content

def _apply_override_file(
self, override_file, pack_name, type, content, global_file
):

"""
Loads override content from override file

:param override_file: Override filename
:type override_file: ``str``
:param pack_name: Name of pack
:type pack_name: ``str``
:param type: Type of resource loading
:type type: ``str``
:param content: Content as loaded from meta information
:type content: ``object``
:param global_file: Whether global file
:type global_file: ``bool``
"""

if not os.path.exists(override_file):
# No override file for pack
LOG.info(f"No override file {override_file} found")
return content

# Read override file
file_name, file_ext = os.path.splitext(override_file)
overrides = self._load(PARSER_FUNCS[file_ext], override_file)
# Apply overrides
if type in overrides.keys():
type_override = overrides[type]
name = content[self.ALLOWED_OVERRIDE_TYPES[type]]
if "defaults" in type_override.keys():
for key in type_override["defaults"].keys():
if key in self.ALLOWED_OVERRIDE_NAMES:
content[key] = type_override["defaults"][key]
LOG.info(
f"Overridden {type} {pack_name}.{name} {key} to default value of {content[key]} from {override_file}"
)
else:
raise ValueError(
f"Override attempted with invalid default key {key} in pack {pack_name}"
)

if global_file:
# No exceptions required in global content file
return content

if "exceptions" in type_override.keys():
if name in type_override["exceptions"]:
for key in type_override["exceptions"][name].keys():
if key in self.ALLOWED_OVERRIDE_NAMES:
content[key] = type_override["exceptions"][name][key]
LOG.info(
f"Overridden {type} {pack_name}.{name} {key} to exception value of {content[key]} from {override_file}"
)
else:
raise ValueError(
f"Override attempted with invalid exceptions key {key} in pack {pack_name}"
)

return content

def _load(self, parser_func, file_path):
with open(file_path, "r", encoding="utf-8") as fd:
try:
return parser_func(fd)
except ValueError:
LOG.exception("Failed loading content from %s.", file_path)
raise
except ParserError:
LOG.exception("Failed loading content from %s.", file_path)
raise
4 changes: 4 additions & 0 deletions st2common/tests/resources/configs/overrides/global.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
sensors:
defaults:
enabled: false
7 changes: 7 additions & 0 deletions st2common/tests/resources/configs/overrides/overpack1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
actions:
defaults:
enabled: False
exceptions:
action2:
enabled: True
11 changes: 11 additions & 0 deletions st2common/tests/resources/configs/overrides/overpack2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
actions:
defaults:
enabled: False
rubbish: False
exceptions:
action2:
enabled: True
sensors:
defaults:
enabled: True
11 changes: 11 additions & 0 deletions st2common/tests/resources/configs/overrides/overpack3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
actions:
defaults:
enabled: False
exceptions:
action2:
rubbish: True
sensors:
exceptions:
sensor1:
enabled: True
4 changes: 4 additions & 0 deletions st2common/tests/resources/configs/overrides/overpack4.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
actions:
defaults:
enabled: False
101 changes: 101 additions & 0 deletions st2common/tests/unit/test_content_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from __future__ import absolute_import

from oslo_config import cfg
import os

import unittest2
Expand All @@ -31,8 +32,10 @@
from mock import Mock

from st2common.content.loader import ContentPackLoader
from st2common.content.loader import OverrideLoader
from st2common.content.loader import LOG
from st2common.constants.meta import yaml_safe_load
from st2tests import config

CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
RESOURCES_DIR = os.path.abspath(os.path.join(CURRENT_DIR, "../resources"))
Expand Down Expand Up @@ -114,6 +117,104 @@ def test_get_content_from_pack_no_sensors(self):
)
self.assertEqual(result, None)

def test_get_override_action_from_default(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"name": "action1", "enabled": True}
loader.override("overpack1", "actions", content)
self.assertFalse(content["enabled"])
content = {"name": "action1", "enabled": False}
loader.override("overpack1", "actions", content)
self.assertFalse(content["enabled"])

def test_get_override_action_from_exception(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"name": "action2", "enabled": True}
loader.override("overpack1", "actions", content)
self.assertTrue(content["enabled"])
content = {"name": "action2", "enabled": False}
loader.override("overpack1", "actions", content)
self.assertTrue(content["enabled"])

def test_get_override_action_from_default_no_exceptions(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"name": "action1", "enabled": True}
loader.override("overpack4", "actions", content)
self.assertFalse(content["enabled"])
content = {"name": "action2", "enabled": True}
loader.override("overpack4", "actions", content)
self.assertFalse(content["enabled"])

def test_get_override_action_from_global_default_no_exceptions(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"class_name": "sensor1", "enabled": True}
loader.override("overpack1", "sensors", content)
self.assertFalse(content["enabled"])

def test_get_override_action_from_global_overridden_by_pack(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"class_name": "sensor1", "enabled": True}
loader.override("overpack2", "sensors", content)
self.assertTrue(content["enabled"])

def test_get_override_action_from_global_overridden_by_pack_exception(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"class_name": "sensor1", "enabled": True}
loader.override("overpack3", "sensors", content)
self.assertTrue(content["enabled"])

def test_get_override_invalid_type(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"name": "action2", "enabled": True}
self.assertRaises(
ValueError,
loader.override,
pack_name="overpack1",
type="wrongtype",
content=content,
)

def test_get_override_invalid_default_key(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"name": "action1", "enabled": True}
self.assertRaises(
ValueError,
loader.override,
pack_name="overpack2",
type="actions",
content=content,
)

def test_get_override_invalid_exceptions_key(self):
config.parse_args()
cfg.CONF.set_override(name="base_path", override=RESOURCES_DIR, group="system")
loader = OverrideLoader()
content = {"name": "action1", "enabled": True}
loader.override("overpack1", "actions", content)
content = {"name": "action2", "enabled": True}
self.assertRaises(
ValueError,
loader.override,
pack_name="overpack3",
type="actions",
content=content,
)


class YamlLoaderTestCase(unittest2.TestCase):
def test_yaml_safe_load(self):
Expand Down