from __future__ import unicode_literals

import boto
import boto.ec2
import boto3
from boto.exception import EC2ResponseError
from botocore.exceptions import ClientError

import pytest
import sure  # noqa

from moto import mock_ec2_deprecated, mock_ec2
from moto.ec2.models import AMIS, OWNER_ID
from moto.core import ACCOUNT_ID
from tests import EXAMPLE_AMI_ID
from tests.helpers import requires_boto_gte


@mock_ec2_deprecated
def test_ami_create_and_delete():
    conn = boto.connect_ec2("the_key", "the_secret")

    initial_ami_count = len(AMIS)
    conn.get_all_volumes().should.have.length_of(0)
    conn.get_all_snapshots().should.have.length_of(initial_ami_count)

    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]

    with pytest.raises(EC2ResponseError) as ex:
        image_id = conn.create_image(
            instance.id, "test-ami", "this is a test ami", dry_run=True
        )
    ex.value.error_code.should.equal("DryRunOperation")
    ex.value.status.should.equal(400)
    ex.value.message.should.equal(
        "An error occurred (DryRunOperation) when calling the CreateImage operation: Request would have succeeded, but DryRun flag is set"
    )

    image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")

    all_images = conn.get_all_images()
    set([i.id for i in all_images]).should.contain(image_id)

    retrieved_image = [i for i in all_images if i.id == image_id][0]

    retrieved_image.id.should.equal(image_id)
    retrieved_image.virtualization_type.should.equal(instance.virtualization_type)
    retrieved_image.architecture.should.equal(instance.architecture)
    retrieved_image.kernel_id.should.equal(instance.kernel)
    retrieved_image.platform.should.equal(instance.platform)
    retrieved_image.creationDate.should_not.be.none
    instance.terminate()

    # Ensure we're no longer creating a volume
    volumes = conn.get_all_volumes()
    volumes.should.have.length_of(0)

    # Validate auto-created snapshot
    snapshots = conn.get_all_snapshots()
    snapshots.should.have.length_of(initial_ami_count + 1)

    retrieved_image_snapshot_id = (
        retrieved_image.block_device_mapping.current_value.snapshot_id
    )
    [s.id for s in snapshots].should.contain(retrieved_image_snapshot_id)
    snapshot = [s for s in snapshots if s.id == retrieved_image_snapshot_id][0]
    snapshot.description.should.equal(
        "Auto-created snapshot for AMI {0}".format(retrieved_image.id)
    )

    # root device should be in AMI's block device mappings
    root_mapping = retrieved_image.block_device_mapping.get(
        retrieved_image.root_device_name
    )
    root_mapping.should_not.be.none

    # Deregister
    with pytest.raises(EC2ResponseError) as ex:
        success = conn.deregister_image(image_id, dry_run=True)
    ex.value.error_code.should.equal("DryRunOperation")
    ex.value.status.should.equal(400)
    ex.value.message.should.equal(
        "An error occurred (DryRunOperation) when calling the DeregisterImage operation: Request would have succeeded, but DryRun flag is set"
    )

    success = conn.deregister_image(image_id)
    success.should.be.true

    with pytest.raises(EC2ResponseError) as cm:
        conn.deregister_image(image_id)
    cm.value.code.should.equal("InvalidAMIID.NotFound")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none


