Skip to content

Instantly share code, notes, and snippets.

@timmc-edx
Created November 12, 2025 20:04
Show Gist options
  • Select an option

  • Save timmc-edx/b0d841bbdff40feb92ea8d0e223c2c58 to your computer and use it in GitHub Desktop.

Select an option

Save timmc-edx/b0d841bbdff40feb92ea8d0e223c2c58 to your computer and use it in GitHub Desktop.
import sys
from pathlib import Path
import yaml
ALL_APPS_DIR = Path('argocd/applications')
def descend_dict(d, ks):
"""
Recursively descend dictionary `d` by list of keys `ks`.
Computes `d[k_1][k_2]...[kn]` unless any intermediate value (starting with d)
is not a dict, in which case returns None.
"""
cur = d
for k in ks:
if not isinstance(cur, dict):
return None
cur = cur.get(k)
return cur
def check_file(yaml_path, app_dir):
try:
with open(yaml_path, 'r') as yp:
yaml_docs = list(yaml.safe_load_all(yp.read()))
except BaseException as e:
# Some of the YAML files are actually templates and can't be parsed. But
# at the time of this writing, none of them were `kind: Application`
# anyhow.
print(f"Warning: Unable to load YAML for {yaml_path}")
return []
error_msgs = []
for (i, doc) in enumerate(yaml_docs):
if not isinstance(doc, dict):
continue # might be list, none, etc.
if doc.get('kind') != 'Application':
continue
# We only want to lint applications defined in edx-internal. Some
# files also don't have a repo URL because they're a kustomization
# patch, so we'll assume that's the case when the URL is missing.
repo_url = descend_dict(doc, ['spec', 'source', 'repoURL'])
if repo_url != None and repo_url != '[email protected]:edx/edx-internal.git':
print(f"Info: Skipping application from external repo {repo_url}: {yaml_path} #{i}")
continue
# Get ahold of the annotation, if it exists
mgp = descend_dict(doc, ['metadata', 'annotations', 'argocd.argoproj.io/manifest-generate-paths'])
if not isinstance(mgp, str) or mgp == '':
error_msgs.append(
f'Doc #{i} is an Application missing '
'.metadata.annotations["argocd.argoproj.io/manifest-generate-paths"]'
)
continue
if ';' in mgp:
error_msgs.append(
f"Doc #{i} has a semicolon in manifest-generate-paths"
"(not supported by this check script)"
)
continue
# The mgp value should be a path (relative to `.spec.source.path`) that
# matches the app dir.
source_path = descend_dict(doc, ['spec', 'source', 'path'])
if not isinstance(source_path, str):
print(f"Warning: Skipping application with repo URL but not source path: {yaml_path} #{i}")
continue
resolved_mgp = (Path(source_path) / mgp).resolve()
if not resolved_mgp.samefile(app_dir):
error_msgs.append(
f"Mismatched doc#{i}: {source_path=} + manifest-generate-paths={mgp} "
f"did not match application dir {app_dir}"
)
return error_msgs
def check_application(app_dir):
errors = []
for root, dirs, files in app_dir.walk():
for fname in files:
if fname.lower().endswith(('.yml', '.yaml')):
yaml_path = root / fname
try:
for error_msg in check_file(yaml_path, app_dir):
errors.append((yaml_path, error_msg))
except BaseException as e:
raise RuntimeError(f"Unexpected error while checking {yaml_path}")
return errors
def main():
errors = []
for app_entry in ALL_APPS_DIR.iterdir():
if app_entry.is_dir():
errors += check_application(app_entry)
print('---')
if errors:
for (fpath, msg) in errors:
print(f"Error in {fpath}: {msg}")
sys.exit(1)
else:
print("No errors found")
sys.exit(0)
if __name__ == '__main__':
main()
@timmc-edx
Copy link
Author

Known problems:

  • A few of our applications are under an intermediate directory. Maybe look for Chart.yaml under the resolved mgp instead? (But one of the other applications doesn't have a Chart.yaml... not sure if that's a bug.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment