Skip to content

Commit e78894c

Browse files
committed
Declare yaml as a dependency with extensions
At the same time, the syntax for the !ENV and !ENVFILE tags is changed to just the env-var name. No need for ${} or $(). Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 022f989 commit e78894c

File tree

8 files changed

+80
-77
lines changed

8 files changed

+80
-77
lines changed

doc/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@ SATOSA is configured using YAML.
3434
All default configuration files, as well as an example WSGI application for the proxy, can be found
3535
in the [example directory](../example).
3636

37-
A configuration value that includes the tag !ENV will have a value of the form `${SOME_ENVIRONMENT_VARIABLE}`
37+
A configuration value that includes the tag !ENV will have a value of the form `SOME_ENVIRONMENT_VARIABLE`
3838
replaced with the value from the process environment variable of the same name. For example if the file
3939
`ldap_attribute_store.yaml' includes
4040

4141
```
42-
bind_password: !ENV ${LDAP_BIND_PASSWORD}
42+
bind_password: !ENV LDAP_BIND_PASSWORD
4343
```
4444

4545
and the SATOSA process environment includes the environment variable `LDAP_BIND_PASSWORD` with
46-
value `my_password` then the configuration for `bind_password` will be `my_password`.
46+
value `my_password` then the configuration value for `bind_password` will be `my_password`.
4747

4848

4949
## <a name="proxy_conf" style="color:#000000">SATOSA proxy configuration</a>: `proxy_conf.yaml.example`

example/plugins/microservices/ldap_attribute_store.yaml.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ config:
99
ldap_url: ldaps://ldap.example.org
1010
bind_dn: cn=admin,dc=example,dc=org
1111
# Obtain bind password from environment variable LDAP_BIND_PASSWORD.
12-
bind_password: !ENV ${LDAP_BIND_PASSWORD}
12+
bind_password: !ENV LDAP_BIND_PASSWORD
1313
# Obtain bind password from file pointed to by
1414
# environment variable LDAP_BIND_PASSWORD_FILE.
15-
# bind_password: !ENVFILE $(LDAP_BIND_PASSWORD)
15+
# bind_password: !ENVFILE LDAP_BIND_PASSWORD
1616
search_base: ou=People,dc=example,dc=org
1717
read_only: true
1818
auto_bind: true

src/satosa/satosa_config.py

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
import logging
55
import os
66
import os.path
7-
import re
87

9-
import yaml
8+
from satosa.exception import SATOSAConfigurationError
9+
from satosa.yaml import load as yaml_load
10+
from satosa.yaml import YAMLError
1011

11-
from .exception import SATOSAConfigurationError
1212

1313
logger = logging.getLogger(__name__)
1414

@@ -145,69 +145,11 @@ def _load_yaml(self, config_file):
145145
:param config_file: config to load. Can be file path or yaml string
146146
:return: Loaded config
147147
"""
148-
# Tag to indicate environment variable: !ENV
149-
tag_env = '!ENV'
150-
151-
# Pattern for environment variable: ${word}
152-
pattern_env = re.compile('.*?\${(\w+)}.*?')
153-
154-
yaml.SafeLoader.add_implicit_resolver(tag_env, pattern_env, None)
155-
156-
def constructor_env_variables(loader, node):
157-
"""
158-
Extracts the environment variable from the node's value.
159-
:param yaml.Loader loader: the yaml loader
160-
:param node: the current node in the yaml
161-
:return: value of the environment variable
162-
"""
163-
value = loader.construct_scalar(node)
164-
match = pattern_env.findall(value)
165-
if match:
166-
new_value = value
167-
for m in match:
168-
new_value = new_value.replace('${' + m + '}',
169-
os.environ.get(m, m))
170-
return new_value
171-
return value
172-
173-
yaml.SafeLoader.add_constructor(tag_env, constructor_env_variables)
174-
175-
# Tag to indicate file pointed to by environment variable: !ENVFILE
176-
tag_env_file = '!ENVFILE'
177-
178-
# Pattern for environment variable: $(word)
179-
pattern_env_file = re.compile('.*?\$\((\w+)\).*?')
180-
181-
yaml.SafeLoader.add_implicit_resolver(tag_env_file,
182-
pattern_env_file, None)
183-
184-
def constructor_envfile_variables(loader, node):
185-
"""
186-
Extracts the environment variable from the node's value.
187-
:param yaml.Loader loader: the yaml loader
188-
:param node: the current node in the yaml
189-
:return: value read from file pointed to by environment variable
190-
"""
191-
value = loader.construct_scalar(node)
192-
match = pattern_env_file.findall(value)
193-
if match:
194-
new_value = value
195-
for m in match:
196-
path = os.environ.get(m, '')
197-
if os.path.exists(path):
198-
with open(path, 'r') as f:
199-
new_value = new_value.replace('$(' + m + ')',
200-
f.read().strip())
201-
return new_value
202-
return value
203-
204-
yaml.SafeLoader.add_constructor(tag_env_file,
205-
constructor_envfile_variables)
206148

207149
try:
208150
with open(os.path.abspath(config_file)) as f:
209-
return yaml.safe_load(f.read())
210-
except yaml.YAMLError as exc:
151+
return yaml_load(f.read())
152+
except YAMLError as exc:
211153
logger.error("Could not parse config as YAML: {}".format(exc))
212154
if hasattr(exc, 'problem_mark'):
213155
mark = exc.problem_mark

src/satosa/yaml.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import os
2+
import re
3+
4+
from yaml import SafeLoader as _safe_loader
5+
from yaml import YAMLError
6+
from yaml import safe_load as load
7+
8+
9+
def _constructor_env_variables(loader, node):
10+
"""
11+
Extracts the environment variable from the node's value.
12+
:param yaml.Loader loader: the yaml loader
13+
:param node: the current node in the yaml
14+
:return: value of the environment variable
15+
"""
16+
raw_value = loader.construct_scalar(node)
17+
new_value = os.environ.get(raw_value)
18+
if new_value is None:
19+
msg = "Cannot construct value from {node}: {value}".format(
20+
node=node, value=new_value
21+
)
22+
raise YAMLError(msg)
23+
return new_value
24+
25+
26+
def _constructor_envfile_variables(loader, node):
27+
"""
28+
Extracts the environment variable from the node's value.
29+
:param yaml.Loader loader: the yaml loader
30+
:param node: the current node in the yaml
31+
:return: value read from file pointed to by environment variable
32+
"""
33+
raw_value = loader.construct_scalar(node)
34+
filepath = os.environ.get(raw_value)
35+
if filepath is None:
36+
msg = "Cannot construct value from {node}: {path}".format(
37+
node=node, path=filepath
38+
)
39+
raise YAMLError(msg)
40+
41+
try:
42+
with open(filepath, "r") as fd:
43+
new_value = fd.read()
44+
except (TypeError, IOError) as e:
45+
msg = "Cannot construct value from {node}: {path}".format(
46+
node=node, path=filepath
47+
)
48+
raise YAMLError(msg) from e
49+
else:
50+
return new_value
51+
52+
53+
TAG_ENV = "!ENV"
54+
TAG_ENV_FILE = "!ENVFILE"
55+
56+
57+
_safe_loader.add_constructor(TAG_ENV, _constructor_env_variables)
58+
_safe_loader.add_constructor(TAG_ENV_FILE, _constructor_envfile_variables)

tests/satosa/test_satosa_config.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,19 @@ def test_can_read_endpoint_configs_from_file(self, satosa_config_dict, modules_k
7878

7979
def test_can_substitute_from_environment_variable(self, monkeypatch):
8080
monkeypatch.setenv("SATOSA_COOKIE_STATE_NAME", "oatmeal_raisin")
81-
config = SATOSAConfig(os.path.join(TEST_RESOURCE_BASE_PATH,
82-
"proxy_conf_environment_test.yaml"))
81+
config = SATOSAConfig(
82+
os.path.join(TEST_RESOURCE_BASE_PATH, "proxy_conf_environment_test.yaml")
83+
)
8384

8485
assert config["COOKIE_STATE_NAME"] == 'oatmeal_raisin'
8586

8687
def test_can_substitute_from_environment_variable_file(self, monkeypatch):
87-
cookie_file = os.path.join(TEST_RESOURCE_BASE_PATH,
88-
'cookie_state_name')
88+
cookie_file = os.path.join(TEST_RESOURCE_BASE_PATH, 'cookie_state_name')
8989
monkeypatch.setenv("SATOSA_COOKIE_STATE_NAME_FILE", cookie_file)
90-
config = SATOSAConfig(os.path.join(TEST_RESOURCE_BASE_PATH,
91-
"proxy_conf_environment_file_test.yaml"))
90+
config = SATOSAConfig(
91+
os.path.join(
92+
TEST_RESOURCE_BASE_PATH, "proxy_conf_environment_file_test.yaml"
93+
)
94+
)
9295

9396
assert config["COOKIE_STATE_NAME"] == 'chocolate_chip'
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
chocolate_chip
1+
chocolate_chip

tests/test_resources/proxy_conf_environment_file_test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ STATE_ENCRYPTION_KEY: state_encryption_key
44

55
INTERNAL_ATTRIBUTES: {"attributes": {}}
66

7-
COOKIE_STATE_NAME: !ENVFILE $(SATOSA_COOKIE_STATE_NAME_FILE)
7+
COOKIE_STATE_NAME: !ENVFILE SATOSA_COOKIE_STATE_NAME_FILE
88

99
BACKEND_MODULES: []
1010
FRONTEND_MODULES: []

tests/test_resources/proxy_conf_environment_test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ STATE_ENCRYPTION_KEY: state_encryption_key
44

55
INTERNAL_ATTRIBUTES: {"attributes": {}}
66

7-
COOKIE_STATE_NAME: !ENV ${SATOSA_COOKIE_STATE_NAME}
7+
COOKIE_STATE_NAME: !ENV SATOSA_COOKIE_STATE_NAME
88

99
BACKEND_MODULES: []
1010
FRONTEND_MODULES: []

0 commit comments

Comments
 (0)