[cloud] new module lambda_policy (PR #24951)

- Fixes to lambda
- reformatting + tests for lambda_facts
- lambda module integration test
- switch lambda and lambda_facts to AnsibleAwsModule
- Get the account ID from STS, GetUser, and finally error message
This commit is contained in:
Michael De La Rue 2017-05-16 14:26:55 +01:00 committed by Ryan S. Brown
commit fbec5ab12d
16 changed files with 1481 additions and 123 deletions

View file

@ -0,0 +1,4 @@
cloud/aws
posix/ci/cloud/aws
execute_lambda
lambda

View file

@ -0,0 +1,3 @@
---
# defaults file for aws_lambda test
lambda_function_name: '{{resource_prefix}}'

View file

@ -0,0 +1,34 @@
from __future__ import print_function
import json
def handler(event, context):
"""
The handler function is the function which gets called each time
the lambda is run.
"""
# printing goes to the cloudwatch log allowing us to simply debug the lambda if we can find
# the log entry.
print("got event:\n" + json.dumps(event))
# if the name parameter isn't present this can throw an exception
# which will result in an amazon chosen failure from the lambda
# which can be completely fine.
name = event["name"]
return {"message": "hello " + name}
def main():
"""
This main function will normally never be called during normal
lambda use. It is here for testing the lambda program only.
"""
event = {"name": "james"}
context = None
print(handler(event, context))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,409 @@
---
#
# Author: Michael De La Rue
# based on ec2_key.yml + lambda.py
- block:
# ============================================================
- name: test with no parameters
lambda:
register: result
ignore_errors: true
- name: assert failure when called with no parameters
assert:
that:
- 'result.failed'
- 'result.msg.startswith("missing required arguments: name")'
# ============================================================
- name: test with no parameters except state absent
lambda:
state=absent
register: result
ignore_errors: true
- name: assert failure when called with no parameters
assert:
that:
- 'result.failed'
- 'result.msg.startswith("missing required arguments: name")'
# ============================================================
- name: test with no role or handler
lambda:
name=ansible-testing-fake-should-not-be-created
runtime="python2.7"
register: result
ignore_errors: true
- name: assert failure when called with no parameters
assert:
that:
- 'result.failed'
- 'result.msg.startswith("state is present but the following are missing: handler")'
# ============================================================
- name: test with all module required variables but no region
lambda:
name=ansible-testing-fake-should-not-be-created
runtime="python2.7"
handler="no-handler"
role=arn:fake-role-doesnt-exist
register: result
ignore_errors: true
- name: assert failure when called with only 'name'
assert:
that:
- 'result.failed'
- 'result.msg == "region must be specified"'
# ============================================================
- name: test with all module required variables, no region and all possible variables set to blank
lambda:
name: ansible-testing-fake-should-not-be-created
state: present
runtime: "python2.7"
role: arn:fake-role-doesnt-exist
handler:
s3_bucket:
s3_key:
s3_object_version:
description:
vpc_subnet_ids:
vpc_security_group_ids:
environment_variables:
dead_letter_arn:
register: result
ignore_errors: true
- name: assert failure when called with only 'name'
assert:
that:
- 'result.failed'
- 'result.msg == "region must be specified"'
# ============================================================
# direct zip file upload
- name: move lambda into place for archive module
copy:
src: "mini_lambda.py"
dest: "{{output_dir}}/mini_lambda.py"
- name: bundle lambda into a zip
archive:
format: zip
path: "{{output_dir}}/mini_lambda.py"
dest: "{{output_dir}}/mini_lambda.zip"
register: zip_res
- name: test state=present - upload the lambda
lambda:
name="{{lambda_function_name}}"
runtime="python2.7"
handler="mini_lambda.handler"
role="ansible_lambda_role"
ec2_region='{{ec2_region}}'
ec2_access_key='{{ec2_access_key}}'
ec2_secret_key='{{ec2_secret_key}}'
security_token='{{security_token}}'
zip_file="{{zip_res.dest}}"
register: result
- name: assert lambda upload succeeded
assert:
that:
- 'not result|failed'
- name: test lambda works
execute_lambda:
name: "{{lambda_function_name}}"
payload:
name: "Mr Ansible Tests"
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
register: result
- name: assert lambda manages to respond as expected
assert:
that:
- 'not result|failed'
- 'result.result.output.message == "hello Mr Ansible Tests"'
# ============================================================
- name: test state=present with security group but no vpc
lambda:
name: "{{lambda_function_name}}"
runtime: "python2.7"
role: "ansible_lambda_role"
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
handler:
description:
vpc_subnet_ids:
vpc_security_group_ids: sg-FA6E
environment_variables:
dead_letter_arn:
register: result
ignore_errors: true
- name: assert lambda fails with proper message
assert:
that:
- 'result|failed'
- 'result.msg != "MODULE FAILURE"'
- 'result.changed == False'
- '"requires at least one security group and one subnet" in result.msg'
# ============================================================
- name: test state=present with all nullable variables explicitly set to null
lambda:
name: "{{lambda_function_name}}"
runtime: "python2.7"
role: "ansible_lambda_role"
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
handler: "mini_lambda.handler"
# These are not allowed because of mutually exclusive.
# s3_bucket:
# s3_key:
# s3_object_version:
description:
vpc_subnet_ids:
vpc_security_group_ids:
environment_variables:
dead_letter_arn:
register: result
- name: assert lambda was updated as expected
assert:
that:
- 'not result|failed'
- 'result.changed == False'
# ============================================================
- name: test state=present triggering a network exception due to bad url
lambda:
name: "{{lambda_function_name}}"
runtime: "python2.7"
role: "ansible_lambda_role"
ec2_url: https://noexist.example.com
ec2_region: '{{ec2_region}}'
ec2_access_key: 'iamnotreallyanaccesskey'
ec2_secret_key: 'thisisabadsecretkey'
security_token: 'andthisisabadsecuritytoken'
zip_file: "{{zip_res.dest}}"
register: result
ignore_errors: true
- name: assert lambda manages to respond as expected
assert:
that:
- 'result|failed'
- 'result.changed == False'
# ============================================================
- name: test state=absent (expect changed=False)
lambda:
name="{{lambda_function_name}}"
ec2_region='{{ec2_region}}'
ec2_access_key='{{ec2_access_key}}'
ec2_secret_key='{{ec2_secret_key}}'
security_token='{{security_token}}'
state=absent
register: result
- name: assert state=absent
assert:
that:
- 'not result|failed'
- 'result.changed == True'
# ============================================================
# parallel lambda creation
- name: parallel lambda creation 1/4
lambda:
name: "{{lambda_function_name}}_1"
runtime: "python2.7"
handler: "mini_lambda.handler"
role: "ansible_lambda_role"
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
async: 1000
register: async_1
- name: parallel lambda creation 2/4
lambda:
name: "{{lambda_function_name}}_2"
runtime: "python2.7"
handler: "mini_lambda.handler"
role: "ansible_lambda_role"
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
async: 1000
register: async_2
- name: parallel lambda creation 3/4
lambda:
name: "{{lambda_function_name}}_3"
runtime: "python2.7"
handler: "mini_lambda.handler"
role: "ansible_lambda_role"
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
async: 1000
register: async_3
- name: parallel lambda creation 4/4
lambda:
name: "{{lambda_function_name}}_4"
runtime: "python2.7"
handler: "mini_lambda.handler"
role: "ansible_lambda_role"
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
register: result
- name: assert lambda manages to respond as expected
assert:
that:
- 'not result|failed'
- name: wait for async job 1
async_status: jid={{ async_1.ansible_job_id }}
register: job_result
until: job_result.finished
retries: 30
- name: wait for async job 2
async_status: jid={{ async_1.ansible_job_id }}
register: job_result
until: job_result.finished
retries: 30
- name: wait for async job 3
async_status: jid={{ async_3.ansible_job_id }}
register: job_result
until: job_result.finished
retries: 30
- name: parallel lambda deletion 1/4
lambda:
name: "{{lambda_function_name}}_1"
state: absent
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
async: 1000
register: async_1
- name: parallel lambda deletion 2/4
lambda:
name: "{{lambda_function_name}}_2"
state: absent
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
async: 1000
register: async_2
- name: parallel lambda deletion 3/4
lambda:
name: "{{lambda_function_name}}_3"
state: absent
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
async: 1000
register: async_3
- name: parallel lambda deletion 4/4
lambda:
name: "{{lambda_function_name}}_4"
state: absent
ec2_region: '{{ec2_region}}'
ec2_access_key: '{{ec2_access_key}}'
ec2_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
zip_file: "{{zip_res.dest}}"
register: result
- name: assert lambda creation has succeeded
assert:
that:
- 'not result|failed'
- name: wait for async job 1
async_status: jid={{ async_1.ansible_job_id }}
register: job_result
until: job_result.finished
retries: 30
- name: wait for async job 2
async_status: jid={{ async_1.ansible_job_id }}
register: job_result
until: job_result.finished
retries: 30
- name: wait for async job 3
async_status: jid={{ async_3.ansible_job_id }}
register: job_result
until: job_result.finished
retries: 30
# ============================================================
# upload via s3 bucket - multi function
# ============================================================
# update already existing function
always:
# ============================================================
- name: test state=absent (expect changed=False)
lambda:
name="{{lambda_function_name}}"
ec2_region='{{ec2_region}}'
ec2_access_key='{{ec2_access_key}}'
ec2_secret_key='{{ec2_secret_key}}'
security_token='{{security_token}}'
state=absent
register: result
- name: assert state=absent
assert:
that:
- 'not result|failed'
- 'result.changed == False'

View file

@ -0,0 +1,2 @@
cloud/aws
posix/ci/cloud/aws

View file

@ -0,0 +1,3 @@
---
# defaults file for aws_lambda test
lambda_function_name: '{{resource_prefix}}-api-endpoint'

View file

@ -0,0 +1,36 @@
from __future__ import print_function
import json
def handler(event, context):
"""
The handler function is the function which gets called each time
the lambda is run.
"""
# printing goes to the cloudwatch log allowing us to simply debug the lambda if we can find
# the log entry.
print("got event:\n" + json.dumps(event))
# if the name parameter isn't present this can throw an exception
# which will result in an amazon chosen failure from the lambda
# which can be completely fine.
name = event["pathParameters"]["greet_name"]
return {"statusCode": 200,
"body": 'hello: "' + name + '"',
"headers": {}}
def main():
"""
This main function will normally never be called during normal
lambda use. It is here for testing the lambda program only.
"""
event = {"name": "james"}
context = None
print(handler(event, context))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,218 @@
---
#
# Author: Michael De La Rue
# based on ec2_key.yml + lambda.py
- block:
# ============================================================
- name: test with no parameters
lambda_policy:
register: result
ignore_errors: true
- name: assert failure when called with no parameters
assert:
that:
- 'result.failed'
- 'result.msg.startswith("missing required arguments: ")'
# ============================================================
- name: test with all required dummy parameters but no region
lambda_policy:
statement_id: dummy
principal: api_fakeway
action: fake:do_something_fake
function_name: dummy_fake_function
ignore_errors: true
register: result
- name: assert failure and appropriate message when called without region
assert:
that:
- 'result.failed'
- '"region must be specified" in result.msg'
# ============================================================
- name: test with all required dummy parameters but no region
lambda_policy:
statement_id: dummy
principal: api_fakeway
action: fake:do_something_fake
function_name: dummy_fake_function
region: null
ignore_errors: true
register: result
- name: assert failure and appropriate message when called false region region
assert:
that:
- 'result.failed'
- '"region must be specified" in result.msg'
# ============================================================
- name: test exceptions generated by forcing bad ec2 url
lambda_policy:
function_name: "{{ lambda_function_name }}"
region: "{{ec2_region}}"
state: present
statement_id: api-gateway-invoke-lambdas
action: lambda:InvokeFunction
principal: apigateway.amazonaws.com
source_arn: "arn:aws:execute-api:no-north-0:1234567:*/*"
ec2_url: https://noexist.example.com
ec2_region: 'no-north-0'
ec2_access_key: 'iamnotreallyanaccesskey'
ec2_secret_key: 'thisisabadsecretkey'
security_token: 'andthisisabadsecuritytoken'
register: result
ignore_errors: true
- name: assert lambda manages to respond as expected
assert:
that:
- 'result|failed'
- 'result.msg != "MODULE FAILURE"'
- 'result.changed == False'
# ============================================================
# direct zip file upload
- name: move lambda into place for archive module
copy:
src: "mini_http_lambda.py"
dest: "{{output_dir}}/mini_http_lambda.py"
- name: bundle lambda into a zip
archive:
format: zip
path: "{{output_dir}}/mini_http_lambda.py"
dest: "{{output_dir}}/mini_http_lambda.zip"
register: zip_res
- name: test state=present - upload the lambda
lambda:
name="{{lambda_function_name}}"
runtime="python2.7"
handler="mini_http_lambda.handler"
role="ansible_lambda_role"
ec2_region='{{ec2_region}}'
aws_access_key='{{aws_access_key}}'
aws_secret_key='{{aws_secret_key}}'
security_token='{{security_token}}'
zip_file="{{zip_res.dest}}"
register: lambda_result
- name: install aws cli - FIXME temporary this should go for a lighterweight solution
command: pip install awscli
register: result
- name: get the aws account ID for use in future commands
command: aws sts get-caller-identity --output text --query 'Account'
environment:
AWS_ACCESS_KEY_ID: '{{aws_access_key}}'
AWS_SECRET_ACCESS_KEY: '{{aws_secret_key}}'
AWS_SESSION_TOKEN: '{{security_token}}'
register: result
- name: register account id
set_fact:
aws_account_id: "{{ result.stdout | replace('\n', '') }}"
- name: register lambda uri for use in template
set_fact:
mini_lambda_uri: "arn:aws:apigateway:{{ec2_region}}:lambda:path/2015-03-31/functions/arn:aws:lambda:{{ec2_region}}:{{aws_account_id}}:function:{{ lambda_result.configuration.function_name }}/invocations"
- name: build API file
template:
src: endpoint-test-swagger-api.yml.j2
dest: "{{output_dir}}/endpoint-test-swagger-api.yml.j2"
- name: deploy new API
aws_api_gateway:
api_file: "{{output_dir}}/endpoint-test-swagger-api.yml.j2"
stage: "lambdabased"
region: '{{ec2_region}}'
aws_access_key: '{{aws_access_key}}'
aws_secret_key: '{{aws_secret_key}}'
security_token: '{{security_token}}'
register: create_result
- name: register api id for later
set_fact:
api_id: "{{ create_result.api_id }}"
- name: check API fails with permissions failure
uri: url="https://{{create_result.api_id}}.execute-api.{{ec2_region}}.amazonaws.com/lambdabased/mini/Mr_Ansible_Tester"
register: unauth_uri_result
ignore_errors: true
- name: assert internal server error due to permissions
assert:
that:
- unauth_uri_result|failed
- 'unauth_uri_result.status == 500'
- name: give api gateway execute permissions on lambda
lambda_policy:
function_name: "{{ lambda_function_name }}"
region: "{{ec2_region}}"
state: present
statement_id: api-gateway-invoke-lambdas
action: lambda:InvokeFunction
principal: apigateway.amazonaws.com
source_arn: "arn:aws:execute-api:{{ ec2_region }}:{{ aws_account_id }}:*/*"
aws_access_key: '{{aws_access_key}}'
aws_secret_key: '{{aws_secret_key}}'
security_token: '{{security_token}}'
- name: check API works with execute permissions
uri: url="https://{{create_result.api_id}}.execute-api.{{ec2_region}}.amazonaws.com/lambdabased/mini/Mr_Ansible_Tester"
register: uri_result
- name: assert API works success
assert:
that:
- 'uri_result'
- name: deploy new API
aws_api_gateway:
api_file: "{{output_dir}}/endpoint-test-swagger-api.yml.j2"
stage: "lambdabased"
region: '{{ec2_region}}'
aws_access_key: '{{aws_access_key}}'
aws_secret_key: '{{aws_secret_key}}'
security_token: '{{security_token}}'
register: create_result
ignore_errors: true
always:
# ============================================================
- name: destroy lambda for test cleanup if created
lambda:
name="{{lambda_function_name}}"
ec2_region='{{ec2_region}}'
ec2_access_key='{{ec2_access_key}}'
ec2_secret_key='{{ec2_secret_key}}'
security_token='{{security_token}}'
state=absent
register: result
- name: destroy API for test cleanup if created
aws_api_gateway:
state: absent
api_id: '{{api_id}}'
region: '{{ec2_region}}'
aws_access_key: '{{ec2_access_key}}'
aws_secret_key: '{{ec2_secret_key}}'
security_token: '{{security_token}}'
register: destroy_result
- name: assert destroy statements succeeded
assert:
that:
- 'destroy_result.changed == True'
- 'not result|failed'

View file

@ -0,0 +1,39 @@
---
swagger: "2.0"
info:
version: "2017-05-11T12:14:59Z"
title: "{{resource_prefix}}LambdaBased_API"
host: "fakeexample.execute-api.us-east-1.amazonaws.com"
basePath: "/lambdabased"
schemes:
- "https"
paths:
/mini/{greet_name}:
get:
produces:
- "application/json"
parameters:
- name: "greet_name"
in: "path"
required: true
type: "string"
responses:
200:
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
responses:
default:
statusCode: "200"
uri: "{{mini_lambda_uri}}"
requestTemplates:
application/json: "{\"statusCode\": 200}"
passthroughBehavior: "when_no_match"
httpMethod: "POST"
contentHandling: "CONVERT_TO_TEXT"
type: "aws_proxy"
definitions:
Empty:
type: "object"
title: "Empty Schema"

View file

@ -51,8 +51,6 @@ lib/ansible/modules/cloud/amazon/elb_classic_lb.py
lib/ansible/modules/cloud/amazon/execute_lambda.py
lib/ansible/modules/cloud/amazon/iam.py
lib/ansible/modules/cloud/amazon/iam_policy.py
lib/ansible/modules/cloud/amazon/lambda.py
lib/ansible/modules/cloud/amazon/lambda_facts.py
lib/ansible/modules/cloud/amazon/rds_subnet_group.py
lib/ansible/modules/cloud/amazon/redshift.py
lib/ansible/modules/cloud/amazon/route53_health_check.py

View file

@ -39,6 +39,7 @@ def set_module_args(args):
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
basic._ANSIBLE_ARGS = to_bytes(args)
base_lambda_config = {
'FunctionName': 'lambda_name',
'Role': 'arn:aws:iam::987654321012:role/lambda_basic_execution',
@ -130,7 +131,8 @@ def test_create_lambda_if_not_exist():
pass
# guard against calling other than for a lambda connection (e.g. IAM)
assert(len(boto3_conn_double.mock_calls) == 1), "multiple boto connections used unexpectedly"
assert(len(boto3_conn_double.mock_calls) > 0), "boto connections never used"
assert(len(boto3_conn_double.mock_calls) < 2), "multiple boto connections used unexpectedly"
assert(len(lambda_client_double.update_function_configuration.mock_calls) == 0), \
"unexpectedly updated lambda configuration when should have only created"
assert(len(lambda_client_double.update_function_code.mock_calls) == 0), \
@ -162,7 +164,8 @@ def test_update_lambda_if_code_changed():
pass
# guard against calling other than for a lambda connection (e.g. IAM)
assert(len(boto3_conn_double.mock_calls) == 1), "multiple boto connections used unexpectedly"
assert(len(boto3_conn_double.mock_calls) > 0), "boto connections never used"
assert(len(boto3_conn_double.mock_calls) < 2), "multiple boto connections used unexpectedly"
assert(len(lambda_client_double.update_function_configuration.mock_calls) == 0), \
"unexpectedly updatede lambda configuration when only code changed"
assert(len(lambda_client_double.update_function_configuration.mock_calls) < 2), \
@ -187,7 +190,8 @@ def test_update_lambda_if_config_changed():
pass
# guard against calling other than for a lambda connection (e.g. IAM)
assert(len(boto3_conn_double.mock_calls) == 1), "multiple boto connections used unexpectedly"
assert(len(boto3_conn_double.mock_calls) > 0), "boto connections never used"
assert(len(boto3_conn_double.mock_calls) < 2), "multiple boto connections used unexpectedly"
assert(len(lambda_client_double.update_function_configuration.mock_calls) > 0), \
"failed to update lambda function when configuration changed"
assert(len(lambda_client_double.update_function_configuration.mock_calls) < 2), \
@ -208,7 +212,8 @@ def test_update_lambda_if_only_one_config_item_changed():
pass
# guard against calling other than for a lambda connection (e.g. IAM)
assert(len(boto3_conn_double.mock_calls) == 1), "multiple boto connections used unexpectedly"
assert(len(boto3_conn_double.mock_calls) > 0), "boto connections never used"
assert(len(boto3_conn_double.mock_calls) < 2), "multiple boto connections used unexpectedly"
assert(len(lambda_client_double.update_function_configuration.mock_calls) > 0), \
"failed to update lambda function when configuration changed"
assert(len(lambda_client_double.update_function_configuration.mock_calls) < 2), \
@ -229,7 +234,8 @@ def test_update_lambda_if_added_environment_variable():
pass
# guard against calling other than for a lambda connection (e.g. IAM)
assert(len(boto3_conn_double.mock_calls) == 1), "multiple boto connections used unexpectedly"
assert(len(boto3_conn_double.mock_calls) > 0), "boto connections never used"
assert(len(boto3_conn_double.mock_calls) < 2), "multiple boto connections used unexpectedly"
assert(len(lambda_client_double.update_function_configuration.mock_calls) > 0), \
"failed to update lambda function when configuration changed"
assert(len(lambda_client_double.update_function_configuration.mock_calls) < 2), \
@ -253,7 +259,8 @@ def test_dont_update_lambda_if_nothing_changed():
pass
# guard against calling other than for a lambda connection (e.g. IAM)
assert(len(boto3_conn_double.mock_calls) == 1), "multiple boto connections used unexpectedly"
assert(len(boto3_conn_double.mock_calls) > 0), "boto connections never used"
assert(len(boto3_conn_double.mock_calls) < 2), "multiple boto connections used unexpectedly"
assert(len(lambda_client_double.update_function_configuration.mock_calls) == 0), \
"updated lambda function when no configuration changed"
assert(len(lambda_client_double.update_function_code.mock_calls) == 0), \

View file

@ -0,0 +1,171 @@
#
# (c) 2017 Michael De La Rue
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
from nose.plugins.skip import SkipTest
import ansible.modules.cloud.amazon.lambda_policy as lambda_policy
from ansible.modules.cloud.amazon.lambda_policy import setup_module_object
from ansible.module_utils.aws.core import HAS_BOTO3
from ansible.module_utils import basic
from ansible.module_utils.basic import to_bytes
from ansible.compat.tests.mock import MagicMock
import json
import copy
from botocore.exceptions import ClientError
# try:
# from botocore import ResourceNotFoundException
# except:
# pass # will be protected by HAS_BOTO3
if not HAS_BOTO3:
raise SkipTest("test_api_gateway.py requires the `boto3` and `botocore` modules")
base_module_args = {
"region": "us-west-1",
"function_name": "this_is_a_test_function",
"state": "present",
"statement_id": "test-allow-lambda",
"principal": 123456,
"action": "lambda:*"
}
def set_module_args(mod_args):
args = json.dumps({'ANSIBLE_MODULE_ARGS': mod_args})
basic._ANSIBLE_ARGS = to_bytes(args)
def test_module_is_created_sensibly():
set_module_args(base_module_args)
module = setup_module_object()
assert module.params['function_name'] == 'this_is_a_test_function'
module_double = MagicMock()
module_double.fail_json_aws.side_effect = Exception("unexpected call to fail_json_aws")
module_double.check_mode = False
fake_module_params_present = {
"state": "present",
"statement_id": "test-allow-lambda",
"principal": "apigateway.amazonaws.com",
"action": "lambda:InvokeFunction",
"source_arn": u'arn:aws:execute-api:us-east-1:123456789:efghijklmn/authorizers/*',
"version": 0,
"alias": None
}
fake_module_params_different = copy.deepcopy(fake_module_params_present)
fake_module_params_different["action"] = "lambda:api-gateway"
fake_module_params_absent = copy.deepcopy(fake_module_params_present)
fake_module_params_absent["state"] = "absent"
fake_policy_return = {
u'Policy': (
u'{"Version":"2012-10-17","Id":"default","Statement":[{"Sid":"1234567890abcdef1234567890abcdef",'
u'"Effect":"Allow","Principal":{"Service":"apigateway.amazonaws.com"},"Action":"lambda:InvokeFunction",'
u'"Resource":"arn:aws:lambda:us-east-1:123456789:function:test_authorizer",'
u'"Condition":{"ArnLike":{"AWS:SourceArn":"arn:aws:execute-api:us-east-1:123456789:abcdefghij/authorizers/1a2b3c"}}},'
u'{"Sid":"2234567890abcdef1234567890abcdef","Effect":"Allow","Principal":{"Service":"apigateway.amazonaws.com"},'
u'"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:us-east-1:123456789:function:test_authorizer",'
u'"Condition":{"ArnLike":{"AWS:SourceArn":"arn:aws:execute-api:us-east-1:123456789:klmnopqrst/authorizers/4d5f6g"}}},'
u'{"Sid":"1234567890abcdef1234567890abcdef","Effect":"Allow","Principal":{"Service":"apigateway.amazonaws.com"},'
u'"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:us-east-1:123456789:function:test_authorizer",'
u'"Condition":{"ArnLike":{"AWS:SourceArn":"arn:aws:execute-api:eu-west-1:123456789:uvwxyzabcd/authorizers/7h8i9j"}}},'
u'{"Sid":"test-allow-lambda","Effect":"Allow","Principal":{"Service":"apigateway.amazonaws.com"},'
u'"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:us-east-1:123456789:function:test_authorizer",'
u'"Condition":{"ArnLike":{"AWS:SourceArn":"arn:aws:execute-api:us-east-1:123456789:efghijklmn/authorizers/*"}}},'
u'{"Sid":"1234567890abcdef1234567890abcdef","Effect":"Allow","Principal":{"Service":"apigateway.amazonaws.com"},'
u'"Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:us-east-1:123456789:function:test_authorizer",'
u'"Condition":{"ArnLike":{"AWS:SourceArn":"arn:aws:execute-api:us-east-1:123456789:opqrstuvwx/authorizers/0k1l2m"}}}]}'),
'ResponseMetadata': {
'RetryAttempts': 0,
'HTTPStatusCode': 200,
'RequestId': 'abcdefgi-1234-a567-b890-123456789abc',
'HTTPHeaders': {
'date': 'Sun, 13 Aug 2017 10:54:17 GMT',
'x-amzn-requestid': 'abcdefgi-1234-a567-b890-123456789abc',
'content-length': '1878',
'content-type': 'application/json',
'connection': 'keep-alive'}}}
error_response = {'Error': {'Code': 'ResourceNotFoundException', 'Message': 'Fake Testing Error'}}
operation_name = 'FakeOperation'
resource_not_found_e = ClientError(error_response, operation_name)
def test_manage_state_adds_missing_permissions():
lambda_client_double = MagicMock()
# Policy actually: not present Requested State: present Should: create
lambda_client_double.get_policy.side_effect = resource_not_found_e
fake_module_params = copy.deepcopy(fake_module_params_present)
module_double.params = fake_module_params
lambda_policy.manage_state(module_double, lambda_client_double)
assert lambda_client_double.get_policy.call_count > 0
assert lambda_client_double.add_permission.call_count > 0
lambda_client_double.remove_permission.assert_not_called()
def test_manage_state_leaves_existing_permissions():
lambda_client_double = MagicMock()
# Policy actually: present Requested State: present Should: do nothing
lambda_client_double.get_policy.return_value = fake_policy_return
fake_module_params = copy.deepcopy(fake_module_params_present)
module_double.params = fake_module_params
lambda_policy.manage_state(module_double, lambda_client_double)
assert lambda_client_double.get_policy.call_count > 0
lambda_client_double.add_permission.assert_not_called()
lambda_client_double.remove_permission.assert_not_called()
def test_manage_state_updates_nonmatching_permissions():
lambda_client_double = MagicMock()
# Policy actually: present Requested State: present Should: do nothing
lambda_client_double.get_policy.return_value = fake_policy_return
fake_module_params = copy.deepcopy(fake_module_params_different)
module_double.params = fake_module_params
lambda_policy.manage_state(module_double, lambda_client_double)
assert lambda_client_double.get_policy.call_count > 0
assert lambda_client_double.add_permission.call_count > 0
assert lambda_client_double.remove_permission.call_count > 0
def test_manage_state_removes_unwanted_permissions():
lambda_client_double = MagicMock()
# Policy actually: present Requested State: not present Should: remove
lambda_client_double.get_policy.return_value = fake_policy_return
fake_module_params = copy.deepcopy(fake_module_params_absent)
module_double.params = fake_module_params
lambda_policy.manage_state(module_double, lambda_client_double)
assert lambda_client_double.get_policy.call_count > 0
lambda_client_double.add_permission.assert_not_called()
assert lambda_client_double.remove_permission.call_count > 0
def test_manage_state_leaves_already_removed_permissions():
lambda_client_double = MagicMock()
# Policy actually: absent Requested State: absent Should: do nothing
lambda_client_double.get_policy.side_effect = resource_not_found_e
fake_module_params = copy.deepcopy(fake_module_params_absent)
module_double.params = fake_module_params
lambda_policy.manage_state(module_double, lambda_client_double)
assert lambda_client_double.get_policy.call_count > 0
lambda_client_double.add_permission.assert_not_called()
lambda_client_double.remove_permission.assert_not_called()