Files
deusdata--codebase-memory-mcp/src/pipeline/pass_k8s.c
T
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

699 lines
26 KiB
C

/*
* pass_k8s.c — Pipeline pass for Kubernetes manifest and Kustomize overlay processing.
*
* For each discovered YAML file:
* 1. Check if it is a kustomize overlay (kustomization.yaml / kustomization.yml)
* → emit a Module node and IMPORTS edges for each resources/bases/patches entry
* 2. Else if it is a generic k8s manifest (apiVersion: detected)
* → emit one Resource node per file (first document only — multi-document YAML is not yet
* supported)
*
* Depends on: pass_infrascan.c (cbm_is_kustomize_file, cbm_is_k8s_manifest, cbm_infra_qn),
* extraction layer (cbm.h), graph_buffer, pipeline internals.
*/
#include "foundation/constants.h"
#include "pipeline/pipeline.h"
#include <stdint.h>
#include "pipeline/pipeline_internal.h"
#include "graph_buffer/graph_buffer.h"
#include "discover/discover.h"
#include "foundation/log.h"
#include "foundation/compat.h"
#include "foundation/compat_fs.h"
#include "foundation/limits.h"
#include "cbm.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/* ── Internal helpers ────────────────────────────────────────────── */
/* Read entire file into heap-allocated buffer. Returns NULL on error.
* Caller must free(). Sets *out_len to byte count. */
static char *k8s_read_file(const char *path, int *out_len) {
FILE *f = cbm_fopen(path, "rb");
if (!f) {
return NULL;
}
(void)fseek(f, 0, SEEK_END);
long size = ftell(f);
(void)fseek(f, 0, SEEK_SET);
if (size <= 0 || size > cbm_max_file_bytes()) { /* generous, env-configurable cap (B4) */
(void)fclose(f);
return NULL;
}
/* +pad: tree-sitter lexer lookahead reads past EOF; keep it in-bounds */
enum { CBM_TS_LOOKAHEAD_PAD = 16 };
char *buf = malloc((size_t)size + CBM_TS_LOOKAHEAD_PAD);
if (!buf) {
(void)fclose(f);
return NULL;
}
size_t nread = fread(buf, SKIP_ONE, size, f);
(void)fclose(f);
if (nread > (size_t)size) {
nread = (size_t)size;
}
memset(buf + nread, 0, CBM_TS_LOOKAHEAD_PAD);
*out_len = (int)nread;
return buf;
}
/* Format int to string for logging. Thread-safe via TLS. */
static const char *itoa_k8s(int val) {
enum { RING_BUF_COUNT = 4, RING_BUF_MASK = 3 };
static CBM_TLS char bufs[RING_BUF_COUNT][CBM_SZ_32];
static CBM_TLS int idx = 0;
int i = idx;
idx = (idx + SKIP_ONE) & RING_BUF_MASK;
snprintf(bufs[i], sizeof(bufs[i]), "%d", val);
return bufs[i];
}
/* Extract the basename of a path (pointer into the string; no allocation). */
static const char *k8s_basename(const char *path) {
const char *p = strrchr(path, '/');
return p ? p + SKIP_ONE : path;
}
/* ── Kustomize handler ───────────────────────────────────────────── */
static void handle_kustomize(cbm_pipeline_ctx_t *ctx, const char *path, const char *rel_path,
CBMFileResult *result) {
/* Emit Module node for this kustomize overlay file */
char *mod_qn = cbm_infra_qn(ctx->project_name, rel_path, "kustomize", NULL);
if (!mod_qn) {
return;
}
int64_t mod_id = cbm_gbuf_upsert_node(ctx->gbuf, "Module", k8s_basename(rel_path), mod_qn,
rel_path, SKIP_ONE, 0, "{\"source\":\"kustomize\"}");
free(mod_qn);
if (mod_id <= 0) {
return;
}
/* If we have a cached extraction result, emit IMPORTS edges for
* resources/bases/patches/components entries */
int import_count = 0;
CBMFileResult *res = result;
bool allocated = false;
if (!res) {
/* Fall back to re-extraction */
int src_len = 0;
char *source = k8s_read_file(path, &src_len);
if (source) {
res = cbm_extract_file(source, src_len, CBM_LANG_KUSTOMIZE, ctx->project_name, rel_path,
CBM_EXTRACT_BUDGET, NULL, NULL);
free(source);
allocated = true;
}
}
if (res) {
for (int j = 0; j < res->imports.count; j++) {
CBMImport *imp = &res->imports.items[j];
if (!imp->module_path) {
continue;
}
/* Compute target file QN */
char *target_qn =
cbm_pipeline_fqn_compute(ctx->project_name, imp->module_path, "__file__");
if (!target_qn) {
continue;
}
const cbm_gbuf_node_t *target = cbm_gbuf_find_by_qn(ctx->gbuf, target_qn);
free(target_qn);
if (target) {
cbm_gbuf_insert_edge(ctx->gbuf, mod_id, target->id, "IMPORTS",
"{\"via\":\"kustomize\"}");
import_count++;
}
}
if (allocated) {
cbm_free_result(res);
}
}
cbm_log_info("pass.k8s.kustomize", "file", rel_path, "imports", itoa_k8s(import_count));
}
/* ── K8s cross-manifest label-selector matching ──────────────────────
* A Service routes traffic to the workload Pods whose labels match the
* Service's spec.selector. We record, per manifest, the Resource node id plus
* its selector label-values (Services) and pod label-values (workloads), then
* after all manifests are processed connect each Service to the workload(s) it
* targets via an INFRA_MAPS edge. This models the runtime traffic path the same
* way k8s itself resolves Service → Endpoints by label.
*
* Matching is by label *value*: a Service selector `app: frontend` matches a
* workload that either carries the pod label value "frontend" OR is named
* "frontend" (the ubiquitous app==name convention, used when the pod template
* omits explicit labels). */
enum { K8S_MAX_LABELS = 16, K8S_MAX_RECORDS = 512, K8S_LABEL_LEN = 128 };
typedef struct {
int64_t node_id;
char name[K8S_LABEL_LEN];
bool is_service;
bool is_workload;
char selector_vals[K8S_MAX_LABELS][K8S_LABEL_LEN]; /* Service spec.selector values */
int n_selector;
char label_vals[K8S_MAX_LABELS][K8S_LABEL_LEN]; /* workload pod-template label values */
int n_label;
} k8s_record_t;
typedef struct {
k8s_record_t *items;
int count;
int cap;
} k8s_record_array_t;
/* Count leading-space indentation of a line (tabs are invalid YAML indent). */
static int k8s_indent(const char *line) {
int n = 0;
while (line[n] == ' ') {
n++;
}
return n;
}
/* Split `key: value` (already de-indented). Returns 1 if a key was found; fills
* key/val (val empty when the key opens a nested block). */
static int k8s_split_kv(const char *t, char *key, size_t key_sz, char *val, size_t val_sz) {
key[0] = '\0';
val[0] = '\0';
if (t[0] == '#' || t[0] == '-' || t[0] == '\0') {
return 0;
}
const char *colon = strchr(t, ':');
if (!colon) {
return 0;
}
size_t klen = (size_t)(colon - t);
if (klen == 0 || klen >= key_sz) {
return 0;
}
memcpy(key, t, klen);
key[klen] = '\0';
const char *v = colon + 1;
while (*v == ' ' || *v == '\t') {
v++;
}
size_t vn = 0;
while (v[vn] && v[vn] != '\r' && v[vn] != '\n' && v[vn] != '#' && vn + 1 < val_sz) {
val[vn] = v[vn];
vn++;
}
/* trim trailing space */
while (vn > 0 && (val[vn - 1] == ' ' || val[vn - 1] == '\t')) {
vn--;
}
val[vn] = '\0';
/* strip surrounding quotes */
if (vn >= 2 && (val[0] == '"' || val[0] == '\'') && val[vn - 1] == val[0]) {
memmove(val, val + 1, vn - 2);
val[vn - 2] = '\0';
}
return 1;
}
static void k8s_add_val(char dst[][K8S_LABEL_LEN], int *n, const char *v) {
if (*n >= K8S_MAX_LABELS || !v || !v[0]) {
return;
}
snprintf(dst[*n], K8S_LABEL_LEN, "%s", v);
(*n)++;
}
/* Scan a single-document k8s manifest's text for the resource name, selector
* label-values (Service) and pod-template label-values (workload). A small
* indentation path-stack distinguishes spec.selector from
* spec.template.metadata.labels and from top-level metadata.name. */
static void k8s_scan_labels(const char *source, k8s_record_t *rec) {
enum { K8S_PATH_DEPTH = 12 };
struct {
int indent;
char key[64];
} stack[K8S_PATH_DEPTH];
int depth = 0;
bool got_name = false;
const char *p = source;
while (p && *p) {
const char *eol = strchr(p, '\n');
size_t len = eol ? (size_t)(eol - p) : strlen(p);
char line[CBM_SZ_512];
size_t cp = len < sizeof(line) - 1 ? len : sizeof(line) - 1;
memcpy(line, p, cp);
line[cp] = '\0';
/* End of first YAML document — stop (one Resource per file). */
const char *trimmed = line;
while (*trimmed == ' ') {
trimmed++;
}
if (strncmp(trimmed, "---", 3) == 0 && line == trimmed) {
break;
}
if (trimmed[0] && trimmed[0] != '#') {
int ind = k8s_indent(line);
while (depth > 0 && stack[depth - 1].indent >= ind) {
depth--;
}
char key[64];
char val[K8S_LABEL_LEN];
if (k8s_split_kv(trimmed, key, sizeof(key), val, sizeof(val))) {
/* Build the current dotted path for context decisions. */
bool under_selector = (depth >= 1 && strcmp(stack[depth - 1].key, "selector") == 0);
bool under_labels = (depth >= 1 && strcmp(stack[depth - 1].key, "labels") == 0);
bool under_metadata = (depth >= 1 && strcmp(stack[depth - 1].key, "metadata") == 0);
if (val[0] == '\0') {
/* Block-opening key: push onto the path stack. */
if (depth < K8S_PATH_DEPTH) {
stack[depth].indent = ind;
snprintf(stack[depth].key, sizeof(stack[depth].key), "%s", key);
depth++;
}
} else {
/* Leaf key: value. */
if (ind == 0 && strcmp(key, "kind") == 0) {
rec->is_service = (strcmp(val, "Service") == 0);
rec->is_workload =
(strcmp(val, "Deployment") == 0 || strcmp(val, "StatefulSet") == 0 ||
strcmp(val, "DaemonSet") == 0 || strcmp(val, "ReplicaSet") == 0 ||
strcmp(val, "ReplicationController") == 0 || strcmp(val, "Pod") == 0 ||
strcmp(val, "Job") == 0 || strcmp(val, "CronJob") == 0);
} else if (!got_name && under_metadata && strcmp(key, "name") == 0) {
snprintf(rec->name, sizeof(rec->name), "%s", val);
got_name = true;
} else if (under_selector) {
/* Service spec.selector matchLabels values. */
k8s_add_val(rec->selector_vals, &rec->n_selector, val);
} else if (under_labels) {
/* Pod-template / metadata labels values. */
k8s_add_val(rec->label_vals, &rec->n_label, val);
}
}
}
}
if (!eol) {
break;
}
p = eol + 1;
}
}
/* True if any of the service's selector values matches the workload. */
static bool k8s_selector_matches(const k8s_record_t *svc, const k8s_record_t *wl) {
for (int s = 0; s < svc->n_selector; s++) {
const char *sv = svc->selector_vals[s];
if (!sv[0]) {
continue;
}
if (wl->name[0] && strcmp(sv, wl->name) == 0) {
return true;
}
for (int l = 0; l < wl->n_label; l++) {
if (strcmp(sv, wl->label_vals[l]) == 0) {
return true;
}
}
}
return false;
}
/* After all manifests are recorded, connect each Service to the workload(s) its
* selector targets via an INFRA_MAPS edge (Service Resource → workload Resource). */
static void k8s_link_selectors(cbm_pipeline_ctx_t *ctx, const k8s_record_array_t *recs) {
int edges = 0;
for (int i = 0; i < recs->count; i++) {
const k8s_record_t *svc = &recs->items[i];
if (!svc->is_service || svc->n_selector == 0 || svc->node_id <= 0) {
continue;
}
for (int j = 0; j < recs->count; j++) {
const k8s_record_t *wl = &recs->items[j];
if (i == j || !wl->is_workload || wl->node_id <= 0) {
continue;
}
if (k8s_selector_matches(svc, wl)) {
char props[CBM_SZ_256];
snprintf(props, sizeof(props),
"{\"kind\":\"selector\",\"service\":\"%s\",\"workload\":\"%s\"}",
svc->name, wl->name);
cbm_gbuf_insert_edge(ctx->gbuf, svc->node_id, wl->node_id, "INFRA_MAPS", props);
edges++;
}
}
}
if (edges > 0) {
cbm_log_info("pass.k8s.selectors", "linked", itoa_k8s(edges));
}
}
/* ── K8s manifest handler ────────────────────────────────────────── */
/* source/src_len are the already-read file bytes (caller retains ownership and
* must free after this call returns). When `rec` is non-NULL it is populated
* with the first Resource's node id, name and label/selector values for later
* cross-manifest selector matching. */
static void handle_k8s_manifest(cbm_pipeline_ctx_t *ctx, const char *path, const char *rel_path,
const char *source, int src_len, k8s_record_t *rec) {
(void)path; /* retained for symmetry; source is always provided now */
int resource_count = 0;
CBMFileResult *res = cbm_extract_file(source, src_len, CBM_LANG_K8S, ctx->project_name,
rel_path, CBM_EXTRACT_BUDGET, NULL, NULL);
if (!res) {
return;
}
/* Compute file node QN for DEFINES edges */
char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, rel_path, "__file__");
const cbm_gbuf_node_t *file_node = file_qn ? cbm_gbuf_find_by_qn(ctx->gbuf, file_qn) : NULL;
free(file_qn);
for (int d = 0; d < res->defs.count; d++) {
CBMDefinition *def = &res->defs.items[d];
if (!def->label || strcmp(def->label, "Resource") != 0) {
continue;
}
if (!def->name || !def->qualified_name) {
continue;
}
int64_t node_id =
cbm_gbuf_upsert_node(ctx->gbuf, "Resource", def->name, def->qualified_name, rel_path,
(int)def->start_line, (int)def->end_line, "{\"source\":\"k8s\"}");
/* DEFINES edge: File → Resource */
if (file_node && node_id > 0) {
cbm_gbuf_insert_edge(ctx->gbuf, file_node->id, node_id, "DEFINES", "{}");
}
/* Capture the first Resource for cross-manifest selector matching. */
if (rec && rec->node_id <= 0 && node_id > 0) {
rec->node_id = node_id;
}
resource_count++;
}
cbm_free_result(res);
/* Record selector / pod-label values for later Service → workload linking. */
if (rec && rec->node_id > 0) {
k8s_scan_labels(source, rec);
}
cbm_log_info("pass.k8s.manifest", "file", rel_path, "resources", itoa_k8s(resource_count));
}
/* ── Helm chart handler ──────────────────────────────────────────── */
static bool is_helm_chart_file(const char *base) {
return strcmp(base, "Chart.yaml") == 0 || strcmp(base, "Chart.yml") == 0;
}
/* Emit a Chart node for a Chart.yaml and a DEPENDS_ON edge to a (shared,
* deduplicated) Chart node per declared dependency (#338). */
static void handle_helm_chart(cbm_pipeline_ctx_t *ctx, const char *rel_path, const char *source) {
cbm_helm_chart_t hc;
if (cbm_parse_helm_chart(source, &hc) != 0) {
return;
}
const char *cname = hc.chart_name[0] ? hc.chart_name : k8s_basename(rel_path);
char *chart_qn = cbm_infra_qn(ctx->project_name, rel_path, "helm-chart", NULL);
if (!chart_qn) {
return;
}
int64_t chart_id = cbm_gbuf_upsert_node(ctx->gbuf, "Chart", cname, chart_qn, rel_path, SKIP_ONE,
0, "{\"source\":\"helm\"}");
free(chart_qn);
char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, rel_path, "__file__");
const cbm_gbuf_node_t *file_node = file_qn ? cbm_gbuf_find_by_qn(ctx->gbuf, file_qn) : NULL;
if (file_node && chart_id > 0) {
cbm_gbuf_insert_edge(ctx->gbuf, file_node->id, chart_id, "DEFINES", "{}");
}
free(file_qn);
int dep_edges = 0;
for (int i = 0; i < hc.dep_count && chart_id > 0; i++) {
/* Stable per-project QN so multiple charts depending on the same chart
* link to one shared dependency node. */
char dep_qn[CBM_SZ_512];
snprintf(dep_qn, sizeof(dep_qn), "%s.__helm_dep__.%s", ctx->project_name, hc.deps[i]);
int64_t dep_id =
cbm_gbuf_upsert_node(ctx->gbuf, "Chart", hc.deps[i], dep_qn, rel_path, SKIP_ONE, 0,
"{\"source\":\"helm\",\"external\":true}");
if (dep_id > 0) {
cbm_gbuf_insert_edge(ctx->gbuf, chart_id, dep_id, "DEPENDS_ON", "{}");
dep_edges++;
}
}
cbm_log_info("pass.k8s.helm", "file", rel_path, "deps", itoa_k8s(dep_edges));
}
/* ── Dependency-manifest handler (go.mod / requirements.txt) ──────── */
static bool is_gomod_file(const char *base) {
return strcmp(base, "go.mod") == 0;
}
static bool is_requirements_file(const char *base) {
return strcmp(base, "requirements.txt") == 0;
}
/* Emit a DEPENDS_ON edge from the manifest file node to a (shared, per-project)
* external Package node. Mirrors the Helm Chart.yaml DEPENDS_ON shape. */
static int emit_dep_edge(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_t *src, const char *rel_path,
const char *ecosystem, const char *name) {
if (!name || !name[0]) {
return 0;
}
char dep_qn[CBM_SZ_512];
snprintf(dep_qn, sizeof(dep_qn), "%s.__%s_dep__.%s", ctx->project_name, ecosystem, name);
char dep_props[CBM_SZ_256];
snprintf(dep_props, sizeof(dep_props), "{\"source\":\"%s\",\"external\":true}", ecosystem);
int64_t dep_id =
cbm_gbuf_upsert_node(ctx->gbuf, "Package", name, dep_qn, rel_path, SKIP_ONE, 0, dep_props);
if (dep_id > 0 && dep_id != src->id) {
cbm_gbuf_insert_edge(ctx->gbuf, src->id, dep_id, "DEPENDS_ON", "{}");
return 1;
}
return 0;
}
/* Copy the first whitespace-delimited token of `line` into `out`. */
static void first_token(const char *line, char *out, size_t out_sz) {
out[0] = '\0';
while (*line == ' ' || *line == '\t') {
line++;
}
size_t n = 0;
while (line[n] && line[n] != ' ' && line[n] != '\t' && line[n] != '\r' && line[n] != '\n' &&
n + 1 < out_sz) {
out[n] = line[n];
n++;
}
out[n] = '\0';
}
/* Parse go.mod `require` directives (single-line and block forms) and emit a
* DEPENDS_ON edge per dependency. go.mod requires are not surfaced as imports
* by the extraction layer, so we parse the manifest text directly here. */
static int parse_gomod_deps(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_t *src,
const char *rel_path, const char *source) {
int edges = 0;
bool in_block = false;
const char *p = source;
while (p && *p) {
const char *eol = strchr(p, '\n');
size_t len = eol ? (size_t)(eol - p) : strlen(p);
char line[CBM_SZ_512];
size_t cp = len < sizeof(line) - 1 ? len : sizeof(line) - 1;
memcpy(line, p, cp);
line[cp] = '\0';
const char *t = line;
while (*t == ' ' || *t == '\t') {
t++;
}
if (in_block) {
if (t[0] == ')') {
in_block = false;
} else if (t[0] && t[0] != '/') {
char name[CBM_SZ_256];
first_token(t, name, sizeof(name));
edges += emit_dep_edge(ctx, src, rel_path, "gomod", name);
}
} else if (strncmp(t, "require", 7) == 0 && (t[7] == ' ' || t[7] == '\t' || t[7] == '(')) {
const char *rest = t + 7;
while (*rest == ' ' || *rest == '\t') {
rest++;
}
if (*rest == '(') {
in_block = true;
} else if (*rest) {
char name[CBM_SZ_256];
first_token(rest, name, sizeof(name));
edges += emit_dep_edge(ctx, src, rel_path, "gomod", name);
}
}
if (!eol) {
break;
}
p = eol + 1;
}
return edges;
}
/* Parse requirements.txt entries (one package spec per line) and emit a
* DEPENDS_ON edge per dependency. The package name is the leading token up to
* the first version/extras/comment delimiter. */
static int parse_requirements_deps(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_t *src,
const char *rel_path, const char *source) {
int edges = 0;
const char *p = source;
while (p && *p) {
const char *eol = strchr(p, '\n');
size_t len = eol ? (size_t)(eol - p) : strlen(p);
char line[CBM_SZ_512];
size_t cp = len < sizeof(line) - 1 ? len : sizeof(line) - 1;
memcpy(line, p, cp);
line[cp] = '\0';
const char *t = line;
while (*t == ' ' || *t == '\t') {
t++;
}
/* Skip blanks, comments, options (-r, --hash), and URLs. */
if (t[0] && t[0] != '#' && t[0] != '-' && strstr(t, "://") == NULL) {
char name[CBM_SZ_256];
size_t n = 0;
while (t[n] && t[n] != '=' && t[n] != '<' && t[n] != '>' && t[n] != '!' &&
t[n] != '~' && t[n] != '[' && t[n] != ';' && t[n] != ' ' && t[n] != '\t' &&
t[n] != '\r' && n + 1 < sizeof(name)) {
name[n] = t[n];
n++;
}
name[n] = '\0';
edges += emit_dep_edge(ctx, src, rel_path, "pypi", name);
}
if (!eol) {
break;
}
p = eol + 1;
}
return edges;
}
static void handle_dep_manifest(cbm_pipeline_ctx_t *ctx, const char *rel_path, const char *source,
const char *ecosystem) {
if (!source) {
return;
}
char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, rel_path, "__file__");
const cbm_gbuf_node_t *src = file_qn ? cbm_gbuf_find_by_qn(ctx->gbuf, file_qn) : NULL;
free(file_qn);
if (!src) {
return;
}
int dep_edges = strcmp(ecosystem, "gomod") == 0
? parse_gomod_deps(ctx, src, rel_path, source)
: parse_requirements_deps(ctx, src, rel_path, source);
cbm_log_info("pass.k8s.depmanifest", "file", rel_path, "deps", itoa_k8s(dep_edges));
}
/* ── Pass entry point ────────────────────────────────────────────── */
int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, int file_count) {
cbm_log_info("pass.start", "pass", "k8s", "files", itoa_k8s(file_count));
cbm_init();
int kustomize_count = 0;
int manifest_count = 0;
int helm_count = 0;
/* Collect per-manifest selector/label records for cross-manifest matching. */
k8s_record_array_t recs = {0};
recs.items = calloc(K8S_MAX_RECORDS, sizeof(*recs.items));
recs.cap = recs.items ? K8S_MAX_RECORDS : 0;
for (int i = 0; i < file_count; i++) {
if (cbm_pipeline_check_cancel(ctx)) {
free(recs.items);
return CBM_NOT_FOUND;
}
const char *path = files[i].path;
const char *rel = files[i].rel_path;
CBMLanguage lang = files[i].language;
const char *base = k8s_basename(rel);
CBMFileResult *cached =
(ctx->result_cache && ctx->result_cache[i]) ? ctx->result_cache[i] : NULL;
if (is_gomod_file(base) || lang == CBM_LANG_GOMOD || is_requirements_file(base)) {
int dep_len = 0;
char *dep_src = k8s_read_file(path, &dep_len);
if (dep_src) {
handle_dep_manifest(ctx, rel, dep_src,
is_requirements_file(base) ? "pypi" : "gomod");
free(dep_src);
}
} else if (cbm_is_kustomize_file(base)) {
handle_kustomize(ctx, path, rel, cached);
kustomize_count++;
} else if (lang == CBM_LANG_YAML || lang == CBM_LANG_K8S) {
/* Read source once to classify (and reuse for uncached extraction). */
int src_len = 0;
char *source = k8s_read_file(path, &src_len);
if (source) {
if (is_helm_chart_file(base)) {
handle_helm_chart(ctx, rel, source);
helm_count++;
} else if (cbm_is_k8s_manifest(base, source)) {
/* Always re-extract with CBM_LANG_K8S regardless of any cached
* result: cached results were produced during the parallel YAML
* pass and contain no "Resource" definitions. Pass the already-
* read source buffer so handle_k8s_manifest does not re-read. */
(void)cached; /* cached YAML result intentionally discarded */
k8s_record_t *rec = (recs.count < recs.cap) ? &recs.items[recs.count] : NULL;
handle_k8s_manifest(ctx, path, rel, source, src_len, rec);
if (rec && rec->node_id > 0) {
recs.count++;
}
manifest_count++;
}
free(source);
}
}
}
/* Connect Services to the workloads their selectors target (INFRA_MAPS). */
k8s_link_selectors(ctx, &recs);
free(recs.items);
cbm_log_info("pass.done", "pass", "k8s", "kustomize", itoa_k8s(kustomize_count), "manifests",
itoa_k8s(manifest_count));
(void)helm_count;
return 0;
}