@requires_boto_gte("2.14.0")
@mock_ec2_deprecated
def test_ami_copy():
    conn = boto.ec2.connect_to_region("us-west-1")

    initial_ami_count = len(AMIS)
    conn.get_all_volumes().should.have.length_of(0)
    conn.get_all_snapshots().should.have.length_of(initial_ami_count)

    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]

    source_image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
    instance.terminate()
    source_image = conn.get_all_images(image_ids=[source_image_id])[0]

    # Boto returns a 'CopyImage' object with an image_id attribute here. Use
    # the image_id to fetch the full info.
    with pytest.raises(EC2ResponseError) as ex:
        copy_image_ref = conn.copy_image(
            source_image.region.name,
            source_image.id,
            "test-copy-ami",
            "this is a test copy ami",
            dry_run=True,
        )
    ex.value.error_code.should.equal("DryRunOperation")
    ex.value.status.should.equal(400)
    ex.value.message.should.equal(
        "An error occurred (DryRunOperation) when calling the CopyImage operation: Request would have succeeded, but DryRun flag is set"
    )

    copy_image_ref = conn.copy_image(
        source_image.region.name,
        source_image.id,
        "test-copy-ami",
        "this is a test copy ami",
    )
    copy_image_id = copy_image_ref.image_id
    copy_image = conn.get_all_images(image_ids=[copy_image_id])[0]

    copy_image.id.should.equal(copy_image_id)
    copy_image.virtualization_type.should.equal(source_image.virtualization_type)
    copy_image.architecture.should.equal(source_image.architecture)
    copy_image.kernel_id.should.equal(source_image.kernel_id)
    copy_image.platform.should.equal(source_image.platform)

    # Ensure we're no longer creating a volume
    conn.get_all_volumes().should.have.length_of(0)

    # Validate auto-created snapshot
    conn.get_all_snapshots().should.have.length_of(initial_ami_count + 2)

    copy_image.block_device_mapping.current_value.snapshot_id.should_not.equal(
        source_image.block_device_mapping.current_value.snapshot_id
    )

    # Copy from non-existent source ID.
    with pytest.raises(EC2ResponseError) as cm:
        conn.copy_image(
            source_image.region.name,
            "ami-abcd1234",
            "test-copy-ami",
            "this is a test copy ami",
        )
    cm.value.code.should.equal("InvalidAMIID.NotFound")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none

    # Copy from non-existent source region.
    with pytest.raises(EC2ResponseError) as cm:
        invalid_region = (
            "us-east-1" if (source_image.region.name != "us-east-1") else "us-west-1"
        )
        conn.copy_image(
            invalid_region, source_image.id, "test-copy-ami", "this is a test copy ami"
        )
    cm.value.code.should.equal("InvalidAMIID.NotFound")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none


@mock_ec2
def test_copy_image_changes_owner_id():
    conn = boto3.client("ec2", region_name="us-east-1")

    # this source AMI ID is from moto/ec2/resources/amis.json
    source_ami_id = "ami-03cf127a"

    # confirm the source ami owner id is different from the default owner id.
    # if they're ever the same it means this test is invalid.
    check_resp = conn.describe_images(ImageIds=[source_ami_id])
    check_resp["Images"][0]["OwnerId"].should_not.equal(OWNER_ID)

    copy_resp = conn.copy_image(
        SourceImageId=source_ami_id,
        Name="new-image",
        Description="a copy of an image",
        SourceRegion="us-east-1",
    )

    describe_resp = conn.describe_images(Owners=["self"])
    describe_resp["Images"][0]["OwnerId"].should.equal(OWNER_ID)
    describe_resp["Images"][0]["ImageId"].should.equal(copy_resp["ImageId"])


@mock_ec2_deprecated
def test_ami_tagging():
    conn = boto.connect_vpc("the_key", "the_secret")
    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]
    conn.create_image(instance.id, "test-ami", "this is a test ami")
    image = conn.get_all_images()[0]

    with pytest.raises(EC2ResponseError) as ex:
        image.add_tag("a key", "some value", dry_run=True)
    ex.value.error_code.should.equal("DryRunOperation")
    ex.value.status.should.equal(400)
    ex.value.message.should.equal(
        "An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set"
    )

    image.add_tag("a key", "some value")

    tag = conn.get_all_tags()[0]
    tag.name.should.equal("a key")
    tag.value.should.equal("some value")

    # Refresh the DHCP options
    image = conn.get_all_images()[0]
    image.tags.should.have.length_of(1)
    image.tags["a key"].should.equal("some value")


@mock_ec2_deprecated
def test_ami_create_from_missing_instance():
    conn = boto.connect_ec2("the_key", "the_secret")
    args = ["i-abcdefg", "test-ami", "this is a test ami"]

    with pytest.raises(EC2ResponseError) as cm:
        conn.create_image(*args)
    cm.value.code.should.equal("InvalidInstanceID.NotFound")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none


