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

Update Ansible Command #51

Merged
merged 23 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
12af1a8
Initial Commit
PhillSimonds Nov 20, 2020
33ccecd
Refactor ansible.py functions
PhillSimonds Nov 21, 2020
b26f0ef
Update schema-enforcer ansible command
PhillSimonds Nov 21, 2020
0e61e21
Update code to make linting tests pass
PhillSimonds Nov 23, 2020
0bb9248
Add documentation for ansible command
PhillSimonds Nov 23, 2020
0371929
Update documentation
PhillSimonds Nov 23, 2020
21d210f
Updates per peer review
PhillSimonds Nov 24, 2020
fa8e627
update doc strings
itdependsnetworks Nov 28, 2020
0a97328
fix tests
itdependsnetworks Nov 28, 2020
83900bd
update ansible example to folder of static and inventory plugin
itdependsnetworks Nov 28, 2020
5f2f6f1
fix tests
itdependsnetworks Nov 28, 2020
a03e39b
remove jsonschema-testing
itdependsnetworks Nov 29, 2020
017499c
Updates per peer review
PhillSimonds Nov 29, 2020
bddd2c5
ps updates
itdependsnetworks Nov 29, 2020
b00571b
Update README.md with reference to ansible command
PhillSimonds Nov 29, 2020
532ca2d
Merge pull request #54 from networktocode-llc/psi/issue-44-kc-doc-str…
itdependsnetworks Nov 30, 2020
e154323
Update examples
PhillSimonds Nov 30, 2020
8d1fb96
Update files to pass YAMLLint
PhillSimonds Nov 30, 2020
08137c2
Update doc strings
PhillSimonds Nov 30, 2020
da448cf
Fix typos per peer review
PhillSimonds Nov 30, 2020
c123f07
Update ansible readme per peer review
PhillSimonds Nov 30, 2020
5b529b8
Fix typos in readme.md
PhillSimonds Nov 30, 2020
3d51c5b
Update documentation per peer review
PhillSimonds Nov 30, 2020
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
291 changes: 291 additions & 0 deletions docs/ansible_command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
# The `ansible` command

The `ansible` command is used to check ansible inventory for adherence to a schema definition. An example exists in the `examples/ansible` folder. With no flags passed in, schema-enforcer will display a line for each property definition that **fails** schema validation along with contextual information elucidating why a given portion of the ansible inventory failed schema validation, the host for which schema validation failed, and the portion of structured data that is failing validation. If all checks pass, `schema-enforcer` will inform the user that all tests have passed.
PhillSimonds marked this conversation as resolved.
Show resolved Hide resolved

## How the inventory is loaded

When the `schema-enforcer ansible` command is run, an ansible inventory is constructed. Each host's properties are extracted from the ansible inventory then validated against schema. Take the following example

```cli
bash $ cd examples/ansible && schema-enforcer ansible
Found 4 hosts in the inventory
FAIL | [ERROR] True is not of type 'string' [HOST] spine1 [PROPERTY] dns_servers:0:address
FAIL | [ERROR] True is not of type 'string' [HOST] spine2 [PROPERTY] dns_servers:0:address
```

The `schema-enforcer ansible` command validates adherence to schema on a **per host** basis. In the example above, both `spine1` and `spine2` devices belong to a group called `spine`

```ini
[nyc:children]
spine
leaf

[spine]
spine1
spine2

[leaf]
leaf1
leaf2
```

The property `dns_servers` is defined only at the `spine` group level in ansible and not at the host level. Below is the `spine.yml` group_vars file.

```yaml
cat group_vars/spine.yaml
---
dns_servers:
- address: true
- address: "10.2.2.2"
interfaces:
swp1:
role: "uplink"
swp2:
role: "uplink"

schema_enforcer_schema_ids:
- "schemas/dns_servers"
- "schemas/interfaces"
```

Though the invalid property (the boolean true for a DNS address) is defined only once, two validation errors are flagged because two different hosts belong to to the `spine` group.

## Command Arguments

### The `--show-checks` flag

The `--show-checks` flag is used to show which ansible inventory hosts will be validated against which schema definition IDs.

```cli
Found 4 hosts in the inventory
Ansible Host Schema ID
--------------------------------------------------------------------------------
leaf1 ['schemas/dns_servers']
leaf2 ['schemas/dns_servers']
spine1 ['schemas/dns_servers', 'schemas/interfaces']
spine2 ['schemas/dns_servers', 'schemas/interfaces']
```

> Note: The ansible inventory hosts can be mapped to schema definition ids in one of a few ways. This is discussed in the Schema Mapping section below

### The `--show-pass` flag
itdependsnetworks marked this conversation as resolved.
Show resolved Hide resolved

The `--show-pass` flag is used to show what schema definition ids each host passes in addition to the schema definition ids each host fails.

```cli
bash$ schema-enforcer ansible --show-pass
Found 4 hosts in the inventory
FAIL | [ERROR] True is not of type 'string' [HOST] spine1 [PROPERTY] dns_servers:0:address
PASS | [HOST] spine1 [SCHEMA ID] schemas/interfaces
FAIL | [ERROR] True is not of type 'string' [HOST] spine2 [PROPERTY] dns_servers:0:address
PASS | [HOST] spine2 [SCHEMA ID] schemas/interfaces
PASS | [HOST] leaf1 [SCHEMA ID] schemas/dns_servers
PASS | [HOST] leaf2 [SCHEMA ID] schemas/dns_servers
```

In the above example, the leaf switches are checked for adherence to the `schemas/dns_servers` definition and the spine switches are checked for adherence to two schema ids; the `schemas/dns_servers` schema id and the `schemas/interfaces` schema id. A PASS statement is printed to stdout for each validation that passes and a FAIL statement is printed for each validation that fails.

### The `--host` flag

The `--host` flag can be used to limit schema validation to a single ansible inventory host. `-h` can also be used as shorthand for `--host`

```cli
bash$ schema-enforcer ansible -h spine2 --show-pass
Found 4 hosts in the inventory
FAIL | [ERROR] True is not of type 'string' [HOST] spine2 [PROPERTY] dns_servers:0:address
PASS | [HOST] spine2 [SCHEMA ID] schemas/interfaces
```

### The `--inventory` flag

The `--inventory` flag (or `-i`) specifies the inventory file which should be used to determine the ansible inventory. The inventory file can be specified in one of two ways:

1) The `--inventory` flag (or `-i`) can be used to pass in the location of an ansible inventory file
2) A `pyproject.toml` file can contain a `[tool.schema_enforcer]` config block setting the `ansible_inventory` paramer. This `pyproject.toml` file must be inside the repository from which the tool is run.

```toml
bash$ cat pyproject.toml
[tool.schema_enforcer]
ansible_inventory = "inventory.ini"
```

If the inventory is set in both ways, the -i flag will take precedence.

> Note: Dynamic inventory sources can not currently be parsed for schema adherence.

## Inventory Variables and Schema Mapping

`schema-enforcer` will check ansible hosts for adherence to defined schema ids in one of two ways.

- By using a list of schema ids defined by the `schema_enforcer_schema_ids` command
- By automatically mapping a schema's top level property to ansible variable keys.
PhillSimonds marked this conversation as resolved.
Show resolved Hide resolved

### Using The `schema_enforcer_schema_ids` ansible inventory variable

This variable can be used to declare which schemas a given host/group of hosts should be checked for adherence to. The value of this variable is a list of the schema ids.

Take for example the `spine` group in our `ansible` exmple. In this example, the schema ids `schemas/dns_servers` and `schemas/interfaces` are declared.

```yaml
bash$ cat group_vars/spine.yml
---
dns_servers:
- address: true
- address: "10.2.2.2"
interfaces:
swp1:
role: "uplink"
swp2:
role: "uplink"

schema_enforcer_schema_ids:
- "schemas/dns_servers"
- "schemas/interfaces"
```

The `$id` property in the following schema definition file is what is being declared by spine group var file above.

