Skip to content

Commit

Permalink
added test and support for passing in set ids
Browse files Browse the repository at this point in the history
  • Loading branch information
cloutierMat committed Oct 9, 2024
1 parent 228f2ea commit 93f156a
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 14 deletions.
8 changes: 5 additions & 3 deletions moto/apigateway/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1312,9 +1312,11 @@ def create_deployment(
) -> Deployment:
if stage_variables is None:
stage_variables = {}
# Since there are no unique values to a deployment, we will use the stage name for the deployment.
# We are also passing a list of deployment ids to generate to prevent overwriting deployments.
deployment_id = ApigwDeploymentIdentifier(
self.account_id, self.region_name, name
).generate()
self.account_id, self.region_name, stage_name=name
).generate(list(self.deployments.keys()))
deployment = Deployment(deployment_id, name, description)
self.deployments[deployment_id] = deployment
if name:
Expand Down Expand Up @@ -2177,7 +2179,7 @@ def create_api_key(self, payload: Dict[str, Any]) -> ApiKey:
self.account_id,
self.region_name,
# The value of an api key must be unique on aws
payload.get("value"),
payload.get("value", ""),
).generate()
key = ApiKey(api_key_id=api_key_id, **payload)
self.keys[key.id] = key
Expand Down
8 changes: 6 additions & 2 deletions moto/apigateway/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
import string
from typing import Any, Dict
from typing import Any, Dict, List, Union

import yaml

Expand All @@ -14,9 +14,10 @@ class ApigwIdentifier(ResourceIdentifier):
def __init__(self, account_id: str, region: str, name: str):
super().__init__(account_id, region, name)

