diff --git a/monitoring/docker-exporter/exporter.py b/monitoring/docker-exporter/exporter.py index 908aa27..1be0bdc 100644 --- a/monitoring/docker-exporter/exporter.py +++ b/monitoring/docker-exporter/exporter.py @@ -167,36 +167,53 @@ def parse_dockerfile_for_image(dockerfile_path): if not os.path.exists(dockerfile_path): return None - image_name = None - try: + arg_defaults = {} + last_from = None with open(dockerfile_path) as df: for line in df: line = line.strip() - # Prefer LABEL with image if present + if not line or line.startswith("#"): + continue + + if line.upper().startswith("ARG "): + arg_body = line[4:].strip() + if "=" in arg_body: + key, value = arg_body.split("=", 1) + arg_defaults[key.strip()] = value.strip() + continue + + # Prefer LABEL with image if present. if "LABEL" in line and "image=" in line: match = re.search(r'image=["\']?([^"\']+)["\']?', line) if match: - image_name = match.group(1) + image_name = normalize_image_name(substitute_dockerfile_args(match.group(1), arg_defaults)) logger.debug(f"Found LABEL image={image_name} in {dockerfile_path}") return image_name - # If no LABEL, use the last FROM line as fallback - df.seek(0) - last_from = None - for line in df: - line = line.strip() if line.upper().startswith("FROM "): - parts = line.split() - if len(parts) >= 2: - last_from = parts[1] - if last_from: - logger.debug(f"Found base FROM {last_from} in {dockerfile_path}") - return last_from + from_clause = line[5:].strip() + if from_clause.startswith("--"): + split_clause = from_clause.split(None, 1) + if len(split_clause) < 2: + continue + from_clause = split_clause[1] + + parts = from_clause.split() + if not parts: + continue + + candidate = substitute_dockerfile_args(parts[0], arg_defaults) + if candidate and candidate.lower() != "scratch": + last_from = normalize_image_name(candidate) + + if last_from: + logger.debug(f"Found base FROM {last_from} in {dockerfile_path}") + return last_from except Exception as e: logger.debug(f"Error reading Dockerfile {dockerfile_path}: {e}") - return image_name + return None def normalize_image_name(image_name): if not image_name: @@ -207,6 +224,38 @@ def normalize_image_name(image_name): return image_name return f"{image_name}:latest" +def is_compose_build_placeholder(image_name, project_name): + if not image_name: + return False + candidate = str(image_name) + project_prefix = f"{project_name}-" + if candidate.startswith(project_prefix): + return True + # Keep backward-compatible behavior for historical default project prefix. + return candidate.startswith("core-") + +def substitute_dockerfile_args(value, arg_defaults): + if not value: + return value + + pattern = re.compile(r"\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)") + + def replacer(match): + expr = match.group(1) + simple = match.group(2) + if simple: + return arg_defaults.get(simple, "") + + if ":-" in expr: + var_name, default_value = expr.split(":-", 1) + return arg_defaults.get(var_name, default_value) + if "-" in expr: + var_name, default_value = expr.split("-", 1) + return arg_defaults.get(var_name, default_value) + return arg_defaults.get(expr, "") + + return pattern.sub(replacer, value) + def expand_compose_path(path_value, project_root): raw = str(path_value) raw = raw.replace("${PROJECT_ROOT}", project_root).replace("$PROJECT_ROOT", project_root) @@ -302,7 +351,11 @@ def parse_compose_services(compose_files, project_name, project_root): from_dockerfile = normalize_image_name(parse_dockerfile_for_image(dockerfile_path)) local_built_image = resolve_local_build_image(svc_name, project_name) - resolved_image = image or local_built_image or from_dockerfile or f"{project_name}-{svc_name}:latest" + placeholder_image = is_compose_build_placeholder(image, project_name) or is_compose_build_placeholder(local_built_image, project_name) + if placeholder_image: + resolved_image = from_dockerfile or image or local_built_image or f"{project_name}-{svc_name}:latest" + else: + resolved_image = image or local_built_image or from_dockerfile or f"{project_name}-{svc_name}:latest" svc_map[svc_name] = { "image": resolved_image,