[Wallaby-Only] Ansible lint custom rule expansion

Expands the existing ansible lint rule to aling
with proposed metadata changes.

Categories and products metadata keys were added to the cep-ansible-installed
playbook in order to make it compliant.

Depends-On: https://review.opendev.org/c/openstack/validations-libs/+/800721
Conflicts:
	 .ansible-lint
	 Resolved in favor of Iff0f37b1a76d81df7e5b20d76cefd0c879a150f6

Signed-off-by: Jiri Podivin <jpodivin@redhat.com>
Change-Id: Ib5fc841e23001816612dcdfd8c3335ae99ba2722
(cherry picked from commit 84c91cfcf4)
This commit is contained in:
Jiri Podivin 2021-06-28 15:03:35 +02:00
parent 4c3d77cbf3
commit c18200cc8b
3 changed files with 127 additions and 62 deletions

View File

@ -1,6 +1,5 @@
exclude_paths:
- releasenotes/
- playbooks/ceph-pg.yaml
- ci/playbooks/
parseable: true
quiet: false

View File

@ -1,6 +1,6 @@
import os
import yaml
from ansiblelint.errors import MatchError
from ansiblelint.rules import AnsibleLintRule
@ -20,6 +20,14 @@ class ValidationHasMetadataRule(AnsibleLintRule):
- group1
- group2
- group3
categories:
- category1
- category2
- category3
products:
- product1
- product2
- product3
"""
description = (
@ -35,102 +43,155 @@ class ValidationHasMetadataRule(AnsibleLintRule):
"The validation playbook must contain "
"a 'metadata' dictionary under vars"
)
no_groups_found = \
"*metadata* should contain a list of group (groups)"
no_classification_found = \
"*metadata* should contain a list of {classification}"
unknown_groups_found = (
"Unkown group(s) '{}' found! "
"The official list of groups are '{}'. "
"To add a new validation group, please add it in the groups.yaml "
"file at the root of the tripleo-validations project."
unknown_classifications_found = (
"Unkown {classification_key}(s) '{unknown_classification}' found! "
"The official list of {classification_key} are '{known_classification}'. "
)
def get_groups(self):
"""Returns a list of group names supported by
tripleo-validations by reading 'groups.yaml'
file located in the base direcotry.
how_to_add_classification = {
'groups': (
"To add a new validation group, please add it in the groups.yaml "
"file at the root of the tripleo-validations project."
)
}
def get_classifications(self, classification='groups'):
"""Returns a list classification names
defined for tripleo-validations in the '{classification}.yaml' file
located in the base repo directory.
"""
results = []
file_path = os.path.abspath(classification + '.yaml')
grp_file_path = os.path.abspath('groups.yaml')
try:
with open(file_path, "r") as definitions:
contents = yaml.safe_load(definitions)
except (PermissionError, OSError):
raise RuntimeError(
"{}.yaml file at '{}' inacessible.".format(
classification,
file_path))
with open(grp_file_path, "r") as grps:
contents = yaml.safe_load(grps)
for grp_name, grp_desc in sorted(contents.items()):
results.append(grp_name)
results = [name for name, _ in contents.items()]
return results
def check_classification(self, metadata, path,
classification_key, strict=False):
"""Check validatity of validation classifications,
such as groups, categories and products.
This one is tricky.
Empty lists in python evaluate as false
So we can't just check for truth value of returned list.
Instead we have to compare the returned value with `None`.
"""
classification = metadata.get(classification_key, None)
if classification is None:
return MatchError(
message=self.no_classification_found.format(
classification=classification_key
),
filename=path,
details=str(metadata))
else:
if not isinstance(classification, list):
return MatchError(
message="*{}* must be a list".format(classification_key),
filename=path,
details=str(metadata))
elif strict:
classifications = self.get_classifications(classification_key)
unknown_classifications = list(
set(classification) - set(classifications))
if unknown_classifications:
message = self.unknown_classifications_found.format(
unknown_classification=unknown_classifications,
known_classification=classifications,
classification_key=classification_key)
message += self.how_to_add_classification.get(classification_key, "")
return MatchError(
message=message,
filename=path,
details=str(metadata))
def matchplay(self, file, data):
results = []
path = file['path']
if file['type'] == 'playbook':
if path.startswith("playbooks/") or \
path.find("tripleo-validations/playbooks/") > 0:
if path.startswith("playbooks/") \
or "tripleo-validations/playbooks/" in path:
# *hosts* line check
hosts = data.get('hosts', None)
if not hosts:
return [({
path: data
}, "No *hosts* key found in the playbook")]
results.append(
MatchError(
message="No *hosts* key found in the playbook",
filename=path,
details=str(data)))
# *vars* lines check
vars = data.get('vars', None)
if not vars:
return [({
path: data
}, self.no_vars_found)]
results.append(
MatchError(
message=self.no_vars_found,
filename=path,
details=str(data)))
else:
if not isinstance(vars, dict):
return [({path: data}, '*vars* should be a dictionary')]
results.append(
MatchError(
message='*vars* must be a dictionary',
filename=path,
details=str(data)))
# *metadata* lines check
metadata = data['vars'].get('metadata', None)
if metadata:
if not isinstance(metadata, dict):
return [(
{path: data},
'*metadata* should be a dictionary')]
results.append(
MatchError(
message='*metadata* must be a dictionary',
filename=path,
details=str(data)))
else:
return [({path: data}, self.no_meta_found)]
results.append(
MatchError(
message=self.no_meta_found,
filename=path,
details=str(data)))
# *metadata>[name|description] lines check
for info in ['name', 'description']:
if not metadata.get(info, None):
results.append((
{path: data},
'*metadata* should contain a %s key' % info))
results.append(
MatchError(
message='*metadata* must contain a %s key' % info,
filename=path,
details=str(data)))
continue
if not isinstance(metadata.get(info), str):
results.append((
{path: data},
'*%s* should be a string' % info))
results.append(
MatchError(
message='*%s* should be a string' % info,
filename=path,
details=str(data)))
# *metadata>groups* lines check
if not metadata.get('groups', None):
results.append((
{path: data},
self.no_groups_found))
else:
if not isinstance(metadata.get('groups'), list):
results.append((
{path: data},
'*groups* should be a list'))
else:
groups = metadata.get('groups')
group_list = self.get_groups()
unknown_groups_list = list(
set(groups) - set(group_list))
if unknown_groups_list:
results.append((
{path: data},
self.unknown_groups_found.format(
unknown_groups_list,
group_list)
))
return results
#Checks for metadata we use to classify validations.
#Groups, categories and products
for classification in ['categories', 'products', 'groups']:
classification_error = self.check_classification(
metadata,
path,
classification,
strict=(classification == 'groups'))
if classification_error:
results.append(classification_error)
return results

View File

@ -8,6 +8,11 @@
groups:
- pre-deployment
- pre-ceph
categories:
- storage
products:
- tripleo
- ceph
fail_without_ceph_ansible: false
ceph_ansible_repo: "centos-ceph-nautilus"
tasks: