Implemented Glue Schema Registry CreateRegistry API (#5234)
This commit is contained in:
parent
25aad70481
commit
6343d24c92
@ -97,3 +97,49 @@ class InvalidInputException(_InvalidOperationException):
|
|||||||
class InvalidStateException(_InvalidOperationException):
|
class InvalidStateException(_InvalidOperationException):
|
||||||
def __init__(self, op, msg):
|
def __init__(self, op, msg):
|
||||||
super().__init__("InvalidStateException", op, msg)
|
super().__init__("InvalidStateException", op, msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceNumberLimitExceededException(_InvalidOperationException):
|
||||||
|
def __init__(self, op, resource):
|
||||||
|
super().__init__(
|
||||||
|
"ResourceNumberLimitExceededException",
|
||||||
|
op,
|
||||||
|
"More "
|
||||||
|
+ resource
|
||||||
|
+ " cannot be created. The maximum limit has been reached.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GSRAlreadyExistsException(_InvalidOperationException):
|
||||||
|
def __init__(self, op, resource, param_name, param_value):
|
||||||
|
super().__init__(
|
||||||
|
"AlreadyExistsException",
|
||||||
|
op,
|
||||||
|
resource + " already exists. " + param_name + ": " + param_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceNameTooLongException(InvalidInputException):
|
||||||
|
def __init__(self, op, param_name):
|
||||||
|
super().__init__(
|
||||||
|
op,
|
||||||
|
"The resource name contains too many or too few characters. Parameter Name: "
|
||||||
|
+ param_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ParamValueContainsInvalidCharactersException(InvalidInputException):
|
||||||
|
def __init__(self, op, param_name):
|
||||||
|
super().__init__(
|
||||||
|
op,
|
||||||
|
"The parameter value contains one or more characters that are not valid. Parameter Name: "
|
||||||
|
+ param_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidNumberOfTagsException(InvalidInputException):
|
||||||
|
def __init__(self, op):
|
||||||
|
super().__init__(
|
||||||
|
op,
|
||||||
|
"New Tags cannot be empty or more than 50",
|
||||||
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@ -19,6 +20,11 @@ from .exceptions import (
|
|||||||
VersionNotFoundException,
|
VersionNotFoundException,
|
||||||
JobNotFoundException,
|
JobNotFoundException,
|
||||||
ConcurrentRunsExceededException,
|
ConcurrentRunsExceededException,
|
||||||
|
GSRAlreadyExistsException,
|
||||||
|
ResourceNumberLimitExceededException,
|
||||||
|
ResourceNameTooLongException,
|
||||||
|
ParamValueContainsInvalidCharactersException,
|
||||||
|
InvalidNumberOfTagsException,
|
||||||
)
|
)
|
||||||
from .utils import PartitionFilter
|
from .utils import PartitionFilter
|
||||||
from ..utilities.paginator import paginate
|
from ..utilities.paginator import paginate
|
||||||
@ -48,6 +54,7 @@ class GlueBackend(BaseBackend):
|
|||||||
self.jobs = OrderedDict()
|
self.jobs = OrderedDict()
|
||||||
self.job_runs = OrderedDict()
|
self.job_runs = OrderedDict()
|
||||||
self.tagger = TaggingService()
|
self.tagger = TaggingService()
|
||||||
|
self.registries = OrderedDict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_vpc_endpoint_service(service_region, zones):
|
def default_vpc_endpoint_service(service_region, zones):
|
||||||
@ -247,6 +254,63 @@ class GlueBackend(BaseBackend):
|
|||||||
def untag_resource(self, resource_arn, tag_keys):
|
def untag_resource(self, resource_arn, tag_keys):
|
||||||
self.tagger.untag_resource_using_names(resource_arn, tag_keys)
|
self.tagger.untag_resource_using_names(resource_arn, tag_keys)
|
||||||
|
|
||||||
|
# TODO: @Himani. Will Refactor validation logic as I find the common validation required for other APIs
|
||||||
|
def create_registry(self, registry_name, description, tags):
|
||||||
|
operation_name = "CreateRegistry"
|
||||||
|
|
||||||
|
registry_name_pattern = re.compile(r"^[a-zA-Z0-9-_$#.]+$")
|
||||||
|
registry_description_pattern = re.compile(
|
||||||
|
r"[\\u0020-\\uD7FF\\uE000-\\uFFFD\\uD800\\uDC00-\\uDBFF\\uDFFF\\r\\n\\t]*"
|
||||||
|
)
|
||||||
|
|
||||||
|
max_registry_name_length = 255
|
||||||
|
max_registries_allowed = 10
|
||||||
|
max_description_length = 2048
|
||||||
|
max_tags_allowed = 50
|
||||||
|
|
||||||
|
if len(self.registries) >= max_registries_allowed:
|
||||||
|
raise ResourceNumberLimitExceededException(
|
||||||
|
operation_name, resource="registries"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
registry_name == ""
|
||||||
|
or len(registry_name.encode("utf-8")) > max_registry_name_length
|
||||||
|
):
|
||||||
|
param_name = "registryName"
|
||||||
|
raise ResourceNameTooLongException(operation_name, param_name)
|
||||||
|
|
||||||
|
if re.match(registry_name_pattern, registry_name) is None:
|
||||||
|
param_name = "registryName"
|
||||||
|
raise ParamValueContainsInvalidCharactersException(
|
||||||
|
operation_name, param_name
|
||||||
|
)
|
||||||
|
|
||||||
|
if registry_name in self.registries:
|
||||||
|
raise GSRAlreadyExistsException(
|
||||||
|
operation_name,
|
||||||
|
resource="Registry",
|
||||||
|
param_name="RegistryName",
|
||||||
|
param_value=registry_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if description and len(description.encode("utf-8")) > max_description_length:
|
||||||
|
param_name = "description"
|
||||||
|
raise ResourceNameTooLongException(operation_name, param_name)
|
||||||
|
|
||||||
|
if description and re.match(registry_description_pattern, description) is None:
|
||||||
|
param_name = "description"
|
||||||
|
raise ParamValueContainsInvalidCharactersException(
|
||||||
|
operation_name, param_name
|
||||||
|
)
|
||||||
|
|
||||||
|
if tags and len(tags) > max_tags_allowed:
|
||||||
|
raise InvalidNumberOfTagsException(operation_name)
|
||||||
|
|
||||||
|
registry = FakeRegistry(registry_name, description, tags)
|
||||||
|
self.registries[registry_name] = registry
|
||||||
|
return registry
|
||||||
|
|
||||||
|
|
||||||
class FakeDatabase(BaseModel):
|
class FakeDatabase(BaseModel):
|
||||||
def __init__(self, database_name, database_input):
|
def __init__(self, database_name, database_input):
|
||||||
@ -631,6 +695,26 @@ class FakeJobRun:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeRegistry(BaseModel):
|
||||||
|
def __init__(self, registry_name, description=None, tags=None):
|
||||||
|
self.name = registry_name
|
||||||
|
self.description = description
|
||||||
|
self.tags = tags
|
||||||
|
self.created_time = datetime.utcnow()
|
||||||
|
self.updated_time = datetime.utcnow()
|
||||||
|
self.registry_arn = (
|
||||||
|
f"arn:aws:glue:us-east-1:{get_account_id()}:registry/{self.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"RegistryArn": self.registry_arn,
|
||||||
|
"RegistryName": self.name,
|
||||||
|
"Description": self.description,
|
||||||
|
"Tags": self.tags,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
glue_backends = BackendDict(
|
glue_backends = BackendDict(
|
||||||
GlueBackend, "glue", use_boto3_regions=False, additional_regions=["global"]
|
GlueBackend, "glue", use_boto3_regions=False, additional_regions=["global"]
|
||||||
)
|
)
|
||||||
|
@ -452,3 +452,10 @@ class GlueResponse(BaseResponse):
|
|||||||
if glue_resource_tags[key] == tags[key]:
|
if glue_resource_tags[key] == tags[key]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def create_registry(self):
|
||||||
|
registry_name = self._get_param("RegistryName")
|
||||||
|
description = self._get_param("Description")
|
||||||
|
tags = self._get_param("Tags")
|
||||||
|
registry = self.glue_backend.create_registry(registry_name, description, tags)
|
||||||
|
return json.dumps(registry.as_dict())
|
||||||
|
@ -7,6 +7,7 @@ import pytest
|
|||||||
import sure # noqa # pylint: disable=unused-import
|
import sure # noqa # pylint: disable=unused-import
|
||||||
from botocore.exceptions import ParamValidationError
|
from botocore.exceptions import ParamValidationError
|
||||||
from botocore.client import ClientError
|
from botocore.client import ClientError
|
||||||
|
from moto.core import ACCOUNT_ID
|
||||||
|
|
||||||
from moto import mock_glue
|
from moto import mock_glue
|
||||||
|
|
||||||
@ -449,3 +450,158 @@ def test_untag_glue_crawler():
|
|||||||
resp = client.get_tags(ResourceArn=resource_arn)
|
resp = client.get_tags(ResourceArn=resource_arn)
|
||||||
|
|
||||||
resp.should.have.key("Tags").equals({"key1": "value1", "key3": "value3"})
|
resp.should.have.key("Tags").equals({"key1": "value1", "key3": "value3"})
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_valid_input():
|
||||||
|
client = create_glue_client()
|
||||||
|
registry_name = "TestRegistry"
|
||||||
|
response = client.create_registry(
|
||||||
|
RegistryName=registry_name,
|
||||||
|
Description="test_create_registry_description",
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
response.should.have.key("RegistryName").equals("TestRegistry")
|
||||||
|
response.should.have.key("Description").equals("test_create_registry_description")
|
||||||
|
response.should.have.key("Tags").equals({"key1": "value1", "key2": "value2"})
|
||||||
|
response.should.have.key("RegistryArn").equals(
|
||||||
|
f"arn:aws:glue:us-east-1:{ACCOUNT_ID}:registry/" + registry_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_valid_partial_input():
|
||||||
|
client = create_glue_client()
|
||||||
|
registry_name = "TestRegistry"
|
||||||
|
response = client.create_registry(RegistryName=registry_name)
|
||||||
|
response.should.have.key("RegistryName").equals("TestRegistry")
|
||||||
|
response.should.have.key("RegistryArn").equals(
|
||||||
|
f"arn:aws:glue:us-east-1:{ACCOUNT_ID}:registry/" + registry_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_invalid_input_registry_name_too_long():
|
||||||
|
client = create_glue_client()
|
||||||
|
registry_name = ""
|
||||||
|
for _ in range(90):
|
||||||
|
registry_name = registry_name + "foobar"
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName=registry_name,
|
||||||
|
Description="test_create_registry_description",
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidInputException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"An error occurred (InvalidInputException) when calling the CreateRegistry operation: The resource name contains too many or too few characters. Parameter Name: registryName"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_more_than_allowed():
|
||||||
|
client = create_glue_client()
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
registry_name = "TestRegistry" + str(i)
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName=registry_name,
|
||||||
|
Description="test_create_registry_description",
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName="TestRegistry10",
|
||||||
|
Description="test_create_registry_description10",
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNumberLimitExceededException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"An error occurred (ResourceNumberLimitExceededException) when calling the CreateRegistry operation: More registries cannot be created. The maximum limit has been reached."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_invalid_registry_name():
|
||||||
|
client = create_glue_client()
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName="A,B,C",
|
||||||
|
Description="test_create_registry_description",
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidInputException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"An error occurred (InvalidInputException) when calling the CreateRegistry operation: The parameter value contains one or more characters that are not valid. Parameter Name: registryName"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_already_exists():
|
||||||
|
client = create_glue_client()
|
||||||
|
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName="TestRegistry1",
|
||||||
|
Description="test_create_registry_description1",
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName="TestRegistry1",
|
||||||
|
Description="test_create_registry_description1",
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("AlreadyExistsException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"An error occurred (AlreadyExistsException) when calling the CreateRegistry operation: Registry already exists. RegistryName: TestRegistry1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_invalid_description_too_long():
|
||||||
|
client = create_glue_client()
|
||||||
|
description = ""
|
||||||
|
for _ in range(300):
|
||||||
|
description = description + "foobar, "
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName="TestRegistry1",
|
||||||
|
Description=description,
|
||||||
|
Tags={"key1": "value1", "key2": "value2"},
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidInputException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"An error occurred (InvalidInputException) when calling the CreateRegistry operation: The resource name contains too many or too few characters. Parameter Name: description"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_registry_invalid_number_of_tags():
|
||||||
|
tags = {}
|
||||||
|
for i in range(51):
|
||||||
|
key = "k" + str(i)
|
||||||
|
val = "v" + str(i)
|
||||||
|
tags[key] = val
|
||||||
|
|
||||||
|
client = create_glue_client()
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_registry(
|
||||||
|
RegistryName="TestRegistry1",
|
||||||
|
Description="test_create_registry_description",
|
||||||
|
Tags=tags,
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidInputException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"An error occurred (InvalidInputException) when calling the CreateRegistry operation: New Tags cannot be empty or more than 50"
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user