Greengrass Implement create_resource_definition (#5221)

This commit is contained in:
cm-iwata 2022-06-16 00:49:59 +09:00 committed by GitHub
parent 1bbdbc2e0d
commit 00f9b47b45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 424 additions and 0 deletions

View File

@ -5,6 +5,7 @@ from datetime import datetime
from moto.core import BaseBackend, BaseModel, get_account_id
from moto.core.utils import BackendDict, iso_8601_datetime_with_milliseconds
from .exceptions import (
GreengrassClientError,
IdNotFoundException,
InvalidContainerDefinitionException,
VersionNotFoundException,
@ -117,6 +118,55 @@ class FakeDeviceDefinitionVersion(BaseModel):
return obj
class FakeResourceDefinition(BaseModel):
def __init__(self, region_name, name, initial_version):
self.region_name = region_name
self.id = str(uuid.uuid4())
self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:/greengrass/definition/resources/{self.id}"
self.created_at_datetime = datetime.utcnow()
self.update_at_datetime = datetime.utcnow()
self.latest_version = ""
self.latest_version_arn = ""
self.name = name
self.initial_version = initial_version
def to_dict(self):
return {
"Arn": self.arn,
"CreationTimestamp": iso_8601_datetime_with_milliseconds(
self.created_at_datetime
),
"Id": self.id,
"LastUpdatedTimestamp": iso_8601_datetime_with_milliseconds(
self.update_at_datetime
),
"LatestVersion": self.latest_version,
"LatestVersionArn": self.latest_version_arn,
"Name": self.name,
}
class FakeResourceDefinitionVersion(BaseModel):
def __init__(self, region_name, resource_definition_id, resources):
self.region_name = region_name
self.resource_definition_id = resource_definition_id
self.resources = resources
self.version = str(uuid.uuid4())
self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:/greengrass/definition/resources/{self.resource_definition_id}/versions/{self.version}"
self.created_at_datetime = datetime.utcnow()
def to_dict(self):
return {
"Arn": self.arn,
"CreationTimestamp": iso_8601_datetime_with_milliseconds(
self.created_at_datetime
),
"Definition": {"Resources": self.resources},
"Id": self.resource_definition_id,
"Version": self.version,
}
class GreengrassBackend(BaseBackend):
def __init__(self, region_name, account_id):
super().__init__(region_name, account_id)
@ -291,5 +341,74 @@ class GreengrassBackend(BaseBackend):
device_definition_version_id
]
def create_resource_definition(self, name, initial_version):
resources = initial_version.get("Resources", [])
GreengrassBackend._validate_resources(resources)
resource_def = FakeResourceDefinition(self.region_name, name, initial_version)
self.resource_definitions[resource_def.id] = resource_def
init_ver = resource_def.initial_version
resources = init_ver.get("Resources", {})
self.create_resource_definition_version(resource_def.id, resources)
return resource_def
def create_resource_definition_version(self, resource_definition_id, resources):
if resource_definition_id not in self.resource_definitions:
raise IdNotFoundException("That resource definition does not exist.")
GreengrassBackend._validate_resources(resources)
resource_def_ver = FakeResourceDefinitionVersion(
self.region_name, resource_definition_id, resources
)
resources_ver = self.resource_definition_versions.get(
resource_def_ver.resource_definition_id, {}
)
resources_ver[resource_def_ver.version] = resource_def_ver
self.resource_definition_versions[
resource_def_ver.resource_definition_id
] = resources_ver
self.resource_definitions[
resource_definition_id
].latest_version = resource_def_ver.version
self.resource_definitions[
resource_definition_id
].latest_version_arn = resource_def_ver.arn
return resource_def_ver
@staticmethod
def _validate_resources(resources):
for resource in resources:
volume_source_path = (
resource.get("ResourceDataContainer", {})
.get("LocalVolumeResourceData", {})
.get("SourcePath", "")
)
if volume_source_path == "/sys" or volume_source_path.startswith("/sys/"):
raise GreengrassClientError(
"400",
"The resources definition is invalid. (ErrorDetails: [Accessing /sys is prohibited])",
)
local_device_resource_data = resource.get("ResourceDataContainer", {}).get(
"LocalDeviceResourceData", {}
)
if local_device_resource_data:
device_source_path = local_device_resource_data["SourcePath"]
if not device_source_path.startswith("/dev"):
raise GreengrassClientError(
"400",
f"The resources definition is invalid. (ErrorDetails: [Device resource path should begin with "
"/dev"
f", but got: {device_source_path}])",
)
greengrass_backends = BackendDict(GreengrassBackend, "greengrass")

View File

@ -229,3 +229,34 @@ class GreengrassResponse(BaseResponse):
device_definition_version_id=device_definition_version_id,
)
return 200, {"status": 200}, json.dumps(res.to_dict(include_detail=True))
def resource_definitions(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if self.method == "POST":
return self.create_resource_definition()
def create_resource_definition(self):
initial_version = self._get_param("InitialVersion")
name = self._get_param("Name")
res = self.greengrass_backend.create_resource_definition(
name=name, initial_version=initial_version
)
return 201, {"status": 201}, json.dumps(res.to_dict())
def resource_definition_versions(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if self.method == "POST":
return self.create_resource_definition_version()
def create_resource_definition_version(self):
resource_definition_id = self.path.split("/")[-2]
resources = self._get_param("Resources")
res = self.greengrass_backend.create_resource_definition_version(
resource_definition_id=resource_definition_id, resources=resources
)
return 201, {"status": 201}, json.dumps(res.to_dict())

View File

@ -17,4 +17,6 @@ url_paths = {
"{0}/greengrass/definition/devices/(?P<definition_id>[^/]+)/?$": response.device_definition,
"{0}/greengrass/definition/devices/(?P<definition_id>[^/]+)/versions$": response.device_definition_versions,
"{0}/greengrass/definition/devices/(?P<definition_id>[^/]+)/versions/(?P<definition_version_id>[^/]+)/?$": response.device_definition_version,
"{0}/greengrass/definition/resources$": response.resource_definitions,
"{0}/greengrass/definition/resources/(?P<definition_id>[^/]+)/versions$": response.resource_definition_versions,
}

View File

@ -0,0 +1,272 @@
import boto3
from botocore.client import ClientError
import freezegun
import pytest
from moto import mock_greengrass
from moto.core import get_account_id
from moto.settings import TEST_SERVER_MODE
ACCOUNT_ID = get_account_id()
@freezegun.freeze_time("2022-06-01 12:00:00")
@mock_greengrass
def test_create_resource_definition():
client = boto3.client("greengrass", region_name="ap-northeast-1")
init_ver = {
"Resources": [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalVolumeResourceData": {
"DestinationPath": "/test_dir",
"GroupOwnerSetting": {"AutoAddGroupOwner": True},
"SourcePath": "/home/ggc_user/test_dir",
}
},
}
]
}
resource_name = "TestResource"
res = client.create_resource_definition(InitialVersion=init_ver, Name=resource_name)
res.should.have.key("Arn")
res.should.have.key("Id")
res.should.have.key("LatestVersion")
res.should.have.key("LatestVersionArn")
res.should.have.key("Name").equals(resource_name)
res["ResponseMetadata"]["HTTPStatusCode"].should.equal(201)
if not TEST_SERVER_MODE:
res.should.have.key("CreationTimestamp").equals("2022-06-01T12:00:00.000Z")
res.should.have.key("LastUpdatedTimestamp").equals("2022-06-01T12:00:00.000Z")
@mock_greengrass
def test_create_resource_definition_with_invalid_volume_resource():
client = boto3.client("greengrass", region_name="ap-northeast-1")
init_ver = {
"Resources": [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalVolumeResourceData": {
"DestinationPath": "/test_dir",
"GroupOwnerSetting": {"AutoAddGroupOwner": True},
"SourcePath": "/sys/foo",
}
},
}
]
}
with pytest.raises(ClientError) as ex:
client.create_resource_definition(InitialVersion=init_ver)
ex.value.response["Error"]["Message"].should.equal(
"The resources definition is invalid. (ErrorDetails: [Accessing /sys is prohibited])"
)
ex.value.response["Error"]["Code"].should.equal("400")
@mock_greengrass
def test_create_resource_definition_with_invalid_local_device_resource():
client = boto3.client("greengrass", region_name="ap-northeast-1")
source_path = "/foo/bar"
init_ver = {
"Resources": [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalDeviceResourceData": {
"SourcePath": source_path,
}
},
}
]
}
with pytest.raises(ClientError) as ex:
client.create_resource_definition(InitialVersion=init_ver)
ex.value.response["Error"]["Message"].should.equal(
f"The resources definition is invalid. (ErrorDetails: [Device resource path should begin with "
"/dev"
f", but got: {source_path}])"
)
ex.value.response["Error"]["Code"].should.equal("400")
@freezegun.freeze_time("2022-06-01 12:00:00")
@mock_greengrass
def test_create_resource_definition_version():
client = boto3.client("greengrass", region_name="ap-northeast-1")
v1_resources = [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalVolumeResourceData": {
"DestinationPath": "/test_dir",
"GroupOwnerSetting": {"AutoAddGroupOwner": True},
"SourcePath": "/home/ggc_user/test_dir",
}
},
}
]
initial_version = {"Resources": v1_resources}
resource_def_res = client.create_resource_definition(
InitialVersion=initial_version, Name="TestResource"
)
resource_def_id = resource_def_res["Id"]
v2_resources = [
{
"Id": "234",
"Name": "test_directory2",
"ResourceDataContainer": {
"LocalVolumeResourceData": {
"DestinationPath": "/test_dir2",
"GroupOwnerSetting": {"AutoAddGroupOwner": True},
"SourcePath": "/home/ggc_user/test_dir2",
}
},
}
]
device_def_ver_res = client.create_resource_definition_version(
ResourceDefinitionId=resource_def_id, Resources=v2_resources
)
device_def_ver_res.should.have.key("Arn")
device_def_ver_res.should.have.key("CreationTimestamp")
device_def_ver_res.should.have.key("Id").equals(resource_def_id)
device_def_ver_res.should.have.key("Version")
if not TEST_SERVER_MODE:
device_def_ver_res["CreationTimestamp"].should.equal("2022-06-01T12:00:00.000Z")
@mock_greengrass
def test_create_resources_definition_version_with_invalid_id():
client = boto3.client("greengrass", region_name="ap-northeast-1")
resources = [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalVolumeResourceData": {
"DestinationPath": "/test_dir",
"GroupOwnerSetting": {"AutoAddGroupOwner": True},
"SourcePath": "/home/ggc_user/test_dir",
}
},
}
]
with pytest.raises(ClientError) as ex:
client.create_resource_definition_version(
ResourceDefinitionId="7b0bdeae-54c7-47cf-9f93-561e672efd9c",
Resources=resources,
)
ex.value.response["Error"]["Message"].should.equal(
"That resource definition does not exist."
)
ex.value.response["Error"]["Code"].should.equal("IdNotFoundException")
@mock_greengrass
def test_create_resources_definition_version_with_volume_resource():
client = boto3.client("greengrass", region_name="ap-northeast-1")
v1_resources = [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalVolumeResourceData": {
"DestinationPath": "/test_dir",
"GroupOwnerSetting": {"AutoAddGroupOwner": True},
"SourcePath": "/home/ggc_user/test_dir",
}
},
}
]
initial_version = {"Resources": v1_resources}
resource_def_res = client.create_resource_definition(
InitialVersion=initial_version, Name="TestResource"
)
resource_def_id = resource_def_res["Id"]
v2_resources = [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalVolumeResourceData": {
"DestinationPath": "/test_dir",
"GroupOwnerSetting": {"AutoAddGroupOwner": True},
"SourcePath": "/sys/block/sda",
}
},
}
]
with pytest.raises(ClientError) as ex:
client.create_resource_definition_version(
ResourceDefinitionId=resource_def_id, Resources=v2_resources
)
ex.value.response["Error"]["Message"].should.equal(
"The resources definition is invalid. (ErrorDetails: [Accessing /sys is prohibited])"
)
ex.value.response["Error"]["Code"].should.equal("400")
@mock_greengrass
def test_create_resources_definition_version_with_invalid_local_device_resource():
client = boto3.client("greengrass", region_name="ap-northeast-1")
v1_resources = [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalDeviceResourceData": {
"SourcePath": "/dev/null",
}
},
}
]
initial_version = {"Resources": v1_resources}
resource_def_res = client.create_resource_definition(
InitialVersion=initial_version, Name="TestResource"
)
resource_def_id = resource_def_res["Id"]
source_path = "/foo/bar"
v2_resources = [
{
"Id": "123",
"Name": "test_directory",
"ResourceDataContainer": {
"LocalDeviceResourceData": {"SourcePath": source_path}
},
}
]
with pytest.raises(ClientError) as ex:
client.create_resource_definition_version(
ResourceDefinitionId=resource_def_id, Resources=v2_resources
)
ex.value.response["Error"]["Message"].should.equal(
f"The resources definition is invalid. (ErrorDetails: [Device resource path should begin with "
"/dev"
f", but got: {source_path}])"
)
ex.value.response["Error"]["Code"].should.equal("400")