@mock_ec2_deprecated
def test_ami_pulls_attributes_from_instance():
    conn = boto.connect_ec2("the_key", "the_secret")
    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]
    instance.modify_attribute("kernel", "test-kernel")

    image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
    image = conn.get_image(image_id)
    image.kernel_id.should.equal("test-kernel")


@mock_ec2_deprecated
def test_ami_uses_account_id_if_valid_access_key_is_supplied():
    access_key = "AKIAXXXXXXXXXXXXXXXX"
    conn = boto.connect_ec2(access_key, "the_secret")
    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]
    instance.modify_attribute("kernel", "test-kernel")

    image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
    images = conn.get_all_images(owners=["self"])
    [(ami.id, ami.owner_id) for ami in images].should.equal([(image_id, ACCOUNT_ID)])


@mock_ec2_deprecated
def test_ami_filters():
    conn = boto.connect_ec2("the_key", "the_secret")

    reservationA = conn.run_instances(EXAMPLE_AMI_ID)
    instanceA = reservationA.instances[0]
    instanceA.modify_attribute("architecture", "i386")
    instanceA.modify_attribute("kernel", "k-1234abcd")
    instanceA.modify_attribute("platform", "windows")
    instanceA.modify_attribute("virtualization_type", "hvm")
    imageA_id = conn.create_image(instanceA.id, "test-ami-A", "this is a test ami")
    imageA = conn.get_image(imageA_id)

    reservationB = conn.run_instances(EXAMPLE_AMI_ID)
    instanceB = reservationB.instances[0]
    instanceB.modify_attribute("architecture", "x86_64")
    instanceB.modify_attribute("kernel", "k-abcd1234")
    instanceB.modify_attribute("platform", "linux")
    instanceB.modify_attribute("virtualization_type", "paravirtual")
    imageB_id = conn.create_image(instanceB.id, "test-ami-B", "this is a test ami")
    imageB = conn.get_image(imageB_id)
    imageB.set_launch_permissions(group_names=("all"))

    amis_by_architecture = conn.get_all_images(filters={"architecture": "x86_64"})
    set([ami.id for ami in amis_by_architecture]).should.contain(imageB.id)
    len(amis_by_architecture).should.equal(39)

    amis_by_kernel = conn.get_all_images(filters={"kernel-id": "k-abcd1234"})
    set([ami.id for ami in amis_by_kernel]).should.equal(set([imageB.id]))

    amis_by_virtualization = conn.get_all_images(
        filters={"virtualization-type": "paravirtual"}
    )
    set([ami.id for ami in amis_by_virtualization]).should.contain(imageB.id)
    len(amis_by_virtualization).should.equal(3)

    amis_by_platform = conn.get_all_images(filters={"platform": "windows"})
    set([ami.id for ami in amis_by_platform]).should.contain(imageA.id)
    len(amis_by_platform).should.equal(25)

    amis_by_id = conn.get_all_images(filters={"image-id": imageA.id})
    set([ami.id for ami in amis_by_id]).should.equal(set([imageA.id]))

    amis_by_state = conn.get_all_images(filters={"state": "available"})
    ami_ids_by_state = [ami.id for ami in amis_by_state]
    ami_ids_by_state.should.contain(imageA.id)
    ami_ids_by_state.should.contain(imageB.id)
    len(amis_by_state).should.equal(40)

    amis_by_name = conn.get_all_images(filters={"name": imageA.name})
    set([ami.id for ami in amis_by_name]).should.equal(set([imageA.id]))

    amis_by_public = conn.get_all_images(filters={"is-public": "true"})
    set([ami.id for ami in amis_by_public]).should.contain(imageB.id)
    len(amis_by_public).should.equal(39)

    amis_by_nonpublic = conn.get_all_images(filters={"is-public": "false"})
    set([ami.id for ami in amis_by_nonpublic]).should.contain(imageA.id)
    len(amis_by_nonpublic).should.equal(1)


