mirror of
https://github.com/wahyd4/home-docker.git
synced 2026-08-09 04:15:52 +10:00
Updates 7 Docker images across the infrastructure manifests (PG14 kept as-is):
media/jackett.yaml
linuxserver/jackett: 0.24.1124 → 0.24.1985 (patch)
media/media.yaml
linuxserver/qbittorrent: version-5.1.2-r4 → version-5.2.1_v2.0.12 (major 5.1→5.2)
home-apps/n8n.yaml
n8nio/n8n: 2.15.0 → 2.23.1 (minor)
db/qdrant.yaml
qdrant/qdrant: v1.16-unprivileged → v1.18.1-unprivileged (minor)
kube-vip/daemonset.yaml
kube-vip/kube-vip: v0.4.4 → v0.9.2 (conservative, stayed within v0.x)
on-demand/once-etcd-job.yaml
etcd: 3.5.1-0 → 3.5.30-0 (patch)
adhoc-config/oauth2-proxy.yaml
oauth2-proxy/oauth2-proxy: v7.6.0 → v7.15.2 (minor)
Note: postgres:14 kept unchanged per PR review feedback.
kube-vip kept at v0.9.2 (latest v0.x) instead of v1.2.0 to avoid
v0→v1 breaking changes.
177 lines
5.7 KiB
Python
177 lines
5.7 KiB
Python
#!/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()
|