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

Schema checker improvements #395

Merged
merged 6 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
39 changes: 30 additions & 9 deletions lib/schema_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import string
import re
from schema import Schema, SchemaError, Optional, Or, Use
# https://docs.green-coding.berlin/docs/measuring/usage-scenario/
#
# networks documentation is different than what i see in the wild!
# name: str
# also isn't networks optional?
Expand Down Expand Up @@ -67,6 +67,19 @@ def valid_service_types(self, value):
raise SchemaError(f"{value} is not 'container'")
return value

def validate_networks_no_invalid_chars(self, networks):
if isinstance(networks, list):
for item in networks:
if item is not None:
self.contains_no_invalid_chars(item)
elif isinstance(networks, dict):
for key, value in networks.items():
self.contains_no_invalid_chars(key)
if value is not None:
self.contains_no_invalid_chars(value)
else:
raise SchemaError("'networks' should be a list or a dictionary")


def check_usage_scenario(self, usage_scenario):
# Anything with Optional() is not needed, but if it exists must conform to the definition specified
Expand All @@ -75,9 +88,7 @@ def check_usage_scenario(self, usage_scenario):
"author": str,
"description": str,

Optional("networks"): {
Use(self.contains_no_invalid_chars): None
},
Optional("networks"): Or(list, dict),

Optional("services"): {
Use(self.contains_no_invalid_chars): {
Expand All @@ -102,25 +113,35 @@ def check_usage_scenario(self, usage_scenario):
Optional("detach"): bool,
Optional("note"): str,
Optional("read-notes-stdout"): bool,
Optional("ignore-errors"): bool
Optional("ignore-errors"): bool,
Optional("shell"): str,
Optional("log-stdout"): bool,
Optional("log-stderr"): bool,
}],
}],

Optional("builds"): {
str:str
},
Optional("builds"): {str:str},

Optional("compose-file"): Use(self.validate_compose_include)
}, ignore_extra_keys=True)

# This check is necessary to do in a seperate pass. If tried to bake into the schema object above,
# it will not know how to handle the value passed when it could be either a dict or list
if 'networks' in usage_scenario:
self.validate_networks_no_invalid_chars(usage_scenario['networks'])

if "builds" not in usage_scenario and usage_scenario.get("services") is not None:
for service in usage_scenario["services"].values():
if "image" not in service:
raise SchemaError("The 'image' key under services is required when 'builds' key is not present.")

usage_scenario_schema.validate(usage_scenario)


# if __name__ == '__main__':
# import yaml

# with open("test-file.yml", encoding='utf8') as f:
# # with open("test-file-2.yaml", encoding='utf8') as f:
# usage_scenario = yaml.safe_load(f)

# SchemaChecker = SchemaChecker(validate_compose_flag=True)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
---
name: Test
author: Dan Mateas
description: test
description: "test that image is required when build is not specified"

networks:
network-name:
networkname:

services:
test-container:
type: container
image: gcb_stress
build: .

flow:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: Test
author: Dan Mateas
description: test

networks:
network-name:

networks:
- network-a
- network-b

services:
test-container:
type: container
image: gcb_stress
build: .

flow:
- name: Stress
container: test-container
commands:
- type: console
command: stress-ng -c 1 -t 1 -q
note: Starting Stress
shell: bash
log-stdout: yes
log-stderr: "no"
28 changes: 28 additions & 0 deletions test/data/usage_scenarios/schema_checker/schema_checker_valid.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: Test
author: Dan Mateas
description: test

networks:
network-name:
network-name-2:

services:
test-container:
type: container
image: gcb_stress
build: .
test-container-2:
type: container
image: fizzbump

flow:
- name: Stress
container: test-container
commands:
- type: console
command: stress-ng -c 1 -t 1 -q
note: Starting Stress
shell: bash
log-stdout: yes
log-stderr: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: Test
author: Dan Mateas
description: test

networks:
network-name:
network-name-2:

services:
test-container:
type: container
image: gcb_stress
build: .

flow:
- name: Stress
container: test-container
commands:
- type: console
command: stress-ng -c 1 -t 1 -q
note: Starting Stress
shell: bash
log-stdout: yes
log-stderr: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: Test
author: Dan Mateas
description: test

networks:
- network-a
- network-b

services:
test-container:
type: container
image: gcb_stress
build: .