@mock_ec2_deprecated
def test_ami_filtering_via_tag():
    conn = boto.connect_vpc("the_key", "the_secret")

    reservationA = conn.run_instances(EXAMPLE_AMI_ID)
    instanceA = reservationA.instances[0]
    imageA_id = conn.create_image(instanceA.id, "test-ami-A", "this is a test ami")
    imageA = conn.get_image(imageA_id)
    imageA.add_tag("a key", "some value")

    reservationB = conn.run_instances(EXAMPLE_AMI_ID)
    instanceB = reservationB.instances[0]
    imageB_id = conn.create_image(instanceB.id, "test-ami-B", "this is a test ami")
    imageB = conn.get_image(imageB_id)
    imageB.add_tag("another key", "some other value")

    amis_by_tagA = conn.get_all_images(filters={"tag:a key": "some value"})
    set([ami.id for ami in amis_by_tagA]).should.equal(set([imageA.id]))

    amis_by_tagB = conn.get_all_images(filters={"tag:another key": "some other value"})
    set([ami.id for ami in amis_by_tagB]).should.equal(set([imageB.id]))


@mock_ec2_deprecated
def test_getting_missing_ami():
    conn = boto.connect_ec2("the_key", "the_secret")

    with pytest.raises(EC2ResponseError) as cm:
        conn.get_image("ami-missing")
    cm.value.code.should.equal("InvalidAMIID.NotFound")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none


@mock_ec2_deprecated
def test_getting_malformed_ami():
    conn = boto.connect_ec2("the_key", "the_secret")

    with pytest.raises(EC2ResponseError) as cm:
        conn.get_image("foo-missing")
    cm.value.code.should.equal("InvalidAMIID.Malformed")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none


@mock_ec2_deprecated
def test_ami_attribute_group_permissions():
    conn = boto.connect_ec2("the_key", "the_secret")
    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]
    image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
    image = conn.get_image(image_id)

    # Baseline
    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.name.should.equal("launch_permission")
    attributes.attrs.should.have.length_of(0)

    ADD_GROUP_ARGS = {
        "image_id": image.id,
        "attribute": "launchPermission",
        "operation": "add",
        "groups": "all",
    }

    REMOVE_GROUP_ARGS = {
        "image_id": image.id,
        "attribute": "launchPermission",
        "operation": "remove",
        "groups": "all",
    }

    # Add 'all' group and confirm
    with pytest.raises(EC2ResponseError) as ex:
        conn.modify_image_attribute(**dict(ADD_GROUP_ARGS, **{"dry_run": True}))
    ex.value.error_code.should.equal("DryRunOperation")
    ex.value.status.should.equal(400)
    ex.value.message.should.equal(
        "An error occurred (DryRunOperation) when calling the ModifyImageAttribute operation: Request would have succeeded, but DryRun flag is set"
    )

    conn.modify_image_attribute(**ADD_GROUP_ARGS)

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs["groups"].should.have.length_of(1)
    attributes.attrs["groups"].should.equal(["all"])
    image = conn.get_image(image_id)
    image.is_public.should.equal(True)

    # Add is idempotent
    conn.modify_image_attribute.when.called_with(**ADD_GROUP_ARGS).should_not.throw(
        EC2ResponseError
    )

    # Remove 'all' group and confirm
    conn.modify_image_attribute(**REMOVE_GROUP_ARGS)

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs.should.have.length_of(0)
    image = conn.get_image(image_id)
    image.is_public.should.equal(False)

    # Remove is idempotent
    conn.modify_image_attribute.when.called_with(**REMOVE_GROUP_ARGS).should_not.throw(
        EC2ResponseError
    )


