Skip to content

Commit 331be65

Browse files
authored
Add play argspec schema validation to schema rule (#4667)
1 parent 3942375 commit 331be65

File tree

11 files changed

+423
-0
lines changed

11 files changed

+423
-0
lines changed

.config/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ ansiblelint
4141
apidoc
4242
apport
4343
argparsing
44+
argspec
4445
argspecs
4546
arxcruz
4647
audgirka
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Valid play argspec
2+
---
3+
short_description: Advanced config management
4+
description: This playbook handles complex infrastructure deployments with comprehensive configuration options
5+
argument_specs:
6+
deploy_infrastructure:
7+
short_description: Deploy complete infrastructure stack
8+
description:
9+
- Deploys a multi-tier application infrastructure
10+
- Configures web servers, databases, and load balancers
11+
author:
12+
- Infrastructure Team (@infra-team)
13+
- DevOps Engineer (@devops-lead)
14+
options:
15+
environment:
16+
type: str
17+
required: true
18+
choices:
19+
- development
20+
- staging
21+
- production
22+
description: Target deployment environment
23+
web_server_count:
24+
type: int
25+
required: false
26+
default: 2
27+
description: Number of web servers to deploy
28+
database_engine:
29+
type: str
30+
required: true
31+
choices:
32+
- postgresql
33+
- mysql
34+
- mongodb
35+
description: Database engine to use
36+
enable_ssl:
37+
type: bool
38+
required: false
39+
default: true
40+
description: Whether to enable SSL/TLS encryption
41+
examples: |
42+
# Basic deployment
43+
- import_playbook: site.yml
44+
vars:
45+
environment: production
46+
database_engine: postgresql
47+
web_server_count: 3
48+
return: ~
49+
configure_monitoring:
50+
short_description: Configure monitoring and alerting systems
51+
description:
52+
- Sets up comprehensive monitoring stack
53+
- Configures alerting rules and notifications
54+
- Integrates with external monitoring services
55+
author:
56+
- Monitoring Team (@monitoring-team)
57+
options:
58+
monitoring_stack:
59+
type: str
60+
required: true
61+
choices:
62+
- prometheus_grafana
63+
- elk_stack
64+
description: Monitoring stack to deploy
65+
alert_channels:
66+
type: list
67+
required: false
68+
default:
69+
- email
70+
description: Alert notification channels
71+
examples: |
72+
# Basic monitoring setup
73+
- import_playbook: site.yml
74+
vars:
75+
monitoring_stack: prometheus_grafana
76+
alert_channels:
77+
- email
78+
- slack
79+
return: ~
80+
81+
security_hardening:
82+
short_description: Apply security hardening configurations
83+
description:
84+
- Implements security best practices
85+
- Configures firewalls and access controls
86+
- Sets up vulnerability scanning
87+
author:
88+
- Security Team (@security-team)
89+
options:
90+
security_level:
91+
type: str
92+
required: true
93+
choices:
94+
- basic
95+
- enhanced
96+
- strict
97+
description: Level of security hardening to apply
98+
enable_firewall:
99+
type: bool
100+
required: false
101+
default: true
102+
description: Whether to configure host-based firewall
103+
vulnerability_scanning:
104+
type: bool
105+
required: false
106+
default: false
107+
description: Enable automated vulnerability scanning
108+
examples: |
109+
# Basic security hardening
110+
- import_playbook: site.yml
111+
vars:
112+
security_level: enhanced
113+
enable_firewall: true
114+
return: ~
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Violates json schema - missing required fields in argument spec
2+
---
3+
short_description: Weather forecast playbook
4+
description: Gets weather data from airport codes
5+
argument_specs:
6+
weather_check:
7+
short_description: Get weather info
8+
# Missing required 'options' and 'examples' fields

src/ansiblelint/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
{"tasks": "**/tasks/**/*.{yaml,yml}"},
5555
{"rulebook": "**/rulebooks/*.{yml,yaml"},
5656
{"pattern": "**/patterns/*/meta/pattern.json"},
57+
{"play-argspec": "**/patterns/*/playbooks/meta/*.{yaml,yml}"},
58+
{"play-argspec": "**/*.meta.{yaml,yml}"},
5759
{"playbook": "**/playbooks/*.{yml,yaml}"},
5860
{"playbook": "**/*playbook*.{yml,yaml}"},
5961
{"role": "**/roles/*/"},

src/ansiblelint/rules/schema.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ project:
4141
[role/metadata.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/playbook/role/metadata.py#L79))
4242
for details.
4343
- `schema[pattern]` validates Ansible patterns.
44+
- `schema[[play-argspec]]` validates Ansible playbook [argspec files](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/validate_argument_spec_module.html).
4445
- `schema[playbook]` validates Ansible playbooks.
4546
- `schema[requirements]` validates Ansible
4647
[requirements](https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#install-multiple-collections-with-a-requirements-file)

src/ansiblelint/rules/schema.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class ValidateSchemaRule(AnsibleLintRule):
7474
"schema[meta-runtime]": "",
7575
"schema[molecule]": "",
7676
"schema[pattern]": "",
77+
"schema[play-argspec]": "",
7778
"schema[playbook]": "",
7879
"schema[requirements]": "",
7980
"schema[role-arg-spec]": "",
@@ -382,6 +383,20 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
382383
],
383384
id="pattern_negative",
384385
),
386+
pytest.param(
387+
"examples/play_argspecs/correct_play_argspec/patterns/example_pattern/playbooks/meta/site.yml",
388+
"play-argspec",
389+
[],
390+
id="play_argspec_positive",
391+
),
392+
pytest.param(
393+
"examples/play_argspecs/incorrect_play_argspec/site.meta.yaml",
394+
"play-argspec",
395+
[
396+
r"\$.argument_specs.weather_check 'options' is a required property.",
397+
],
398+
id="play_argspec_negative",
399+
),
385400
),
386401
)
387402
def test_schema(

src/ansiblelint/schemas/__store__.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
"pattern": {
3939
"url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/pattern.json"
4040
},
41+
"play-argspec": {
42+
"url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/play-argspec.json"
43+
},
4144
"playbook": {
4245
"etag": "4f8cbba62fcf8a1fa6e8ef5e42696aec5b0876487478df83a7ffdf8bdbb4abcf",
4346
"url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/playbook.json"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/play-argspec.json",
4+
"title": "Ansible Playbook Argspec Schema",
5+
"description": "See https://docs.ansible.com/ansible/latest/collections/ansible/builtin/validate_argument_spec_module.html",
6+
"examples": [
7+
"extensions/patterns/*/playbooks/meta/*.yml",
8+
"extensions/patterns/*/playbooks/meta/*.yaml",
9+
"**/*.meta.yml",
10+
"**/*.meta.yaml"
11+
],
12+
"type": "object",
13+
"properties": {
14+
"short_description": {
15+
"type": "string",
16+
"description": "Brief description of the playbook"
17+
},
18+
"description": {
19+
"type": "string",
20+
"description": "Detailed description of the playbook"
21+
},
22+
"argument_specs": {
23+
"type": "object",
24+
"description": "Argument specifications for the playbook",
25+
"minProperties": 1,
26+
"patternProperties": {
27+
"^[a-zA-Z_][a-zA-Z0-9_]*$": {
28+
"type": "object",
29+
"properties": {
30+
"short_description": {
31+
"type": "string",
32+
"description": "Brief description of the argument spec"
33+
},
34+
"description": {
35+
"type": "array",
36+
"items": {
37+
"type": "string"
38+
},
39+
"description": "Detailed description lines"
40+
},
41+
"author": {
42+
"type": "array",
43+
"items": {
44+
"type": "string"
45+
},
46+
"description": "List of authors"
47+
},
48+
"options": {
49+
"type": "object",
50+
"description": "Available options/parameters",
51+
"patternProperties": {
52+
"^[a-zA-Z_][a-zA-Z0-9_]*$": {
53+
"type": "object",
54+
"properties": {
55+
"type": {
56+
"type": "string",
57+
"enum": [
58+
"str",
59+
"int",
60+
"bool",
61+
"float",
62+
"list",
63+
"dict",
64+
"path",
65+
"raw"
66+
],
67+
"description": "Parameter type"
68+
},
69+
"required": {
70+
"type": "boolean",
71+
"description": "Whether the parameter is required"
72+
},
73+
"default": {
74+
"description": "Default value for the parameter"
75+
},
76+
"choices": {
77+
"type": "array",
78+
"description": "Valid choices for the parameter"
79+
},
80+
"description": {
81+
"type": "string",
82+
"description": "Description of the parameter"
83+
}
84+
},
85+
"additionalProperties": true
86+
}
87+
},
88+
"additionalProperties": false
89+
},
90+
"examples": {
91+
"type": "string",
92+
"description": "Usage examples in YAML format"
93+
},
94+
"return": {
95+
"description": "Return value specification"
96+
}
97+
},
98+
"required": ["short_description", "options", "examples"],
99+
"additionalProperties": true
100+
}
101+
},
102+
"additionalProperties": false
103+
}
104+
},
105+
"required": ["short_description", "description", "argument_specs"],
106+
"additionalProperties": false
107+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Violates json schema - missing required fields in argument spec
2+
---
3+
short_description: Weather forecast playbook
4+
description: Gets weather data from airport codes
5+
argument_specs:
6+
weather_check:
7+
short_description: Get weather info
8+
# Missing required 'options' and 'examples' fields
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# ajv errors
2+
3+
```json
4+
[
5+
{
6+
"instancePath": "/argument_specs/weather_check",
7+
"keyword": "required",
8+
"message": "must have required property 'options'",
9+
"params": {
10+
"missingProperty": "options"
11+
},
12+
"schemaPath": "#/properties/argument_specs/patternProperties/%5E%5Ba-zA-Z_%5D%5Ba-zA-Z0-9_%5D*%24/required"
13+
},
14+
{
15+
"instancePath": "/argument_specs/weather_check",
16+
"keyword": "required",
17+
"message": "must have required property 'examples'",
18+
"params": {
19+
"missingProperty": "examples"
20+
},
21+
"schemaPath": "#/properties/argument_specs/patternProperties/%5E%5Ba-zA-Z_%5D%5Ba-zA-Z0-9_%5D*%24/required"
22+
}
23+
]
24+
```
25+
26+
# check-jsonschema
27+
28+
stdout:
29+
30+
```json
31+
{
32+
"status": "fail",
33+
"successes": [],
34+
"errors": [
35+
{
36+
"filename": "negative_test/play_argspec/site.meta.yaml",
37+
"path": "$.argument_specs.weather_check",
38+
"message": "'options' is a required property",
39+
"has_sub_errors": false
40+
},
41+
{
42+
"filename": "negative_test/play_argspec/site.meta.yaml",
43+
"path": "$.argument_specs.weather_check",
44+
"message": "'examples' is a required property",
45+
"has_sub_errors": false
46+
}
47+
],
48+
"parse_errors": []
49+
}
50+
```

0 commit comments

Comments
 (0)