Files
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

361 lines
13 KiB
C

// extract_k8s.c — K8s manifest and Kustomize file extractor.
//
// For CBM_LANG_KUSTOMIZE: walks top-level block_mapping_pair nodes whose key
// matches "resources", "bases", "patches", "components", or
// "patchesStrategicMerge", then emits one CBMImport per block_sequence item.
//
// For CBM_LANG_K8S: finds apiVersion, kind, and metadata.name scalars in the
// first document's block_mapping and emits one CBMDefinition with label
// "Resource" and name "Kind/metadata-name".
#include "cbm.h"
#include "arena.h"
#include "helpers.h"
#include "tree_sitter/api.h"
#include "foundation/constants.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* Local constants. */
enum {
K8S_BUF_SIZE = 256,
RESULT_BUF_SIZE = 512,
};
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
// Return the raw source text for a scalar node (plain, single-quoted, or
// double-quoted). Surrounding quote characters are stripped for quoted forms.
// Handles flow_node wrappers transparently by descending into the first named
// child (the tree-sitter YAML grammar often wraps scalars in flow_node).
// Returns NULL for non-scalar node types.
static const char *get_scalar_text(CBMArena *a, TSNode node, const char *source) {
enum { MAX_UNWRAP = 4 };
for (int depth = 0; depth < MAX_UNWRAP; depth++) {
const char *type = ts_node_type(node);
if (strcmp(type, "flow_node") == 0) {
TSNode inner = ts_node_named_child(node, 0);
if (ts_node_is_null(inner)) {
return NULL;
}
node = inner;
continue;
}
if (strcmp(type, "plain_scalar") == 0) {
return cbm_node_text(a, node, source);
}
if (strcmp(type, "double_quote_scalar") == 0 || strcmp(type, "single_quote_scalar") == 0) {
const char *raw = cbm_node_text(a, node, source);
if (!raw) {
return NULL;
}
size_t len = strlen(raw);
if (len >= PAIR_LEN) {
return cbm_arena_strndup(a, raw + SKIP_ONE, len - PAIR_LEN);
}
return raw;
}
break;
}
return NULL;
}
// Return true if the key text of a block_mapping_pair matches one of the
// Kustomize resource-list field names.
static int is_kustomize_list_key(const char *key) {
return (strcmp(key, "resources") == 0 || strcmp(key, "bases") == 0 ||
strcmp(key, "patches") == 0 || strcmp(key, "components") == 0 ||
strcmp(key, "patchesStrategicMerge") == 0 || strcmp(key, "crds") == 0);
}
// ---------------------------------------------------------------------------
// Kustomize extraction
// ---------------------------------------------------------------------------
// Walk a block_sequence node and emit one CBMImport per block_sequence_item
// scalar child, using key_name as the local_name.
static void emit_kustomize_sequence(CBMExtractCtx *ctx, TSNode seq_node, const char *key_name) {
CBMArena *a = ctx->arena;
uint32_t n = ts_node_child_count(seq_node);
for (uint32_t i = 0; i < n; i++) {
TSNode item = ts_node_child(seq_node, i);
if (strcmp(ts_node_type(item), "block_sequence_item") != 0) {
continue;
}
// block_sequence_item has one named child: the value
uint32_t ic = ts_node_child_count(item);
for (uint32_t j = 0; j < ic; j++) {
TSNode val = ts_node_child(item, j);
const char *scalar = get_scalar_text(a, val, ctx->source);
if (!scalar) {
continue;
}
CBMImport imp = {
.local_name = cbm_arena_strdup(a, key_name),
.module_path = cbm_arena_strdup(a, scalar),
};
cbm_imports_push(&ctx->result->imports, a, imp);
}
}
}
// Unwrap a YAML node through optional block_node wrapper to get block_mapping.
// Returns null node if not a block_mapping.
static TSNode unwrap_block_mapping(TSNode doc_child) {
TSNode mapping = ts_node_named_child(doc_child, 0);
if (ts_node_is_null(mapping)) {
return mapping;
}
if (strcmp(ts_node_type(mapping), "block_node") == 0) {
mapping = ts_node_named_child(mapping, 0);
}
if (ts_node_is_null(mapping) || strcmp(ts_node_type(mapping), "block_mapping") != 0) {
TSNode null_node = {0};
return null_node;
}
return mapping;
}
// Process a single block_mapping_pair for kustomize list keys.
static void process_kustomize_pair(CBMExtractCtx *ctx, TSNode pair) {
if (strcmp(ts_node_type(pair), "block_mapping_pair") != 0) {
return;
}
TSNode key_node = ts_node_named_child(pair, 0);
if (ts_node_is_null(key_node)) {
return;
}
const char *key_text = get_scalar_text(ctx->arena, key_node, ctx->source);
if (!key_text || !is_kustomize_list_key(key_text)) {
return;
}
TSNode val_node = ts_node_named_child(pair, SKIP_ONE);
if (ts_node_is_null(val_node)) {
return;
}
if (strcmp(ts_node_type(val_node), "block_node") == 0) {
val_node = ts_node_named_child(val_node, 0);
}
if (ts_node_is_null(val_node) || strcmp(ts_node_type(val_node), "block_sequence") != 0) {
return;
}
emit_kustomize_sequence(ctx, val_node, key_text);
}
// Forward declaration: defined with the K8s-manifest helpers below.
static TSNode unwrap_pair_value(TSNode pair);
// Emit a "Class" def named after the document's `kind` scalar. A kustomization
// file has no metadata.name, so the def name is the bare kind ("Kustomization").
// Mirrors the K8s manifest kind-def so Kustomize resources are also discoverable.
static void emit_kustomize_kind_def(CBMExtractCtx *ctx, TSNode mapping) {
CBMArena *a = ctx->arena;
uint32_t pair_n = ts_node_child_count(mapping);
for (uint32_t pi = 0; pi < pair_n; pi++) {
TSNode pair = ts_node_child(mapping, pi);
if (strcmp(ts_node_type(pair), "block_mapping_pair") != 0) {
continue;
}
TSNode key_node = ts_node_named_child(pair, 0);
if (ts_node_is_null(key_node)) {
continue;
}
const char *key = get_scalar_text(a, key_node, ctx->source);
if (!key || strcmp(key, "kind") != 0) {
continue;
}
TSNode val_node = unwrap_pair_value(pair);
if (ts_node_is_null(val_node)) {
continue;
}
const char *kind = get_scalar_text(a, val_node, ctx->source);
if (!kind || !kind[0]) {
continue;
}
CBMDefinition def = {0};
def.name = cbm_arena_strdup(a, kind);
def.qualified_name = cbm_arena_sprintf(a, "%s.%s", ctx->module_qn, kind);
def.label = cbm_arena_strdup(a, "Resource");
def.file_path = ctx->rel_path;
def.start_line = ts_node_start_point(mapping).row + TS_LINE_OFFSET;
def.end_line = ts_node_end_point(mapping).row + TS_LINE_OFFSET;
cbm_defs_push(&ctx->result->defs, a, def);
return;
}
}
static void extract_kustomize(CBMExtractCtx *ctx) {
TSNode root = ctx->root;
uint32_t root_n = ts_node_child_count(root);
for (uint32_t si = 0; si < root_n; si++) {
TSNode stream_child = ts_node_child(root, si);
if (strcmp(ts_node_type(stream_child), "document") != 0) {
continue;
}
TSNode mapping = unwrap_block_mapping(stream_child);
if (ts_node_is_null(mapping)) {
continue;
}
emit_kustomize_kind_def(ctx, mapping);
uint32_t pair_n = ts_node_child_count(mapping);
for (uint32_t pi = 0; pi < pair_n; pi++) {
process_kustomize_pair(ctx, ts_node_child(mapping, pi));
}
}
}
// ---------------------------------------------------------------------------
// K8s manifest extraction
// ---------------------------------------------------------------------------
// Extract the "name" scalar from a metadata block_mapping.
static void extract_metadata_name(CBMArena *a, TSNode meta_mapping, const char *source,
char *meta_name_buf, size_t meta_sz) {
if (ts_node_is_null(meta_mapping) || strcmp(ts_node_type(meta_mapping), "block_mapping") != 0) {
return;
}
uint32_t mn = ts_node_child_count(meta_mapping);
for (uint32_t mi = 0; mi < mn; mi++) {
TSNode mpair = ts_node_child(meta_mapping, mi);
if (strcmp(ts_node_type(mpair), "block_mapping_pair") != 0) {
continue;
}
TSNode mkey = ts_node_named_child(mpair, 0);
if (ts_node_is_null(mkey)) {
continue;
}
const char *mkey_text = get_scalar_text(a, mkey, source);
if (!mkey_text || strcmp(mkey_text, "name") != 0) {
continue;
}
TSNode mval = ts_node_named_child(mpair, SKIP_ONE);
if (ts_node_is_null(mval)) {
continue;
}
const char *meta_name = get_scalar_text(a, mval, source);
if (meta_name) {
snprintf(meta_name_buf, meta_sz, "%s", meta_name);
}
}
}
// Unwrap a block_mapping_pair value through optional block_node.
static TSNode unwrap_pair_value(TSNode pair) {
TSNode val_node = ts_node_named_child(pair, SKIP_ONE);
if (ts_node_is_null(val_node)) {
return val_node;
}
if (strcmp(ts_node_type(val_node), "block_node") == 0) {
val_node = ts_node_named_child(val_node, 0);
}
return val_node;
}
// Descend into the first block_mapping of a document and extract
// kind and metadata.name. Returns void; fills kind_buf and meta_name_buf.
static void extract_k8s_scalars(CBMExtractCtx *ctx, TSNode mapping, char *kind_buf, size_t kind_sz,
char *meta_name_buf, size_t meta_sz) {
CBMArena *a = ctx->arena;
kind_buf[0] = '\0';
meta_name_buf[0] = '\0';
uint32_t n = ts_node_child_count(mapping);
for (uint32_t i = 0; i < n; i++) {
TSNode pair = ts_node_child(mapping, i);
if (strcmp(ts_node_type(pair), "block_mapping_pair") != 0) {
continue;
}
TSNode key_node = ts_node_named_child(pair, 0);
if (ts_node_is_null(key_node)) {
continue;
}
const char *key = get_scalar_text(a, key_node, ctx->source);
if (!key) {
continue;
}
TSNode val_node = unwrap_pair_value(pair);
if (ts_node_is_null(val_node)) {
continue;
}
if (strcmp(key, "kind") == 0) {
const char *v = get_scalar_text(a, val_node, ctx->source);
if (v) {
snprintf(kind_buf, kind_sz, "%s", v);
}
} else if (strcmp(key, "metadata") == 0) {
extract_metadata_name(a, val_node, ctx->source, meta_name_buf, meta_sz);
}
}
}
static void extract_k8s_manifest(CBMExtractCtx *ctx) {
CBMArena *a = ctx->arena;
TSNode root = ctx->root;
uint32_t root_n = ts_node_child_count(root);
for (uint32_t si = 0; si < root_n; si++) {
TSNode stream_child = ts_node_child(root, si);
if (strcmp(ts_node_type(stream_child), "document") != 0) {
continue;
}
TSNode mapping = ts_node_named_child(stream_child, 0);
if (ts_node_is_null(mapping)) {
continue;
}
if (strcmp(ts_node_type(mapping), "block_node") == 0) {
mapping = ts_node_named_child(mapping, 0);
}
if (ts_node_is_null(mapping) || strcmp(ts_node_type(mapping), "block_mapping") != 0) {
continue;
}
char kind_buf[K8S_BUF_SIZE] = {0};
char meta_name_buf[K8S_BUF_SIZE] = {0};
extract_k8s_scalars(ctx, mapping, kind_buf, sizeof(kind_buf), meta_name_buf,
sizeof(meta_name_buf));
// Skip malformed manifests (no kind or no metadata.name)
if (kind_buf[0] == '\0' || meta_name_buf[0] == '\0') {
continue;
}
char def_name[RESULT_BUF_SIZE];
snprintf(def_name, sizeof(def_name), "%s/%s", kind_buf, meta_name_buf);
CBMDefinition def = {0};
def.name = cbm_arena_strdup(a, def_name);
def.qualified_name = cbm_arena_sprintf(a, "%s.%s", ctx->module_qn, def_name);
// "Resource" is the canonical def label for a K8s resource kind. It is a
// valid graph label and is what the K8s pipeline pass (pass_k8s.c) filters
// on to upsert Resource nodes and emit INFRA_MAPS edges.
def.label = cbm_arena_strdup(a, "Resource");
def.file_path = ctx->rel_path;
def.start_line = ts_node_start_point(mapping).row + TS_LINE_OFFSET;
def.end_line = ts_node_end_point(mapping).row + TS_LINE_OFFSET;
cbm_defs_push(&ctx->result->defs, a, def);
break; // Only the first document per file
}
}
// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------
void cbm_extract_k8s(CBMExtractCtx *ctx) {
if (ctx->language == CBM_LANG_KUSTOMIZE) {
extract_kustomize(ctx);
} else if (ctx->language == CBM_LANG_K8S) {
extract_k8s_manifest(ctx);
}
}