feat: add v03 secret-plane smoke sync
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
version: 1
|
||||||
|
kind: hwlab-v03-secret-plane
|
||||||
|
metadata:
|
||||||
|
id: hwlab-v03-secret-plane
|
||||||
|
owner: HWLAB
|
||||||
|
spec: pikasTech/HWLAB#2234
|
||||||
|
boundary: D601/v0.3 only
|
||||||
|
secretPlane:
|
||||||
|
enabled: true
|
||||||
|
issue: pikasTech/HWLAB#2234
|
||||||
|
valuesPrinted: false
|
||||||
|
store:
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
name: hwlab-secret-plane-vault-cluster
|
||||||
|
refreshInterval: 1m
|
||||||
|
secrets:
|
||||||
|
- id: hwlab-secret-plane-smoke
|
||||||
|
externalSecretName: hwlab-secret-plane-smoke
|
||||||
|
targetSecretName: hwlab-secret-plane-smoke
|
||||||
|
purpose: Low-risk D601/v0.3 smoke secret for platform-infra secret-plane integration.
|
||||||
|
data:
|
||||||
|
- targetKey: password
|
||||||
|
remoteRef: hwlab-secret-plane/poc
|
||||||
|
property: password
|
||||||
|
consumers:
|
||||||
|
- serviceId: hwlab-cloud-api
|
||||||
|
kind: Deployment
|
||||||
|
injectAs: env
|
||||||
|
envName: HWLAB_SECRET_PLANE_SMOKE
|
||||||
@@ -328,6 +328,7 @@ lanes:
|
|||||||
accessControl:
|
accessControl:
|
||||||
usersRef: config/hwlab-access-control/users.d601-v03.yaml#spec.users
|
usersRef: config/hwlab-access-control/users.d601-v03.yaml#spec.users
|
||||||
navProfilesRef: config/hwlab-access-control/nav-profiles.yaml#spec.profiles
|
navProfilesRef: config/hwlab-access-control/nav-profiles.yaml#spec.profiles
|
||||||
|
secretPlaneRef: config/hwlab-v03/secrets.yaml#secretPlane
|
||||||
workbench:
|
workbench:
|
||||||
traceTimeline:
|
traceTimeline:
|
||||||
autoExpandRunning: false
|
autoExpandRunning: false
|
||||||
@@ -766,6 +767,7 @@ lanes:
|
|||||||
HWLAB_WORKBENCH_EMPTY_SESSION_GC_BATCH_SIZE: "100"
|
HWLAB_WORKBENCH_EMPTY_SESSION_GC_BATCH_SIZE: "100"
|
||||||
HWLAB_WORKBENCH_EMPTY_SESSION_GC_JITTER_MS: "15000"
|
HWLAB_WORKBENCH_EMPTY_SESSION_GC_JITTER_MS: "15000"
|
||||||
HWLAB_WORKBENCH_EMPTY_SESSION_GC_FAILURE_BACKOFF_MS: "120000"
|
HWLAB_WORKBENCH_EMPTY_SESSION_GC_FAILURE_BACKOFF_MS: "120000"
|
||||||
|
HWLAB_SECRET_PLANE_SMOKE: secretRef:hwlab-secret-plane-smoke/password
|
||||||
configMapMounts:
|
configMapMounts:
|
||||||
- name: hwpod-preinstalled-specs
|
- name: hwpod-preinstalled-specs
|
||||||
configMapName: hwlab-v03-hwpod-preinstalled-specs
|
configMapName: hwlab-v03-hwpod-preinstalled-specs
|
||||||
|
|||||||
@@ -5521,10 +5521,88 @@ function runtimeConfigMapsManifest({ configMaps, namespace, labels, annotations
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev", externalPostgres = false, workbenchRedis = false, runtimeConfigMaps = false } = {}) {
|
async function runtimeSecretPlaneConfigForProfile(deploy, profile) {
|
||||||
|
if (!isRuntimeLane(profile)) return null;
|
||||||
|
const laneConfig = deploy?.lanes?.[profile];
|
||||||
|
const secretPlane = laneConfig?.secretPlaneRef
|
||||||
|
? await readConfigRef(laneConfig.secretPlaneRef, `${profile}.secretPlaneRef`)
|
||||||
|
: laneConfig?.secretPlane;
|
||||||
|
if (!secretPlane || typeof secretPlane !== "object" || Array.isArray(secretPlane)) return null;
|
||||||
|
if (secretPlane.enabled !== true) return null;
|
||||||
|
return cloneJson(secretPlane);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtimeSecretPlaneManifest({ config, namespace, labels, annotations }) {
|
||||||
|
const store = config?.store;
|
||||||
|
assert.ok(store && typeof store === "object" && !Array.isArray(store), "secretPlane.store must be an object");
|
||||||
|
assert.equal(store.kind, "ClusterSecretStore", "secretPlane.store.kind must be ClusterSecretStore for D601/v0.3");
|
||||||
|
assert.ok(typeof store.name === "string" && store.name.trim(), "secretPlane.store.name must be set");
|
||||||
|
const secrets = Array.isArray(config.secrets) ? config.secrets : [];
|
||||||
|
assert.ok(secrets.length > 0, "secretPlane.secrets must not be empty");
|
||||||
|
return {
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "List",
|
||||||
|
items: secrets.map((secret, index) => runtimeSecretPlaneExternalSecret({ config, secret, index, namespace, labels, annotations, store }))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtimeSecretPlaneExternalSecret({ config, secret, index, namespace, labels, annotations, store }) {
|
||||||
|
const externalSecretName = requiredSecretPlaneString(secret?.externalSecretName, `secretPlane.secrets[${index}].externalSecretName`);
|
||||||
|
const targetSecretName = requiredSecretPlaneString(secret?.targetSecretName, `secretPlane.secrets[${index}].targetSecretName`);
|
||||||
|
const data = Array.isArray(secret?.data) ? secret.data : [];
|
||||||
|
assert.ok(data.length > 0, `secretPlane.secrets[${index}].data must not be empty`);
|
||||||
|
const sourceRefs = data.map((item) => requiredSecretPlaneString(item?.remoteRef, `secretPlane.secrets[${index}].data.remoteRef`));
|
||||||
|
return {
|
||||||
|
apiVersion: "external-secrets.io/v1",
|
||||||
|
kind: "ExternalSecret",
|
||||||
|
metadata: {
|
||||||
|
name: externalSecretName,
|
||||||
|
namespace,
|
||||||
|
labels: {
|
||||||
|
...labels,
|
||||||
|
"app.kubernetes.io/name": externalSecretName,
|
||||||
|
"app.kubernetes.io/component": "external-secret",
|
||||||
|
"hwlab.pikastech.local/secret-plane": String(config.issue ?? "pikasTech/HWLAB#2234")
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
...annotations,
|
||||||
|
"hwlab.pikastech.local/source-ref": sourceRefs.join(","),
|
||||||
|
"hwlab.pikastech.local/values-printed": "false"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
refreshInterval: requiredSecretPlaneString(config.refreshInterval, "secretPlane.refreshInterval"),
|
||||||
|
secretStoreRef: {
|
||||||
|
name: requiredSecretPlaneString(store.name, "secretPlane.store.name"),
|
||||||
|
kind: store.kind
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
name: targetSecretName,
|
||||||
|
creationPolicy: "Owner"
|
||||||
|
},
|
||||||
|
data: data.map((item, itemIndex) => ({
|
||||||
|
secretKey: requiredSecretPlaneString(item?.targetKey, `secretPlane.secrets[${index}].data[${itemIndex}].targetKey`),
|
||||||
|
remoteRef: {
|
||||||
|
key: requiredSecretPlaneString(item?.remoteRef, `secretPlane.secrets[${index}].data[${itemIndex}].remoteRef`),
|
||||||
|
property: requiredSecretPlaneString(item?.property, `secretPlane.secrets[${index}].data[${itemIndex}].property`)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredSecretPlaneString(value, label) {
|
||||||
|
assert.equal(typeof value, "string", `${label} must be a string`);
|
||||||
|
const trimmed = value.trim();
|
||||||
|
assert.ok(trimmed, `${label} must not be empty`);
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev", externalPostgres = false, workbenchRedis = false, runtimeConfigMaps = false, externalSecrets = false } = {}) {
|
||||||
const resources = ["namespace.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"];
|
const resources = ["namespace.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"];
|
||||||
if (!isRuntimeLane(profile)) resources.splice(1, 0, "code-agent-codex-config.yaml");
|
if (!isRuntimeLane(profile)) resources.splice(1, 0, "code-agent-codex-config.yaml");
|
||||||
if (runtimeConfigMaps) resources.splice(3, 0, "configmaps.yaml");
|
if (runtimeConfigMaps) resources.splice(3, 0, "configmaps.yaml");
|
||||||
|
if (externalSecrets) resources.splice(resources.indexOf("workloads.yaml"), 0, "external-secrets.yaml");
|
||||||
if (isRuntimeLane(profile)) {
|
if (isRuntimeLane(profile)) {
|
||||||
if (externalPostgres) resources.push("external-postgres.yaml");
|
if (externalPostgres) resources.push("external-postgres.yaml");
|
||||||
else resources.push("postgres.yaml");
|
else resources.push("postgres.yaml");
|
||||||
@@ -5666,6 +5744,7 @@ async function plannedFiles(args) {
|
|||||||
const externalPostgres = externalPostgresConfigForLane(deploy, profile);
|
const externalPostgres = externalPostgresConfigForLane(deploy, profile);
|
||||||
const workbenchRedis = workbenchRuntimeRedisConfigForProfile(deploy, profile);
|
const workbenchRedis = workbenchRuntimeRedisConfigForProfile(deploy, profile);
|
||||||
const runtimeConfigMaps = runtimeConfigMapsForProfile(deploy, profile);
|
const runtimeConfigMaps = runtimeConfigMapsForProfile(deploy, profile);
|
||||||
|
const runtimeSecretPlane = await runtimeSecretPlaneConfigForProfile(deploy, profile);
|
||||||
const profileLabels = { ...baseLabels, "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile) };
|
const profileLabels = { ...baseLabels, "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile) };
|
||||||
const runtimeNamespace = cloneJson(namespaceTemplate);
|
const runtimeNamespace = cloneJson(namespaceTemplate);
|
||||||
runtimeNamespace.metadata.name = namespace;
|
runtimeNamespace.metadata.name = namespace;
|
||||||
@@ -5673,12 +5752,13 @@ async function plannedFiles(args) {
|
|||||||
annotate(runtimeNamespace.metadata, annotations);
|
annotate(runtimeNamespace.metadata, annotations);
|
||||||
const runtimePath = runtimePathForProfile(profile);
|
const runtimePath = runtimePathForProfile(profile);
|
||||||
const endpoints = profileEndpoint(args, profile);
|
const endpoints = profileEndpoint(args, profile);
|
||||||
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent, externalPostgres: Boolean(externalPostgres), workbenchRedis: Boolean(workbenchRedis), runtimeConfigMaps: runtimeConfigMaps.length > 0 }));
|
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent, externalPostgres: Boolean(externalPostgres), workbenchRedis: Boolean(workbenchRedis), runtimeConfigMaps: runtimeConfigMaps.length > 0, externalSecrets: Boolean(runtimeSecretPlane) }));
|
||||||
putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace);
|
putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace);
|
||||||
if (!isRuntimeLane(profile)) putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations));
|
if (!isRuntimeLane(profile)) putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations));
|
||||||
putJson(`${runtimePath}/services.yaml`, transformServices({ services, namespace, labels: profileLabels, annotations, profile, deploy }));
|
putJson(`${runtimePath}/services.yaml`, transformServices({ services, namespace, labels: profileLabels, annotations, profile, deploy }));
|
||||||
putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile));
|
putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile));
|
||||||
if (runtimeConfigMaps.length > 0) putJson(`${runtimePath}/configmaps.yaml`, runtimeConfigMapsManifest({ configMaps: runtimeConfigMaps, namespace, labels: profileLabels, annotations }));
|
if (runtimeConfigMaps.length > 0) putJson(`${runtimePath}/configmaps.yaml`, runtimeConfigMapsManifest({ configMaps: runtimeConfigMaps, namespace, labels: profileLabels, annotations }));
|
||||||
|
if (runtimeSecretPlane) putJson(`${runtimePath}/external-secrets.yaml`, runtimeSecretPlaneManifest({ config: runtimeSecretPlane, namespace, labels: profileLabels, annotations }));
|
||||||
putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, catalog: artifactCatalog, source, sourceBranch: args.sourceBranch, gitReadUrl: args.gitReadUrl, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile, useDeployImages: args.useDeployImages, metricsSidecarSha256 }));
|
putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, catalog: artifactCatalog, source, sourceBranch: args.sourceBranch, gitReadUrl: args.gitReadUrl, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile, useDeployImages: args.useDeployImages, metricsSidecarSha256 }));
|
||||||
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, sourceBranch: args.sourceBranch, sourceRepo: args.sourceRepo, gitReadUrl: args.gitReadUrl, deploy, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages, metricsSidecarSha256 }));
|
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, sourceBranch: args.sourceBranch, sourceRepo: args.sourceRepo, gitReadUrl: args.gitReadUrl, deploy, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages, metricsSidecarSha256 }));
|
||||||
if (isRuntimeLane(profile) && externalPostgres) putJson(`${runtimePath}/external-postgres.yaml`, externalPostgresManifest({ profile, config: externalPostgres, source }));
|
if (isRuntimeLane(profile) && externalPostgres) putJson(`${runtimePath}/external-postgres.yaml`, externalPostgresManifest({ profile, config: externalPostgres, source }));
|
||||||
|
|||||||
@@ -566,6 +566,7 @@ test("v03 render keeps node identity as data instead of generated structure", as
|
|||||||
assert.ok(generatedPaths.includes("tekton-v03/pipeline.yaml"));
|
assert.ok(generatedPaths.includes("tekton-v03/pipeline.yaml"));
|
||||||
assert.ok(generatedPaths.includes("runtime-v03/external-postgres.yaml"));
|
assert.ok(generatedPaths.includes("runtime-v03/external-postgres.yaml"));
|
||||||
assert.ok(generatedPaths.includes("runtime-v03/configmaps.yaml"));
|
assert.ok(generatedPaths.includes("runtime-v03/configmaps.yaml"));
|
||||||
|
assert.ok(generatedPaths.includes("runtime-v03/external-secrets.yaml"));
|
||||||
const pipeline = await readFile(path.join(outDir, "tekton-v03", "pipeline.yaml"), "utf8");
|
const pipeline = await readFile(path.join(outDir, "tekton-v03", "pipeline.yaml"), "utf8");
|
||||||
assert.match(pipeline, /--external-service-report-dir \/workspace\/source\/service-results/u);
|
assert.match(pipeline, /--external-service-report-dir \/workspace\/source\/service-results/u);
|
||||||
assert.match(pipeline, /--ci-plan-path \/workspace\/source\/affected-services\.json/u);
|
assert.match(pipeline, /--ci-plan-path \/workspace\/source\/affected-services\.json/u);
|
||||||
@@ -577,11 +578,20 @@ test("v03 render keeps node identity as data instead of generated structure", as
|
|||||||
const workloads = await readFile(path.join(outDir, "runtime-v03", "workloads.yaml"), "utf8");
|
const workloads = await readFile(path.join(outDir, "runtime-v03", "workloads.yaml"), "utf8");
|
||||||
const workloadsJson = JSON.parse(workloads);
|
const workloadsJson = JSON.parse(workloads);
|
||||||
const configMapsJson = JSON.parse(await readFile(path.join(outDir, "runtime-v03", "configmaps.yaml"), "utf8"));
|
const configMapsJson = JSON.parse(await readFile(path.join(outDir, "runtime-v03", "configmaps.yaml"), "utf8"));
|
||||||
|
const externalSecretsJson = JSON.parse(await readFile(path.join(outDir, "runtime-v03", "external-secrets.yaml"), "utf8"));
|
||||||
const configMapNames = (configMapsJson.items ?? []).map((item) => item.metadata?.name).sort();
|
const configMapNames = (configMapsJson.items ?? []).map((item) => item.metadata?.name).sort();
|
||||||
assert.deepEqual(configMapNames, ["hwlab-v03-hwpod-preinstalled-specs", "hwlab-v03-project-management-bootstrap"]);
|
assert.deepEqual(configMapNames, ["hwlab-v03-hwpod-preinstalled-specs", "hwlab-v03-project-management-bootstrap"]);
|
||||||
|
const smokeExternalSecret = (externalSecretsJson.items ?? []).find((item) => item.kind === "ExternalSecret" && item.metadata?.name === "hwlab-secret-plane-smoke");
|
||||||
|
assert.ok(smokeExternalSecret, "expected v03 secret-plane smoke ExternalSecret");
|
||||||
|
assert.equal(smokeExternalSecret.metadata?.namespace, "hwlab-v03");
|
||||||
|
assert.equal(smokeExternalSecret.spec?.secretStoreRef?.kind, "ClusterSecretStore");
|
||||||
|
assert.equal(smokeExternalSecret.spec?.secretStoreRef?.name, "hwlab-secret-plane-vault-cluster");
|
||||||
|
assert.equal(smokeExternalSecret.spec?.target?.name, "hwlab-secret-plane-smoke");
|
||||||
|
assert.deepEqual(smokeExternalSecret.spec?.data, [{ secretKey: "password", remoteRef: { key: "hwlab-secret-plane/poc", property: "password" } }]);
|
||||||
const cloudApi = (workloadsJson.items ?? []).find((item) => item.kind === "Deployment" && item.metadata?.name === "hwlab-cloud-api");
|
const cloudApi = (workloadsJson.items ?? []).find((item) => item.kind === "Deployment" && item.metadata?.name === "hwlab-cloud-api");
|
||||||
const cloudApiContainer = collectContainersFromItem(cloudApi).find((container) => container.name === "hwlab-cloud-api");
|
const cloudApiContainer = collectContainersFromItem(cloudApi).find((container) => container.name === "hwlab-cloud-api");
|
||||||
const cloudApiEnv = new Map((cloudApiContainer?.env ?? []).map((entry) => [entry.name, entry.value]));
|
const cloudApiEnv = new Map((cloudApiContainer?.env ?? []).map((entry) => [entry.name, entry.value]));
|
||||||
|
const cloudApiEnvEntries = new Map((cloudApiContainer?.env ?? []).map((entry) => [entry.name, entry]));
|
||||||
assert.equal(cloudApi?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/source-commit"], sourceRevision);
|
assert.equal(cloudApi?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/source-commit"], sourceRevision);
|
||||||
assert.equal(cloudApi?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/artifact-source-commit"], sourceRevision);
|
assert.equal(cloudApi?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/artifact-source-commit"], sourceRevision);
|
||||||
assert.equal(cloudApi?.spec?.template?.metadata?.annotations?.["hwlab.pikastech.local/source-commit"], sourceRevision);
|
assert.equal(cloudApi?.spec?.template?.metadata?.annotations?.["hwlab.pikastech.local/source-commit"], sourceRevision);
|
||||||
@@ -591,6 +601,7 @@ test("v03 render keeps node identity as data instead of generated structure", as
|
|||||||
assert.equal(cloudApiEnv.get("HWLAB_GITOPS_SOURCE_COMMIT"), sourceRevision);
|
assert.equal(cloudApiEnv.get("HWLAB_GITOPS_SOURCE_COMMIT"), sourceRevision);
|
||||||
assert.equal(cloudApiEnv.get("HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT"), sourceRevision);
|
assert.equal(cloudApiEnv.get("HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT"), sourceRevision);
|
||||||
assert.match(cloudApiEnv.get("HWLAB_ENVIRONMENT_IMAGE") ?? "", /hwlab-cloud-api-env:env-stale/u);
|
assert.match(cloudApiEnv.get("HWLAB_ENVIRONMENT_IMAGE") ?? "", /hwlab-cloud-api-env:env-stale/u);
|
||||||
|
assert.deepEqual(cloudApiEnvEntries.get("HWLAB_SECRET_PLANE_SMOKE")?.valueFrom?.secretKeyRef, { name: "hwlab-secret-plane-smoke", key: "password", optional: true });
|
||||||
assert.ok((cloudApi?.spec?.template?.spec?.volumes ?? []).some((volume) => volume.name === "hwpod-preinstalled-specs" && volume.configMap?.name === "hwlab-v03-hwpod-preinstalled-specs"));
|
assert.ok((cloudApi?.spec?.template?.spec?.volumes ?? []).some((volume) => volume.name === "hwpod-preinstalled-specs" && volume.configMap?.name === "hwlab-v03-hwpod-preinstalled-specs"));
|
||||||
assert.ok((cloudApiContainer?.volumeMounts ?? []).some((mount) => mount.name === "hwpod-preinstalled-specs" && mount.mountPath === "/etc/hwlab/hwpod-specs"));
|
assert.ok((cloudApiContainer?.volumeMounts ?? []).some((mount) => mount.name === "hwpod-preinstalled-specs" && mount.mountPath === "/etc/hwlab/hwpod-specs"));
|
||||||
assert.ok((cloudApiContainer?.env ?? []).some((entry) => entry.name === "HWLAB_HWPOD_SPEC_REGISTRY_DIRS" && entry.value === "/etc/hwlab/hwpod-specs"));
|
assert.ok((cloudApiContainer?.env ?? []).some((entry) => entry.name === "HWLAB_HWPOD_SPEC_REGISTRY_DIRS" && entry.value === "/etc/hwlab/hwpod-specs"));
|
||||||
|
|||||||
Reference in New Issue
Block a user