blob: e1891251b75b0f7a615eef39b296e3897d8cdb51 [file]
#!/usr/bin/env python3
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Validate .asf.yaml structural correctness.
Checks YAML syntax, root-level structure, and type constraints for
known fields. Does NOT reject unknown keys — the ASF infra schema
evolves independently and unrecognized keys are silently ignored by
the ASF gitbox service.
Reference: https://github.com/apache/infrastructure-asfyaml
"""
import sys
try:
import yaml
except ImportError:
sys.exit(
"PyYAML is required: pip install pyyaml\n"
"Or use: python3 -c \"import pip; pip.main(['install', 'pyyaml'])\""
)
ASF_YAML_PATH = ".asf.yaml"
def validate():
errors = []
try:
with open(ASF_YAML_PATH, "r") as f:
data = yaml.safe_load(f)
except FileNotFoundError:
print(f"SKIP: {ASF_YAML_PATH} not found")
return 0
except yaml.YAMLError as e:
print(f"ERROR: Invalid YAML syntax in {ASF_YAML_PATH}: {e}")
return 1
if not isinstance(data, dict):
print(f"ERROR: {ASF_YAML_PATH} root must be a mapping")
return 1
# Validate types of known sections
for key in ("github", "notifications", "staging", "publish"):
if key in data and not isinstance(data[key], dict):
errors.append(f"'{key}' must be a mapping, got {type(data[key]).__name__}")
github = data.get("github")
if isinstance(github, dict):
# features values must be booleans
features = github.get("features")
if isinstance(features, dict):
for key, val in features.items():
if not isinstance(val, bool):
errors.append(
f"'github.features.{key}' must be a boolean, "
f"got {type(val).__name__}"
)
# enabled_merge_buttons values must be booleans or strings
merge_buttons = github.get("enabled_merge_buttons")
if isinstance(merge_buttons, dict):
for key, val in merge_buttons.items():
if not isinstance(val, (bool, str)):
errors.append(
f"'github.enabled_merge_buttons.{key}' must be bool or string, "
f"got {type(val).__name__}"
)
# labels must be a list of strings
labels = github.get("labels")
if labels is not None and not isinstance(labels, list):
errors.append(f"'github.labels' must be a list, got {type(labels).__name__}")
if errors:
print(f"ERROR: {ASF_YAML_PATH} validation failed:")
for err in errors:
print(f" - {err}")
return 1
print(f"OK: {ASF_YAML_PATH} is valid")
return 0
if __name__ == "__main__":
sys.exit(validate())