```yaml
bash$ cat schema/schemas/interfaces.yml
---
$schema: "http://json-schema.org/draft-07/schema#"
$id: "schemas/interfaces"
description: "Interfaces configuration schema."
type: "object"
properties:
interfaces:
type: "object"
patternProperties:
^swp.*$:
properties:
type:
type: "string"
description:
type: "string"
role:
type: "string"
```

### Using the `schema_enforcer_automap_default` ansible inventory variable

This variable specifies whether or not to use automapping. It defaults to true. When automapping is in use, schema enforcer will automatically map map schema IDs to host variables if the variable's name matches a top level property defined in the schema. This happens by default when no `schema_enforcer_schema_ids` property is declared.
PhillSimonds marked this conversation as resolved.
Show resolved Hide resolved

The leaf group in the included ansible example does not declare any schemas per the `schema_enforcer_schema_ids` property.

```yaml
bash$ cat group_vars/leaf.yml
---
dns_servers:
- address: "10.1.1.1"
- address: "10.2.2.2"
```

Yet when schema enforcer is run against one of the leaf hosts, we can see it's host vars are checked for adherence to the dns_servers.yml schema.

```cli
schema-enforcer ansible -h leaf1 --show-pass
Found 4 hosts in the inventory
PASS | [HOST] leaf1 [SCHEMA ID] schemas/dns_servers
ALL SCHEMA VALIDATION CHECKS PASSED
```

This is done because `schema-enforcer` maps the `dns_servers` key in the `group_vars/leaf.yml` to the `dns_servers` top level property in the `schema/schemas/dns.yml` schema definition file.

```yaml
cat schema/schemas/dns.yml
---
$schema: "http://json-schema.org/draft-07/schema#"
$id: "schemas/dns_servers"
description: "DNS Server Configuration schema."
type: "object"
properties:
dns_servers:
$ref: "../definitions/arrays/ip.yml#ipv4_hosts"
required:
- "dns_servers"
```

> Note: The order listed above is the order in which the options for mapping schema ids to variables occurs.
> Note: Schema ID to host property mapping methods are **mutually exclusive**. This means that if a `schema_definition_schema_ids` variable is declared in an ansible hosts/groups file, automatic mapping of schema IDs to variables will not occur.

The `schema_enforcer_automap_default` variable can be declared in an ansible host or group file. This variable defaults to true if not set. If set to false, the automapping behaviour described above will not occur. For instance, if we change the `schema_enforcer_automap_default` variable for leaf switches to false then re-run schema validation, no checks will be performed because automapping is disabled.

```yaml
bash$ cat group_vars/leaf.yml
---
dns_servers:
- address: "10.1.1.1"
- address: "10.2.2.2"

schema_enforcer_automap_default: false
```

```yaml
bash$ schema-enforcer ansible -h leaf1 --show-checks
Found 4 hosts in the inventory
Ansible Host Schema ID
--------------------------------------------------------------------------------
leaf1 []
```

## Advanced Options

### The `schema_enforcer_strict` variable
PhillSimonds marked this conversation as resolved.
Show resolved Hide resolved

The `schema_enforcer_strict` variable can be declared in an ansible host or group file. This varaible defaults to false if not set. If set to true, the `schema-enforcer` tool checks for `strict` adherence to schema. This means that no additional properties can be specified as variables beyond those that are defined in the schema. Two major caveats apply to using the `schema_enforcer_strict` variable.

1) If the `schema_enforcer_strict` variable is set to true, the `schema_enforcer_schema_ids` variabe **MUST** be defined as a list of one and only one schema ID. If it is either not defined at all or defined as something other than a list with one element, an error will be printed to the screen and the tool will exit before performing any validations.
2) The schema ID referenced by `schema_enforcer_schema_ids` **MUST** include all variables defined for the ansible host/group. If an ansible variable not defined in the schema is defined for a given host, schema validation will fail as, when strict mode is run, properties not defined in the schema are not allowed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused if this is testing the assimilation of data per ansible inventory, or just a particular file? If it is a file, how does it know what file to look for? How does this work with dynamic inventory?

