Introduce YAML schema validation for YAML examples (#2488)

* Add schema key to yaml examples

* Update testing requirements
This commit is contained in:
Julio Castillo
2024-08-08 23:09:22 +02:00
committed by GitHub
parent 04c2f90351
commit 9386764f66
7 changed files with 52 additions and 23 deletions

View File

@@ -215,23 +215,23 @@ name: Foo (level 1)
iam:
roles/viewer:
- group:a@example.com
# tftest-file id=h-0-0 path=data/hierarchy/foo/_config.yaml
# tftest-file id=h-0-0 path=data/hierarchy/foo/_config.yaml schema=folder.schema.json
```
```yaml
name: Bar (level 1)
parent: folders/4567890
# tftest-file id=h-1-0 path=data/hierarchy/bar/_config.yaml
# tftest-file id=h-1-0 path=data/hierarchy/bar/_config.yaml schema=folder.schema.json
```
```yaml
name: Foo Baz (level 2)
# tftest-file id=h-0-1 path=data/hierarchy/foo/baz/_config.yaml
# tftest-file id=h-0-1 path=data/hierarchy/foo/baz/_config.yaml schema=folder.schema.json
```
```yaml
name: Bar Baz (level 2)
# tftest-file id=h-1-1 path=data/hierarchy/bar/baz/_config.yaml
# tftest-file id=h-1-1 path=data/hierarchy/bar/baz/_config.yaml schema=folder.schema.json
```
One project defined within the folder hierarchy:
@@ -241,7 +241,7 @@ billing_account: 012345-67890A-BCDEF0
services:
- container.googleapis.com
- storage.googleapis.com
# tftest-file id=h-1-1-p0 path=data/hierarchy/bar/baz/bar-baz-iac-0.yaml
# tftest-file id=h-1-1-p0 path=data/hierarchy/bar/baz/bar-baz-iac-0.yaml schema=project.schema.json
```
More traditional project definitions via the project factory data:
@@ -274,7 +274,7 @@ service_accounts:
- roles/compute.networkUser
billing_budgets:
- test-100
# tftest-file id=prj-app-1 path=data/projects/prj-app-1.yaml
# tftest-file id=prj-app-1 path=data/projects/prj-app-1.yaml schema=project.schema.json
```
```yaml
@@ -311,7 +311,7 @@ shared_vpc_service_config:
europe-west1/prod-default-ew1:
- group:team-1@example.com
# tftest-file id=prj-app-2 path=data/projects/prj-app-2.yaml
# tftest-file id=prj-app-2 path=data/projects/prj-app-2.yaml schema=project.schema.json
```
This project uses a reference to a hierarchy folder, and defines a controlling project via the `automation` attributes:
@@ -347,7 +347,7 @@ automation:
- group:devops@example.org
# tftest-file id=prj-app-3 path=data/projects/prj-app-3.yaml
# tftest-file id=prj-app-3 path=data/projects/prj-app-3.yaml schema=project.schema.json
```
And a billing budget:
@@ -370,7 +370,7 @@ update_rules:
disable_default_iam_recipients: true
monitoring_notification_channels:
- billing-default
# tftest-file id=budget-test-100 path=data/budgets/test-100.yaml
# tftest-file id=budget-test-100 path=data/budgets/test-100.yaml schema=budget.schema.json
```
<!-- TFDOC OPTS files:1 -->

View File

@@ -256,7 +256,7 @@
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z0-9_-]+$": {
"^[a-z-]+\\.googleapis\\.com$": {
"type": "array",
"items": {
"type": "string"
@@ -561,4 +561,4 @@
}
}
}
}
}

View File

@@ -252,14 +252,14 @@ module "test" {
conditions:
- members:
- user:user1@example.com
# tftest-file id=a1 path=data/access-levels/identity-user1.yaml
# tftest-file id=a1 path=data/access-levels/identity-user1.yaml schema=access-level.schema.json
```
```yaml
conditions:
- regions:
- IT
# tftest-file id=a2 path=data/access-levels/geo-it.yaml
# tftest-file id=a2 path=data/access-levels/geo-it.yaml schema=access-level.schema.json
```
```yaml
@@ -275,7 +275,7 @@ to:
resources:
- projects/123456789
# tftest-file id=e1 path=data/egress-policies/gcs-sa-foo.yaml
# tftest-file id=e1 path=data/egress-policies/gcs-sa-foo.yaml schema=egress-policy.schema.json
```
```yaml
@@ -293,7 +293,7 @@ to:
- RegionsService.Get
resources:
- "*"
# tftest-file id=i1 path=data/ingress-policies/sa-tf-test.yaml
# tftest-file id=i1 path=data/ingress-policies/sa-tf-test.yaml schema=ingress-policy.schema.json
```
```yaml
@@ -307,7 +307,7 @@ to:
- service_name: "*"
resources:
- projects/1234567890
# tftest-file id=i2 path=data/ingress-policies/sa-tf-test-geo.yaml
# tftest-file id=i2 path=data/ingress-policies/sa-tf-test-geo.yaml schema=ingress-policy.schema.json
```
## Notes

View File

@@ -19,7 +19,7 @@ from pathlib import Path
import marko
import pytest
from .utils import Example, File, get_tftest_directive
from .utils import File, TerraformExample, YamlExample, get_tftest_directive
FABRIC_ROOT = Path(__file__).parents[2]
@@ -73,16 +73,25 @@ def pytest_generate_tests(metafunc, test_group='example',
name = f'{path}:{last_header}'
if index > 1:
name += f' {index}'
ids.append(f'{path}:{last_header}:{index}')
ids.append(f'terraform:{path}:{last_header}:{index}')
# if test is marked with 'serial' in tftest line then add them to this xdist group
# this, together with `--dist loadgroup` will ensure that those tests will be run one after another
# even if multiple workers are used
# see: https://pytest-xdist.readthedocs.io/en/latest/distribution.html
marks = [pytest.mark.xdist_group('serial')
] if 'serial' in directive.args else []
example = Example(name, code, path, files[last_header], fixtures,
child.lang, directive)
example = TerraformExample(name, code, path, files[last_header],
fixtures, child.lang, directive)
examples.append(pytest.param(example, marks=marks))
elif child.lang == "yaml":
schema = directive.kwargs.get('schema')
name = directive.kwargs.get('id')
if directive.name == "tftest-file" and schema:
schema = module / 'schemas' / schema
example = YamlExample(code, module, schema)
yaml_path = directive.kwargs['path']
ids.append(f'yaml:{path}:{last_header}:{yaml_path}:{index}')
examples.append(pytest.param(example))
elif isinstance(child, marko.block.Heading):
last_header = child.children[0].children
index = 0

View File

@@ -12,14 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import re
import shutil
import subprocess
import tempfile
from pathlib import Path
import jsonschema
import yaml
from .utils import TerraformExample, YamlExample
BASE_PATH = Path(__file__).parent
@@ -45,7 +49,7 @@ def prepare_files(example, test_path, files, fixtures):
destination.write_text(example.fixtures[f])
def test_example(plan_validator, example):
def _test_terraform_example(plan_validator, example):
directive = example.directive
# for tfvars-based tests, create the temporary directory with the
@@ -101,3 +105,17 @@ def test_example(plan_validator, example):
'terraform fmt -check -diff -no-color main.tf'.split(), cwd=tmp_path,
stdout=subprocess.PIPE, encoding='utf-8')
assert result.returncode == 0, f'terraform code not formatted correctly\n{result.stdout}'
def _test_yaml_example(example):
yaml_object = yaml.safe_load(example.body)
schema = json.load(example.schema.open())
jsonschema.validate(instance=yaml_object, schema=schema)
def test_example(plan_validator, example):
match example:
case TerraformExample():
_test_terraform_example(plan_validator, example)
case YamlExample():
_test_yaml_example(example)

View File

@@ -16,8 +16,9 @@ import collections
import re
Directive = collections.namedtuple('Directive', 'name args kwargs')
Example = collections.namedtuple(
'Example', 'name code module files fixtures type directive')
TerraformExample = collections.namedtuple(
'TerraformExample', 'name code module files fixtures type directive')
YamlExample = collections.namedtuple('YamlExample', 'body module schema')
File = collections.namedtuple('File', 'path content')

View File

@@ -5,3 +5,4 @@ marko>=1.2.2
deepdiff>=6.2.3
python-hcl2>=4.3.0
pytest-xdist>=3.1.0
jsonschema>=4.22.0