diff --git a/changelogs/fragments/837-aws_ssm-envars.yml b/changelogs/fragments/837-aws_ssm-envars.yml new file mode 100644 index 00000000000..4fb4cbdeaa2 --- /dev/null +++ b/changelogs/fragments/837-aws_ssm-envars.yml @@ -0,0 +1,5 @@ +minor_changes: +- aws_ssm - Add support for ``endpoint`` parameter (https://github.com/ansible-collections/amazon.aws/pull/837). + +bugfixes: +- aws_ssm - Fix environment variables for client configuration (e.g., AWS_PROFILE, AWS_ACCESS_KEY_ID) (https://github.com/ansible-collections/amazon.aws/pull/837). diff --git a/plugins/lookup/aws_ssm.py b/plugins/lookup/aws_ssm.py index fd02b377949..1c380636e7b 100644 --- a/plugins/lookup/aws_ssm.py +++ b/plugins/lookup/aws_ssm.py @@ -67,6 +67,10 @@ type: string choices: ['error', 'skip', 'warn'] version_added: 2.0.0 + endpoint: + description: Use a custom endpoint when connecting to SSM service. + type: string + version_added: 3.3.0 extends_documentation_fragment: - amazon.aws.aws_boto3 ''' @@ -135,6 +139,8 @@ from ansible.utils.display import Display from ansible.module_utils.six import string_types +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_conn +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import get_aws_connection_info from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO3 from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code @@ -142,31 +148,11 @@ display = Display() -def _boto3_conn(region, credentials): - if 'boto_profile' in credentials: - boto_profile = credentials.pop('boto_profile') - else: - boto_profile = None - - try: - connection = boto3.session.Session(profile_name=boto_profile).client('ssm', region, **credentials) - except (botocore.exceptions.ProfileNotFound, botocore.exceptions.PartialCredentialsError): - if boto_profile: - try: - connection = boto3.session.Session(profile_name=boto_profile).client('ssm', region) - # FIXME: we should probably do better passing on of the error information - except (botocore.exceptions.ProfileNotFound, botocore.exceptions.PartialCredentialsError): - raise AnsibleError("Insufficient credentials found.") - else: - raise AnsibleError("Insufficient credentials found.") - return connection - - class LookupModule(LookupBase): def run(self, terms, variables=None, boto_profile=None, aws_profile=None, aws_secret_key=None, aws_access_key=None, aws_security_token=None, region=None, bypath=False, shortnames=False, recursive=False, decrypt=True, on_missing="error", - on_denied="error"): + on_denied="error", endpoint=None): ''' :arg terms: a list of lookups to run. e.g. ['parameter_name', 'parameter_name_too' ] @@ -180,6 +166,7 @@ def run(self, terms, variables=None, boto_profile=None, aws_profile=None, :kwarg recursive: Set to True to recurse below the path (requires bypath=True) :kwarg on_missing: Action to take if the SSM parameter is missing :kwarg on_denied: Action to take if access to the SSM parameter is denied + :kwarg endpoint: Endpoint for SSM client :returns: A list of parameter values or a list of dictionaries if bypath=True. ''' @@ -195,16 +182,36 @@ def run(self, terms, variables=None, boto_profile=None, aws_profile=None, ret = [] ssm_dict = {} - credentials = {} + self.params = variables + + cli_region, cli_endpoint, cli_boto_params = get_aws_connection_info(self, boto3=True) + + if region: + cli_region = region + + if endpoint: + cli_endpoint = endpoint + + # For backward compatibility + if aws_access_key: + cli_boto_params.update({'aws_access_key_id': aws_access_key}) + if aws_secret_key: + cli_boto_params.update({'aws_secret_access_key': aws_secret_key}) + if aws_security_token: + cli_boto_params.update({'aws_session_token': aws_security_token}) + if boto_profile: + cli_boto_params.update({'profile_name': boto_profile}) if aws_profile: - credentials['boto_profile'] = aws_profile - else: - credentials['boto_profile'] = boto_profile - credentials['aws_secret_access_key'] = aws_secret_key - credentials['aws_access_key_id'] = aws_access_key - credentials['aws_session_token'] = aws_security_token + cli_boto_params.update({'profile_name': aws_profile}) + + cli_boto_params.update(dict( + conn_type='client', + resource='ssm', + region=cli_region, + endpoint=cli_endpoint, + )) - client = _boto3_conn(region, credentials) + client = boto3_conn(module=self, **cli_boto_params) ssm_dict['WithDecryption'] = decrypt diff --git a/tests/unit/plugins/lookup/test_aws_ssm.py b/tests/unit/plugins/lookup/test_aws_ssm.py index d54f1dc62fb..b224740a693 100644 --- a/tests/unit/plugins/lookup/test_aws_ssm.py +++ b/tests/unit/plugins/lookup/test_aws_ssm.py @@ -18,9 +18,11 @@ # Make coding more python3-ish from __future__ import (absolute_import, division, print_function) + __metaclass__ = type import pytest +from unittest.mock import ANY from copy import copy from ansible.errors import AnsibleError @@ -119,8 +121,16 @@ def test_lookup_variable(mocker): assert(isinstance(retval, list)) assert(len(retval) == 1) assert(retval[0] == "simplevalue") - boto3_client_double.assert_called_with('ssm', 'eu-west-1', aws_access_key_id='notakey', - aws_secret_access_key="notasecret", aws_session_token=None) + boto3_client_double.assert_called_with( + 'ssm', + region_name='eu-west-1', + aws_access_key_id='notakey', + aws_secret_access_key="notasecret", + aws_session_token=None, + endpoint_url=None, + config=ANY, + verify=None, + ) def test_path_lookup_variable(mocker): @@ -140,8 +150,16 @@ def test_path_lookup_variable(mocker): retval = lookup.run(["/testpath"], {}, **args) assert(retval[0]["/testpath/won"] == "simple_value_won") assert(retval[0]["/testpath/too"] == "simple_value_too") - boto3_client_double.assert_called_with('ssm', 'eu-west-1', aws_access_key_id='notakey', - aws_secret_access_key="notasecret", aws_session_token=None) + boto3_client_double.assert_called_with( + 'ssm', + region_name='eu-west-1', + aws_access_key_id='notakey', + aws_secret_access_key="notasecret", + aws_session_token=None, + endpoint_url=None, + config=ANY, + verify=None, + ) get_paginator_fn.assert_called_with('get_parameters_by_path') paginator.paginate.assert_called_with(Path="/testpath", Recursive=True, WithDecryption=True) paginator.paginate.return_value.build_full_result.assert_called_with()