@mock_ec2_deprecated
def test_ami_attribute_user_permissions():
    conn = boto.connect_ec2("the_key", "the_secret")
    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]
    image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
    image = conn.get_image(image_id)

    # Baseline
    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.name.should.equal("launch_permission")
    attributes.attrs.should.have.length_of(0)

    # Both str and int values should work.
    USER1 = "123456789011"
    USER2 = 123456789022

    ADD_USERS_ARGS = {
        "image_id": image.id,
        "attribute": "launchPermission",
        "operation": "add",
        "user_ids": [USER1, USER2],
    }

    REMOVE_USERS_ARGS = {
        "image_id": image.id,
        "attribute": "launchPermission",
        "operation": "remove",
        "user_ids": [USER1, USER2],
    }

    REMOVE_SINGLE_USER_ARGS = {
        "image_id": image.id,
        "attribute": "launchPermission",
        "operation": "remove",
        "user_ids": [USER1],
    }

    # Add multiple users and confirm
    conn.modify_image_attribute(**ADD_USERS_ARGS)

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs["user_ids"].should.have.length_of(2)
    set(attributes.attrs["user_ids"]).should.equal(set([str(USER1), str(USER2)]))
    image = conn.get_image(image_id)
    image.is_public.should.equal(False)

    # Add is idempotent
    conn.modify_image_attribute.when.called_with(**ADD_USERS_ARGS).should_not.throw(
        EC2ResponseError
    )

    # Remove single user and confirm
    conn.modify_image_attribute(**REMOVE_SINGLE_USER_ARGS)

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs["user_ids"].should.have.length_of(1)
    set(attributes.attrs["user_ids"]).should.equal(set([str(USER2)]))
    image = conn.get_image(image_id)
    image.is_public.should.equal(False)

    # Remove multiple users and confirm
    conn.modify_image_attribute(**REMOVE_USERS_ARGS)

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs.should.have.length_of(0)
    image = conn.get_image(image_id)
    image.is_public.should.equal(False)

    # Remove is idempotent
    conn.modify_image_attribute.when.called_with(**REMOVE_USERS_ARGS).should_not.throw(
        EC2ResponseError
    )


@mock_ec2
def test_ami_describe_executable_users():
    conn = boto3.client("ec2", region_name="us-east-1")
    ec2 = boto3.resource("ec2", "us-east-1")
    ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
    response = conn.describe_instances(
        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
    )
    instance_id = response["Reservations"][0]["Instances"][0]["InstanceId"]
    image_id = conn.create_image(InstanceId=instance_id, Name="TestImage")["ImageId"]

    USER1 = "123456789011"

    ADD_USER_ARGS = {
        "ImageId": image_id,
        "Attribute": "launchPermission",
        "OperationType": "add",
        "UserIds": [USER1],
    }

    # Add users and get no images
    conn.modify_image_attribute(**ADD_USER_ARGS)

    attributes = conn.describe_image_attribute(
        ImageId=image_id, Attribute="LaunchPermissions", DryRun=False
    )
    attributes["LaunchPermissions"].should.have.length_of(1)
    attributes["LaunchPermissions"][0]["UserId"].should.equal(USER1)
    images = conn.describe_images(ExecutableUsers=[USER1])["Images"]
    images.should.have.length_of(1)
    images[0]["ImageId"].should.equal(image_id)


@mock_ec2
def test_ami_describe_executable_users_negative():
    conn = boto3.client("ec2", region_name="us-east-1")
    ec2 = boto3.resource("ec2", "us-east-1")
    ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
    response = conn.describe_instances(
        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
    )
    instance_id = response["Reservations"][0]["Instances"][0]["InstanceId"]
    image_id = conn.create_image(InstanceId=instance_id, Name="TestImage")["ImageId"]

    USER1 = "123456789011"
    USER2 = "113355789012"

    ADD_USER_ARGS = {
        "ImageId": image_id,
        "Attribute": "launchPermission",
        "OperationType": "add",
        "UserIds": [USER1],
    }

    # Add users and get no images
    # Add users and get no images
    conn.modify_image_attribute(**ADD_USER_ARGS)

    attributes = conn.describe_image_attribute(
        ImageId=image_id, Attribute="LaunchPermissions", DryRun=False
    )
    attributes["LaunchPermissions"].should.have.length_of(1)
    attributes["LaunchPermissions"][0]["UserId"].should.equal(USER1)
    images = conn.describe_images(ExecutableUsers=[USER2])["Images"]
    images.should.have.length_of(0)