How does defining the same data within multiple groups or group_vars/host_vars affect this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated some of the verbiage to make this a little more clear.

  • No matter where the inventory is rendered from, you end up with a data structure of the defined variables per host. This per-host data is what is validated for schema adherence on a per host basis.
  • The tool doesn't yet officially support dynamic inventory. We've tested it and had it working, but we had to set environment variables to do so. I created an issue (Add options for parsing Ansible dynamic inventory #55) to track adding this feature in as I'd like have how a dynamic inventory is configured for use and how it's constructed clearly designed, documented, and articulated with it's own section and examples.
  • The flattening of vars per host is done per ansible's rules for doing so

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying that for me. I went back and re-read it after the updates, and I would say that there are still 3 things that cause me not to read the docs that way, but to read it as validating the variables associated with a file

  • The near identical schema and file names (makes sense that they have similar names, and not a problem if the next point is agreed upon)
  • There is a single variable file hosting all of the data
  • There aren't any ansible variables in the vars/schema (ansible_user, ansilbe_password)

I think adding an additional vars file (host or group) and adding some common variables used by the ansible framework would help push the reader into the interpretation you have defined above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the input. Agreed. I'm going to go ahead and close this PR and submit another one with the example you've referenced in the interest of getting this one buttoned up :).


> Note: If either of these conditions are not met, an error message will be printed to stdout and the tool will stop execution before evaluating host variables against schema.

In the following example, the leaf.yml group vars file has been modified so that all hosts which belong to it are checked for strict enforcement against the `schemas/dns_servers` schema id.

```yaml
bash$ cat group_vars/leaf.yml
---
dns_servers:
- address: "10.1.1.1"
- address: "10.2.2.2"

schema_enforcer_schema_ids:
- schemas/dns_servers

schema_enforcer_strict: true
```

When `schema-enforcer` is run, it shows checks passing as expected

```cli
schema-enforcer ansible -h leaf1 --show-pass
Found 4 hosts in the inventory
PASS | [HOST] leaf1 [SCHEMA ID] schemas/dns_servers
ALL SCHEMA VALIDATION CHECKS PASSED
```

If we do the same thing for the spine switches then run validation, we two validation errors -- one indicating that the `dns_servers` property failed validation because its first address is of type `bool`, and one indicating that `interfaces` is an additional property which falls outside of the declared schema definition (`schemas/dns_servers') and is not allowed.

```yaml
bash$ cat group_vars/spine.yml
---
dns_servers:
- address: true
- address: "10.2.2.2"
interfaces:
swp1:
role: "uplink"
swp2:
role: "uplink"

schema_enforcer_schema_ids:
- "schemas/dns_servers"

schema_enforcer_strict: true
```

```cli
bash$ schema-enforcer ansible -h spine1 --show-pass
Found 4 hosts in the inventory
FAIL | [ERROR] True is not of type 'string' [HOST] spine1 [PROPERTY] dns_servers:0:address
FAIL | [ERROR] Additional properties are not allowed ('interfaces' was unexpected) [HOST] spine1 [PROPERTY]
```
2 changes: 1 addition & 1 deletion examples/ansible/group_vars/leaf.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
dns_servers:
- address: 12
- address: "10.1.1.1"
- address: "10.2.2.2"
3 changes: 0 additions & 3 deletions examples/ansible/group_vars/nyc.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
---
jsonschema_mapping:
dns_servers: ["schemas/dns_servers"]
interfaces: ["schemas/interfaces"]
6 changes: 5 additions & 1 deletion examples/ansible/group_vars/spine.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
---
dns_servers:
- address: "10.1.1.1"
- address: false
- address: "10.2.2.2"
interfaces:
swp1:
role: "uplink"
swp2:
role: "uplink"

schema_enforcer_schema_ids:
- "schemas/dns_servers"
- "schemas/interfaces"
2 changes: 1 addition & 1 deletion examples/ansible/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[tool.jsonschema_testing]
[tool.schema_enforcer]
ansible_inventory = "inventory.ini"
2 changes: 2 additions & 0 deletions examples/ansible/schema/schemas/interfaces.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ properties:
type: "string"
description:
type: "string"
role:
type: "string"
Loading