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

feat: create json schema for user-input recipe #222

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
353 changes: 353 additions & 0 deletions gdk/static/user_input_recipe_schema.json

Large diffs are not rendered by default.

64 changes: 63 additions & 1 deletion tests/gdk/common/test_RecipeValidator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
from unittest import TestCase

import pytest
import yaml
import json
import os

from jsonschema import validate, exceptions

from gdk.common.CaseInsensitive import CaseInsensitiveDict
from gdk.common.RecipeValidator import RecipeValidator
Expand All @@ -17,7 +22,6 @@ def test_load_from_file_json(self):
"valid_component_recipe.json").resolve()
validator = RecipeValidator(json_file)
recipe_data = validator._load_recipe()
print(recipe_data)
assert isinstance(recipe_data, CaseInsensitiveDict)
assert "manifests" in recipe_data
assert "artifacts" in recipe_data["manifests"][0]
Expand Down Expand Up @@ -53,3 +57,61 @@ def test_load_from_invalid_type(self):
with pytest.raises(ValueError) as e:
validator._load_recipe()
assert "Invalid recipe source type" in e.value.args[0]


# =========================== Tests for recipe schema ===========================

# Load the JSON schema
with open('gdk/static/user_input_recipe_schema.json', 'r') as schema_file:
schema = json.load(schema_file)

# Get the list of valid recipe files
valid_recipe_files_json = [
os.path.join('tests/gdk/static/sample_recipes', filename)
for filename in os.listdir('tests/gdk/static/sample_recipes')
if filename.startswith('recipe') and filename.endswith('.json')
]
valid_recipe_files_yaml = [
os.path.join('tests/gdk/static/sample_recipes', filename)
for filename in os.listdir('tests/gdk/static/sample_recipes')
if filename.startswith('recipe') and filename.endswith('.yaml')
]
# Get the list of invalid recipe files
invalid_recipe_files = [
os.path.join('tests/gdk/static/sample_recipes', filename)
for filename in os.listdir('tests/gdk/static/sample_recipes')
if filename.startswith('invalid') and filename.endswith('.json')
]


# Function to recursively convert keys to lowercase
def convert_keys_to_lowercase(input_dict):
if isinstance(input_dict, dict):
return {key.lower(): convert_keys_to_lowercase(value) for key, value in input_dict.items()}
elif isinstance(input_dict, list):
return [convert_keys_to_lowercase(item) for item in input_dict]
else:
return input_dict


# Define the test function
@pytest.mark.parametrize("recipe_file", valid_recipe_files_json)
def test_valid_recipes_json(recipe_file):
with open(recipe_file, 'r') as recipe_file:
recipe_data = json.load(recipe_file)
validate(instance=convert_keys_to_lowercase(recipe_data), schema=schema)


@pytest.mark.parametrize("recipe_file", valid_recipe_files_yaml)
def test_valid_recipes_yaml(recipe_file):
with open(recipe_file, 'r') as recipe_file:
recipe_data = yaml.safe_load(recipe_file)
validate(instance=convert_keys_to_lowercase(recipe_data), schema=schema)


