Merge pull request #2654 from DeusData/fix/image-unknown-registry

fix(docker): never invent a registry host for an image
This commit is contained in:
Martin Vogel
2026-10-16 18:15:01 +02:00
committed by GitHub
6 changed files with 209 additions and 70 deletions
+38 -18
View File
@@ -11,9 +11,10 @@
* ENV); a reference without a value leaves the image unknown, and an unknown
* image is no edge.
*
* Image identity is the normalised repository: docker.io for an image
* without a registry host, library/ for an official one-segment image;
* tag and digest stay on the edge.
* Image identity is the repository as written, tag and digest dropped (they
* stay on the edge). Its registry is the host the reference names -- Docker
* Hub's spellings as docker.io, with library/ for an official one-segment
* image -- else "unknown": no host is invented for a reference naming none.
*
* PHASE 4 P4.1b: what the container runs. The last stage's ENTRYPOINT (else
* CMD) SPAWNS -- from the Dockerfile's Module {api:dockerfile}:
@@ -268,7 +269,6 @@ static const char *dk_expand(dk_t *d, const dk_var_t *vars, const char *text) {
return out;
}
/* The repository identity of image reference `ref` into out. */
/* Only characters an image reference can hold (name, :tag, @digest) -- a
* template's placeholder (`<.Values.image>`, `<tpl>`) or an expression is
* no image. */
@@ -281,7 +281,15 @@ static bool dk_ref_chars(const char *ref) {
return ref[0] != '\0';
}
static bool dk_normalize(const char *ref, char *out, size_t sz) {
/* The registry of a reference that names no host. */
static const char DK_REGISTRY_UNKNOWN[] = "unknown";
/* The repository identity of image reference `ref` into out, the registry
* its host names into reg. A reference without a host is the repository as
* written, its registry unknown: docker.io is only Docker's default -- a
* short-name search list (Podman, CRI-O) or a local-only build tag resolves
* elsewhere or nowhere. */
static bool dk_normalize(const char *ref, char *out, size_t sz, char *reg, size_t reg_sz) {
if (!dk_ref_chars(ref)) {
return false;
}
@@ -300,15 +308,12 @@ static bool dk_normalize(const char *ref, char *out, size_t sz) {
return false;
}
char *first_end = strchr(r, '/');
if (!first_end) {
snprintf(out, sz, "docker.io/library/%s", r);
return true;
}
size_t fl = (size_t)(first_end - r);
bool host = memchr(r, '.', fl) || memchr(r, ':', fl) ||
(fl == strlen("localhost") && strncmp(r, "localhost", fl) == 0);
size_t fl = first_end ? (size_t)(first_end - r) : 0;
bool host = first_end && (memchr(r, '.', fl) || memchr(r, ':', fl) ||
(fl == strlen("localhost") && strncmp(r, "localhost", fl) == 0));
if (!host) {
snprintf(out, sz, "docker.io/%s", r);
snprintf(out, sz, "%s", r);
snprintf(reg, reg_sz, "%s", DK_REGISTRY_UNKNOWN);
return true;
}
const char *rest = first_end + SKIP_ONE;
@@ -318,8 +323,10 @@ static bool dk_normalize(const char *ref, char *out, size_t sz) {
(fl == strlen("registry-1.docker.io") && strncmp(r, "registry-1.docker.io", fl) == 0);
if (hub) {
snprintf(out, sz, "docker.io/%s%s", strchr(rest, '/') ? "" : "library/", rest);
snprintf(reg, reg_sz, "docker.io");
} else {
snprintf(out, sz, "%.*s/%s", (int)fl, r, rest);
snprintf(reg, reg_sz, "%.*s", (int)fl, r);
}
return true;
}
@@ -365,15 +372,20 @@ static int dk_stage_of(const dk_t *d, const char *ref) {
int64_t cbm_pipeline_image_node(cbm_pipeline_ctx_t *ctx, const char *ref) {
char repo[CBM_SZ_512];
if (!ref || dk_ieq(ref, "scratch") || !dk_normalize(ref, repo, sizeof(repo))) {
char reg[CBM_SZ_512];
if (!ref || dk_ieq(ref, "scratch") ||
!dk_normalize(ref, repo, sizeof(repo), reg, sizeof(reg))) {
return 0;
}
char qn[CBM_SZ_1K];
snprintf(qn, sizeof(qn), "__image__%s", repo);
char esc_repo[CBM_SZ_1K];
char esc_reg[CBM_SZ_1K];
cbm_json_escape(esc_repo, (int)sizeof(esc_repo), repo);
cbm_json_escape(esc_reg, (int)sizeof(esc_reg), reg);
char node_props[CBM_SZ_2K];
snprintf(node_props, sizeof(node_props), "{\"repository\":\"%s\"}", esc_repo);
snprintf(node_props, sizeof(node_props), "{\"repository\":\"%s\",\"registry\":\"%s\"}",
esc_repo, esc_reg);
return cbm_gbuf_upsert_node(ctx->gbuf, "Image", repo, qn, "", 0, 0, node_props);
}
@@ -2054,7 +2066,7 @@ static int dk_hub_match(const char *tag, const char *const *comps, int nc) {
}
/* The Image of a component: `<repo>:<pattern>` (`*-frontend`: the
* prefix's version masked) {repository, component, tag_pattern}. */
* prefix's version masked) {repository, registry, component, tag_pattern}. */
static int64_t dk_hub_image(cbm_pipeline_ctx_t *ctx, const char *repo, const char *comp,
char pre_sep, char suf_sep) {
char head[PAIR_LEN + SKIP_ONE] = {0};
@@ -2073,16 +2085,24 @@ static int64_t dk_hub_image(cbm_pipeline_ctx_t *ctx, const char *repo, const cha
snprintf(pattern, sizeof(pattern), "%s%s%s", head, comp, tail);
snprintf(name, sizeof(name), "%s:%s", repo, pattern);
snprintf(qn, sizeof(qn), "__image__%s", name);
char same[CBM_SZ_1K];
char reg[CBM_SZ_512];
if (!dk_normalize(repo, same, sizeof(same), reg, sizeof(reg))) {
snprintf(reg, sizeof(reg), "%s", DK_REGISTRY_UNKNOWN);
}
char esc_r[CBM_SZ_1K];
char esc_g[CBM_SZ_1K];
char esc_c[CBM_SZ_1K];
char esc_p[CBM_SZ_1K];
cbm_json_escape(esc_r, (int)sizeof(esc_r), repo);
cbm_json_escape(esc_g, (int)sizeof(esc_g), reg);
cbm_json_escape(esc_c, (int)sizeof(esc_c), comp);
cbm_json_escape(esc_p, (int)sizeof(esc_p), pattern);
char props[CBM_SZ_4K];
snprintf(props, sizeof(props),
"{\"repository\":\"%s\",\"component\":\"%s\",\"tag_pattern\":\"%s\"}", esc_r, esc_c,
esc_p);
"{\"repository\":\"%s\",\"registry\":\"%s\",\"component\":\"%s\","
"\"tag_pattern\":\"%s\"}",
esc_r, esc_g, esc_c, esc_p);
return cbm_gbuf_upsert_node(ctx->gbuf, "Image", name, qn, "", 0, 0, props);
}
+4 -3
View File
@@ -1332,9 +1332,10 @@ bool cbm_ansible_is_sequence(const char *src);
* (pass_terraform.c). */
bool cbm_repo_rel_join(const char *dir, const char *v, char *out, size_t sz);
/* The Image node of image reference `ref` (its normalised repository:
* docker.io for no registry host, library/ for an official image; tag and
* digest dropped), made when new; 0 for scratch or no image. */
/* The Image node of image reference `ref` (its repository as written, tag
* and digest dropped; Docker Hub's host spellings as docker.io with library/
* for an official image; registry "unknown" when the reference names no
* host -- none is invented), made when new; 0 for scratch or no image. */
int64_t cbm_pipeline_image_node(cbm_pipeline_ctx_t *ctx, const char *ref);
/* A chart repository URL as an identity (pass_k8s.c): `url` trimmed, its
+5 -2
View File
@@ -29,8 +29,11 @@ typedef struct cbm_store_read_scope cbm_store_read_scope_t;
* 1 #1108: File-node QNs keep the file extension.
* 2 Kubernetes objects: one Resource per YAML document, QN
* <file>::<group>/<Kind>/<declared namespace or ->/<name>; a
* Dockerfile.<variant> keeps its suffix in its QNs. */
#define CBM_INDEX_FORMAT_VERSION 2
* Dockerfile.<variant> keeps its suffix in its QNs.
* 3 Image identity invents no registry: a reference without a host is
* __image__<repository as written>, registry "unknown" -- no longer
* __image__docker.io/[library/]<repository>. */
#define CBM_INDEX_FORMAT_VERSION 3
#define CBM_STORE_CANCELLED (-3)
#define CBM_STORE_SCAN_LIMIT (-4)
#define CBM_STORE_CALLBACK_ERR (-5)
+103 -42
View File
@@ -1704,7 +1704,7 @@ TEST(probe_mcp_trace_run) {
ASSERT_NOT_NULL(r);
/* qualified names are grouped by prefix, split at a dot */
ASSERT_NOT_NULL(strstr(r, "Deployment/-/server"));
ASSERT_NOT_NULL(strstr(r, "\"__image__docker\",\"rows\":[[\"io/acme/server\""));
ASSERT_NOT_NULL(strstr(r, "[\"__image__acme/server\"")); /* dotless: the whole QN */
ASSERT_NOT_NULL(strstr(r, ".svc\",\"rows\":[[\"Dockerfile\"")); /* its Module */
ASSERT_NOT_NULL(strstr(r, ".cmd.server\",\"rows\":[[\"main\"")); /* what it starts */
ASSERT_NOT_NULL(strstr(r, "Deployment/-/server#env/server/LISTEN_PORT"));
@@ -1961,8 +1961,8 @@ static char *gpf_images(const GpfFile *files, int nfiles) {
} while (0)
/* A Dockerfile DEPENDS_ON each image it builds on: FROM and COPY --from,
* with ARG defaults expanded, under the image's repository (docker.io and
* library/ filled in, a registry host kept, tag and digest dropped). A stage
* with ARG defaults expanded, under the image's repository (as written: a
* registry host kept, none invented; tag and digest dropped). A stage
* (by name or index), scratch and an ARG without a value are no image. */
TEST(probe_dockerfile_bases_are_images) {
static const GpfFile files[] = {
@@ -1979,12 +1979,72 @@ TEST(probe_dockerfile_bases_are_images) {
"COPY --from=registry.example.com:5000/team/tools:2 /bin/x /x\n"}};
GPF_ASSERT_IMAGES(files, 1,
"@svc/Dockerfile -> docker.io/bitnami/kubectl (copy-from)\n"
"@svc/Dockerfile -> docker.io/library/golang (from)\n"
"@svc/Dockerfile -> gcr.io/distroless/static (from)\n"
"@svc/Dockerfile -> golang (from)\n"
"@svc/Dockerfile -> registry.example.com:5000/team/tools (copy-from)");
PASS();
}
/* Every Image node as "<name> [<registry>]", sorted, one per line. */
static char *gpf_image_nodes(const GpfFile *files, int nfiles) {
GpfProj lp;
cbm_store_t *store = gpf_index_files(&lp, files, nfiles);
char *out = NULL;
if (store) {
sqlite3_stmt *st = NULL;
const char *sql = "SELECT coalesce(group_concat(x, char(10)), '') FROM (SELECT name || ' [' "
"|| coalesce(json_extract(properties,'$.registry'), '-') || ']' AS x "
"FROM nodes WHERE project=?1 AND label='Image' ORDER BY x)";
if (sqlite3_prepare_v2(cbm_store_get_db(store), sql, -1, &st, NULL) == SQLITE_OK) {
sqlite3_bind_text(st, 1, lp.project, -1, SQLITE_STATIC);
if (sqlite3_step(st) == SQLITE_ROW) {
out = strdup((const char *)sqlite3_column_text(st, 0));
}
}
sqlite3_finalize(st);
}
gpf_cleanup(&lp, store);
return out;
}
/* An image reference that names no registry host keeps it unknown: the Image
* is the repository as written, registry "unknown" -- never docker.io, which
* is only Docker's default (a short-name search list or a local-only build
* tag resolves elsewhere or nowhere). A stated host is kept, its Docker Hub
* spellings as docker.io with library/ for an official one-segment name.
* Every source alike: FROM, a K8s container, a compose service, a build tag. */
TEST(probe_image_without_registry_host_stays_unknown) {
static const GpfFile files[] = {
{"svc/Dockerfile", "FROM nginx:1.27\nFROM acme/base:2 AS b\nFROM docker.io/redis:7 AS c\n"
"FROM index.docker.io/acme/tool AS d\n"
"FROM ghcr.io/acme/app@sha256:0123 AS e\n"
"FROM localhost:5000/team/x AS f\n"},
{"app.yaml", "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: web\nspec:\n"
" template:\n spec:\n containers:\n - name: web\n"
" image: busybox:1.36\n - name: side\n"
" image: quay.io/acme/sidecar:1\n"},
{"compose.yaml", "services:\n web:\n image: acme/web:1\n"},
{"build.sh", "#!/bin/sh\ncd \"$(dirname \"$0\")\"\ndocker build -t myapp:dev svc\n"}};
char *got = gpf_image_nodes(files, 4);
static const char want[] = "acme/base [unknown]\n"
"acme/web [unknown]\n"
"busybox [unknown]\n"
"docker.io/acme/tool [docker.io]\n"
"docker.io/library/redis [docker.io]\n"
"ghcr.io/acme/app [ghcr.io]\n"
"localhost:5000/team/x [localhost:5000]\n"
"myapp [unknown]\n"
"nginx [unknown]\n"
"quay.io/acme/sidecar [quay.io]";
if (!got || strcmp(got, want) != 0) {
fprintf(stderr, "image nodes got:\n%s\nwant:\n%s\n", got ? got : "(null)", want);
free(got);
FAIL("Image nodes and registries");
}
free(got);
PASS();
}
/* A build file named for its variant is a Dockerfile of its own (its own
* Module, beside the plain Dockerfile): Dockerfile.debug, Dockerfile-test,
* Containerfile, pipeline.Dockerfile. A template of one (Dockerfile.j2) and a
@@ -2000,10 +2060,10 @@ TEST(probe_dockerfile_variants_are_dockerfiles) {
{"tools/Dockerfile.j2", "FROM {{ base_image }}\n"}};
GPF_ASSERT_IMAGES(files, 7,
"@Containerfile -> quay.io/podman/stable (from)\n"
"@svc/Dockerfile -> docker.io/library/alpine (from)\n"
"@svc/Dockerfile-test -> docker.io/library/ubuntu (from)\n"
"@svc/Dockerfile.debug -> docker.io/library/debian (from)\n"
"@tools/pipeline.Dockerfile -> docker.io/library/node (from)");
"@svc/Dockerfile -> alpine (from)\n"
"@svc/Dockerfile-test -> ubuntu (from)\n"
"@svc/Dockerfile.debug -> debian (from)\n"
"@tools/pipeline.Dockerfile -> node (from)");
PASS();
}
@@ -2083,8 +2143,8 @@ TEST(probe_k8s_container_images_are_images) {
" image: <image-built-from-dockerfile>\n"}};
char *got = gpf_image_users(files, 1);
static const char want[] = "CronJob/nightly -> ghcr.io/acme/job\n"
"Deployment/web -> docker.io/library/busybox\n"
"Deployment/web -> docker.io/library/nginx\n"
"Deployment/web -> busybox\n"
"Deployment/web -> nginx\n"
"Widget/w -> quay.io/acme/widget";
if (!got || strcmp(got, want) != 0) {
fprintf(stderr, "image users got:\n%s\nwant:\n%s\n", got ? got : "(null)", want);
@@ -2145,10 +2205,10 @@ TEST(probe_build_configs_build_images_from_dockerfiles) {
" missing:\n image: acme/missing\n build: ./nope\n"
" unknown:\n image: ${NOPE}/x\n build: ./src/web\n"}};
char *got = gpf_builds(files, 5);
static const char want[] = "docker.io/library/api -> src/api/Dockerfile.prod (skaffold)\n"
"docker.io/library/dev-web -> src/web/Dockerfile (skaffold)\n"
"docker.io/library/web -> src/web/Dockerfile (skaffold)\n"
"ghcr.io/acme/frontend -> src/web/Dockerfile (compose)";
static const char want[] = "api -> src/api/Dockerfile.prod (skaffold)\n"
"dev-web -> src/web/Dockerfile (skaffold)\n"
"ghcr.io/acme/frontend -> src/web/Dockerfile (compose)\n"
"web -> src/web/Dockerfile (skaffold)";
if (!got || strcmp(got, want) != 0) {
fprintf(stderr, "builds got:\n%s\nwant:\n%s\n", got ? got : "(null)", want);
free(got);
@@ -2203,10 +2263,10 @@ TEST(probe_compose_services_are_resources) {
" worker:\n build:\n context: .\n dockerfile: worker/Dockerfile\n"}};
char *got = gpf_compose(files, 3);
static const char want[] =
"Service/cache -> Image:docker.io/library/redis (USAGE image)\n"
"Service/cache -> Image:redis (USAGE image)\n"
"Service/cache -> Service/db (DEPENDS_ON depends_on)\n"
"Service/db -> Image:docker.io/library/postgres (USAGE image)\n"
"Service/web -> Image:docker.io/acme/web (USAGE image)\n"
"Service/db -> Image:postgres (USAGE image)\n"
"Service/web -> Image:acme/web (USAGE image)\n"
"Service/web -> Module:web/Dockerfile (DEPENDS_ON build)\n"
"Service/web -> Service/cache (DEPENDS_ON depends_on)\n"
"Service/web -> Service/db (DEPENDS_ON depends_on)\n"
@@ -2314,17 +2374,17 @@ TEST(probe_image_hub_splits_by_component) {
" image: acme/solo:2\n"}};
enum { NF = sizeof(files) / sizeof(files[0]) };
char *got = gpf_builds(files, NF);
static const char want_builds[] = "docker.io/acme/demo:*-a -> svc/a/Dockerfile (compose)\n"
"docker.io/acme/demo:*-b -> svc/b/Dockerfile (compose)\n"
"docker.io/acme/demo:*-c -> svc/c/Dockerfile (compose)\n"
"docker.io/acme/solo -> solo/Dockerfile (compose)\n"
"docker.io/acme/tool -> tool/Dockerfile (compose)\n"
"docker.io/acme/tool:test -> tool/Dockerfile.test (compose)\n"
"docker.io/acme/x -> w1/Dockerfile (compose)\n"
"docker.io/acme/x -> w2/Dockerfile.alt (compose)\n"
"docker.io/acme/x:*-v -> v/Dockerfile (compose)\n"
"docker.io/acme/y:*-p -> p/Dockerfile (compose)\n"
"docker.io/acme/y:*-q -> q/Dockerfile (compose)";
static const char want_builds[] = "acme/demo:*-a -> svc/a/Dockerfile (compose)\n"
"acme/demo:*-b -> svc/b/Dockerfile (compose)\n"
"acme/demo:*-c -> svc/c/Dockerfile (compose)\n"
"acme/solo -> solo/Dockerfile (compose)\n"
"acme/tool -> tool/Dockerfile (compose)\n"
"acme/tool:test -> tool/Dockerfile.test (compose)\n"
"acme/x -> w1/Dockerfile (compose)\n"
"acme/x -> w2/Dockerfile.alt (compose)\n"
"acme/x:*-v -> v/Dockerfile (compose)\n"
"acme/y:*-p -> p/Dockerfile (compose)\n"
"acme/y:*-q -> q/Dockerfile (compose)";
if (!got || strcmp(got, want_builds) != 0) {
fprintf(stderr, "builds got:\n%s\nwant:\n%s\n", got ? got : "(null)", want_builds);
free(got);
@@ -2332,20 +2392,20 @@ TEST(probe_image_hub_splits_by_component) {
}
free(got);
got = gpf_image_users(files, NF);
static const char want_users[] = "Deployment/b -> docker.io/acme/demo:*-b\n"
"Deployment/s -> docker.io/acme/solo\n"
"Deployment/z -> docker.io/acme/demo\n"
"Service/a -> docker.io/acme/demo:*-a\n"
"Service/b -> docker.io/acme/demo:*-b\n"
"Service/c -> docker.io/acme/demo:*-c\n"
"Service/p -> docker.io/acme/y:*-p\n"
"Service/q -> docker.io/acme/y:*-q\n"
"Service/solo -> docker.io/acme/solo\n"
"Service/tool -> docker.io/acme/tool\n"
"Service/tooltest -> docker.io/acme/tool:test\n"
"Service/v -> docker.io/acme/x:*-v\n"
"Service/w1 -> docker.io/acme/x\n"
"Service/w2 -> docker.io/acme/x";
static const char want_users[] = "Deployment/b -> acme/demo:*-b\n"
"Deployment/s -> acme/solo\n"
"Deployment/z -> acme/demo\n"
"Service/a -> acme/demo:*-a\n"
"Service/b -> acme/demo:*-b\n"
"Service/c -> acme/demo:*-c\n"
"Service/p -> acme/y:*-p\n"
"Service/q -> acme/y:*-q\n"
"Service/solo -> acme/solo\n"
"Service/tool -> acme/tool\n"
"Service/tooltest -> acme/tool:test\n"
"Service/v -> acme/x:*-v\n"
"Service/w1 -> acme/x\n"
"Service/w2 -> acme/x";
if (!got || strcmp(got, want_users) != 0) {
fprintf(stderr, "image users got:\n%s\nwant:\n%s\n", got ? got : "(null)", want_users);
free(got);
@@ -6143,6 +6203,7 @@ SUITE(grammar_probe_f) {
RUN_TEST(probe_mcp_trace_run);
RUN_TEST(probe_mcp_trace_deploy);
RUN_TEST(probe_dockerfile_bases_are_images);
RUN_TEST(probe_image_without_registry_host_stays_unknown);
RUN_TEST(probe_dockerfile_variants_are_dockerfiles);
RUN_TEST(probe_dockerfile_entrypoint_runs);
RUN_TEST(probe_k8s_container_images_are_images);
+54
View File
@@ -16,6 +16,7 @@
#include "test_framework.h"
#include "repro_harness.h" /* RProj, rh_index_files, rh_count_label, rh_cleanup */
#include "foundation/log.h"
#include <sqlite3.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -243,7 +244,60 @@ TEST(index_format_legacy_index_rebuilds_and_repairs) {
PASS();
}
/* ── Test 3: an index from before format 3 drops its invented hosts ─── */
/* Format 2 stored an image without a registry host under an invented
* docker.io identity. Refreshed in place, only touched files would get the
* new identity: both forms of one image in a single graph. */
static const RFile k_image_files[] = {
{"a/Dockerfile", "FROM nginx:1.27\n"},
{"k8s.yaml", "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: web\nspec:\n"
" template:\n spec:\n containers:\n - name: web\n"
" image: nginx:1.27\n"},
};
TEST(index_format_v2_images_rebuild_without_invented_host) {
RProj lp;
cbm_store_t *q0 = rh_index_files(&lp, k_image_files, 2);
ASSERT_NOT_NULL(q0);
cbm_store_close(q0);
/* The format-2 shape: the same node, edges kept, under its old identity. */
cbm_store_t *w = cbm_store_open_path(lp.dbpath);
ASSERT_NOT_NULL(w);
ASSERT_EQ(sqlite3_exec(cbm_store_get_db(w),
"UPDATE nodes SET name='docker.io/library/nginx', "
"qualified_name='__image__docker.io/library/nginx', "
"properties='{\"repository\":\"docker.io/library/nginx\"}' "
"WHERE label='Image' AND name='nginx'",
NULL, NULL, NULL),
SQLITE_OK);
ASSERT_EQ(sqlite3_changes(cbm_store_get_db(w)), 1);
ASSERT_EQ(cbm_store_set_format_version(w, 2), CBM_STORE_OK);
cbm_store_close(w);
char *resp = index_capture(&lp);
ASSERT_NOT_NULL(resp);
free(resp);
if (!strstr(g_log_buf, "format_change_reindex")) {
FAIL("a format-2 index was not routed through the full-reindex path");
}
cbm_store_t *r = cbm_store_open_path(lp.dbpath);
ASSERT_NOT_NULL(r);
cbm_node_t n = {0};
ASSERT_EQ(cbm_store_find_node_by_qn(r, lp.project, "__image__docker.io/library/nginx", &n),
CBM_STORE_NOT_FOUND);
cbm_node_free_fields(&n);
ASSERT_EQ(cbm_store_find_node_by_qn(r, lp.project, "__image__nginx", &n), CBM_STORE_OK);
ASSERT_NOT_NULL(n.properties_json);
ASSERT_NOT_NULL(strstr(n.properties_json, "\"registry\":\"unknown\""));
cbm_node_free_fields(&n);
rh_cleanup(&lp, r);
PASS();
}
SUITE(index_format) {
RUN_TEST(index_format_siblings_distinct_and_searchable);
RUN_TEST(index_format_legacy_index_rebuilds_and_repairs);
RUN_TEST(index_format_v2_images_rebuild_without_invented_host);
}
+5 -5
View File
@@ -3980,10 +3980,10 @@ static char *incremental_compose_edit(bool legacy, cbm_incremental_route_t *rout
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/composeedit.db", tmp);
static const char old_img[] = "SELECT COUNT(*) FROM nodes WHERE label='Image' AND "
"name='docker.io/acme/web-old'";
"name='acme/web-old'";
static const char new_edge[] =
"SELECT COUNT(*) FROM edges e JOIN nodes s ON s.id=e.source_id WHERE e.type='DEPENDS_ON' "
"AND s.name='docker.io/acme/web-new'";
"AND s.name='acme/web-new'";
if (k8s_inc_index(tmp, db_path) != 0 || k8s_inc_count(db_path, old_img) != 1) {
th_rmtree(tmp);
return NULL;
@@ -4131,13 +4131,13 @@ static char *incremental_image_split(bool rename, cbm_incremental_route_t *route
static const char to_repo[] =
"SELECT COUNT(*) FROM edges e JOIN nodes s ON s.id=e.source_id JOIN nodes t ON "
"t.id=e.target_id WHERE e.type='USAGE' AND s.name='Service/runner' AND "
"t.name='docker.io/acme/demo'";
"t.name='acme/demo'";
static const char to_b[] =
"SELECT COUNT(*) FROM edges e JOIN nodes s ON s.id=e.source_id JOIN nodes t ON "
"t.id=e.target_id WHERE e.type='USAGE' AND s.name='Service/runner' AND "
"t.name='docker.io/acme/demo:*-b'";
"t.name='acme/demo:*-b'";
static const char b_img[] =
"SELECT COUNT(*) FROM nodes WHERE label='Image' AND name='docker.io/acme/demo:*-b'";
"SELECT COUNT(*) FROM nodes WHERE label='Image' AND name='acme/demo:*-b'";
if (k8s_inc_index(tmp, db_path) != 0 || k8s_inc_count(db_path, to_b) != 1) {
th_rmtree(tmp);
return NULL;