Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial work for split out of s3control #4714

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 63 additions & 1 deletion IMPLEMENTATION_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4215,6 +4215,69 @@
- [ ] write_get_object_response
</details>

## s3control
<details>
<summary>5% implemented</summary>

- [ ] create_access_point
- [ ] create_access_point_for_object_lambda
- [ ] create_bucket
- [ ] create_job
- [ ] create_multi_region_access_point
- [ ] delete_access_point
- [ ] delete_access_point_for_object_lambda
- [ ] delete_access_point_policy
- [ ] delete_access_point_policy_for_object_lambda
- [ ] delete_bucket
- [ ] delete_bucket_lifecycle_configuration
- [ ] delete_bucket_policy
- [ ] delete_bucket_tagging
- [ ] delete_job_tagging
- [ ] delete_multi_region_access_point
- [X] delete_public_access_block
- [ ] delete_storage_lens_configuration
- [ ] delete_storage_lens_configuration_tagging
- [ ] describe_job
- [ ] describe_multi_region_access_point_operation
- [ ] get_access_point
- [ ] get_access_point_configuration_for_object_lambda
- [ ] get_access_point_for_object_lambda
- [ ] get_access_point_policy
- [ ] get_access_point_policy_for_object_lambda
- [ ] get_access_point_policy_status
- [ ] get_access_point_policy_status_for_object_lambda
- [ ] get_bucket
- [ ] get_bucket_lifecycle_configuration
- [ ] get_bucket_policy
- [ ] get_bucket_tagging
- [ ] get_job_tagging
- [ ] get_multi_region_access_point
- [ ] get_multi_region_access_point_policy
- [ ] get_multi_region_access_point_policy_status
- [X] get_public_access_block
- [ ] get_storage_lens_configuration
- [ ] get_storage_lens_configuration_tagging
- [ ] list_access_points
- [ ] list_access_points_for_object_lambda
- [ ] list_jobs
- [ ] list_multi_region_access_points
- [ ] list_regional_buckets
- [ ] list_storage_lens_configurations
- [ ] put_access_point_configuration_for_object_lambda
- [ ] put_access_point_policy
- [ ] put_access_point_policy_for_object_lambda
- [ ] put_bucket_lifecycle_configuration
- [ ] put_bucket_policy
- [ ] put_bucket_tagging
- [ ] put_job_tagging
- [ ] put_multi_region_access_point_policy
- [X] put_public_access_block
- [ ] put_storage_lens_configuration
- [ ] put_storage_lens_configuration_tagging
- [ ] update_job_priority
- [ ] update_job_status
</details>

## sagemaker
<details>
<summary>15% implemented</summary>
Expand Down Expand Up @@ -5205,7 +5268,6 @@
- route53-recovery-readiness
- route53domains
- rum
- s3control
- s3outposts
- sagemaker-a2i-runtime
- sagemaker-edge
Expand Down
87 changes: 87 additions & 0 deletions docs/docs/services/s3control.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
.. _implementedservice_s3control:

.. |start-h3| raw:: html

<h3>

.. |end-h3| raw:: html

</h3>

=========
s3control
=========

.. autoclass:: moto.s3control.models.S3ControlBackend

|start-h3| Example usage |end-h3|

.. sourcecode:: python

@mock_s3control
def test_s3control_behaviour:
boto3.client("s3control")
...



|start-h3| Implemented features for this service |end-h3|

- [ ] create_access_point
- [ ] create_access_point_for_object_lambda
- [ ] create_bucket
- [ ] create_job
- [ ] create_multi_region_access_point
- [ ] delete_access_point
- [ ] delete_access_point_for_object_lambda
- [ ] delete_access_point_policy
- [ ] delete_access_point_policy_for_object_lambda
- [ ] delete_bucket
- [ ] delete_bucket_lifecycle_configuration
- [ ] delete_bucket_policy
- [ ] delete_bucket_tagging
- [ ] delete_job_tagging
- [ ] delete_multi_region_access_point
- [X] delete_public_access_block
- [ ] delete_storage_lens_configuration
- [ ] delete_storage_lens_configuration_tagging
- [ ] describe_job
- [ ] describe_multi_region_access_point_operation
- [ ] get_access_point
- [ ] get_access_point_configuration_for_object_lambda
- [ ] get_access_point_for_object_lambda
- [ ] get_access_point_policy
- [ ] get_access_point_policy_for_object_lambda
- [ ] get_access_point_policy_status
- [ ] get_access_point_policy_status_for_object_lambda
- [ ] get_bucket
- [ ] get_bucket_lifecycle_configuration
- [ ] get_bucket_policy
- [ ] get_bucket_tagging
- [ ] get_job_tagging
- [ ] get_multi_region_access_point
- [ ] get_multi_region_access_point_policy
- [ ] get_multi_region_access_point_policy_status
- [X] get_public_access_block
- [ ] get_storage_lens_configuration
- [ ] get_storage_lens_configuration_tagging
- [ ] list_access_points
- [ ] list_access_points_for_object_lambda
- [ ] list_jobs
- [ ] list_multi_region_access_points
- [ ] list_regional_buckets
- [ ] list_storage_lens_configurations
- [ ] put_access_point_configuration_for_object_lambda
- [ ] put_access_point_policy
- [ ] put_access_point_policy_for_object_lambda
- [ ] put_bucket_lifecycle_configuration
- [ ] put_bucket_policy
- [ ] put_bucket_tagging
- [ ] put_job_tagging
- [ ] put_multi_region_access_point_policy
- [X] put_public_access_block
- [ ] put_storage_lens_configuration
- [ ] put_storage_lens_configuration_tagging
- [ ] update_job_priority
- [ ] update_job_status

