Greengrass Implement create_resource_definition (#5221)
This commit is contained in:
parent
1bbdbc2e0d
commit
00f9b47b45
@ -5,6 +5,7 @@ from datetime import datetime
|
|||||||
from moto.core import BaseBackend, BaseModel, get_account_id
|
from moto.core import BaseBackend, BaseModel, get_account_id
|
||||||
from moto.core.utils import BackendDict, iso_8601_datetime_with_milliseconds
|
from moto.core.utils import BackendDict, iso_8601_datetime_with_milliseconds
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
|
GreengrassClientError,
|
||||||
IdNotFoundException,
|
IdNotFoundException,
|
||||||
InvalidContainerDefinitionException,
|
InvalidContainerDefinitionException,
|
||||||
VersionNotFoundException,
|
VersionNotFoundException,
|
||||||
@ -117,6 +118,55 @@ class FakeDeviceDefinitionVersion(BaseModel):
|
|||||||
return obj
|
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):
|
class GreengrassBackend(BaseBackend):
|
||||||
def __init__(self, region_name, account_id):
|
def __init__(self, region_name, account_id):
|
||||||
super().__init__(region_name, account_id)
|
super().__init__(region_name, account_id)
|
||||||
@ -291,5 +341,74 @@ class GreengrassBackend(BaseBackend):
|
|||||||
device_definition_version_id
|
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")
|
greengrass_backends = BackendDict(GreengrassBackend, "greengrass")
|
||||||
|
@ -229,3 +229,34 @@ class GreengrassResponse(BaseResponse):
|
|||||||
device_definition_version_id=device_definition_version_id,
|
device_definition_version_id=device_definition_version_id,
|
||||||
)
|
)
|
||||||
return 200, {"status": 200}, json.dumps(res.to_dict(include_detail=True))
|
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())
|
||||||
|
@ -17,4 +17,6 @@ url_paths = {
|
|||||||
"{0}/greengrass/definition/devices/(?P<definition_id>[^/]+)/?$": response.device_definition,
|
"{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$": response.device_definition_versions,
|
||||||
"{0}/greengrass/definition/devices/(?P<definition_id>[^/]+)/versions/(?P<definition_version_id>[^/]+)/?$": response.device_definition_version,
|
"{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,
|
||||||
}
|
}
|
||||||
|
272
tests/test_greengrass/test_greengrass_resource.py
Normal file
272
tests/test_greengrass/test_greengrass_resource.py
Normal 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")
|
Loading…
Reference in New Issue
Block a user