1#
2# (c) 2017 Michael De La Rue
3#
4# This file is part of Ansible
5#
6# Ansible is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# Ansible is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
18
19# Make coding more python3-ish
20from __future__ import (absolute_import, division, print_function)
21
22import pytest
23from copy import copy
24
25from ansible.errors import AnsibleError
26
27from ansible.plugins.lookup import aws_ssm
28
29try:
30    import boto3
31    from botocore.exceptions import ClientError
32except ImportError:
33    pytestmark = pytest.mark.skip("This test requires the boto3 and botocore Python libraries")
34
35simple_variable_success_response = {
36    'Parameters': [
37        {
38            'Name': 'simple_variable',
39            'Type': 'String',
40            'Value': 'simplevalue',
41            'Version': 1
42        }
43    ],
44    'InvalidParameters': [],
45    'ResponseMetadata': {
46        'RequestId': '12121212-3434-5656-7878-9a9a9a9a9a9a',
47        'HTTPStatusCode': 200,
48        'HTTPHeaders': {
49            'x-amzn-requestid': '12121212-3434-5656-7878-9a9a9a9a9a9a',
50            'content-type': 'application/x-amz-json-1.1',
51            'content-length': '116',
52            'date': 'Tue, 23 Jan 2018 11:04:27 GMT'
53        },
54        'RetryAttempts': 0
55    }
56}
57
58path_success_response = copy(simple_variable_success_response)
59path_success_response['Parameters'] = [
60    {'Name': '/testpath/too', 'Type': 'String', 'Value': 'simple_value_too', 'Version': 1},
61    {'Name': '/testpath/won', 'Type': 'String', 'Value': 'simple_value_won', 'Version': 1}
62]
63
64missing_variable_response = copy(simple_variable_success_response)
65missing_variable_response['Parameters'] = []
66missing_variable_response['InvalidParameters'] = ['missing_variable']
67
68some_missing_variable_response = copy(simple_variable_success_response)
69some_missing_variable_response['Parameters'] = [
70    {'Name': 'simple', 'Type': 'String', 'Value': 'simple_value', 'Version': 1},
71    {'Name': '/testpath/won', 'Type': 'String', 'Value': 'simple_value_won', 'Version': 1}
72]
73some_missing_variable_response['InvalidParameters'] = ['missing_variable']
74
75
76dummy_credentials = {}
77dummy_credentials['boto_profile'] = None
78dummy_credentials['aws_secret_key'] = "notasecret"
79dummy_credentials['aws_access_key'] = "notakey"
80dummy_credentials['aws_security_token'] = None
81dummy_credentials['region'] = 'eu-west-1'
82
83
84def test_lookup_variable(mocker):
85    lookup = aws_ssm.LookupModule()
86    lookup._load_name = "aws_ssm"
87
88    boto3_double = mocker.MagicMock()
89    boto3_double.Session.return_value.client.return_value.get_parameters.return_value = simple_variable_success_response
90    boto3_client_double = boto3_double.Session.return_value.client
91
92    with mocker.patch.object(boto3, 'session', boto3_double):
93        retval = lookup.run(["simple_variable"], {}, **dummy_credentials)
94    assert(retval[0] == "simplevalue")
95    boto3_client_double.assert_called_with('ssm', 'eu-west-1', aws_access_key_id='notakey',
96                                           aws_secret_access_key="notasecret", aws_session_token=None)
97
98
99def test_path_lookup_variable(mocker):
100    lookup = aws_ssm.LookupModule()
101    lookup._load_name = "aws_ssm"
102
103    boto3_double = mocker.MagicMock()
104    get_path_fn = boto3_double.Session.return_value.client.return_value.get_parameters_by_path
105    get_path_fn.return_value = path_success_response
106    boto3_client_double = boto3_double.Session.return_value.client
107
108    with mocker.patch.object(boto3, 'session', boto3_double):
109        args = copy(dummy_credentials)
110        args["bypath"] = 'true'
111        retval = lookup.run(["/testpath"], {}, **args)
112    assert(retval[0]["/testpath/won"] == "simple_value_won")
113    assert(retval[0]["/testpath/too"] == "simple_value_too")
114    boto3_client_double.assert_called_with('ssm', 'eu-west-1', aws_access_key_id='notakey',
115                                           aws_secret_access_key="notasecret", aws_session_token=None)
116    get_path_fn.assert_called_with(Path="/testpath", Recursive=False, WithDecryption=True)
117
118
119def test_return_none_for_missing_variable(mocker):
120    """
121    during jinja2 templates, we can't shouldn't normally raise exceptions since this blocks the ability to use defaults.
122
123    for this reason we return ```None``` for missing variables
124    """
125    lookup = aws_ssm.LookupModule()
126    lookup._load_name = "aws_ssm"
127
128    boto3_double = mocker.MagicMock()
129    boto3_double.Session.return_value.client.return_value.get_parameters.return_value = missing_variable_response
130
131    with mocker.patch.object(boto3, 'session', boto3_double):
132        retval = lookup.run(["missing_variable"], {}, **dummy_credentials)
133    assert(retval[0] is None)
134
135
136def test_match_retvals_to_call_params_even_with_some_missing_variables(mocker):
137    """
138    If we get a complex list of variables with some missing and some not, we still have to return a
139    list which matches with the original variable list.
140    """
141    lookup = aws_ssm.LookupModule()
142    lookup._load_name = "aws_ssm"
143
144    boto3_double = mocker.MagicMock()
145    boto3_double.Session.return_value.client.return_value.get_parameters.return_value = some_missing_variable_response
146
147    with mocker.patch.object(boto3, 'session', boto3_double):
148        retval = lookup.run(["simple", "missing_variable", "/testpath/won", "simple"], {}, **dummy_credentials)
149    assert(retval == ["simple_value", None, "simple_value_won", "simple_value"])
150
151
152error_response = {'Error': {'Code': 'ResourceNotFoundException', 'Message': 'Fake Testing Error'}}
153operation_name = 'FakeOperation'
154
155
156def test_warn_denied_variable(mocker):
157    lookup = aws_ssm.LookupModule()
158    lookup._load_name = "aws_ssm"
159
160    boto3_double = mocker.MagicMock()
161    boto3_double.Session.return_value.client.return_value.get_parameters.side_effect = ClientError(error_response, operation_name)
162
163    with pytest.raises(AnsibleError):
164        with mocker.patch.object(boto3, 'session', boto3_double):
165            lookup.run(["denied_variable"], {}, **dummy_credentials)
166