#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

DOCUMENTATION = r"""
---
module: ec2_ami_info
version_added: 1.0.0
short_description: Gather information about ec2 AMIs
description:
  - Gather information about ec2 AMIs
author:
  - Prasad Katti (@prasadkatti)
options:
  image_ids:
    description: One or more image IDs.
    aliases: [image_id]
    type: list
    elements: str
    default: []
  filters:
    description:
      - A dict of filters to apply. Each dict item consists of a filter key and a filter value.
      - See U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html) for possible filters.
      - Filter names and values are case sensitive.
    type: dict
    default: {}
  owners:
    description:
      - Filter the images by the owner. Valid options are an AWS account ID, self,
        or an AWS owner alias ( amazon | aws-marketplace | microsoft ).
    aliases: [owner]
    type: list
    elements: str
    default: []
  executable_users:
    description:
      - Filter images by users with explicit launch permissions. Valid options are an AWS account ID, self, or all (public AMIs).
    aliases: [executable_user]
    type: list
    elements: str
    default: []
  describe_image_attributes:
    description:
      - Describe attributes (like launchPermission) of the images found.
    default: false
    type: bool

extends_documentation_fragment:
  - amazon.aws.common.modules
  - amazon.aws.region.modules
  - amazon.aws.boto3
"""

EXAMPLES = r"""
# Note: These examples do not set authentication details, see the AWS Guide for details.

- name: gather information about an AMI using ami-id
  amazon.aws.ec2_ami_info:
    image_ids: ami-5b488823

- name: gather information about all AMIs with tag key Name and value webapp
  amazon.aws.ec2_ami_info:
    filters:
      "tag:Name": webapp

- name: gather information about an AMI with 'AMI Name' equal to foobar
  amazon.aws.ec2_ami_info:
    filters:
      name: foobar

- name: gather information about Ubuntu 17.04 AMIs published by Canonical (099720109477)
  amazon.aws.ec2_ami_info:
    owners: 099720109477
    filters:
      name: "ubuntu/images/ubuntu-zesty-17.04-*"
"""

RETURN = r"""
images:
  description: A list of images.
  returned: always
  type: list
  elements: dict
  contains:
    architecture:
      description: The architecture of the image.
      returned: always
      type: str
      sample: x86_64
    block_device_mappings:
      description: Any block device mapping entries.
      returned: always
      type: list
      elements: dict
      contains:
        device_name:
          description: The device name exposed to the instance.
          returned: always
          type: str
          sample: /dev/sda1
        ebs:
          description: EBS volumes
          returned: always
          type: complex
    creation_date:
      description: The date and time the image was created.
      returned: always
      type: str
      sample: '2017-10-16T19:22:13.000Z'
    description:
      description: The description of the AMI.
      type: str
      sample: ''
    ena_support:
      description: Whether enhanced networking with ENA is enabled.
      returned: always
      type: bool
      sample: true
    hypervisor:
      description: The hypervisor type of the image.
      returned: always
      type: str
      sample: xen
    image_id:
      description: The ID of the AMI.
      returned: always
      type: str
      sample: ami-5b466623
    image_location:
      description: The location of the AMI.
      returned: always
      type: str
      sample: 123456789012/Webapp
    image_type:
      description: The type of image.
      returned: always
      type: str
      sample: machine
    launch_permissions:
      description: A List of AWS accounts may launch the AMI.
      returned: When image is owned by calling account and I(describe_image_attributes=true).
      type: list
      elements: dict
      contains:
        group:
            description: A value of 'all' means the AMI is public.
            type: str
        user_id:
            description: An AWS account ID with permissions to launch the AMI.
            type: str
      sample: [{"group": "all"}, {"user_id": "123456789012"}]
    name:
      description: The name of the AMI that was provided during image creation.
      returned: always
      type: str
      sample: Webapp
    owner_id:
      description: The AWS account ID of the image owner.
      returned: always
      type: str
      sample: '123456789012'
    platform_details:
      description: Platform of image.
      returned: always
      type: str
      sample: "Windows"
    public:
      description: Whether the image has public launch permissions.
      returned: always
      type: bool
      sample: true
    root_device_name:
      description: The device name of the root device.
      returned: always
      type: str
      sample: /dev/sda1
    root_device_type:
      description: The type of root device used by the AMI.
      returned: always
      type: str
      sample: ebs
    sriov_net_support:
      description: Whether enhanced networking is enabled.
      type: str
      sample: simple
    state:
      description: The current state of the AMI.
      returned: always
      type: str
      sample: available
    tags:
      description: Any tags assigned to the image.
      returned: always
      type: dict
    usage_operation:
      description: The operation of the Amazon EC2 instance and the billing code that is associated with the AMI.
      returned: always
      type: str
      sample: "RunInstances"
    virtualization_type:
      description: The type of virtualization of the AMI.
      returned: always
      type: str
      sample: hvm
"""

