Skip to content

Commit

Permalink
feat(PRECHECK): use jsonschema to validate precheck env.yml
Browse files Browse the repository at this point in the history
  • Loading branch information
niall-byrne committed Jun 8, 2021
1 parent 61398f5 commit d04ee04
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 34 deletions.
1 change: 1 addition & 0 deletions build.spec
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ a = Analysis(['entrypoint.py'],
pathex=[],
binaries=[],
datas=[
('mac_maker', 'mac_maker'),
(os.path.join(site.getsitepackages()[0], 'ansible'), 'ansible'),
(os.path.join(site.getsitepackages()[0], 'ansible_collections'), 'ansible_collections'),
(os.path.join(site.getsitepackages()[0], f'mac_maker-{pkg_resources.get_distribution("mac_maker").version}.dist-info'), f'mac_maker-{pkg_resources.get_distribution("mac_maker").version}.dist-info')
Expand Down
1 change: 1 addition & 0 deletions documentation/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pib_cli==0.1.0
sphinx-autopackagesummary>=1.3
sphinx-autodoc-annotation>=1.0
sphinx-click>=2.5.0
sphinx-jsonschema>=1.16.8
1 change: 1 addition & 0 deletions documentation/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
'sphinx_autopackagesummary',
'sphinx_autodoc_annotation',
'sphinx_click.ext',
'sphinx-jsonschema',
]

autosummary_generate = True
Expand Down
55 changes: 55 additions & 0 deletions documentation/source/project/3.profiles.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Mac Maker Profiles
==================

Profiles are essentially a standardized Ansible Playbook, with explicitly defined dependencies, and some a some extra metadata.

The directory structure is defined as follows:

::

root
├── profile
│ │
│ ├── __precheck__
│ │ │
│ │ ├── env.yml
│ │ └── notes.txt
│ │
│ ├── requirements.yml
│ └── install.yml
└── pyproject.toml


=================
Precheck Metadata
=================

This information exists to give the end user of a profile a way of validating they have set any required environment variables, and provides a way for them to review any release notes.

- The `notes.txt` file contains free form release notes about the profile.
- The `env.yml` file defines all required environment files consumed by the profile.

.. toctree::
profiles/env.yml.spec.rst

===================
Galaxy Requirements
===================

The `requirements.yml` file is the Ansible Galaxy dependencies definition file. Any external roles or collections the profile requires should be defined here. Mac Maker will download and install these dependencies prior to applying your profile.

- `requirements.yml File Specification <https://docs.ansible.com/ansible/devel/user_guide/collections_using.html#install-multiple-collections-with-a-requirements-file>`_

======================
`install.yml` Playbook
======================

The `install.yml` playbook is the Ansible `entrypoint` into the profile. This playbook is called when Mac Maker applies the profile.

All roles and tasks should be defined in this file, and should be accessed from relative paths to this file.

(Frequently it makes sense to created sub-folders under profile to nest `task`, `vars` and `handler` files for a profile).


4 changes: 4 additions & 0 deletions documentation/source/project/profiles/env.yml.spec.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
`env.yml` File Specification
=============================================

.. jsonschema:: ../../../../mac_maker/schemas/env_v1.json
23 changes: 23 additions & 0 deletions mac_maker/schemas/env_v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Mac Maker v1 Profile Environment File Specification",
"type": "array",
"items":
{
"type": "object",
"properties":
{
"name":
{
"type": "string",
"description": "The name of the environment variable that is required to be set, in order to apply this profile."
},
"description":
{
"type": "string",
"description": "A description of the environment variable explaining it's purpose and function."
}
},
"required": ["name", "description"]
}
}
32 changes: 18 additions & 14 deletions mac_maker/utilities/precheck.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Profile precheck validator."""

import json
import os
from pathlib import Path

import yaml
from jsonschema import ValidationError, validate


class PrecheckConfigException(BaseException):
Expand All @@ -13,29 +16,30 @@ class PrecheckConfig:
"""Profile precheck validator."""

syntax_error = "Invalid YAML syntax."
schema_definition = (
Path(os.path.dirname(__file__)).parent / "schemas" / "env_v1.json"
)

def __init__(self, yaml_document):
self.schema = self._load_schema()

try:
self.parsed_yaml = yaml.safe_load(yaml_document)
except yaml.YAMLError as exc:
raise PrecheckConfigException from exc
raise PrecheckConfigException(self.syntax_error) from exc

def _load_schema(self) -> dict:
with open(self.schema_definition) as fhandle:
schema = json.load(fhandle)
return schema

def is_valid_env_file(self):
"""Validate an environment variable requirements file."""

self._validate_is_list()
self._validate_contains_env_dictionaries()

def _validate_is_list(self):
if not isinstance(self.parsed_yaml, list):
raise PrecheckConfigException(self.syntax_error)

def _validate_contains_env_dictionaries(self):
for instance in self.parsed_yaml:
if isinstance(instance, dict):
if 'name' in instance and 'description' in instance:
continue
raise PrecheckConfigException(self.syntax_error)
try:
validate(self.parsed_yaml, self.schema)
except ValidationError as exc:
raise PrecheckConfigException(self.syntax_error) from exc

def validate_environment(self):
"""Validate the environment against the parsed configuration file."""
Expand Down
22 changes: 2 additions & 20 deletions mac_maker/utilities/tests/test_precheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
class TestValidator(TestCase):
"""Test the precheck YAML configuration validator."""

def test_not_a_list(self):
def test_invalid_env_file(self):
yaml_data = "not a list"
validator = PrecheckConfig(yaml_data)

Expand All @@ -21,25 +21,7 @@ def test_not_a_list(self):

self.assertEqual(str(exc.exception), PrecheckConfig.syntax_error)

def test_list_of_non_dicts(self):
yaml_data = "[1,2,3,4]"
validator = PrecheckConfig(yaml_data)

with self.assertRaises(PrecheckConfigException) as exc:
validator.is_valid_env_file()

self.assertEqual(str(exc.exception), PrecheckConfig.syntax_error)

def test_list_wrong_dicts(self):
yaml_data = '[{"one" : "two"}]'
validator = PrecheckConfig(yaml_data)

with self.assertRaises(PrecheckConfigException) as exc:
validator.is_valid_env_file()

self.assertEqual(str(exc.exception), PrecheckConfig.syntax_error)

def test_correct_dicts(self):
def test_correct(self):
yaml_data = '[{"name" : "name", "description": "description"}]'
validator = PrecheckConfig(yaml_data)
validator.is_valid_env_file()
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
PyYAML = "^5.4.1"
ansible = "^4.0.0"
click = ">=7.0.0,<8.0.0"
jsonschema = "^3.2.0"
pyinstaller = "^4.3"
python = ">=3.8.0,<3.10.0"
requests = "^2.25.1"
Expand All @@ -63,6 +64,7 @@
sphinx-autodoc-annotation = "^1.0"
sphinx-autopackagesummary = "^1.3"
sphinx-click = "^2.5.0"
sphinx-jsonschema = "^1.16.8"

[[tool.poetry.packages]]
include = "mac_maker"
Expand Down

0 comments on commit d04ee04

Please sign in to comment.