Skip to content

Commit

Permalink
[6.0.0] codebuild_project - update tags param to use dict rather than…
Browse files Browse the repository at this point in the history
… list of dicts (ansible-collections#1643)

[6.0.0] codebuild_project - update tags param to use dict rather than list of dicts

SUMMARY
Passing list of dicts was deprecated, move it to dict
fixes: ansible-collections#1546
Originally deprecated in ansible-collections#1221 as part of the tagging cleanup.
ISSUE TYPE

Feature Pull Request

COMPONENT NAME
codebuild_project
ADDITIONAL INFORMATION

Reviewed-by: Alina Buzachis <None>
  • Loading branch information
tremble authored and abikouo committed Sep 18, 2023
1 parent b5a5dea commit 5aa9b17
Showing 1 changed file with 128 additions and 120 deletions.
248 changes: 128 additions & 120 deletions codebuild_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
short_description: Create or delete an AWS CodeBuild project
notes:
- For details of the parameters and returns see U(http://boto3.readthedocs.io/en/latest/reference/services/codebuild.html).
- I(tags) changed from boto3 format to standard dict format in release 6.0.0.
description:
- Create or delete a CodeBuild projects on AWS, used for building code artifacts from source code.
- Prior to release 5.0.0 this module was called C(community.aws.aws_codebuild).
Expand Down Expand Up @@ -137,23 +138,6 @@
description:
- The AWS Key Management Service (AWS KMS) customer master key (CMK) to be used for encrypting the build output artifacts.
type: str
tags:
description:
- A set of tags for the build project.
- Mutually exclusive with the I(resource_tags) parameter.
- In release 6.0.0 this parameter will accept a simple dictionary
instead of the list of dictionaries format. To use the simple
dictionary format prior to release 6.0.0 the I(resource_tags) can
be used instead of I(tags).
type: list
elements: dict
suboptions:
key:
description: The name of the Tag.
type: str
value:
description: The value of the Tag.
type: str
vpc_config:
description:
- The VPC config enables AWS CodeBuild to access resources in an Amazon VPC.
Expand All @@ -164,32 +148,12 @@
default: 'present'
choices: ['present', 'absent']
type: str
resource_tags:
description:
- A dictionary representing the tags to be applied to the build project.
- If the I(resource_tags) parameter is not set then tags will not be modified.
- Mutually exclusive with the I(tags) parameter.
type: dict
required: false
purge_tags:
description:
- If I(purge_tags=true) and I(tags) is set, existing tags will be purged
from the resource to match exactly what is defined by I(tags) parameter.
- If the I(resource_tags) parameter is not set then tags will not be modified, even
if I(purge_tags=True).
- Tag keys beginning with C(aws:) are reserved by Amazon and can not be
modified. As such they will be ignored for the purposes of the
I(purge_tags) parameter. See the Amazon documentation for more information
U(https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html#tag-conventions).
type: bool
default: true
required: false
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
- amazon.aws.boto3
- amazon.aws.boto3.modules
- amazon.aws.common.modules
- amazon.aws.region.modules
- amazon.aws.tags.modules
'''

EXAMPLES = r'''
Expand Down Expand Up @@ -326,107 +290,153 @@
sample: "2018-04-17T16:56:03.245000+02:00"
'''

try:
import botocore
except ImportError:
pass # Handled by AnsibleAWSModule

from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict

from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.core import get_boto3_client_method_parameters
from ansible_collections.amazon.aws.plugins.module_utils.botocore import get_boto3_client_method_parameters
from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError
from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list
from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict
from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule


try:
import botocore
except ImportError:
pass # Handled by AnsibleAWSModule
class CodeBuildAnsibleAWSError(AnsibleAWSError):
pass


def create_or_update_project(client, params, module):
resp = {}
name = params['name']
# clean up params
formatted_params = snake_dict_to_camel_dict(dict((k, v) for k, v in params.items() if v is not None))
permitted_create_params = get_boto3_client_method_parameters(client, 'create_project')
permitted_update_params = get_boto3_client_method_parameters(client, 'update_project')
def do_create_project(client, params, formatted_params):

formatted_create_params = dict((k, v) for k, v in formatted_params.items() if k in permitted_create_params)
formatted_update_params = dict((k, v) for k, v in formatted_params.items() if k in permitted_update_params)
if params["source"] is None or params["artifacts"] is None:
raise CodeBuildAnsibleAWSError(
message="The source and artifacts parameters must be provided "
"when creating a new project. No existing project was found.")

# Check if project with that name already exists and if so update existing:
found = describe_project(client=client, name=name, module=module)
changed = False
if params["tags"] is not None:
formatted_params["tags"] = ansible_dict_to_boto3_tag_list(
params["tags"],
tag_name_key_name="key",
tag_value_key_name="value"
)

permitted_create_params = get_boto3_client_method_parameters(client, "create_project")
formatted_create_params = dict((k, v) for k, v in formatted_params.items() if k in permitted_create_params)

if 'name' in found:
found_project = found
found_tags = found_project.pop('tags', [])
# Support tagging using a dict instead of the list of dicts
if params['resource_tags'] is not None:
if params['purge_tags']:
tags = dict()
else:
tags = boto3_tag_list_to_ansible_dict(found_tags)
tags.update(params['resource_tags'])
formatted_update_params['tags'] = ansible_dict_to_boto3_tag_list(tags, tag_name_key_name='key', tag_value_key_name='value')

resp = update_project(client=client, params=formatted_update_params, module=module)
updated_project = resp['project']

# Prep both dicts for sensible change comparison:
found_project.pop('lastModified')
updated_project.pop('lastModified')
updated_tags = updated_project.pop('tags', [])
found_project['ResourceTags'] = boto3_tag_list_to_ansible_dict(found_tags)
updated_project['ResourceTags'] = boto3_tag_list_to_ansible_dict(updated_tags)

if updated_project != found_project:
changed = True
updated_project['tags'] = updated_tags
return resp, changed
# Or create new project:
try:
if params['source'] is None or params['artifacts'] is None:
module.fail_json(
"The source and artifacts parameters must be provided when "
"creating a new project. No existing project was found.")
resp = client.create_project(**formatted_create_params)
changed = True
return resp, changed
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to create CodeBuild project")
raise CodeBuildAnsibleAWSError(
message="Unable to create CodeBuild project",
exception=e,
)


def merge_tags(found_tags, tags, purge_tags):
if purge_tags:
return tags

merged_tags = boto3_tag_list_to_ansible_dict(found_tags)
merged_tags.update(tags)
return merged_tags


def format_tags(tags):
return ansible_dict_to_boto3_tag_list(
tags,
tag_name_key_name="key",
tag_value_key_name="value",
)


def do_update_project(client, params, formatted_params, found_project):
permitted_update_params = get_boto3_client_method_parameters(client, "update_project")
formatted_update_params = dict((k, v) for k, v in formatted_params.items() if k in permitted_update_params)

found_tags = found_project.pop('tags', [])
if params["tags"] is not None:
formatted_update_params["tags"] = format_tags(
merge_tags(found_tags, params["tags"], params["purge_tags"]),
)

resp = update_project(client=client, params=formatted_update_params)
updated_project = resp["project"]

# Prep both dicts for sensible change comparison:
found_project.pop("lastModified")
updated_project.pop("lastModified")
updated_tags = updated_project.pop("tags", [])
found_project["ResourceTags"] = boto3_tag_list_to_ansible_dict(found_tags)
updated_project["ResourceTags"] = boto3_tag_list_to_ansible_dict(updated_tags)

changed = (updated_project != found_project)

updated_project["tags"] = updated_tags
return resp, changed


def update_project(client, params, module):
def create_or_update_project(client, params):
resp = {}
name = params['name']
# clean up params
formatted_params = snake_dict_to_camel_dict(dict((k, v) for k, v in params.items() if v is not None))

# Check if project with that name already exists and if so update existing:
found = describe_project(client=client, name=name)
changed = False

if "name" not in found:
return do_create_project(client, params, formatted_params)

return do_update_project(client, params, formatted_params, found)


def update_project(client, params):
name = params["name"]

try:
resp = client.update_project(**params)
return resp
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to update CodeBuild project")
raise CodeBuildAnsibleAWSError(
message="Unable to update CodeBuild project",
exception=e,
)


def delete_project(client, name, module):
found = describe_project(client=client, name=name, module=module)
changed = False
if 'name' in found:
# Mark as changed when a project with that name existed before calling delete
changed = True
def delete_project(client, name):
found = describe_project(client=client, name=name)
if "name" not in found:
return {}, False

try:
resp = client.delete_project(name=name)
return resp, changed
return resp, True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to delete CodeBuild project")
raise CodeBuildAnsibleAWSError(
message="Unable to update CodeBuild project",
exception=e,
)


def describe_project(client, name, module):
def describe_project(client, name):
project = {}
try:
projects = client.batch_get_projects(names=[name])['projects']
if len(projects) > 0:
project = projects[0]
return project
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to describe CodeBuild projects")
raise CodeBuildAnsibleAWSError(
message="Unable to describe CodeBuild projects",
exception=e,
)


def format_project_result(project_result):
Expand All @@ -450,8 +460,7 @@ def main():
service_role=dict(),
timeout_in_minutes=dict(type='int', default=60),
encryption_key=dict(no_log=False),
tags=dict(type='list', elements='dict'),
resource_tags=dict(type='dict'),
tags=dict(type='dict', aliases=["resource_tags"]),
purge_tags=dict(type='bool', default=True),
vpc_config=dict(type='dict'),
state=dict(choices=['present', 'absent'], default='present')
Expand All @@ -463,22 +472,21 @@ def main():
state = module.params.get('state')
changed = False

if module.params['tags']:
module.deprecate(
'The tags parameter currently uses a non-standard format and has '
'been deprecated. In release 6.0.0 this paramater will accept '
'a simple key/value pair dictionary instead of the current list '
'of dictionaries. It is recommended to migrate to using the '
'resource_tags parameter which already accepts the simple dictionary '
'format.', version='6.0.0', collection_name='community.aws')

if state == 'present':
project_result, changed = create_or_update_project(
client=client_conn,
params=module.params,
module=module)
elif state == 'absent':
project_result, changed = delete_project(client=client_conn, name=module.params['name'], module=module)
try:
if state == 'present':
project_result, changed = create_or_update_project(
client=client_conn,
params=module.params,
)
elif state == 'absent':
project_result, changed = delete_project(
client=client_conn,
name=module.params['name'],
)
except CodeBuildAnsibleAWSError as e:
if e.exception:
module.fail_json_aws(e.exception, msg=e.message)
module.fail_json(msg=e.message)

formatted_result = format_project_result(project_result)
module.exit_json(changed=changed, **formatted_result)
Expand Down

0 comments on commit 5aa9b17

Please sign in to comment.