@mock_ec2
def test_ami_describe_executable_users_and_filter():
    conn = boto3.client("ec2", region_name="us-east-1")
    ec2 = boto3.resource("ec2", "us-east-1")
    ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
    response = conn.describe_instances(
        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
    )
    instance_id = response["Reservations"][0]["Instances"][0]["InstanceId"]
    image_id = conn.create_image(InstanceId=instance_id, Name="ImageToDelete")[
        "ImageId"
    ]

    USER1 = "123456789011"

    ADD_USER_ARGS = {
        "ImageId": image_id,
        "Attribute": "launchPermission",
        "OperationType": "add",
        "UserIds": [USER1],
    }

    # Add users and get no images
    conn.modify_image_attribute(**ADD_USER_ARGS)

    attributes = conn.describe_image_attribute(
        ImageId=image_id, Attribute="LaunchPermissions", DryRun=False
    )
    attributes["LaunchPermissions"].should.have.length_of(1)
    attributes["LaunchPermissions"][0]["UserId"].should.equal(USER1)
    images = conn.describe_images(
        ExecutableUsers=[USER1], Filters=[{"Name": "state", "Values": ["available"]}]
    )["Images"]
    images.should.have.length_of(1)
    images[0]["ImageId"].should.equal(image_id)


@mock_ec2_deprecated
def test_ami_attribute_user_and_group_permissions():
    """
    Boto supports adding/removing both users and groups at the same time.
    Just spot-check this -- input variations, idempotency, etc are validated
      via user-specific and group-specific tests above.
    """
    conn = boto.connect_ec2("the_key", "the_secret")
    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]
    image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
    image = conn.get_image(image_id)

    # Baseline
    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.name.should.equal("launch_permission")
    attributes.attrs.should.have.length_of(0)

    USER1 = "123456789011"
    USER2 = "123456789022"

    ADD_ARGS = {
        "image_id": image.id,
        "attribute": "launchPermission",
        "operation": "add",
        "groups": ["all"],
        "user_ids": [USER1, USER2],
    }

    REMOVE_ARGS = {
        "image_id": image.id,
        "attribute": "launchPermission",
        "operation": "remove",
        "groups": ["all"],
        "user_ids": [USER1, USER2],
    }

    # Add and confirm
    conn.modify_image_attribute(**ADD_ARGS)

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs["user_ids"].should.have.length_of(2)
    set(attributes.attrs["user_ids"]).should.equal(set([USER1, USER2]))
    set(attributes.attrs["groups"]).should.equal(set(["all"]))
    image = conn.get_image(image_id)
    image.is_public.should.equal(True)

    # Remove and confirm
    conn.modify_image_attribute(**REMOVE_ARGS)

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs.should.have.length_of(0)
    image = conn.get_image(image_id)
    image.is_public.should.equal(False)


