Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added string interpolation to molecule.yml (#709)
Allows the embedding of environment variables inside molecule.yml. Originally requested in #643. Fixes: #707
- Loading branch information
Showing
6 changed files
with
225 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# Copyright 2015 Docker, Inc. | ||
# | ||
# 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. | ||
|
||
# Taken from Docker Compose: | ||
# https://github.com/docker/compose/blob/master/compose/config/interpolation.py | ||
|
||
import string | ||
|
||
|
||
class InvalidInterpolation(Exception): | ||
def __init__(self, string): | ||
self.string = string | ||
|
||
|
||
class Interpolator(object): | ||
""" | ||
Configuration options may contain environment variables. For example, | ||
suppose the shell contains `VERIFIER_NAME=testinfra` and the following | ||
molecule.yml is supplied. | ||
.. code-block:: yaml | ||
verifier: | ||
- name: ${VERIFIER_NAME} | ||
Molecule will substitute `$VERIFIER_NAME` with the value of the | ||
`VERIFIER_NAME` environment variable. | ||
.. warning:: | ||
If an environment variable is not set, Molecule substitutes with an empty | ||
string. | ||
Both `$VARIABLE` and `${VARIABLE}` syntax are supported. Extended | ||
shell-style features, such as `${VARIABLE-default}` and | ||
`${VARIABLE:-default}` are also supported. | ||
If a literal dollar sign is needed in a configuration, use a double dollar | ||
sign (`$$`). | ||
""" | ||
|
||
def __init__(self, templater, mapping): | ||
self.templater = templater | ||
self.mapping = mapping | ||
|
||
def interpolate(self, string): | ||
try: | ||
return self.templater(string).substitute(self.mapping) | ||
except ValueError: | ||
raise InvalidInterpolation(string) | ||
|
||
|
||
class TemplateWithDefaults(string.Template): | ||
idpattern = r'[_a-z][_a-z0-9]*(?::?-[^}]+)?' | ||
|
||
# Modified from python2.7/string.py | ||
def substitute(self, mapping): | ||
# Helper function for .sub() | ||
def convert(mo): | ||
# Check the most common path first. | ||
named = mo.group('named') or mo.group('braced') | ||
if named is not None: | ||
if ':-' in named: | ||
var, _, default = named.partition(':-') | ||
return mapping.get(var) or default | ||
if '-' in named: | ||
var, _, default = named.partition('-') | ||
return mapping.get(var, default) | ||
val = mapping.get(named, '') | ||
return '%s' % (val, ) | ||
if mo.group('escaped') is not None: | ||
return self.delimiter | ||
if mo.group('invalid') is not None: | ||
self._invalid(mo) | ||
raise ValueError('Unrecognized named group in pattern', | ||
self.pattern) | ||
|
||
return self.pattern.sub(convert, self.template) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,8 +21,8 @@ | |
import os | ||
|
||
import pytest | ||
import yaml | ||
|
||
from molecule import util | ||
from molecule import config | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# Copyright 2015 Docker, Inc. | ||
# | ||
# 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. | ||
|
||
import pytest | ||
|
||
from molecule import interpolation | ||
|
||
|
||
@pytest.fixture | ||
def mock_env(): | ||
return { | ||
'FOO': 'foo', | ||
'BAR': '', | ||
'DEPENDENCY_NAME': 'galaxy', | ||
'VERIFIER_NAME': 'testinfra' | ||
} | ||
|
||
|
||
@pytest.fixture | ||
def interpolator_instance(mock_env): | ||
return interpolation.Interpolator(interpolation.TemplateWithDefaults, | ||
mock_env).interpolate | ||
|
||
|
||
def test_escaped_interpolation(interpolator_instance): | ||
assert '${foo}' == interpolator_instance('$${foo}') | ||
|
||
|
||
def test_invalid_interpolation(interpolator_instance): | ||
with pytest.raises(interpolation.InvalidInterpolation): | ||
interpolator_instance('${') | ||
with pytest.raises(interpolation.InvalidInterpolation): | ||
interpolator_instance('$}') | ||
with pytest.raises(interpolation.InvalidInterpolation): | ||
interpolator_instance('${}') | ||
with pytest.raises(interpolation.InvalidInterpolation): | ||
interpolator_instance('${ }') | ||
with pytest.raises(interpolation.InvalidInterpolation): | ||
interpolator_instance('${ foo}') | ||
with pytest.raises(interpolation.InvalidInterpolation): | ||
interpolator_instance('${foo }') | ||
with pytest.raises(interpolation.InvalidInterpolation): | ||
interpolator_instance('${foo!}') | ||
|
||
|
||
def test_interpolate_missing_no_default(interpolator_instance): | ||
assert 'This var' == interpolator_instance('This ${missing} var') | ||
assert 'This var' == interpolator_instance('This ${BAR} var') | ||
|
||
|
||
def test_interpolate_with_value(interpolator_instance): | ||
assert 'This foo var' == interpolator_instance('This $FOO var') | ||
assert 'This foo var' == interpolator_instance('This ${FOO} var') | ||
|
||
|
||
def test_interpolate_missing_with_default(interpolator_instance): | ||
assert 'ok def' == interpolator_instance('ok ${missing:-def}') | ||
assert 'ok def' == interpolator_instance('ok ${missing-def}') | ||
assert 'ok /non:-alphanumeric' == interpolator_instance( | ||
'ok ${BAR:-/non:-alphanumeric}') | ||
|
||
|
||
def test_interpolate_with_empty_and_default_value(interpolator_instance): | ||
assert 'ok def' == interpolator_instance('ok ${BAR:-def}') | ||
assert 'ok ' == interpolator_instance('ok ${BAR-def}') | ||
|
||
|
||
def test_interpolate_with_molecule_yaml(interpolator_instance): | ||
data = """ | ||
--- | ||
dependency: | ||
name: $DEPENDENCY_NAME | ||
driver: | ||
name: docker | ||
lint: | ||
name: ansible-lint | ||
platforms: | ||
- name: instance-1 | ||
provisioner: | ||
name: ansible | ||
scenario: | ||
name: default | ||
verifier: | ||
name: ${VERIFIER_NAME} | ||
options: | ||
$FOO: bar | ||
""".strip() | ||
|
||
x = """ | ||
--- | ||
dependency: | ||
name: galaxy | ||
driver: | ||
name: docker | ||
lint: | ||
name: ansible-lint | ||
platforms: | ||
- name: instance-1 | ||
provisioner: | ||
name: ansible | ||
scenario: | ||
name: default | ||
verifier: | ||
name: testinfra | ||
options: | ||
foo: bar | ||
""".strip() | ||
|
||
assert x == interpolator_instance(data) |