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.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")
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
}
|
||||
|
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