@mock_ec2_deprecated
def test_ami_attribute_error_cases():
    conn = boto.connect_ec2("the_key", "the_secret")
    reservation = conn.run_instances(EXAMPLE_AMI_ID)
    instance = reservation.instances[0]
    image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
    image = conn.get_image(image_id)

    # Error: Add with group != 'all'
    with pytest.raises(EC2ResponseError) as cm:
        conn.modify_image_attribute(
            image.id, attribute="launchPermission", operation="add", groups="everyone"
        )
    cm.value.code.should.equal("InvalidAMIAttributeItemValue")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none

    # Error: Add with user ID that isn't an integer.
    with pytest.raises(EC2ResponseError) as cm:
        conn.modify_image_attribute(
            image.id,
            attribute="launchPermission",
            operation="add",
            user_ids="12345678901A",
        )
    cm.value.code.should.equal("InvalidAMIAttributeItemValue")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none

    # Error: Add with user ID that is > length 12.
    with pytest.raises(EC2ResponseError) as cm:
        conn.modify_image_attribute(
            image.id,
            attribute="launchPermission",
            operation="add",
            user_ids="1234567890123",
        )
    cm.value.code.should.equal("InvalidAMIAttributeItemValue")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none

    # Error: Add with user ID that is < length 12.
    with pytest.raises(EC2ResponseError) as cm:
        conn.modify_image_attribute(
            image.id,
            attribute="launchPermission",
            operation="add",
            user_ids="12345678901",
        )
    cm.value.code.should.equal("InvalidAMIAttributeItemValue")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none

    # Error: Add with one invalid user ID among other valid IDs, ensure no
    # partial changes.
    with pytest.raises(EC2ResponseError) as cm:
        conn.modify_image_attribute(
            image.id,
            attribute="launchPermission",
            operation="add",
            user_ids=["123456789011", "foo", "123456789022"],
        )
    cm.value.code.should.equal("InvalidAMIAttributeItemValue")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none

    attributes = conn.get_image_attribute(image.id, attribute="launchPermission")
    attributes.attrs.should.have.length_of(0)

    # Error: Add with invalid image ID
    with pytest.raises(EC2ResponseError) as cm:
        conn.modify_image_attribute(
            "ami-abcd1234", attribute="launchPermission", operation="add", groups="all"
        )
    cm.value.code.should.equal("InvalidAMIID.NotFound")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none

    # Error: Remove with invalid image ID
    with pytest.raises(EC2ResponseError) as cm:
        conn.modify_image_attribute(
            "ami-abcd1234",
            attribute="launchPermission",
            operation="remove",
            groups="all",
        )
    cm.value.code.should.equal("InvalidAMIID.NotFound")
    cm.value.status.should.equal(400)
    cm.value.request_id.should_not.be.none


@mock_ec2
def test_ami_describe_non_existent():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    # Valid pattern but non-existent id
    img = ec2.Image("ami-abcd1234")
    with pytest.raises(ClientError):
        img.load()
    # Invalid ami pattern
    img = ec2.Image("not_an_ami_id")
    with pytest.raises(ClientError):
        img.load()


@mock_ec2
def test_ami_registration():
    ec2 = boto3.client("ec2", region_name="us-east-1")
    image_id = ec2.register_image(Name="test-register-image").get("ImageId", "")
    images = ec2.describe_images(ImageIds=[image_id]).get("Images", [])
    assert images[0]["Name"] == "test-register-image", "No image was registered."
    assert images[0]["RootDeviceName"] == "/dev/sda1", "Wrong root device name."
    assert images[0]["State"] == "available", "State should be available."


@mock_ec2
def test_ami_filter_wildcard():
    ec2_resource = boto3.resource("ec2", region_name="us-west-1")
    ec2_client = boto3.client("ec2", region_name="us-west-1")

    instance = ec2_resource.create_instances(
        ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
    )[0]
    instance.create_image(Name="test-image")

    # create an image with the same owner but will not match the filter
    instance.create_image(Name="not-matching-image")

    my_images = ec2_client.describe_images(
        Owners=[ACCOUNT_ID], Filters=[{"Name": "name", "Values": ["test*"]}]
    )["Images"]
    my_images.should.have.length_of(1)


@mock_ec2
def test_ami_filter_by_owner_id():
    client = boto3.client("ec2", region_name="us-east-1")

    ubuntu_id = "099720109477"

    ubuntu_images = client.describe_images(Owners=[ubuntu_id])
    all_images = client.describe_images()

    ubuntu_ids = [ami["OwnerId"] for ami in ubuntu_images["Images"]]
    all_ids = [ami["OwnerId"] for ami in all_images["Images"]]

    # Assert all ubuntu_ids are the same and one equals ubuntu_id
    assert all(ubuntu_ids) and ubuntu_ids[0] == ubuntu_id
    # Check we actually have a subset of images
    assert len(ubuntu_ids) < len(all_ids)


@mock_ec2
def test_ami_filter_by_self():
    ec2_resource = boto3.resource("ec2", region_name="us-west-1")
    ec2_client = boto3.client("ec2", region_name="us-west-1")

    my_images = ec2_client.describe_images(Owners=["self"])["Images"]
    my_images.should.have.length_of(0)

    # Create a new image
    instance = ec2_resource.create_instances(
        ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
    )[0]
    instance.create_image(Name="test-image")

    my_images = ec2_client.describe_images(Owners=["self"])["Images"]
    my_images.should.have.length_of(1)