2 changes: 1 addition & 1 deletion moto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def f(*args, **kwargs):
mock_elasticache = lazy_load(
".elasticache", "mock_elasticache", boto3_name="elasticache"
)
mock_s3control = lazy_load(".s3control", "mock_s3control", boto3_name="s3control")


class MockAll(ContextDecorator):
Expand Down Expand Up @@ -206,7 +207,6 @@ def __exit__(self, *exc):
__title__ = "moto"
__version__ = "2.2.20.dev"


try:
# Need to monkey-patch botocore requests back to underlying urllib3 classes
from botocore.awsrequest import (
Expand Down
1 change: 1 addition & 0 deletions moto/backend_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"https?://(?P<bucket_name>[a-zA-Z0-9\\-_.]*)\\.?s3(.*)\\.amazonaws.com"
),
),
("s3control", re.compile("https?://(.+)\\.s3-control\\.(.+)\\.amazonaws\\.com")),
("sagemaker", re.compile("https?://api.sagemaker\\.(.+)\\.amazonaws.com")),
("sdb", re.compile("https?://sdb\\.(.+)\\.amazonaws\\.com")),
("secretsmanager", re.compile("https?://secretsmanager\\.(.+)\\.amazonaws\\.com")),
Expand Down
5 changes: 5 additions & 0 deletions moto/s3control/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""s3control module initialization; sets value for base decorator."""
from .models import s3control_backends
from ..core.models import base_decorator

mock_s3control = base_decorator(s3control_backends)
35 changes: 35 additions & 0 deletions moto/s3control/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Exceptions raised by the s3control service."""
from moto.s3.exceptions import S3ClientError


class NoSuchPublicAccessBlockConfiguration(S3ClientError):
code = 404

def __init__(self, *args, **kwargs):
super(NoSuchPublicAccessBlockConfiguration, self).__init__(
"NoSuchPublicAccessBlockConfiguration",
"The public access block configuration was not found",
*args,
**kwargs,
)


class InvalidPublicAccessBlockConfiguration(S3ClientError):
code = 400

def __init__(self, *args, **kwargs):
super(InvalidPublicAccessBlockConfiguration, self).__init__(
"InvalidRequest",
"Must specify at least one configuration.",
*args,
**kwargs,
)


class WrongPublicAccessBlockAccountIdError(S3ClientError):
code = 403