try:
    from botocore.exceptions import BotoCoreError
    from botocore.exceptions import ClientError
except ImportError:
    pass  # Handled by AnsibleAWSModule

from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict

from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict
from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list


class AmiInfoFailure(Exception):
    def __init__(self, original_e, user_message):
        self.original_e = original_e
        self.user_message = user_message
        super().__init__(self)


def build_request_args(executable_users, filters, image_ids, owners):
    request_args = {
        "ExecutableUsers": [str(user) for user in executable_users],
        "ImageIds": [str(image_id) for image_id in image_ids],
    }

    # describe_images is *very* slow if you pass the `Owners`
    # param (unless it's self), for some reason.
    # Converting the owners to filters and removing from the
    # owners param greatly speeds things up.
    # Implementation based on aioue's suggestion in #24886
    for owner in owners:
        if owner.isdigit():
            if "owner-id" not in filters:
                filters["owner-id"] = list()
            filters["owner-id"].append(owner)
        elif owner == "self":
            # self not a valid owner-alias filter (https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html)
            request_args["Owners"] = [str(owner)]
        else:
            if "owner-alias" not in filters:
                filters["owner-alias"] = list()
            filters["owner-alias"].append(owner)

    request_args["Filters"] = ansible_dict_to_boto3_filter_list(filters)

    request_args = {k: v for k, v in request_args.items() if v}

    return request_args


def get_images(ec2_client, request_args):
    try:
        images = ec2_client.describe_images(aws_retry=True, **request_args)
    except (ClientError, BotoCoreError) as err:
        raise AmiInfoFailure(err, "error describing images")
    return images


def get_image_attribute(ec2_client, image_id):
    try:
        launch_permissions = ec2_client.describe_image_attribute(
            aws_retry=True, Attribute="launchPermission", ImageId=image_id
        )
    except (ClientError, BotoCoreError) as err:
        raise AmiInfoFailure(err, "error describing image attribute")
    return launch_permissions


def list_ec2_images(ec2_client, module, request_args):
    images = get_images(ec2_client, request_args)["Images"]
    images = [camel_dict_to_snake_dict(image) for image in images]

    for image in images:
        try:
            image_id = image["image_id"]
            image["tags"] = boto3_tag_list_to_ansible_dict(image.get("tags", []))
            if module.params.get("describe_image_attributes"):
                launch_permissions = get_image_attribute(ec2_client, image_id).get("LaunchPermissions", [])
                image["launch_permissions"] = [camel_dict_to_snake_dict(perm) for perm in launch_permissions]
        except is_boto3_error_code("AuthFailure"):
            # describing launch permissions of images owned by others is not permitted, but shouldn't cause failures
            pass
        except (ClientError, BotoCoreError) as err:  # pylint: disable=duplicate-except
            raise AmiInfoFailure(err, "Failed to describe AMI")

    images.sort(key=lambda e: e.get("creation_date", ""))  # it may be possible that creation_date does not always exist

    return images


def main():
    argument_spec = dict(
        describe_image_attributes=dict(default=False, type="bool"),
        executable_users=dict(default=[], type="list", elements="str", aliases=["executable_user"]),
        filters=dict(default={}, type="dict"),
        image_ids=dict(default=[], type="list", elements="str", aliases=["image_id"]),
        owners=dict(default=[], type="list", elements="str", aliases=["owner"]),
    )

    module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True)

    ec2_client = module.client("ec2", retry_decorator=AWSRetry.jittered_backoff())

    request_args = build_request_args(
        executable_users=module.params["executable_users"],
        filters=module.params["filters"],
        image_ids=module.params["image_ids"],
        owners=module.params["owners"],
    )

    images = list_ec2_images(ec2_client, module, request_args)

    module.exit_json(images=images)


if __name__ == "__main__":
    main()