@mock_ec2
def test_ami_snapshots_have_correct_owner():
    ec2_client = boto3.client("ec2", region_name="us-west-1")

    images_response = ec2_client.describe_images()

    owner_id_to_snapshot_ids = {}
    for image in images_response["Images"]:
        owner_id = image["OwnerId"]
        snapshot_ids = [
            block_device_mapping["Ebs"]["SnapshotId"]
            for block_device_mapping in image["BlockDeviceMappings"]
        ]
        existing_snapshot_ids = owner_id_to_snapshot_ids.get(owner_id, [])
        owner_id_to_snapshot_ids[owner_id] = existing_snapshot_ids + snapshot_ids
        # adding an assertion to volumeType
        assert (
            image.get("BlockDeviceMappings", {})[0].get("Ebs", {}).get("VolumeType")
            == "standard"
        )
    for owner_id in owner_id_to_snapshot_ids:
        snapshots_rseponse = ec2_client.describe_snapshots(
            SnapshotIds=owner_id_to_snapshot_ids[owner_id]
        )

        for snapshot in snapshots_rseponse["Snapshots"]:
            assert owner_id == snapshot["OwnerId"]


@mock_ec2
def test_create_image_with_tag_specification():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    client = boto3.client("ec2", region_name="us-west-1")
    tag_specifications = [
        {
            "ResourceType": "image",
            "Tags": [
                {
                    "Key": "Base_AMI_Name",
                    "Value": "Deep Learning Base AMI (Amazon Linux 2) Version 31.0",
                },
                {"Key": "OS_Version", "Value": "AWS Linux 2",},
            ],
        },
    ]
    instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0]
    image_id = client.create_image(
        InstanceId=instance.instance_id,
        Name="test-image",
        Description="test ami",
        TagSpecifications=tag_specifications,
    )["ImageId"]

    image = client.describe_images(ImageIds=[image_id])["Images"][0]
    image["Tags"].should.equal(tag_specifications[0]["Tags"])

    with pytest.raises(ClientError) as ex:
        client.create_image(
            InstanceId=instance.instance_id,
            Name="test-image",
            Description="test ami",
            TagSpecifications=[
                {
                    "ResourceType": "invalid-resource-type",
                    "Tags": [{"Key": "key", "Value": "value"}],
                }
            ],
        )
    ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue")
    ex.value.response["Error"]["Message"].should.equal(
        "'invalid-resource-type' is not a valid taggable resource type for this operation."
    )


@mock_ec2
def test_ami_filter_by_empty_tag():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    client = boto3.client("ec2", region_name="us-west-1")

    fake_images = []
    instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0]
    for i in range(10):
        image = client.create_image(
            InstanceId=instance.instance_id,
            Name="MyAMI{}".format(i),
            Description="Test",
        )

        ec2.create_tags(
            Resources=[image["ImageId"]],
            Tags=[
                {
                    "Key": "Base_AMI_Name",
                    "Value": "Deep Learning Base AMI (Amazon Linux 2) Version 31.0",
                },
                {"Key": "OS_Version", "Value": "AWS Linux 2"},
            ],
        )
        fake_images.append(image)
    # Add release tags to some of the images in the middle
    for image in fake_images[3:6]:
        ec2.create_tags(
            Resources=[image["ImageId"]], Tags=[{"Key": "RELEASE", "Value": ""}]
        )
    images_filter = [
        {
            "Name": "tag:Base_AMI_Name",
            "Values": ["Deep Learning Base AMI (Amazon Linux 2) Version 31.0"],
        },
        {"Name": "tag:OS_Version", "Values": ["AWS Linux 2"]},
        {"Name": "tag:RELEASE", "Values": [""]},
    ]
    assert len(client.describe_images(Filters=images_filter)["Images"]) == 3