flow:
- name: Stress
container: test-container
commands:
- type: console
command: stress-ng -c 1 -t 1 -q
note: Starting Stress
shell: bash
log-stdout: yes
log-stderr: false
56 changes: 52 additions & 4 deletions test/lib/test_schema_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,33 @@

def test_schema_checker_valid():
usage_scenario_name = 'schema_checker_valid.yml'
usage_scenario_path = os.path.join(CURRENT_DIR, '../data/usage_scenarios/', usage_scenario_name)
usage_scenario_path = os.path.join(CURRENT_DIR, '../data/usage_scenarios/schema_checker/', usage_scenario_name)
with open(usage_scenario_path, encoding='utf8') as file:
usage_scenario = yaml.safe_load(file)
schema_checker = SchemaChecker(validate_compose_flag=True)
schema_checker.check_usage_scenario(usage_scenario)

def test_schema_checker_invalid():
usage_scenario_name = 'schema_checker_invalid_1.yml'
usage_scenario_path = os.path.join(CURRENT_DIR, '../data/usage_scenarios/', usage_scenario_name)
def test_schema_checker_both_network_types_valid():
## Check first that it works in case a, with the network listed as keys
usage_scenario_name_a = 'schema_checker_valid_network_as_keys.yml'
usage_scenario_path_a = os.path.join(CURRENT_DIR, '../data/usage_scenarios/schema_checker/', usage_scenario_name_a)
with open(usage_scenario_path_a, encoding='utf8') as file:
usage_scenario_a = yaml.safe_load(file)
schema_checker_a = SchemaChecker(validate_compose_flag=True)
schema_checker_a.check_usage_scenario(usage_scenario_a)

## Also check that it works in case b, with the networks as a list
usage_scenario_name_b = 'schema_checker_valid_network_as_list.yml'
usage_scenario_path_b = os.path.join(CURRENT_DIR, '../data/usage_scenarios/schema_checker/', usage_scenario_name_b)
with open(usage_scenario_path_b, encoding='utf8') as file:
usage_scenario_b = yaml.safe_load(file)
schema_checker_b = SchemaChecker(validate_compose_flag=True)
schema_checker_b.check_usage_scenario(usage_scenario_b)


def test_schema_checker_invalid_missing_description():
usage_scenario_name = 'schema_checker_invalid_missing_description.yml'
usage_scenario_path = os.path.join(CURRENT_DIR, '../data/usage_scenarios/schema_checker/', usage_scenario_name)
with open(usage_scenario_path, encoding='utf8') as file:
usage_scenario = yaml.safe_load(file)

Expand All @@ -35,3 +53,33 @@ def test_schema_checker_invalid():
expected_exception = "Missing key: 'description'"
assert expected_exception in str(error.value), \
Tests.assertion_info(f"Exception: {expected_exception}", str(error.value))


def test_schema_checker_invalid_image_req_when_no_build():
usage_scenario_name = 'schema_checker_invalid_image_builds.yml'
usage_scenario_path = os.path.join(CURRENT_DIR, '../data/usage_scenarios/schema_checker/', usage_scenario_name)
with open(usage_scenario_path, encoding='utf8') as file:
usage_scenario = yaml.safe_load(file)

schema_checker = SchemaChecker(validate_compose_flag=True)
with pytest.raises(SchemaError) as error:
schema_checker.check_usage_scenario(usage_scenario)

expected_exception = "The 'image' key under services is required when 'builds' key is not present."
assert expected_exception in str(error.value), \
Tests.assertion_info(f"Exception: {expected_exception}", str(error.value))

def test_schema_checker_invalid_wrong_type():
usage_scenario_name = 'schema_checker_invalid_wrong_type.yml'
usage_scenario_path = os.path.join(CURRENT_DIR, '../data/usage_scenarios/schema_checker/', usage_scenario_name)
with open(usage_scenario_path, encoding='utf8') as file:
usage_scenario = yaml.safe_load(file)

schema_checker = SchemaChecker(validate_compose_flag=True)
with pytest.raises(SchemaError) as error:
schema_checker.check_usage_scenario(usage_scenario)

expected_exception = "Key 'log-stderr' error:\n'no' should be instance of 'bool'"
print(error.value)
assert expected_exception in str(error.value), \
Tests.assertion_info(f"Exception: {expected_exception}", str(error.value))
Loading