#!/usr/bin/env python3 """Extract all Docker image references from K8s YAML manifests.""" import os import re import sys import fnmatch REPO = "/home/ai-bot/Code/home-docker" EXCLUDE_DIRS = ["archive", "telephone", "legacy-docker-compose", ".github", ".git"] # Patterns to skip: :latest, no tag, or templated variables like ${...} SKIP_TAG_PATTERNS = [ re.compile(r'^latest$'), re.compile(r'^\$\{?.*\}?$'), ] def should_exclude(path): rel = os.path.relpath(path, REPO) parts = rel.split(os.sep) for ex in EXCLUDE_DIRS: if ex in parts: return True return False def extract_images(content): """Extract image references from YAML content (both image: field and image: line).""" images = set() for line in content.splitlines(): # Match image: field in YAML m = re.match(r'^\s*image:\s*["\']?(.+?)["\']?\s*$', line) if not m: # Also match inline: image: "..." m = re.search(r'image:\s*["\']?([a-zA-Z0-9._/-]+(?::[a-zA-Z0-9._-]+)?)["\']?', line) if m: img = m.group(1).strip() # Skip if it contains env var patterns if '${' in img or '$(' in img or img.startswith('$'): continue images.add(img) continue img = m.group(1).strip().rstrip(',') # Handle YAML multiline/complex values img = img.strip('"').strip("'") # Skip if it contains env var patterns if '${' in img or '$(' in img or img.startswith('$'): continue # Skip empty if not img: continue images.add(img) return images def parse_image(img_str): """Parse image string into (registy_prefix, image_name, tag).""" img_str = img_str.strip() # Check for explicit registry (docker.io, ghcr.io, quay.io, etc.) registry = "" if '/' in img_str: parts = img_str.split('/') # Check if first part looks like a registry (contains . or :port) if len(parts) >= 2 and ('.' in parts[0] or ':' in parts[0] or parts[0] == 'localhost' or parts[0] == 'docker.io'): registry = parts[0] image_path = '/'.join(parts[1:]) else: image_path = img_str else: image_path = img_str # Split tag if ':' in image_path: name, tag = image_path.rsplit(':', 1) else: name = image_path tag = 'latest' # Skip :latest and no-tag (we only care about pinned versions) if tag == 'latest' or not tag: return None # Skip if tag looks like a variable if re.match(r'^\$\{', tag) or tag.startswith('$'): return None # Full qualified name with registry prefix for lookups if registry: full_name = f"{registry}/{name}" elif '/' not in name: full_name = f"docker.io/library/{name}" else: # Default to docker.io full_name = f"docker.io/{name}" return { 'raw': img_str, 'registry': registry or 'docker.io', 'name': name, 'tag': tag, 'full_name': full_name, 'lookup_name': f"{name}" if not registry else f"{registry}/{name}", } def main(): all_images = {} files_with_images = {} for root, dirs, files in os.walk(REPO): # Skip excluded dirs dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS and not d.startswith('.')] for fname in files: if not (fname.endswith('.yaml') or fname.endswith('.yml')): continue fpath = os.path.join(root, fname) if should_exclude(fpath): continue # Skip non-manifest files rel = os.path.relpath(fpath, REPO) if rel.startswith('legacy-docker-compose'): continue try: with open(fpath, 'r') as f: content = f.read() except Exception as e: print(f"Error reading {fpath}: {e}", file=sys.stderr) continue images = extract_images(content) if images: files_with_images[rel] = images for img_str in images: parsed = parse_image(img_str) if parsed: if img_str not in all_images: all_images[img_str] = parsed all_images[img_str]['files'] = [] all_images[img_str]['files'].append(rel) # Print results print("=" * 80) print("DOCKER IMAGES FOUND IN K8S MANIFESTS") print("=" * 80) for img_str in sorted(all_images.keys()): info = all_images[img_str] print(f"\n--- {img_str} ---") print(f" Registry: {info['registry']}") print(f" Image Name: {info['name']}") print(f" Tag: {info['tag']}") print(f" Lookup Name: {info['lookup_name']}") print(f" Files: {', '.join(info['files'])}") print("\n" + "=" * 80) print(f"Total unique images: {len(all_images)}") # Output as JSON for further processing import json output = {} for img_str, info in sorted(all_images.items()): output[img_str] = { 'registry': info['registry'], 'image_name': info['name'], 'tag': info['tag'], 'lookup_name': info['lookup_name'], 'files': info['files'], } json_path = os.path.join(REPO, 'scripts', 'images.json') with open(json_path, 'w') as f: json.dump(output, f, indent=2) print(f"\nJSON output written to: {json_path}") if __name__ == '__main__': main()