mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[cloud] support tags in ec2_group module (#22472)
* Add tags support to cloud/amazon/ec2_group * Finish making ec2_group tag support boto3 compatible. Add integration tests to validate that tags are working as expected.
This commit is contained in:
parent
050de6d6b5
commit
2ceff476bf
2 changed files with 196 additions and 22 deletions
|
@ -86,6 +86,19 @@ options:
|
||||||
required: false
|
required: false
|
||||||
default: 'true'
|
default: 'true'
|
||||||
aliases: []
|
aliases: []
|
||||||
|
tags:
|
||||||
|
version_added: "2.4"
|
||||||
|
description:
|
||||||
|
- A dictionary of one or more tags to assign to the security group.
|
||||||
|
required: false
|
||||||
|
purge_tags:
|
||||||
|
version_added: "2.4"
|
||||||
|
description:
|
||||||
|
- If yes, existing tags will be purged from the resource to match exactly what is defined by I(tags) parameter. If the I(tags) parameter is not set then
|
||||||
|
tags will not be modified.
|
||||||
|
required: false
|
||||||
|
default: yes
|
||||||
|
choices: [ 'yes', 'no' ]
|
||||||
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- aws
|
- aws
|
||||||
|
@ -252,14 +265,13 @@ owner_id:
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.ec2 import boto3_conn
|
from ansible.module_utils.ec2 import boto3_conn
|
||||||
from ansible.module_utils.ec2 import get_aws_connection_info
|
from ansible.module_utils.ec2 import get_aws_connection_info
|
||||||
from ansible.module_utils.ec2 import ec2_argument_spec
|
from ansible.module_utils.ec2 import ec2_argument_spec
|
||||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
|
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
|
||||||
from ansible.module_utils.ec2 import HAS_BOTO3
|
from ansible.module_utils.ec2 import HAS_BOTO3
|
||||||
from ansible.module_utils.ec2 import boto3_tag_list_to_ansible_dict
|
from ansible.module_utils.ec2 import boto3_tag_list_to_ansible_dict, ansible_dict_to_boto3_tag_list, compare_aws_tags
|
||||||
from ansible.module_utils.ec2 import AWSRetry
|
from ansible.module_utils.ec2 import AWSRetry
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
@ -501,9 +513,7 @@ def serialize_group_grant(group_id, rule):
|
||||||
'ToPort': rule['to_port'],
|
'ToPort': rule['to_port'],
|
||||||
'UserIdGroupPairs': [{'GroupId': group_id}]}
|
'UserIdGroupPairs': [{'GroupId': group_id}]}
|
||||||
|
|
||||||
convert_ports_to_int(permission)
|
return fix_port_and_protocol(permission)
|
||||||
|
|
||||||
return permission
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_revoke(grant, rule):
|
def serialize_revoke(grant, rule):
|
||||||
|
@ -514,7 +524,7 @@ def serialize_revoke(grant, rule):
|
||||||
permission = {'IpProtocol': rule['IpProtocol'],
|
permission = {'IpProtocol': rule['IpProtocol'],
|
||||||
'FromPort': fromPort,
|
'FromPort': fromPort,
|
||||||
'ToPort': toPort,
|
'ToPort': toPort,
|
||||||
'UserIdGroupPairs': [{'GroupId': grant['GroupId'], 'UserId': grant['UserId']}]
|
'UserIdGroupPairs': [{'GroupId': grant['GroupId']}]
|
||||||
}
|
}
|
||||||
elif 'CidrIp' in grant:
|
elif 'CidrIp' in grant:
|
||||||
permission = {'IpProtocol': rule['IpProtocol'],
|
permission = {'IpProtocol': rule['IpProtocol'],
|
||||||
|
@ -528,10 +538,7 @@ def serialize_revoke(grant, rule):
|
||||||
'ToPort': toPort,
|
'ToPort': toPort,
|
||||||
'Ipv6Ranges': [grant]
|
'Ipv6Ranges': [grant]
|
||||||
}
|
}
|
||||||
if rule['IpProtocol'] in ('all', '-1', -1):
|
return fix_port_and_protocol(permission)
|
||||||
del permission['FromPort']
|
|
||||||
del permission['ToPort']
|
|
||||||
return permission
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_ip_grant(rule, thisip, ethertype):
|
def serialize_ip_grant(rule, thisip, ethertype):
|
||||||
|
@ -539,21 +546,26 @@ def serialize_ip_grant(rule, thisip, ethertype):
|
||||||
'FromPort': rule['from_port'],
|
'FromPort': rule['from_port'],
|
||||||
'ToPort': rule['to_port']}
|
'ToPort': rule['to_port']}
|
||||||
if ethertype == "ipv4":
|
if ethertype == "ipv4":
|
||||||
permission.update({'IpRanges': [{'CidrIp': thisip}]})
|
permission['IpRanges'] = [{'CidrIp': thisip}]
|
||||||
elif ethertype == "ipv6":
|
elif ethertype == "ipv6":
|
||||||
permission.update({'Ipv6Ranges': [{'CidrIpv6': thisip}]})
|
permission['Ipv6Ranges'] = [{'CidrIpv6': thisip}]
|
||||||
|
|
||||||
convert_ports_to_int(permission)
|
return fix_port_and_protocol(permission)
|
||||||
|
|
||||||
|
|
||||||
|
def fix_port_and_protocol(permission):
|
||||||
|
for key in ['FromPort', 'ToPort']:
|
||||||
|
if key in permission:
|
||||||
|
if permission[key] is None:
|
||||||
|
del permission[key]
|
||||||
|
else:
|
||||||
|
permission[key] = int(permission[key])
|
||||||
|
|
||||||
|
permission['IpProtocol'] = str(permission['IpProtocol'])
|
||||||
|
|
||||||
return permission
|
return permission
|
||||||
|
|
||||||
|
|
||||||
def convert_ports_to_int(permission):
|
|
||||||
for key in ['FromPort', 'ToPort']:
|
|
||||||
if permission[key] is not None:
|
|
||||||
permission[key] = int(permission[key])
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = ec2_argument_spec()
|
argument_spec = ec2_argument_spec()
|
||||||
argument_spec.update(dict(
|
argument_spec.update(dict(
|
||||||
|
@ -565,7 +577,9 @@ def main():
|
||||||
rules_egress=dict(type='list'),
|
rules_egress=dict(type='list'),
|
||||||
state=dict(default='present', type='str', choices=['present', 'absent']),
|
state=dict(default='present', type='str', choices=['present', 'absent']),
|
||||||
purge_rules=dict(default=True, required=False, type='bool'),
|
purge_rules=dict(default=True, required=False, type='bool'),
|
||||||
purge_rules_egress=dict(default=True, required=False, type='bool')
|
purge_rules_egress=dict(default=True, required=False, type='bool'),
|
||||||
|
tags=dict(required=False, type='dict', aliases=['resource_tags']),
|
||||||
|
purge_tags=dict(default=True, required=False, type='bool')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
|
@ -587,6 +601,8 @@ def main():
|
||||||
state = module.params.get('state')
|
state = module.params.get('state')
|
||||||
purge_rules = module.params['purge_rules']
|
purge_rules = module.params['purge_rules']
|
||||||
purge_rules_egress = module.params['purge_rules_egress']
|
purge_rules_egress = module.params['purge_rules_egress']
|
||||||
|
tags = module.params['tags']
|
||||||
|
purge_tags = module.params['purge_tags']
|
||||||
|
|
||||||
if state == 'present' and not description:
|
if state == 'present' and not description:
|
||||||
module.fail_json(msg='Must provide description when state is present.')
|
module.fail_json(msg='Must provide description when state is present.')
|
||||||
|
@ -670,11 +686,30 @@ def main():
|
||||||
while True:
|
while True:
|
||||||
group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0]
|
group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0]
|
||||||
if not group['IpPermissionsEgress']:
|
if not group['IpPermissionsEgress']:
|
||||||
time.sleep(0.1)
|
pass
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
|
if tags is not None:
|
||||||
|
current_tags = boto3_tag_list_to_ansible_dict(group.get('Tags', []))
|
||||||
|
tags_need_modify, tags_to_delete = compare_aws_tags(current_tags, tags, purge_tags)
|
||||||
|
if tags_to_delete:
|
||||||
|
try:
|
||||||
|
client.delete_tags(Resources=[group['GroupId']], Tags=[{'Key': tag} for tag in tags_to_delete])
|
||||||
|
except botocore.exceptions.ClientError as e:
|
||||||
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# Add/update tags
|
||||||
|
if tags_need_modify:
|
||||||
|
try:
|
||||||
|
client.create_tags(Resources=[group['GroupId']], Tags=ansible_dict_to_boto3_tag_list(tags_need_modify))
|
||||||
|
except botocore.exceptions.ClientError as e:
|
||||||
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
changed = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg="Unsupported state requested: %s" % state)
|
module.fail_json(msg="Unsupported state requested: %s" % state)
|
||||||
|
|
||||||
|
@ -838,11 +873,12 @@ def main():
|
||||||
if group:
|
if group:
|
||||||
security_group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0]
|
security_group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0]
|
||||||
security_group = camel_dict_to_snake_dict(security_group)
|
security_group = camel_dict_to_snake_dict(security_group)
|
||||||
security_group['tags'] = boto3_tag_list_to_ansible_dict(security_group.get('tags', {}),
|
security_group['tags'] = boto3_tag_list_to_ansible_dict(security_group.get('tags', []),
|
||||||
tag_name_key_name='key', tag_value_key_name='value')
|
tag_name_key_name='key', tag_value_key_name='value')
|
||||||
module.exit_json(changed=changed, **security_group)
|
module.exit_json(changed=changed, **security_group)
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=changed, group_id=None)
|
module.exit_json(changed=changed, group_id=None)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -491,6 +491,144 @@
|
||||||
- 'result.vpc_id == vpc_result.vpc.id'
|
- 'result.vpc_id == vpc_result.vpc.id'
|
||||||
- 'result.group_id.startswith("sg-")'
|
- 'result.group_id.startswith("sg-")'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
- name: test adding tags (expected changed=true)
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
description: '{{ec2_group_description}}'
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
vpc_id: '{{ vpc_result.vpc.id }}'
|
||||||
|
state: present
|
||||||
|
rules:
|
||||||
|
- proto: "tcp"
|
||||||
|
from_port: 8182
|
||||||
|
to_port: 8182
|
||||||
|
cidr_ip: "1.1.1.1/32"
|
||||||
|
tags:
|
||||||
|
tag1: test1
|
||||||
|
tag2: test2
|
||||||
|
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert that tags were added (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.tags == {"tag1": "test1", "tag2": "test2"}'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
- name: test that tags are present (expected changed=False)
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
description: '{{ec2_group_description}}'
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
vpc_id: '{{ vpc_result.vpc.id }}'
|
||||||
|
state: present
|
||||||
|
purge_rules_egress: false
|
||||||
|
rules:
|
||||||
|
- proto: "tcp"
|
||||||
|
from_port: 8182
|
||||||
|
to_port: 8182
|
||||||
|
cidr_ip: "1.1.1.1/32"
|
||||||
|
tags:
|
||||||
|
tag1: test1
|
||||||
|
tag2: test2
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert that tags were not changed (expected changed=False)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.tags == {"tag1": "test1", "tag2": "test2"}'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
- name: test purging tags (expected changed=True)
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
description: '{{ec2_group_description}}'
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
vpc_id: '{{ vpc_result.vpc.id }}'
|
||||||
|
state: present
|
||||||
|
rules:
|
||||||
|
- proto: "tcp"
|
||||||
|
from_port: 8182
|
||||||
|
to_port: 8182
|
||||||
|
cidr_ip: "1.1.1.1/32"
|
||||||
|
tags:
|
||||||
|
tag1: test1
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert that tag2 was removed (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.tags == {"tag1": "test1"}'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
- name: assert that tags are left as-is if not specified (expected changed=False)
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
description: '{{ec2_group_description}}'
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
vpc_id: '{{ vpc_result.vpc.id }}'
|
||||||
|
state: present
|
||||||
|
rules:
|
||||||
|
- proto: "tcp"
|
||||||
|
from_port: 8182
|
||||||
|
to_port: 8182
|
||||||
|
cidr_ip: "1.1.1.1/32"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert that the tags stayed the same (expected changed=false)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.tags == {"tag1": "test1"}'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
- name: test purging all tags (expected changed=True)
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
description: '{{ec2_group_description}}'
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
vpc_id: '{{ vpc_result.vpc.id }}'
|
||||||
|
state: present
|
||||||
|
rules:
|
||||||
|
- proto: "tcp"
|
||||||
|
from_port: 8182
|
||||||
|
to_port: 8182
|
||||||
|
cidr_ip: "1.1.1.1/32"
|
||||||
|
tags: {}
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert that tag1 was removed (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'not result.tags'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
- name: test state=absent (expected changed=true)
|
- name: test state=absent (expected changed=true)
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name: '{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
|
|
Loading…
Add table
Reference in a new issue