def generate(self) -> str:
def generate(self, existing_ids: Union[List[str], None] = None) -> str:
return generate_str_id(
self,
existing_ids,
length=10,
include_digits=True,
lower_case=True,
Expand All @@ -37,6 +38,9 @@ class ApigwAuthorizerIdentifier(ApigwIdentifier):
class ApigwDeploymentIdentifier(ApigwIdentifier):
resource = "deployment"

def __init__(self, account_id: str, region: str, stage_name: str):
super().__init__(account_id, region, stage_name)


class ApigwModelIdentifier(ApigwIdentifier):
resource = "model"
Expand Down
9 changes: 6 additions & 3 deletions moto/secretsmanager/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import string

from moto.moto_api._internal import mock_random as random
from moto.utilities.id_generator import ResourceIdentifier, generate_str_id
from moto.utilities.id_generator import ExistingIds, ResourceIdentifier, generate_str_id
from moto.utilities.utils import ARN_PARTITION_REGEX, get_partition


Expand Down Expand Up @@ -104,9 +104,12 @@ class SecretsManagerSecretIdentifier(ResourceIdentifier):
def __init__(self, account_id: str, region: str, secret_id: str):
super().__init__(account_id, region, name=secret_id)

def generate(self) -> str:
def generate(self, existing_ids: ExistingIds = None) -> str:
id_string = generate_str_id(
resource_identifier=self, length=6, include_digits=False
existing_ids=existing_ids,
resource_identifier=self,
length=6,
include_digits=False,
)
return (
f"arn:{get_partition(self.region)}:secretsmanager:{self.region}:"
Expand Down
23 changes: 17 additions & 6 deletions moto/utilities/id_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from moto.moto_api._internal import mock_random

ExistingIds = Union[List[str], None]


class ResourceIdentifier(abc.ABC):
"""
Expand All @@ -23,7 +25,9 @@ def __init__(self, account_id: str, region: str, name: str):
self.name = name or ""

@abc.abstractmethod
def generate(self) -> str: ...
def generate(self, existing_ids: ExistingIds = None) -> str: ...

""" If `existing_ids` is provided, we will not return a custom id if it is already on the list"""

@property
def unique_identifier(self) -> str:
Expand Down Expand Up @@ -74,10 +78,12 @@ def add_id_source(
self._id_sources.append(id_source)

def find_id_from_sources(
self, resource_identifier: ResourceIdentifier
self, resource_identifier: ResourceIdentifier, existing_ids: List[str]
) -> Union[str, None]:
for id_source in self._id_sources:
if found_id := id_source(resource_identifier):
if (
found_id := id_source(resource_identifier)
) and found_id not in existing_ids:
return found_id
return None

Expand All @@ -88,18 +94,23 @@ def find_id_from_sources(
def moto_id(fn: Callable[..., str]) -> Callable[..., str]:
# Decorator for helping in creation of static ids within Moto.
def _wrapper(
resource_identifier: ResourceIdentifier, **kwargs: Dict[str, Any]
resource_identifier: ResourceIdentifier,
existing_ids: ExistingIds,
**kwargs: Dict[str, Any],
) -> str:
if found_id := moto_id_manager.find_id_from_sources(resource_identifier):
if found_id := moto_id_manager.find_id_from_sources(
resource_identifier, existing_ids or []
):
return found_id
return fn(resource_identifier, **kwargs)
return fn(resource_identifier, existing_ids, **kwargs)

return _wrapper


@moto_id
def generate_str_id( # type: ignore
resource_identifier: ResourceIdentifier,
existing_ids: ExistingIds,
length: int = 20,
include_digits: bool = True,
lower_case: bool = False,
Expand Down
17 changes: 17 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

from moto import mock_aws
from moto.utilities.id_generator import ResourceIdentifier, moto_id_manager


@pytest.fixture(scope="function")
Expand All @@ -16,3 +17,19 @@ def account_id():
with mock_aws():
identity = boto3.client("sts", "us-east-1").get_caller_identity()
yield identity["Account"]


@pytest.fixture
def set_custom_id():
set_ids = []

def _set_custom_id(resource_identifier: ResourceIdentifier, custom_id):
moto_id_manager.set_custom_id(
resource_identifier=resource_identifier, custom_id=custom_id
)
set_ids.append(resource_identifier)

yield _set_custom_id

for resource_identifier in set_ids:
moto_id_manager.unset_custom_id(resource_identifier)
133 changes: 133 additions & 0 deletions tests/test_apigateway/test_apigateway_custom_ids.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import boto3

from moto import mock_aws
from moto.apigateway.utils import (
ApigwApiKeyIdentifier,
ApigwDeploymentIdentifier,
ApigwModelIdentifier,
ApigwRequestValidatorIdentifier,
ApigwResourceIdentifier,
ApigwRestApiValidatorIdentifier,
ApigwUsagePlanIdentifier,
)

API_ID = "ApiId"
API_KEY_ID = "ApiKeyId"
DEPLOYMENT_ID = "DeployId"
MODEL_ID = "ModelId"
PET_1_RESOURCE_ID = "Pet1Id"
PET_2_RESOURCE_ID = "Pet2Id"
REQUEST_VALIDATOR_ID = "ReqValId"
ROOT_RESOURCE_ID = "RootId"
USAGE_PLAN_ID = "UPlanId"


@mock_aws
def test_custom_id_rest_api(set_custom_id, account_id):
region_name = "us-west-2"
rest_api_name = "my-api"
model_name = "modelName"
request_validator_name = "request-validator-name"
stage_name = "stage-name"

client = boto3.client("apigateway", region_name=region_name)

set_custom_id(
ApigwRestApiValidatorIdentifier(account_id, region_name, rest_api_name), API_ID
)
set_custom_id(
ApigwResourceIdentifier(account_id, region_name, path_name="/"),
ROOT_RESOURCE_ID,
)
set_custom_id(
ApigwResourceIdentifier(
account_id, region_name, parent_id=ROOT_RESOURCE_ID, path_name="pet"
),
PET_1_RESOURCE_ID,
)
set_custom_id(
ApigwResourceIdentifier(
account_id, region_name, parent_id=PET_1_RESOURCE_ID, path_name="pet"
),
PET_2_RESOURCE_ID,
)
set_custom_id(ApigwModelIdentifier(account_id, region_name, model_name), MODEL_ID)
set_custom_id(
ApigwRequestValidatorIdentifier(
account_id, region_name, request_validator_name
),
REQUEST_VALIDATOR_ID,
)
set_custom_id(
ApigwDeploymentIdentifier(account_id, region_name, stage_name=stage_name),
DEPLOYMENT_ID,
)

rest_api = client.create_rest_api(name=rest_api_name)
assert rest_api["id"] == API_ID
assert rest_api["rootResourceId"] == ROOT_RESOURCE_ID

pet_resource_1 = client.create_resource(
restApiId=API_ID, parentId=ROOT_RESOURCE_ID, pathPart="pet"
)
assert pet_resource_1["id"] == PET_1_RESOURCE_ID

# we create a second resource with the same path part to ensure we can pass different ids
pet_resource_2 = client.create_resource(
restApiId=API_ID, parentId=PET_1_RESOURCE_ID, pathPart="pet"
)
assert pet_resource_2["id"] == PET_2_RESOURCE_ID

model = client.create_model(
restApiId=API_ID,
name=model_name,
schema="EMPTY",
contentType="application/json",
)
assert model["id"] == MODEL_ID

request_validator = client.create_request_validator(
restApiId=API_ID, name=request_validator_name
)
assert request_validator["id"] == REQUEST_VALIDATOR_ID

# Creating the resource to make a deployment
client.put_method(
restApiId=API_ID,
httpMethod="ANY",
resourceId=PET_2_RESOURCE_ID,
authorizationType="NONE",
)
client.put_integration(
restApiId=API_ID, resourceId=PET_2_RESOURCE_ID, httpMethod="ANY", type="MOCK"
)
deployment = client.create_deployment(restApiId=API_ID, stageName=stage_name)
assert deployment["id"] == DEPLOYMENT_ID


@mock_aws
def test_custom_id_api_key(account_id, set_custom_id):
region_name = "us-west-2"
api_key_value = "01234567890123456789"
usage_plan_name = "usage-plan"

client = boto3.client("apigateway", region_name=region_name)

set_custom_id(
ApigwApiKeyIdentifier(account_id, region_name, value=api_key_value), API_KEY_ID
)
set_custom_id(
ApigwUsagePlanIdentifier(account_id, region_name, usage_plan_name),
USAGE_PLAN_ID,
)

api_key = client.create_api_key(name="api-key", value=api_key_value)
usage_plan = client.create_usage_plan(name=usage_plan_name)

# verify that we can create a usage plan key using the custom ids
client.create_usage_plan_key(
usagePlanId=USAGE_PLAN_ID, keyId=API_KEY_ID, keyType="API_KEY"
)

assert api_key["id"] == API_KEY_ID
assert usage_plan["id"] == USAGE_PLAN_ID
18 changes: 18 additions & 0 deletions tests/test_secretsmanager/test_secretsmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from moto import mock_aws, settings
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
from moto.secretsmanager.utils import SecretsManagerSecretIdentifier

from . import secretsmanager_aws_verified

Expand Down Expand Up @@ -1950,3 +1951,20 @@ def test_update_secret_version_stage_dont_specify_current_stage(secret_arn=None)
err["Message"]
== f"The parameter RemoveFromVersionId can't be empty. Staging label AWSCURRENT is currently attached to version {current_version}, so you must explicitly reference that version in RemoveFromVersionId."
)


@mock_aws
def test_create_secret_custom_id(account_id, set_custom_id):
secret_suffix = "randomSuffix"
secret_name = "secret-name"
region_name = "us-east-1"

client = boto3.client("secretsmanager", region_name=region_name)

set_custom_id(
SecretsManagerSecretIdentifier(account_id, region_name, secret_name),
secret_suffix,
)
secret = client.create_secret(Name=secret_name, SecretString="my secret")

assert secret["ARN"].split(":")[-1] == f"{secret_name}-{secret_suffix}"

0 comments on commit 93f156a

Please sign in to comment.