Merge ~ballot/content-cache-charm/+git/content-cache-charm:site_unique into content-cache-charm:master

Proposed by Benjamin Allot
Status: Work in progress
Proposed branch: ~ballot/content-cache-charm/+git/content-cache-charm:site_unique
Merge into: content-cache-charm:master
Diff against target: 84 lines (+48/-3)
2 files modified
lib/utils.py (+38/-0)
reactive/content_cache.py (+10/-3)
Reviewer Review Type Date Requested Status
Content Cache Charmers Pending
Review via email: mp+383828@code.launchpad.net
To post a comment you must log in.
dcdc954... by Benjamin Allot

WIP

Unmerged commits

dcdc954... by Benjamin Allot

WIP

7971dd9... by Benjamin Allot

Duplicate keys of the same node level will now return an error.

This will block the charm.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/utils.py b/lib/utils.py
2index f06effc..c8ef458 100644
3--- a/lib/utils.py
4+++ b/lib/utils.py
5@@ -6,6 +6,14 @@ import re
6 import shutil
7 import subprocess
8
9+from yaml.constructor import ConstructorError
10+
11+try:
12+ from yaml import CLoader as Loader
13+except ImportError:
14+ from yaml import Loader
15+
16+
17 BASE_CACHE_PORT = 6080
18 BASE_BACKEND_PORT = 8080
19 BACKEND_PORT_LIMIT = 61000 # sysctl net.ipv4.ip_local_port_range
20@@ -218,3 +226,33 @@ def package_version(package):
21 if not version:
22 return None
23 return version
24+
25+
26+class SafeDuplicateKeysLoader(yaml.SafeLoader):
27+ """A YAML Loader refusing to load duplicate keys."""
28+
29+ def construct_mapping(self, node: yaml.nodes.MappingNode, deep=False: bool) -> dict:
30+ """Construct the dict without duplicate keys.
31+
32+ This is a redefinition of :py:function:`yaml.constructor.BaseConstructor.construct_mapping`.
33+ It throws a :py:class:`yaml.constructor.ConstructorError` exception if duplicate keys are present at any level.
34+
35+ :param node: The yaml representation to load.
36+ :param deep: Do a deep copy of the yaml node.
37+ :returns: The dictionary representing the yaml
38+
39+
40+
41+def no_duplicates_constructor(loader, node, deep=False):
42+ """Check for duplicate keys."""
43+
44+ mapping = {}
45+ for key_node, value_node in node.value:
46+ key = loader.construct_object(key_node, deep=deep)
47+ value = loader.construct_object(value_node, deep=deep)
48+ if key in mapping:
49+ raise ConstructorError("while constructing a mapping", node.start_mark,
50+ "found duplicate key (%s)" % key, key_node.start_mark)
51+ mapping[key] = value
52+
53+ return loader.construct_mapping(node, deep)
54diff --git a/reactive/content_cache.py b/reactive/content_cache.py
55index d396779..b80fb60 100644
56--- a/reactive/content_cache.py
57+++ b/reactive/content_cache.py
58@@ -22,6 +22,8 @@ from lib import haproxy as HAProxy
59
60 SYSCTL_CONF_PATH = '/etc/sysctl.d/90-content-cache.conf'
61
62+yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, utils.no_duplicates_constructor)
63+
64
65 @reactive.hook('upgrade-charm')
66 def upgrade_charm():
67@@ -580,9 +582,14 @@ def ports_map_lookup(ports_map, site, base_port, blacklist_ports=None, key=None)
68
69
70 def sites_from_config(sites_yaml, sites_secrets=None, blacklist_ports=None):
71- conf = yaml.safe_load(sites_yaml)
72- # 'configs' is special and used to host YAML anchors so let's remove it
73- conf.pop('configs', '')
74+ try:
75+ conf = yaml.safe_load(sites_yaml)
76+ # 'configs' is special and used to host YAML anchors so let's remove it
77+ conf.pop('configs', '')
78+ except yaml.constructor.ConstructorError as exc:
79+ hookenv.log(str(exc))
80+ return False
81+
82 sites = interpolate_secrets(conf, sites_secrets)
83 cache_port = 0
84 backend_port = 0

Subscribers

People subscribed via source and target branches