#!/usr/bin/env python3 import os import yaml import subprocess import logging from jinja2 import Environment, PackageLoader, select_autoescape logging.basicConfig(level=logging.INFO) repo_owner = os.getenv("REPO_OWNER") or os.getenv( "GITHUB_REPOSITORY_OWNER", "default_owner" ) repo_name = os.getenv("REPO_NAME") or os.getenv("GITHUB_REPOSITORY", "default_repo") env = Environment(loader=PackageLoader("render-readme"), autoescape=select_autoescape()) def load_metadata(file_path): try: with open(file_path, "r") as f: return yaml.safe_load(f) except yaml.YAMLError as e: logging.error(f"Error loading YAML file {file_path}: {e}") except FileNotFoundError: logging.error(f"File {file_path} not found.") return None def process_metadata(apps_dir): app_images = [] for subdir, _, files in os.walk(apps_dir): if "metadata.yaml" not in files: continue # Skip if metadata file not found meta = load_metadata(os.path.join(subdir, "metadata.yaml")) if not meta: continue # Skip if metadata couldn't be loaded dockerfile_path = os.path.join(subdir, "Dockerfile") docker_labels = load_docker_labels(dockerfile_path) # Compliance check and badge setting goss_file = os.path.join(subdir, "ci", "goss.yaml") print(goss_file) badge = run_compliance_check(goss_file) for channel in meta.get("channels", []): name = ( meta["app"] if channel.get("stable", False) else f"{meta['app']}-{channel['name']}" ) image = { "name": name, "channel": channel["name"], "version": meta["version"], "platforms": channel["platforms"], "tests_enabled": channel["tests"]["enabled"], "tests_type": channel["tests"]["type"], "html_url": f"https://code.252.no/{repo_owner}/containers/{name}", "owner": repo_owner, "maintainer": docker_labels.get("maintainer"), "description": docker_labels.get("org.opencontainers.image.description"), "source": docker_labels.get("org.opencontainers.image.source"), "vendor": docker_labels.get("org.opencontainers.image.vendor"), "authors": docker_labels.get("org.opencontainers.image.authors"), "badge": badge, } app_images.append(image) logging.info(f"Added image {name} from channel {channel['name']} with badge {badge}") return app_images def load_docker_labels(dockerfile_path): labels = {} try: with open(dockerfile_path, "r") as f: for line in f: if line.startswith("LABEL"): label_parts = line.split("=", 1) if len(label_parts) == 2: key, value = label_parts key = key.replace("LABEL ", "").strip().replace("\"", "") value = value.strip().replace("\"", "") labels[key] = value except FileNotFoundError: logging.warning(f"Dockerfile {dockerfile_path} not found.") return labels def run_compliance_check(goss_file, image_name): """Run compliance test using dgoss and return appropriate badge path.""" if not os.path.exists(goss_file): logging.warning(f"Compliance file {goss_file} not found.") return "assets/build-failing-red.svg" # Default to failing badge if no compliance file # Set up the environment variables needed for dgoss env = os.environ.copy() env["CONTAINER_RUNTIME"] = "docker" env["GOSS_FILE"] = goss_file env["GOSS_OPTS"] = "--retry-timeout 60s --sleep 2s --color --format documentation" env["GOSS_SLEEP"] = "2" env["GOSS_FILES_STRATEGY"] = "cp" print("Running dgoss with file:", goss_file, "on image:", image_name) # Run dgoss against the container image result = subprocess.run( ["dgoss", "run", image_name], capture_output=True, env=env, shell=True # Necessary to handle dgoss's internal shell scripts ) output = result.stdout.decode() print(output) # Decode and print output for logging if result.returncode == 0: logging.info(f"Compliance check passed for {goss_file}") return "assets/badges/build-passing-brightgreen.svg" else: logging.error(f"Compliance check failed for {goss_file}") return "assets/build-failing-red.svg" if __name__ == "__main__": apps_dir = "./apps" app_images = process_metadata(apps_dir) try: template = env.get_template("README.org.j2") with open("./README.org", "w") as f: f.write(template.render(app_images=app_images)) logging.info("README.org successfully generated.") except Exception as e: logging.error(f"Error rendering template: {e}")