Add ecr registry policy (#4159)
* Add ecr.put_registry_policy * Add ecr.get_registry_policy * Add ecr.delete_registry_policy * Add ecr registry policy test for Terraform and cleanup
This commit is contained in:
parent
5e6b7ee529
commit
6f361e6afb
@ -16,6 +16,18 @@ class LifecyclePolicyNotFoundException(JsonRESTError):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RegistryPolicyNotFoundException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, registry_id):
|
||||||
|
super().__init__(
|
||||||
|
error_type=__class__.__name__,
|
||||||
|
message=(
|
||||||
|
f"Registry policy does not exist in the registry with id '{registry_id}'"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RepositoryAlreadyExistsException(JsonRESTError):
|
class RepositoryAlreadyExistsException(JsonRESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
@ -21,6 +22,7 @@ from moto.ecr.exceptions import (
|
|||||||
InvalidParameterException,
|
InvalidParameterException,
|
||||||
RepositoryPolicyNotFoundException,
|
RepositoryPolicyNotFoundException,
|
||||||
LifecyclePolicyNotFoundException,
|
LifecyclePolicyNotFoundException,
|
||||||
|
RegistryPolicyNotFoundException,
|
||||||
)
|
)
|
||||||
from moto.ecr.policy_validation import EcrLifecyclePolicyValidator
|
from moto.ecr.policy_validation import EcrLifecyclePolicyValidator
|
||||||
from moto.iam.exceptions import MalformedPolicyDocument
|
from moto.iam.exceptions import MalformedPolicyDocument
|
||||||
@ -294,6 +296,7 @@ class Image(BaseObject):
|
|||||||
class ECRBackend(BaseBackend):
|
class ECRBackend(BaseBackend):
|
||||||
def __init__(self, region_name):
|
def __init__(self, region_name):
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
|
self.registry_policy = None
|
||||||
self.repositories: Dict[str, Repository] = {}
|
self.repositories: Dict[str, Repository] = {}
|
||||||
self.tagger = TaggingService(tagName="tags")
|
self.tagger = TaggingService(tagName="tags")
|
||||||
|
|
||||||
@ -764,6 +767,55 @@ class ECRBackend(BaseBackend):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _validate_registry_policy_action(self, policy_text):
|
||||||
|
# only CreateRepository & ReplicateImage actions are allowed
|
||||||
|
VALID_ACTIONS = {"ecr:CreateRepository", "ecr:ReplicateImage"}
|
||||||
|
|
||||||
|
policy = json.loads(policy_text)
|
||||||
|
for statement in policy["Statement"]:
|
||||||
|
if set(statement["Action"]) - VALID_ACTIONS:
|
||||||
|
raise MalformedPolicyDocument()
|
||||||
|
|
||||||
|
def put_registry_policy(self, policy_text):
|
||||||
|
try:
|
||||||
|
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_text)
|
||||||
|
iam_policy_document_validator.validate()
|
||||||
|
|
||||||
|
self._validate_registry_policy_action(policy_text)
|
||||||
|
except MalformedPolicyDocument:
|
||||||
|
raise InvalidParameterException(
|
||||||
|
"Invalid parameter at 'PolicyText' failed to satisfy constraint: "
|
||||||
|
"'Invalid registry policy provided'"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.registry_policy = policy_text
|
||||||
|
|
||||||
|
return {
|
||||||
|
"registryId": ACCOUNT_ID,
|
||||||
|
"policyText": policy_text,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_registry_policy(self):
|
||||||
|
if not self.registry_policy:
|
||||||
|
raise RegistryPolicyNotFoundException(ACCOUNT_ID)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"registryId": ACCOUNT_ID,
|
||||||
|
"policyText": self.registry_policy,
|
||||||
|
}
|
||||||
|
|
||||||
|
def delete_registry_policy(self):
|
||||||
|
policy = self.registry_policy
|
||||||
|
if not policy:
|
||||||
|
raise RegistryPolicyNotFoundException(ACCOUNT_ID)
|
||||||
|
|
||||||
|
self.registry_policy = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"registryId": ACCOUNT_ID,
|
||||||
|
"policyText": policy,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ecr_backends = {}
|
ecr_backends = {}
|
||||||
for region, ec2_backend in ec2_backends.items():
|
for region, ec2_backend in ec2_backends.items():
|
||||||
|
@ -108,10 +108,6 @@ class ECRResponse(BaseResponse):
|
|||||||
)
|
)
|
||||||
return json.dumps(response)
|
return json.dumps(response)
|
||||||
|
|
||||||
def can_paginate(self):
|
|
||||||
if self.is_not_dryrun("CanPaginate"):
|
|
||||||
raise NotImplementedError("ECR.can_paginate is not yet implemented")
|
|
||||||
|
|
||||||
def complete_layer_upload(self):
|
def complete_layer_upload(self):
|
||||||
if self.is_not_dryrun("CompleteLayerUpload"):
|
if self.is_not_dryrun("CompleteLayerUpload"):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
@ -128,12 +124,6 @@ class ECRResponse(BaseResponse):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_presigned_url(self):
|
|
||||||
if self.is_not_dryrun("GeneratePresignedUrl"):
|
|
||||||
raise NotImplementedError(
|
|
||||||
"ECR.generate_presigned_url is not yet implemented"
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_authorization_token(self):
|
def get_authorization_token(self):
|
||||||
registry_ids = self._get_param("registryIds")
|
registry_ids = self._get_param("registryIds")
|
||||||
if not registry_ids:
|
if not registry_ids:
|
||||||
@ -159,10 +149,6 @@ class ECRResponse(BaseResponse):
|
|||||||
"ECR.get_download_url_for_layer is not yet implemented"
|
"ECR.get_download_url_for_layer is not yet implemented"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_paginator(self):
|
|
||||||
if self.is_not_dryrun("GetPaginator"):
|
|
||||||
raise NotImplementedError("ECR.get_paginator is not yet implemented")
|
|
||||||
|
|
||||||
def get_repository_policy(self):
|
def get_repository_policy(self):
|
||||||
registry_id = self._get_param("registryId")
|
registry_id = self._get_param("registryId")
|
||||||
repository_name = self._get_param("repositoryName")
|
repository_name = self._get_param("repositoryName")
|
||||||
@ -173,10 +159,6 @@ class ECRResponse(BaseResponse):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_waiter(self):
|
|
||||||
if self.is_not_dryrun("GetWaiter"):
|
|
||||||
raise NotImplementedError("ECR.get_waiter is not yet implemented")
|
|
||||||
|
|
||||||
def initiate_layer_upload(self):
|
def initiate_layer_upload(self):
|
||||||
if self.is_not_dryrun("InitiateLayerUpload"):
|
if self.is_not_dryrun("InitiateLayerUpload"):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
@ -278,3 +260,14 @@ class ECRResponse(BaseResponse):
|
|||||||
registry_id=registry_id, repository_name=repository_name,
|
registry_id=registry_id, repository_name=repository_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def put_registry_policy(self):
|
||||||
|
policy_text = self._get_param("policyText")
|
||||||
|
|
||||||
|
return json.dumps(self.ecr_backend.put_registry_policy(policy_text=policy_text))
|
||||||
|
|
||||||
|
def get_registry_policy(self):
|
||||||
|
return json.dumps(self.ecr_backend.get_registry_policy())
|
||||||
|
|
||||||
|
def delete_registry_policy(self):
|
||||||
|
return json.dumps(self.ecr_backend.delete_registry_policy())
|
||||||
|
@ -44,6 +44,7 @@ TestAccAWSEc2TransitGatewayVpnAttachmentDataSource
|
|||||||
TestAccAWSEc2TransitGatewayPeeringAttachment
|
TestAccAWSEc2TransitGatewayPeeringAttachment
|
||||||
TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource
|
TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource
|
||||||
TestAccAWSEcrLifecyclePolicy
|
TestAccAWSEcrLifecyclePolicy
|
||||||
|
TestAccAWSEcrRegistryPolicy
|
||||||
TestAccAWSEcrRepository
|
TestAccAWSEcrRepository
|
||||||
TestAccAWSEcrRepositoryDataSource
|
TestAccAWSEcrRepositoryDataSource
|
||||||
TestAccAWSEcrRepositoryPolicy
|
TestAccAWSEcrRepositoryPolicy
|
||||||
|
@ -1955,3 +1955,161 @@ def test_delete_lifecycle_policy_error_policy_not_exists():
|
|||||||
f"for the repository with name '{repo_name}' "
|
f"for the repository with name '{repo_name}' "
|
||||||
f"in the registry with id '{ACCOUNT_ID}'"
|
f"in the registry with id '{ACCOUNT_ID}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecr
|
||||||
|
def test_put_registry_policy():
|
||||||
|
# given
|
||||||
|
client = boto3.client("ecr", region_name="eu-central-1")
|
||||||
|
policy = {
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": ["arn:aws:iam::111111111111:root", "222222222222"]
|
||||||
|
},
|
||||||
|
"Action": ["ecr:CreateRepository", "ecr:ReplicateImage"],
|
||||||
|
"Resource": "*",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.put_registry_policy(policyText=json.dumps(policy))
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["registryId"].should.equal(ACCOUNT_ID)
|
||||||
|
json.loads(response["policyText"]).should.equal(policy)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecr
|
||||||
|
def test_put_registry_policy_error_invalid_action():
|
||||||
|
# given
|
||||||
|
client = boto3.client("ecr", region_name="eu-central-1")
|
||||||
|
policy = {
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {"AWS": "arn:aws:iam::111111111111:root"},
|
||||||
|
"Action": [
|
||||||
|
"ecr:CreateRepository",
|
||||||
|
"ecr:ReplicateImage",
|
||||||
|
"ecr:DescribeRepositories",
|
||||||
|
],
|
||||||
|
"Resource": "*",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
with pytest.raises(ClientError) as e:
|
||||||
|
client.put_registry_policy(policyText=json.dumps(policy))
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.value
|
||||||
|
ex.operation_name.should.equal("PutRegistryPolicy")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidParameterException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"Invalid parameter at 'PolicyText' failed to satisfy constraint: "
|
||||||
|
"'Invalid registry policy provided'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecr
|
||||||
|
def test_get_registry_policy():
|
||||||
|
# given
|
||||||
|
client = boto3.client("ecr", region_name="eu-central-1")
|
||||||
|
policy = {
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": ["arn:aws:iam::111111111111:root", "222222222222"]
|
||||||
|
},
|
||||||
|
"Action": ["ecr:CreateRepository", "ecr:ReplicateImage"],
|
||||||
|
"Resource": "*",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
client.put_registry_policy(policyText=json.dumps(policy))
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.get_registry_policy()
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["registryId"].should.equal(ACCOUNT_ID)
|
||||||
|
json.loads(response["policyText"]).should.equal(policy)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecr
|
||||||
|
def test_get_registry_policy_error_policy_not_exists():
|
||||||
|
# given
|
||||||
|
client = boto3.client("ecr", region_name="eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
with pytest.raises(ClientError) as e:
|
||||||
|
client.get_registry_policy()
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.value
|
||||||
|
ex.operation_name.should.equal("GetRegistryPolicy")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("RegistryPolicyNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
f"Registry policy does not exist in the registry with id '{ACCOUNT_ID}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecr
|
||||||
|
def test_delete_registry_policy():
|
||||||
|
# given
|
||||||
|
client = boto3.client("ecr", region_name="eu-central-1")
|
||||||
|
policy = {
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": ["arn:aws:iam::111111111111:root", "222222222222"]
|
||||||
|
},
|
||||||
|
"Action": ["ecr:CreateRepository", "ecr:ReplicateImage"],
|
||||||
|
"Resource": "*",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
client.put_registry_policy(policyText=json.dumps(policy))
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.delete_registry_policy()
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["registryId"].should.equal(ACCOUNT_ID)
|
||||||
|
json.loads(response["policyText"]).should.equal(policy)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as e:
|
||||||
|
client.get_registry_policy()
|
||||||
|
|
||||||
|
e.value.response["Error"]["Code"].should.contain("RegistryPolicyNotFoundException")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecr
|
||||||
|
def test_delete_registry_policy_error_policy_not_exists():
|
||||||
|
# given
|
||||||
|
client = boto3.client("ecr", region_name="eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
with pytest.raises(ClientError) as e:
|
||||||
|
client.delete_registry_policy()
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.value
|
||||||
|
ex.operation_name.should.equal("DeleteRegistryPolicy")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("RegistryPolicyNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
f"Registry policy does not exist in the registry with id '{ACCOUNT_ID}'"
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user