def __init__(self):
super(WrongPublicAccessBlockAccountIdError, self).__init__(
"AccessDenied", "Access Denied"
)
98 changes: 98 additions & 0 deletions moto/s3control/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""S3ControlBackend class with methods for supported APIs."""
from collections import defaultdict

from boto3 import Session

from moto.core import BaseBackend, BaseModel
from moto.s3control.exceptions import (
NoSuchPublicAccessBlockConfiguration,
WrongPublicAccessBlockAccountIdError,
)


class PublicAccessBlock(BaseModel):
def __init__(
self,
block_public_acls=False,
ignore_public_acls=False,
block_public_policy=False,
restrict_public_buckets=False,
):
# The boto XML appears to expect these values to exist as lowercase strings...
self.block_public_acls = block_public_acls or "false"
self.ignore_public_acls = ignore_public_acls or "false"
self.block_public_policy = block_public_policy or "false"
self.restrict_public_buckets = restrict_public_buckets or "false"

def to_config_dict(self):
# Need to make the string values booleans for Config:
return {
"blockPublicAcls": convert_str_to_bool(self.block_public_acls),
"ignorePublicAcls": convert_str_to_bool(self.ignore_public_acls),
"blockPublicPolicy": convert_str_to_bool(self.block_public_policy),
"restrictPublicBuckets": convert_str_to_bool(self.restrict_public_buckets),
}


def convert_str_to_bool(item):
"""Converts a boolean string to a boolean value"""
if isinstance(item, str):
return item.lower() == "true"

return False


class S3ControlBackend(BaseBackend):
"""Implementation of S3Control APIs."""

def __init__(self, region_name):
self.initialized = defaultdict()
self.region_name = region_name
self.public_access_block_configuration = defaultdict()

def reset(self):
"""Re-initialize all attributes for this instance."""
region_name = self.region_name
self.__dict__ = {}
self.__init__(region_name)

@staticmethod
def get_public_access_block(account_id):
if account_id not in s3control_backends:
raise WrongPublicAccessBlockAccountIdError()
s3control_backend = s3control_backends[account_id]
if not s3control_backend.initialized or s3control_backend is None:
raise NoSuchPublicAccessBlockConfiguration()
return s3control_backend.public_access_block_configuration

def put_public_access_block(self, account_id, public_access_block_configuration):
if account_id not in s3control_backends:
s3control_backends[account_id] = S3ControlBackend(self.region_name)
s3control_backend = s3control_backends[account_id]
else:
s3control_backend = s3control_backends[account_id]
s3control_backend.initialized = True
s3control_backend.public_access_block_configuration = PublicAccessBlock(
public_access_block_configuration.get("BlockPublicAcls"),
public_access_block_configuration.get("IgnorePublicAcls"),
public_access_block_configuration.get("BlockPublicPolicy"),
public_access_block_configuration.get("RestrictPublicBuckets"),
)

@staticmethod
def delete_public_access_block(account_id):
s3control_backend = s3control_backends[account_id]
s3control_backend.initialized = False


s3control_backends = {}
for available_region in Session().get_available_regions("s3control"):
s3control_backends[available_region] = S3ControlBackend(available_region)
for available_region in Session().get_available_regions(
"s3control", partition_name="aws-us-gov"
):
s3control_backends[available_region] = S3ControlBackend(available_region)
for available_region in Session().get_available_regions(
"s3control", partition_name="aws-cn"
):
s3control_backends[available_region] = S3ControlBackend(available_region)
61 changes: 61 additions & 0 deletions moto/s3control/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Handles incoming s3control requests, invokes methods, returns responses."""
import json

import xmltodict

from moto.core.responses import BaseResponse
from .models import s3control_backends


class S3ControlResponse(BaseResponse):
SERVICE_NAME = "s3control"
"""Handler for S3Control requests and responses."""

@property
def s3control_backend(self):
"""Return backend instance specific for this region."""
return s3control_backends[self.region]

# add methods from here

def get_public_access_block(self):
account_id = self.headers["x-amz-account-id"]
public_access_block_configuration = self.s3control_backend.get_public_access_block(
account_id=account_id,
)
template = self.response_template(GET_PUBLIC_ACCESS_BLOCK_TEMPLATE)
return template.render(
BlockPublicAcls=public_access_block_configuration.block_public_acls,
IgnorePublicAcls=public_access_block_configuration.ignore_public_acls,
BlockPublicPolicy=public_access_block_configuration.block_public_policy,
RestrictPublicBuckets=public_access_block_configuration.restrict_public_buckets,
)

def put_public_access_block(self):
account_id = self.headers["x-amz-account-id"]
pab_config = self._parse_pab_config(self.body)
self.s3control_backend.put_public_access_block(
account_id, pab_config["PublicAccessBlockConfiguration"]
)
return json.dumps({})

def delete_public_access_block(self):
account_id = self.headers["x-amz-account-id"]
self.s3control_backend.delete_public_access_block(account_id=account_id,)
return json.dumps({})

def _parse_pab_config(self, body):
parsed_xml = xmltodict.parse(body)
parsed_xml["PublicAccessBlockConfiguration"].pop("@xmlns", None)

return parsed_xml


GET_PUBLIC_ACCESS_BLOCK_TEMPLATE = """
<PublicAccessBlockConfiguration xmlns="http://awss3control.amazonaws.com/doc/2018-08-20/">
<BlockPublicAcls>{{ BlockPublicAcls }}</BlockPublicAcls>
<IgnorePublicAcls>{{ IgnorePublicAcls }}</IgnorePublicAcls>
<BlockPublicPolicy>{{ BlockPublicPolicy }}</BlockPublicPolicy>
<RestrictPublicBuckets>{{ RestrictPublicBuckets }}</RestrictPublicBuckets>
</PublicAccessBlockConfiguration>
"""