mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-07 08:43:35 +08:00
fix(helm): resolve file:// chart dependencies and keep their repository
Chart.yaml dependencies became one external Chart node per name and nothing else: a `file://../common` dependency -- the library chart in the next directory -- was an "external" chart, every edge lost where the dependency comes from, and the parser kept at most 128 entries. - The parser reads each entry's name and repository (keys in any order; lists nested in an entry, such as import-values, name no dependency of their own) into a growing array. - A `file://` dependency that names a chart of this repository links DEPENDS_ON to that chart's node, resolved after every Chart.yaml is read so the order of the files does not matter. - Any other dependency -- remote, OCI, or a `file://` path the repository cannot resolve (deckhouse names its build container's /deckhouse/...) -- links to the shared external node as before. - Every DEPENDS_ON edge carries the entry's repository. On real input: kubermatic 15/15 and deckhouse 2/2 dependency edges now carry their repository, prometheus-community/helm-charts 8 of 10 (two entries name none); Chart nodes and edge counts unchanged. None of the 17 corpora has a relative `file://` dependency; the new pipeline probe covers it. Probes: the local-chart link fails with the file:// branch disabled, the nested-list parser test fails with a parser that takes every dash and key in the block. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -1272,14 +1272,51 @@ static const char *helm_match_key(const char *content, const char *eol, const ch
|
||||
return content + kl + 1;
|
||||
}
|
||||
|
||||
/* Append an empty dependency entry; returns it, or NULL when out of memory. */
|
||||
static cbm_helm_dep_t *helm_push_dep(cbm_helm_chart_t *out) {
|
||||
if (out->dep_count == out->dep_cap) {
|
||||
enum { HELM_DEPS_MIN = 8 };
|
||||
int cap = out->dep_cap ? out->dep_cap * 2 : HELM_DEPS_MIN;
|
||||
cbm_helm_dep_t *grown =
|
||||
cbm_realloc(CBM_MEM_CLASS_OTHER, out->deps, (size_t)cap * sizeof(*grown));
|
||||
if (!grown) {
|
||||
return NULL;
|
||||
}
|
||||
out->deps = grown;
|
||||
out->dep_cap = cap;
|
||||
}
|
||||
cbm_helm_dep_t *dep = &out->deps[out->dep_count++];
|
||||
dep->name[0] = '\0';
|
||||
dep->repository[0] = '\0';
|
||||
return dep;
|
||||
}
|
||||
|
||||
void cbm_helm_chart_free(cbm_helm_chart_t *hc) {
|
||||
if (!hc) {
|
||||
return;
|
||||
}
|
||||
cbm_free(CBM_MEM_CLASS_OTHER, hc->deps);
|
||||
hc->deps = NULL;
|
||||
hc->dep_count = 0;
|
||||
hc->dep_cap = 0;
|
||||
}
|
||||
|
||||
int cbm_parse_helm_chart(const char *source, cbm_helm_chart_t *out) {
|
||||
if (!source || !out) {
|
||||
if (!out) {
|
||||
return -1;
|
||||
}
|
||||
out->chart_name[0] = '\0';
|
||||
out->deps = NULL;
|
||||
out->dep_count = 0;
|
||||
out->dep_cap = 0;
|
||||
if (!source) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool in_deps = false;
|
||||
cbm_helm_dep_t *cur = NULL; /* the dependency entry being read */
|
||||
int item_indent = -1; /* indent of the entries' "- " */
|
||||
int key_col = -1; /* column of an entry's keys */
|
||||
const char *p = source;
|
||||
while (*p) {
|
||||
const char *eol = strchr(p, '\n');
|
||||
@@ -1296,26 +1333,35 @@ int cbm_parse_helm_chart(const char *source, cbm_helm_chart_t *out) {
|
||||
if (indent == 0) {
|
||||
/* New top-level key ends any dependencies block. */
|
||||
in_deps = (helm_match_key(content, eol, "dependencies") != NULL);
|
||||
cur = NULL;
|
||||
item_indent = -1;
|
||||
key_col = -1;
|
||||
const char *nv = helm_match_key(content, eol, "name");
|
||||
if (!in_deps && nv && out->chart_name[0] == '\0') {
|
||||
helm_copy_scalar(nv, eol, out->chart_name, CBM_HELM_NAME_MAX);
|
||||
}
|
||||
} else if (in_deps) {
|
||||
/* List item: optional leading "- ", then look for name:. */
|
||||
/* A list item ("- ") at the entries' indent opens an entry; its
|
||||
* keys share the dash line or follow at the same key column.
|
||||
* Deeper lines (import-values, tags) belong to the entry. */
|
||||
const char *q = content;
|
||||
if (*q == '-') {
|
||||
bool opens = *q == '-' && (item_indent < 0 || indent == item_indent);
|
||||
if (opens) {
|
||||
item_indent = indent;
|
||||
q++;
|
||||
while (q < eol && *q == ' ') {
|
||||
q++;
|
||||
}
|
||||
key_col = indent + (int)(q - content);
|
||||
cur = helm_push_dep(out);
|
||||
}
|
||||
const char *nv = helm_match_key(q, eol, "name");
|
||||
if (nv && out->dep_count < CBM_HELM_MAX_DEPS) {
|
||||
char buf[CBM_HELM_NAME_MAX];
|
||||
helm_copy_scalar(nv, eol, buf, CBM_HELM_NAME_MAX);
|
||||
if (buf[0]) {
|
||||
memcpy(out->deps[out->dep_count], buf, CBM_HELM_NAME_MAX);
|
||||
out->dep_count++;
|
||||
if (cur && (opens || indent == key_col)) {
|
||||
const char *nv = helm_match_key(q, eol, "name");
|
||||
const char *rv = helm_match_key(q, eol, "repository");
|
||||
if (nv) {
|
||||
helm_copy_scalar(nv, eol, cur->name, CBM_HELM_NAME_MAX);
|
||||
} else if (rv) {
|
||||
helm_copy_scalar(rv, eol, cur->repository, CBM_HELM_REPO_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1325,5 +1371,13 @@ int cbm_parse_helm_chart(const char *source, cbm_helm_chart_t *out) {
|
||||
}
|
||||
p = eol + 1;
|
||||
}
|
||||
/* Keep only named entries (a dependency without a name names nothing). */
|
||||
int kept = 0;
|
||||
for (int i = 0; i < out->dep_count; i++) {
|
||||
if (out->deps[i].name[0]) {
|
||||
out->deps[kept++] = out->deps[i];
|
||||
}
|
||||
}
|
||||
out->dep_count = kept;
|
||||
return (out->chart_name[0] || out->dep_count > 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
+128
-14
@@ -23,6 +23,7 @@
|
||||
#include "helpers.h" /* cbm_kind_in_set_free_cache */
|
||||
#include "foundation/slab_alloc.h" /* cbm_slab_destroy_thread */
|
||||
#include "foundation/platform.h" /* cbm_default_worker_count */
|
||||
#include "foundation/str_util.h" /* cbm_json_escape */
|
||||
#include "pipeline/worker_pool.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
@@ -30,6 +31,8 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define K8S_SLEN(s) (sizeof(s) - 1)
|
||||
|
||||
/* ── Internal helpers ────────────────────────────────────────────── */
|
||||
|
||||
/* Read entire file into heap-allocated buffer. Returns NULL on error.
|
||||
@@ -594,17 +597,125 @@ 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) {
|
||||
/* A chart's `file://` dependency: the chart in that directory of this
|
||||
* repository. Linked once every Chart.yaml has been read, so a dependency
|
||||
* indexed after its dependent still resolves. One that names no chart of this
|
||||
* repository (an absolute build-container path, a directory without a chart)
|
||||
* stays a declared dependency on the external node, like a remote one. */
|
||||
typedef struct {
|
||||
int64_t chart_id;
|
||||
char name[CBM_HELM_NAME_MAX]; /* dependency name, for the external fallback */
|
||||
char *chart_rel; /* the depending Chart.yaml */
|
||||
char *target_qn; /* QN of the depended-on Chart node; NULL = unresolvable */
|
||||
char *props; /* edge properties */
|
||||
} k8s_local_dep_t;
|
||||
|
||||
typedef struct {
|
||||
k8s_local_dep_t *items;
|
||||
int count;
|
||||
int cap;
|
||||
} k8s_local_deps_t;
|
||||
|
||||
static bool k8s_is_file_repository(const char *repository) {
|
||||
return strncmp(repository, "file://", K8S_SLEN("file://")) == 0;
|
||||
}
|
||||
|
||||
static char *k8s_dep_props(const char *repository) {
|
||||
char escaped[CBM_HELM_REPO_MAX * 2];
|
||||
cbm_json_escape(escaped, (int)sizeof(escaped), repository);
|
||||
size_t n = strlen(escaped) + sizeof("{\"repository\":\"\"}");
|
||||
char *props = cbm_alloc(CBM_MEM_CLASS_OTHER, n);
|
||||
if (props) {
|
||||
snprintf(props, n, "{\"repository\":\"%s\"}", escaped);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/* DEPENDS_ON from `chart_id` to the shared external Chart node of `name`
|
||||
* (one per name and project, so charts depending on the same chart meet). */
|
||||
static bool k8s_external_dep(cbm_pipeline_ctx_t *ctx, int64_t chart_id, const char *name,
|
||||
const char *chart_rel, const char *props) {
|
||||
char dep_qn[CBM_SZ_512];
|
||||
snprintf(dep_qn, sizeof(dep_qn), "%s.__helm_dep__.%s", ctx->project_name, name);
|
||||
int64_t dep_id = cbm_gbuf_upsert_node(ctx->gbuf, "Chart", name, dep_qn, chart_rel, SKIP_ONE, 0,
|
||||
"{\"source\":\"helm\",\"external\":true}");
|
||||
if (dep_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
cbm_gbuf_insert_edge(ctx->gbuf, chart_id, dep_id, "DEPENDS_ON", props ? props : "{}");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void k8s_local_deps_push(k8s_local_deps_t *deps, int64_t chart_id, const char *chart_rel,
|
||||
const char *project, const cbm_helm_dep_t *dep) {
|
||||
const char *dir = dep->repository + K8S_SLEN("file://");
|
||||
char *target_qn = NULL;
|
||||
if (dir[0] && dir[0] != '/') { /* an absolute path names the build host, not this repo */
|
||||
char rel[CBM_HELM_REPO_MAX + K8S_SLEN("./") + K8S_SLEN("/Chart.yaml")];
|
||||
snprintf(rel, sizeof(rel), "%s%s/Chart.yaml", strncmp(dir, "../", 3) == 0 ? "" : "./", dir);
|
||||
char *path = cbm_pipeline_resolve_relative_import(chart_rel, rel);
|
||||
target_qn = path ? cbm_infra_qn(project, path, "helm-chart", NULL) : NULL;
|
||||
free(path);
|
||||
}
|
||||
if (deps->count == deps->cap) {
|
||||
int cap = deps->cap ? deps->cap * 2 : K8S_GROW_MIN;
|
||||
k8s_local_dep_t *grown =
|
||||
cbm_realloc(CBM_MEM_CLASS_OTHER, deps->items, (size_t)cap * sizeof(*grown));
|
||||
if (!grown) {
|
||||
free(target_qn);
|
||||
return;
|
||||
}
|
||||
deps->items = grown;
|
||||
deps->cap = cap;
|
||||
}
|
||||
k8s_local_dep_t *d = &deps->items[deps->count++];
|
||||
*d = (k8s_local_dep_t){.chart_id = chart_id, .target_qn = target_qn};
|
||||
snprintf(d->name, sizeof(d->name), "%s", dep->name);
|
||||
d->chart_rel = strdup(chart_rel);
|
||||
d->props = k8s_dep_props(dep->repository);
|
||||
}
|
||||
|
||||
static void k8s_local_deps_free(k8s_local_deps_t *deps) {
|
||||
for (int i = 0; i < deps->count; i++) {
|
||||
free(deps->items[i].target_qn);
|
||||
free(deps->items[i].chart_rel);
|
||||
cbm_free(CBM_MEM_CLASS_OTHER, deps->items[i].props);
|
||||
}
|
||||
cbm_free(CBM_MEM_CLASS_OTHER, deps->items);
|
||||
*deps = (k8s_local_deps_t){0};
|
||||
}
|
||||
|
||||
static void k8s_link_local_deps(cbm_pipeline_ctx_t *ctx, k8s_local_deps_t *deps) {
|
||||
for (int i = 0; i < deps->count; i++) {
|
||||
const k8s_local_dep_t *d = &deps->items[i];
|
||||
const cbm_gbuf_node_t *target =
|
||||
d->target_qn ? cbm_gbuf_find_by_qn(ctx->gbuf, d->target_qn) : NULL;
|
||||
if (target && target->label && strcmp(target->label, "Chart") == 0 &&
|
||||
target->id != d->chart_id) {
|
||||
cbm_gbuf_insert_edge(ctx->gbuf, d->chart_id, target->id, "DEPENDS_ON", d->props);
|
||||
} else if (d->chart_rel) {
|
||||
(void)k8s_external_dep(ctx, d->chart_id, d->name, d->chart_rel, d->props);
|
||||
}
|
||||
}
|
||||
k8s_local_deps_free(deps);
|
||||
}
|
||||
|
||||
/* Emit a Chart node for a Chart.yaml and a DEPENDS_ON edge per declared
|
||||
* dependency (#338): to the chart in this repository for a resolvable
|
||||
* `file://` dependency (once all charts are read), otherwise to a shared
|
||||
* external Chart node per dependency name, the repository on the edge. */
|
||||
static void handle_helm_chart(cbm_pipeline_ctx_t *ctx, const char *rel_path, const char *source,
|
||||
k8s_local_deps_t *local_deps) {
|
||||
cbm_helm_chart_t hc;
|
||||
if (cbm_parse_helm_chart(source, &hc) != 0) {
|
||||
cbm_helm_chart_free(&hc);
|
||||
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) {
|
||||
cbm_helm_chart_free(&hc);
|
||||
return;
|
||||
}
|
||||
int64_t chart_id = cbm_gbuf_upsert_node(ctx->gbuf, "Chart", cname, chart_qn, rel_path, SKIP_ONE,
|
||||
@@ -620,18 +731,16 @@ static void handle_helm_chart(cbm_pipeline_ctx_t *ctx, const char *rel_path, con
|
||||
|
||||
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++;
|
||||
const cbm_helm_dep_t *dep = &hc.deps[i];
|
||||
if (k8s_is_file_repository(dep->repository)) {
|
||||
k8s_local_deps_push(local_deps, chart_id, rel_path, ctx->project_name, dep);
|
||||
continue;
|
||||
}
|
||||
char *props = dep->repository[0] ? k8s_dep_props(dep->repository) : NULL;
|
||||
dep_edges += k8s_external_dep(ctx, chart_id, dep->name, rel_path, props);
|
||||
cbm_free(CBM_MEM_CLASS_OTHER, props);
|
||||
}
|
||||
cbm_helm_chart_free(&hc);
|
||||
cbm_log_info("pass.k8s.helm", "file", rel_path, "deps", itoa_k8s(dep_edges));
|
||||
}
|
||||
|
||||
@@ -872,6 +981,7 @@ int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
|
||||
|
||||
/* Collect per-manifest selector/label records for cross-manifest matching. */
|
||||
k8s_record_array_t recs = {0};
|
||||
k8s_local_deps_t local_deps = {0};
|
||||
|
||||
k8s_prep_t *prep = cbm_calloc(CBM_MEM_CLASS_OTHER, K8S_CHUNK * sizeof(*prep));
|
||||
if (!prep) {
|
||||
@@ -884,6 +994,7 @@ int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
|
||||
if (cbm_pipeline_check_cancel(ctx)) {
|
||||
cbm_free(CBM_MEM_CLASS_OTHER, prep);
|
||||
k8s_records_free(&recs);
|
||||
k8s_local_deps_free(&local_deps);
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
memset(prep, 0, (size_t)K8S_CHUNK * sizeof(*prep));
|
||||
@@ -908,7 +1019,7 @@ int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
|
||||
is_requirements_file(base) ? "pypi" : "gomod");
|
||||
break;
|
||||
case K8S_HELM:
|
||||
handle_helm_chart(ctx, rel, p->source);
|
||||
handle_helm_chart(ctx, rel, p->source, &local_deps);
|
||||
helm_count++;
|
||||
break;
|
||||
case K8S_MANIFEST:
|
||||
@@ -928,6 +1039,7 @@ int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
|
||||
if (cancelled) {
|
||||
cbm_free(CBM_MEM_CLASS_OTHER, prep);
|
||||
k8s_records_free(&recs);
|
||||
k8s_local_deps_free(&local_deps);
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
@@ -936,6 +1048,8 @@ int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
|
||||
/* Connect Services to the workloads their selectors target (INFRA_MAPS). */
|
||||
k8s_link_selectors(ctx, &recs);
|
||||
k8s_records_free(&recs);
|
||||
/* Link `file://` chart dependencies now that every Chart.yaml is read. */
|
||||
k8s_link_local_deps(ctx, &local_deps);
|
||||
|
||||
cbm_log_info("pass.done", "pass", "k8s", "manifests", itoa_k8s(manifest_count));
|
||||
(void)helm_count;
|
||||
|
||||
@@ -995,17 +995,24 @@ int cbm_parse_shell_source(const char *source, cbm_shell_result_t *out);
|
||||
/* Parse a Terraform file from source text. Returns 0 if parsed, -1 if empty. */
|
||||
int cbm_parse_terraform_source(const char *source, cbm_terraform_result_t *out);
|
||||
|
||||
/* Helm Chart.yaml parse result: chart name + dependency chart names (#338). */
|
||||
enum { CBM_HELM_MAX_DEPS = 128, CBM_HELM_NAME_MAX = 128 };
|
||||
/* Helm Chart.yaml parse result: chart name + its dependencies (#338). */
|
||||
enum { CBM_HELM_NAME_MAX = 128, CBM_HELM_REPO_MAX = 512 };
|
||||
typedef struct {
|
||||
char name[CBM_HELM_NAME_MAX];
|
||||
char repository[CBM_HELM_REPO_MAX]; /* "" when the entry names none */
|
||||
} cbm_helm_dep_t;
|
||||
typedef struct {
|
||||
char chart_name[CBM_HELM_NAME_MAX];
|
||||
char deps[CBM_HELM_MAX_DEPS][CBM_HELM_NAME_MAX];
|
||||
cbm_helm_dep_t *deps; /* heap-owned; release with cbm_helm_chart_free */
|
||||
int dep_count;
|
||||
int dep_cap;
|
||||
} cbm_helm_chart_t;
|
||||
|
||||
/* Parse a Helm Chart.yaml: top-level `name:` and `dependencies:` list names.
|
||||
* Returns 0 if parsed (name or deps found), -1 otherwise. */
|
||||
/* Parse a Helm Chart.yaml: top-level `name:` and every `dependencies:` entry's
|
||||
* name and repository. Returns 0 if parsed (name or deps found), -1 otherwise;
|
||||
* either way the caller releases `out` with cbm_helm_chart_free. */
|
||||
int cbm_parse_helm_chart(const char *source, cbm_helm_chart_t *out);
|
||||
void cbm_helm_chart_free(cbm_helm_chart_t *hc);
|
||||
|
||||
/* Build an infrastructure QN. Caller must free the returned string. */
|
||||
char *cbm_infra_qn(const char *project_name, const char *rel_path, const char *infra_type,
|
||||
|
||||
@@ -138,6 +138,7 @@ typedef struct {
|
||||
int modules; /* Module label */
|
||||
int classes; /* Class label */
|
||||
int resources; /* Resource label (k8s) */
|
||||
int charts; /* Chart label (helm) */
|
||||
int imports; /* IMPORTS edges */
|
||||
int depends_on; /* DEPENDS_ON edges */
|
||||
int infra_maps; /* INFRA_MAPS edges */
|
||||
@@ -154,6 +155,7 @@ static GpfMetrics gpf_metrics_files(const GpfFile *files, int nfiles) {
|
||||
m.modules = gpf_count_label(store, lp.project, "Module");
|
||||
m.classes = gpf_count_label(store, lp.project, "Class");
|
||||
m.resources = gpf_count_label(store, lp.project, "Resource");
|
||||
m.charts = gpf_count_label(store, lp.project, "Chart");
|
||||
m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS");
|
||||
m.depends_on = cbm_store_count_edges_by_type(store, lp.project, "DEPENDS_ON");
|
||||
m.infra_maps = cbm_store_count_edges_by_type(store, lp.project, "INFRA_MAPS");
|
||||
@@ -741,6 +743,52 @@ TEST(probe_kustomize_extensionless_file) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A `file://` dependency is the chart in that directory of this repository,
|
||||
* not a published one: `app` depends on the local `common`, and no external
|
||||
* Chart node stands in for it. `common` is read after `app`. */
|
||||
TEST(probe_helm_file_dependency_links_local_chart) {
|
||||
static const GpfFile files[] = {
|
||||
{"charts/app/Chart.yaml",
|
||||
"apiVersion: v2\n"
|
||||
"name: app\n"
|
||||
"version: 1.0.0\n"
|
||||
"dependencies:\n"
|
||||
" - name: common\n"
|
||||
" repository: file://../common\n"
|
||||
" version: 1.x\n"},
|
||||
{"charts/common/Chart.yaml",
|
||||
"apiVersion: v2\n"
|
||||
"name: common\n"
|
||||
"type: library\n"
|
||||
"version: 1.2.0\n"}
|
||||
};
|
||||
GpfMetrics m = gpf_metrics_files(files, 2);
|
||||
ASSERT_TRUE(m.ok);
|
||||
ASSERT_EQ(m.charts, 2);
|
||||
ASSERT_EQ(m.depends_on, 1);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A `file://` path this repository cannot resolve (deckhouse names its build
|
||||
* container's `/deckhouse/...`) still declares a dependency: it stays the
|
||||
* external Chart node, the repository on its edge, rather than vanishing. */
|
||||
TEST(probe_helm_unresolved_file_dependency_stays_external) {
|
||||
static const GpfFile files[] = {
|
||||
{"modules/policy/Chart.yaml",
|
||||
"apiVersion: v2\n"
|
||||
"name: policy\n"
|
||||
"version: 1.0.0\n"
|
||||
"dependencies:\n"
|
||||
" - name: constraint-templates\n"
|
||||
" repository: \"file:///deckhouse/modules/policy/charts/constraint_templates\"\n"}
|
||||
};
|
||||
GpfMetrics m = gpf_metrics_files(files, 1);
|
||||
ASSERT_TRUE(m.ok);
|
||||
ASSERT_EQ(m.charts, 2);
|
||||
ASSERT_EQ(m.depends_on, 1);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A remote base is outside the repository. The import resolver's symbol-name
|
||||
* guess walked its URL segments and linked kubermatic's gcp addon to the
|
||||
* `deploy` function of hack/ci/deploy.sh. A kustomize entry is a path,
|
||||
@@ -1951,6 +1999,8 @@ SUITE(grammar_probe_f) {
|
||||
RUN_TEST(probe_k8s_selector_after_leading_separator);
|
||||
RUN_TEST(probe_kustomize_extensionless_file);
|
||||
RUN_TEST(probe_kustomize_remote_base_names_no_symbol);
|
||||
RUN_TEST(probe_helm_file_dependency_links_local_chart);
|
||||
RUN_TEST(probe_helm_unresolved_file_dependency_stays_external);
|
||||
RUN_TEST(probe_kustomize_nested_sibling_entries);
|
||||
RUN_TEST(probe_kustomize_single_module);
|
||||
|
||||
|
||||
+51
-2
@@ -14165,8 +14165,11 @@ TEST(helm_parse_chart_dependencies_issue338) {
|
||||
ASSERT_EQ(cbm_parse_helm_chart(src, &hc), 0);
|
||||
ASSERT_STR_EQ(hc.chart_name, "mychart");
|
||||
ASSERT_EQ(hc.dep_count, 2);
|
||||
ASSERT_STR_EQ(hc.deps[0], "postgresql");
|
||||
ASSERT_STR_EQ(hc.deps[1], "redis");
|
||||
ASSERT_STR_EQ(hc.deps[0].name, "postgresql");
|
||||
ASSERT_STR_EQ(hc.deps[0].repository, "https://charts.bitnami.com/bitnami");
|
||||
ASSERT_STR_EQ(hc.deps[1].name, "redis");
|
||||
ASSERT_STR_EQ(hc.deps[1].repository, "https://charts.bitnami.com/bitnami");
|
||||
cbm_helm_chart_free(&hc);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -14175,6 +14178,50 @@ TEST(helm_parse_chart_no_deps_issue338) {
|
||||
ASSERT_EQ(cbm_parse_helm_chart("name: solo\nversion: 0.1.0\n", &hc), 0);
|
||||
ASSERT_STR_EQ(hc.chart_name, "solo");
|
||||
ASSERT_EQ(hc.dep_count, 0);
|
||||
cbm_helm_chart_free(&hc);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* An entry's keys come in any order; lists nested in an entry
|
||||
* (import-values, tags) belong to it and name no dependency of their own. */
|
||||
TEST(helm_parse_chart_dependency_entries) {
|
||||
const char *src = "name: app\n"
|
||||
"dependencies:\n"
|
||||
" - repository: file://../common\n"
|
||||
" name: common\n"
|
||||
" import-values:\n"
|
||||
" - child: default.data\n"
|
||||
" parent: imported\n"
|
||||
" - name: not-a-dependency\n"
|
||||
" - name: redis\n"
|
||||
" tags:\n"
|
||||
" - cache\n";
|
||||
cbm_helm_chart_t hc;
|
||||
ASSERT_EQ(cbm_parse_helm_chart(src, &hc), 0);
|
||||
ASSERT_EQ(hc.dep_count, 2);
|
||||
ASSERT_STR_EQ(hc.deps[0].name, "common");
|
||||
ASSERT_STR_EQ(hc.deps[0].repository, "file://../common");
|
||||
ASSERT_STR_EQ(hc.deps[1].name, "redis");
|
||||
ASSERT_STR_EQ(hc.deps[1].repository, "");
|
||||
cbm_helm_chart_free(&hc);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* No dependency count decides what is read: the parser kept 128. */
|
||||
TEST(helm_parse_chart_many_dependencies) {
|
||||
enum { DEPS = 130 };
|
||||
char *src = malloc(DEPS * 32 + 64);
|
||||
ASSERT_NOT_NULL(src);
|
||||
int n = snprintf(src, 64, "name: umbrella\ndependencies:\n");
|
||||
for (int i = 0; i < DEPS; i++) {
|
||||
n += snprintf(src + n, 32, " - name: dep-%03d\n", i);
|
||||
}
|
||||
cbm_helm_chart_t hc;
|
||||
ASSERT_EQ(cbm_parse_helm_chart(src, &hc), 0);
|
||||
free(src);
|
||||
ASSERT_EQ(hc.dep_count, DEPS);
|
||||
ASSERT_STR_EQ(hc.deps[DEPS - 1].name, "dep-129");
|
||||
cbm_helm_chart_free(&hc);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -19702,6 +19749,8 @@ SUITE(pipeline) {
|
||||
RUN_TEST(infra_parse_terraform_empty);
|
||||
RUN_TEST(helm_parse_chart_dependencies_issue338);
|
||||
RUN_TEST(helm_parse_chart_no_deps_issue338);
|
||||
RUN_TEST(helm_parse_chart_dependency_entries);
|
||||
RUN_TEST(helm_parse_chart_many_dependencies);
|
||||
/* Infrascan: QN helper */
|
||||
RUN_TEST(infra_qn_helper);
|
||||
/* Infrascan: pipeline integration */
|
||||
|
||||
Reference in New Issue
Block a user