@pytest.mark.parametrize("recipe_file", invalid_recipe_files)
def test_invalid_recipes_raise_error(recipe_file):
with open(recipe_file, 'r') as recipe_file:
recipe_data = json.load(recipe_file)
with pytest.raises(exceptions.ValidationError):
validate(instance=convert_keys_to_lowercase(recipe_data), schema=schema)
29 changes: 29 additions & 0 deletions tests/gdk/static/sample_recipes/invalid_recipe_extra_property.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "ggV2HelloWorld",
"ComponentVersion": "{COMPONENT_VERSION}",
"ComponentDescription": "This is simple Hello World component written in Python.",
"ComponentPublisher": "{COMPONENT_AUTHOR}",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "World"
}
},
"Manifests": [
{
"Platform": {
"os": "all"
},
"Artifacts": [
{
"URI": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/ggV2HelloWorld.zip",
"Unarchive": "ZIP"
}
],
"Lifecycle": {
"run": "python3 -u {artifacts:decompressedPath}/ggV2HelloWorld/main.py {configuration:/Message}"
}
}
],
"NewField": "extra data"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "ggV2HelloWorld",
"ComponentVersion": "32.7",
"ComponentDescription": "This is simple Hello World component written in Python.",
"ComponentPublisher": "{COMPONENT_AUTHOR}",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "World"
}
},
"Manifests": [
{
"Platform": {
"os": "all"
},
"Artifacts": [
{
"URI": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/ggV2HelloWorld.zip",
"Unarchive": "ZYP"
}
],
"Lifecycle": {
"run": "python3 -u {artifacts:decompressedPath}/ggV2HelloWorld/main.py {configuration:/Message}"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": 27,
"ComponentVersion": "{COMPONENT_VERSION}",
"ComponentDescription": "This is simple Hello World component written in Python.",
"ComponentPublisher": "{COMPONENT_AUTHOR}",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "World"
}
},
"Manifests": [
{
"Platform": {
"os": "all"
},
"Artifacts": [
{
"URI": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/ggV2HelloWorld.zip",
"Unarchive": "ZIP",
"Permission": {
"read": "OWNER",
"execute": "ALL"
}
}
],
"Lifecycle": {
"run": "python3 -u {artifacts:decompressedPath}/ggV2HelloWorld/main.py {configuration:/Message}"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"ComponentName": "ggV2HelloWorld",
"ComponentVersion": "{COMPONENT_VERSION}",
"ComponentDescription": "This is simple Hello World component written in Python.",
"ComponentPublisher": "{COMPONENT_AUTHOR}",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "World"
}
},
"Manifests": [
{
"Platform": {
"os": "all"
},
"Artifacts": [
{
"URI": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/ggV2HelloWorld.zip",
"Unarchive": "ZIP"
}
],
"Lifecycle": {
"run": "python3 -u {artifacts:decompressedPath}/ggV2HelloWorld/main.py {configuration:/Message}"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"RecipeFormatVersion": "2020-01-25",
"ComponentMeme": "ggV2HelloWorld",
"ComponentVersion": "{COMPONENT_VERSION}",
"ComponentDescription": "This is simple Hello World component written in Python.",
"ComponentPublisher": "{COMPONENT_AUTHOR}",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "World"
}
},
"Manifests": [
{
"Platform": {
"os": "all"
},
"Artifacts": [
{
"URI": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/ggV2HelloWorld.zip",
"Unarchive": "ZIP"
}
],
"Lifecycle": {
"run": "python3 -u {artifacts:decompressedPath}/ggV2HelloWorld/main.py {configuration:/Message}"
}
}
]
}
32 changes: 32 additions & 0 deletions tests/gdk/static/sample_recipes/recipe_case_insensitive.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"recipeFormatVersion": "2020-01-25",
"cOmPonentName": "ggV2HelloWorld",
"ComponentVersion": "{COMPONENT_VERSION}",
"ComponentDescription": "This is simple Hello World component written in Python.",
"ComponentPublisher": "{COMPONENT_AUTHOR}",
"ComponentConfiguration": {
"DefaultCONfiguration": {
"MessaGe": "World"
}
},
"ManifestS": [
{
"PLATFORM": {
"os": "all"
},
"ArtIfacts": [
{
"uRi": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/ggV2HelloWorld.zip",
"uNarchive": "ZIP",
"Permission": {
"read": "OWNER",
"execute": "ALL"
}
}
],
"LifeCycle": {
"run": "python3 -u {artifacts:decompressedPath}/ggV2HelloWorld/main.py {configuration:/Message}"
}
}
]
}
21 changes: 21 additions & 0 deletions tests/gdk/static/sample_recipes/recipe_case_insensitive.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
recipeFormatVersion: '2020-01-25'
cOmPonentName: ggV2HelloWorld
ComponentVersion: '{COMPONENT_VERSION}'
ComponentDesCription: This is simple Hello World component written in Python.
ComponentPubLISher: '{COMPONENT_AUTHOR}'
ComponentConFIGuration:
DefaultCONfiguration:
MessaGe: World
ManifestS:
- PLATFORM:
oS: all
ArtIfacts:
- uRi: 's3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/ggV2HelloWorld.zip'
uNarchive: ZIP
PermisSion:
rEad: OWNER
execuTe: ALL
LifeCycle:
ruN: >-
python3 -u {artifacts:decompressedPath}/ggV2HelloWorld/main.py
{configuration:/Message}
27 changes: 27 additions & 0 deletions tests/gdk/static/sample_recipes/recipe_gg_client_device_auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "{COMPONENT_NAME}",
"ComponentVersion": "{COMPONENT_VERSION}",
"ComponentType": "aws.greengrass.plugin",
"ComponentDescription": "The client device auth component authenticates client devices and authorizes client device actions, so client devices can connect to your Greengrass core device. This component creates the certificate vended by the local MQTT broker.",
"ComponentPublisher": "{COMPONENT_PUBLISHER}",
"ComponentDependencies": {
"aws.greengrass.Nucleus": {
"VersionRequirement": ">=2.2.0 <2.10.0",
"DependencyType": "SOFT"
}
},
"Manifests": [
{
"Platform": {
"os": "*"
},
"Lifecycle": {},
"Artifacts": [
{
"URI": "s3://aws.greengrass.clientdevices.Auth.jar"
}
]
}
]
}
27 changes: 27 additions & 0 deletions tests/gdk/static/sample_recipes/recipe_gg_disk_spooler.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "{COMPONENT_NAME}",
"ComponentVersion": "{COMPONENT_VERSION}",
"ComponentType": "aws.greengrass.plugin",
"ComponentDescription": "The Disk Spooler component stores MQTT messages destined for AWS IoT Core in a database, as opposed to in memory, when offline.",
"ComponentPublisher": "{COMPONENT_PUBLISHER}",
"ComponentDependencies": {
"aws.greengrass.Nucleus": {
"VersionRequirement": ">=2.2.0 <2.11.0",
"DependencyType": "SOFT"
}
},
"Manifests": [
{
"Platform": {
"os": "*"
},
"Lifecycle": {},
"Artifacts": [
{
"URI": "s3://aws.greengrass.DiskSpooler.jar"
}
]
}
]
}
Loading