Introduce YAML schema validation for YAML examples (#2488)
* Add schema key to yaml examples * Update testing requirements
This commit is contained in:
@@ -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 -->
|
||||
|
||||
@@ -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 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user