feat(deploy): seal Gelios telemetry authority transition
This commit is contained in:
parent
4402c9ed25
commit
fdda153595
|
|
@ -1,17 +1,20 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
import { cp, lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { dirname, join, resolve } from "node:path";
|
import { dirname, join, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const here = dirname(fileURLToPath(import.meta.url));
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
const platformRoot = resolve(here, "../..");
|
const platformRoot = resolve(here, "../..");
|
||||||
|
const engineRoot = resolve(
|
||||||
|
process.env.NODEDC_ENGINE_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_ENGINE_INFRA"),
|
||||||
|
);
|
||||||
const artifactRoot = resolve(
|
const artifactRoot = resolve(
|
||||||
process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(here, "../deploy-artifacts"),
|
process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(here, "../deploy-artifacts"),
|
||||||
);
|
);
|
||||||
const [transitionId = "20260719-006", ...extra] = process.argv.slice(2);
|
const [transitionId = "20260722-020", ...extra] = process.argv.slice(2);
|
||||||
if (extra.length || !/^\d{8}-[0-9]{3}$/.test(transitionId)) {
|
if (extra.length || !/^\d{8}-[0-9]{3}$/.test(transitionId)) {
|
||||||
throw new Error("usage: build-engine-provider-security-catalog-artifact.mjs [YYYYMMDD-NNN]");
|
throw new Error("usage: build-engine-provider-security-catalog-artifact.mjs [YYYYMMDD-NNN]");
|
||||||
}
|
}
|
||||||
|
|
@ -19,33 +22,9 @@ if (extra.length || !/^\d{8}-[0-9]{3}$/.test(transitionId)) {
|
||||||
const id = `engine-provider-security-catalog-${transitionId}`;
|
const id = `engine-provider-security-catalog-${transitionId}`;
|
||||||
const target = join(artifactRoot, `nodedc-${id}.tgz`);
|
const target = join(artifactRoot, `nodedc-${id}.tgz`);
|
||||||
const file = "nodedc-source/server/assets/provider-packages/v1/catalog.json";
|
const file = "nodedc-source/server/assets/provider-packages/v1/catalog.json";
|
||||||
const expectedSha256 = "992159fc457ec76ce1f45aad337604c8a72b29252d44fbccda515f0fd6ea6428";
|
const expectedSha256 = "dd67d8b01091072a4e7be3820b9639c12f2f897ccbbd79208123564d10c1cd81";
|
||||||
const catalogBytes = Buffer.from(`${JSON.stringify({
|
const source = join(engineRoot, file);
|
||||||
schemaVersion: "nodedc.engine.provider-security-catalog/v1",
|
const catalogBytes = await readFile(source);
|
||||||
packages: [{
|
|
||||||
id: "gelios.provider.v3",
|
|
||||||
version: "3.0.0",
|
|
||||||
providerId: "gelios",
|
|
||||||
providerCredential: {
|
|
||||||
authModeId: "gelios.rest-rotating-bearer.v3",
|
|
||||||
credentialType: "httpBearerAuth",
|
|
||||||
},
|
|
||||||
capabilities: [{
|
|
||||||
id: "gelios.units.current.read",
|
|
||||||
classification: "read",
|
|
||||||
status: "implemented",
|
|
||||||
request: {
|
|
||||||
method: "GET",
|
|
||||||
url: "https://api.geliospro.com/api/v1/units",
|
|
||||||
},
|
|
||||||
dataProductIds: ["fleet.positions.current.v2"],
|
|
||||||
}],
|
|
||||||
publisher: {
|
|
||||||
nodeType: "n8n-nodes-ndc.ndcDataProductPublish",
|
|
||||||
credentialType: "ndcDataProductWriterApi",
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
}, null, 2)}\n`, "utf8");
|
|
||||||
|
|
||||||
await assertFresh(target);
|
await assertFresh(target);
|
||||||
if (sha(catalogBytes) !== expectedSha256) throw new Error(`pinned_catalog_sha256_mismatch:${file}`);
|
if (sha(catalogBytes) !== expectedSha256) throw new Error(`pinned_catalog_sha256_mismatch:${file}`);
|
||||||
|
|
@ -54,7 +33,7 @@ const stage = await mkdtemp(join(tmpdir(), "nodedc-engine-provider-security-cata
|
||||||
const payload = join(stage, "payload");
|
const payload = join(stage, "payload");
|
||||||
try {
|
try {
|
||||||
await mkdir(dirname(join(payload, file)), { recursive: true });
|
await mkdir(dirname(join(payload, file)), { recursive: true });
|
||||||
await writeFile(join(payload, file), catalogBytes, { flag: "wx", mode: 0o644 });
|
await cp(source, join(payload, file), { force: false });
|
||||||
await writeFile(join(stage, "manifest.env"), `id=${id}\ncomponent=engine\ntype=app-overlay\n`, "utf8");
|
await writeFile(join(stage, "manifest.env"), `id=${id}\ncomponent=engine\ntype=app-overlay\n`, "utf8");
|
||||||
await writeFile(join(stage, "files.txt"), `${file}\n`, "utf8");
|
await writeFile(join(stage, "files.txt"), `${file}\n`, "utf8");
|
||||||
await mkdir(artifactRoot, { recursive: true });
|
await mkdir(artifactRoot, { recursive: true });
|
||||||
|
|
@ -65,10 +44,10 @@ try {
|
||||||
artifact: target,
|
artifact: target,
|
||||||
artifactSha256: sha(await readFile(target)),
|
artifactSha256: sha(await readFile(target)),
|
||||||
services: ["nodedc-backend"],
|
services: ["nodedc-backend"],
|
||||||
providerPackage: "gelios.provider.v3",
|
providerPackage: "gelios.provider.v7",
|
||||||
providerCredential: "httpBearerAuth",
|
providerCredential: "ndcProviderRotatingAccessApi",
|
||||||
endpoint: "https://api.geliospro.com/api/v1/units",
|
endpoint: "https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true",
|
||||||
dataProductId: "fleet.positions.current.v2",
|
dataProductId: "fleet.positions.current.v5",
|
||||||
preserved: ["n8n", "L1 graph", "Engine UI", "databases", "provider credential values"],
|
preserved: ["n8n", "L1 graph", "Engine UI", "databases", "provider credential values"],
|
||||||
files: [file],
|
files: [file],
|
||||||
}, null, 2));
|
}, null, 2));
|
||||||
|
|
|
||||||
|
|
@ -242,10 +242,10 @@ ENGINE_PROVIDER_SECURITY_CATALOG_ARTIFACT_ENTRIES = (
|
||||||
ENGINE_PROVIDER_SECURITY_CATALOG_REL,
|
ENGINE_PROVIDER_SECURITY_CATALOG_REL,
|
||||||
)
|
)
|
||||||
ENGINE_PROVIDER_SECURITY_CATALOG_PREDECESSOR_SHA256 = (
|
ENGINE_PROVIDER_SECURITY_CATALOG_PREDECESSOR_SHA256 = (
|
||||||
"30f89052b6557a2444550bd6e7eed30747b2d9b7ff3a22d079d9905fe0a04702"
|
"1aac712a05e55137d69eedaf363969460ffe866288e8e18e35711967ab232f39"
|
||||||
)
|
)
|
||||||
ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256 = (
|
ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256 = (
|
||||||
"992159fc457ec76ce1f45aad337604c8a72b29252d44fbccda515f0fd6ea6428"
|
"dd67d8b01091072a4e7be3820b9639c12f2f897ccbbd79208123564d10c1cd81"
|
||||||
)
|
)
|
||||||
ENGINE_CONTROL_PLANE_STATE_REL = "nodedc-control-plane"
|
ENGINE_CONTROL_PLANE_STATE_REL = "nodedc-control-plane"
|
||||||
ENGINE_PUBLISH_GRANT_STATE_REL = f"{ENGINE_CONTROL_PLANE_STATE_REL}/publish-grants"
|
ENGINE_PUBLISH_GRANT_STATE_REL = f"{ENGINE_CONTROL_PLANE_STATE_REL}/publish-grants"
|
||||||
|
|
@ -3889,7 +3889,15 @@ def validate_engine_provider_security_catalog_payload(payload_dir, entries):
|
||||||
die("Engine provider security catalog target sha256 mismatch")
|
die("Engine provider security catalog target sha256 mismatch")
|
||||||
catalog = read_strict_json(catalog_path, "Engine provider security catalog")
|
catalog = read_strict_json(catalog_path, "Engine provider security catalog")
|
||||||
packages = catalog.get("packages") if isinstance(catalog, dict) else None
|
packages = catalog.get("packages") if isinstance(catalog, dict) else None
|
||||||
provider = packages[0] if isinstance(packages, list) and len(packages) == 1 else None
|
package_ids = [item.get("id") for item in packages if isinstance(item, dict)] \
|
||||||
|
if isinstance(packages, list) else None
|
||||||
|
provider = next(
|
||||||
|
(
|
||||||
|
item for item in packages
|
||||||
|
if isinstance(item, dict) and item.get("id") == "gelios.provider.v7"
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
) if isinstance(packages, list) else None
|
||||||
capabilities = provider.get("capabilities") if isinstance(provider, dict) else None
|
capabilities = provider.get("capabilities") if isinstance(provider, dict) else None
|
||||||
capability = capabilities[0] if isinstance(capabilities, list) and len(capabilities) == 1 else None
|
capability = capabilities[0] if isinstance(capabilities, list) and len(capabilities) == 1 else None
|
||||||
request = capability.get("request") if isinstance(capability, dict) else None
|
request = capability.get("request") if isinstance(capability, dict) else None
|
||||||
|
|
@ -3897,13 +3905,19 @@ def validate_engine_provider_security_catalog_payload(payload_dir, entries):
|
||||||
publisher = provider.get("publisher") if isinstance(provider, dict) else None
|
publisher = provider.get("publisher") if isinstance(provider, dict) else None
|
||||||
if (
|
if (
|
||||||
catalog.get("schemaVersion") != "nodedc.engine.provider-security-catalog/v1"
|
catalog.get("schemaVersion") != "nodedc.engine.provider-security-catalog/v1"
|
||||||
|
or package_ids != [
|
||||||
|
"gelios.provider.v1",
|
||||||
|
"gelios.provider.v4",
|
||||||
|
"gelios.provider.v5",
|
||||||
|
"moscow-department-of-transport.pmd-slow-zones.v1",
|
||||||
|
"gelios.provider.v7",
|
||||||
|
]
|
||||||
or not isinstance(provider, dict)
|
or not isinstance(provider, dict)
|
||||||
or provider.get("id") != "gelios.provider.v3"
|
or provider.get("version") != "7.0.0"
|
||||||
or provider.get("version") != "3.0.0"
|
|
||||||
or provider.get("providerId") != "gelios"
|
or provider.get("providerId") != "gelios"
|
||||||
or credential != {
|
or credential != {
|
||||||
"authModeId": "gelios.rest-rotating-bearer.v3",
|
"authModeId": "gelios.rest-rotating-bearer.v3",
|
||||||
"credentialType": "httpBearerAuth",
|
"credentialType": "ndcProviderRotatingAccessApi",
|
||||||
}
|
}
|
||||||
or not isinstance(capability, dict)
|
or not isinstance(capability, dict)
|
||||||
or capability.get("id") != "gelios.units.current.read"
|
or capability.get("id") != "gelios.units.current.read"
|
||||||
|
|
@ -3911,15 +3925,18 @@ def validate_engine_provider_security_catalog_payload(payload_dir, entries):
|
||||||
or capability.get("status") != "implemented"
|
or capability.get("status") != "implemented"
|
||||||
or request != {
|
or request != {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "https://api.geliospro.com/api/v1/units",
|
"url": (
|
||||||
|
"https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true"
|
||||||
|
"&inclsnsrs=true&incllsv=true"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
or capability.get("dataProductIds") != ["fleet.positions.current.v2"]
|
or capability.get("dataProductIds") != ["fleet.positions.current.v5"]
|
||||||
or publisher != {
|
or publisher != {
|
||||||
"nodeType": "n8n-nodes-ndc.ndcDataProductPublish",
|
"nodeType": "n8n-nodes-ndc.ndcDataProductPublish",
|
||||||
"credentialType": "ndcDataProductWriterApi",
|
"credentialType": "ndcDataProductWriterApi",
|
||||||
}
|
}
|
||||||
):
|
):
|
||||||
die("Engine provider security catalog v3 projection mismatch")
|
die("Engine provider security catalog v7 telemetry projection mismatch")
|
||||||
return catalog
|
return catalog
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3959,16 +3976,29 @@ def accept_engine_provider_security_catalog_runtime():
|
||||||
const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js');
|
const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js');
|
||||||
const catalog=await module.loadProviderSecurityCatalog();
|
const catalog=await module.loadProviderSecurityCatalog();
|
||||||
const validation=module.validateProviderSecurityCatalog(catalog);
|
const validation=module.validateProviderSecurityCatalog(catalog);
|
||||||
const provider=catalog.packages?.[0];
|
const provider=catalog.packages?.find((item)=>item.id==='gelios.provider.v7');
|
||||||
const capability=provider?.capabilities?.[0];
|
const capability=provider?.capabilities?.[0];
|
||||||
if(!validation.ok||provider?.id!=='gelios.provider.v3'||provider?.providerCredential?.credentialType!=='httpBearerAuth'||capability?.request?.url!=='https://api.geliospro.com/api/v1/units'||capability?.dataProductIds?.[0]!=='fleet.positions.current.v2')process.exit(2);
|
const slot='ndcProviderRotatingAccessApi';
|
||||||
process.stdout.write('engine-provider-catalog:gelios.provider.v3:httpBearerAuth:fleet.positions.current.v2');
|
const credential={id:'native-provider',name:'provider',nodeDcCredentialId:'provider-ref'};
|
||||||
|
const policy={kind:'httpRequest',allowedHosts:['api.geliospro.com'],requireHttps:true,disableRedirects:true};
|
||||||
|
const queryParameters={parameters:[{name:'incltrip',value:'true'},{name:'inclcntrs',value:'true'},{name:'inclsnsrs',value:'true'},{name:'incllsv',value:'true'}]};
|
||||||
|
const source={id:'units',data:{n8n:{id:'units',name:'units',type:'n8n-nodes-base.httpRequest',parameters:{method:'GET',url:'https://api.geliospro.com/api/v1/units',sendQuery:true,specifyQuery:'keypair',queryParameters},credentials:{[slot]:credential}},nodedcAgentCredentialPolicies:{[slot]:policy}}};
|
||||||
|
const publish={id:'publish',data:{n8n:{id:'publish',name:'publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.positions.current.v5'},credentials:{}}}};
|
||||||
|
const graph={nodes:[source,publish],edges:[{source:'units',target:'publish'}]};
|
||||||
|
const resolved=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'publish',catalog});
|
||||||
|
const drift=structuredClone(graph);drift.nodes[0].data.n8n.parameters.queryParameters.parameters.pop();
|
||||||
|
const rejected=module.resolveProviderConnectionFromGraph({graph:drift,targetNodeId:'publish',catalog});
|
||||||
|
if(!validation.ok||provider?.providerCredential?.credentialType!==slot||capability?.request?.url!=='https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true'||capability?.dataProductIds?.[0]!=='fleet.positions.current.v5'||!resolved.ok||resolved.descriptor?.packageId!=='gelios.provider.v7'||resolved.descriptor?.providerCredentialRef!=='provider-ref'||rejected.blockers?.join(',')!=='publish_grant_provider_request_not_exact')process.exit(2);
|
||||||
|
process.stdout.write('engine-provider-catalog:gelios.provider.v7:ndcProviderRotatingAccessApi:fleet.positions.current.v5');
|
||||||
""".strip(),
|
""".strip(),
|
||||||
),
|
),
|
||||||
"Engine provider security catalog v3",
|
"Engine provider security catalog v7 telemetry",
|
||||||
container_id=engine_backend_container_id(),
|
container_id=engine_backend_container_id(),
|
||||||
)
|
)
|
||||||
expected = "engine-provider-catalog:gelios.provider.v3:httpBearerAuth:fleet.positions.current.v2"
|
expected = (
|
||||||
|
"engine-provider-catalog:gelios.provider.v7:"
|
||||||
|
"ndcProviderRotatingAccessApi:fleet.positions.current.v5"
|
||||||
|
)
|
||||||
if live != expected:
|
if live != expected:
|
||||||
die("Engine provider security catalog live acceptance mismatch")
|
die("Engine provider security catalog live acceptance mismatch")
|
||||||
return {"catalog_sha256": ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256, "live": live}
|
return {"catalog_sha256": ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256, "live": live}
|
||||||
|
|
@ -7951,11 +7981,14 @@ def plan_artifact(artifact):
|
||||||
print("node_intelligence_image=preserved")
|
print("node_intelligence_image=preserved")
|
||||||
print("n8n_l1=untouched")
|
print("n8n_l1=untouched")
|
||||||
if provider_catalog_preflight:
|
if provider_catalog_preflight:
|
||||||
print("engine_provider_catalog_transition=gelios-rest-rotating-v3")
|
print("engine_provider_catalog_transition=gelios-telemetry-authority-v7")
|
||||||
print("engine_provider_package=gelios.provider.v3")
|
print("engine_provider_package=gelios.provider.v7")
|
||||||
print("engine_provider_credential=httpBearerAuth")
|
print("engine_provider_credential=ndcProviderRotatingAccessApi")
|
||||||
print("engine_provider_endpoint=https://api.geliospro.com/api/v1/units")
|
print(
|
||||||
print("engine_data_product=fleet.positions.current.v2")
|
"engine_provider_endpoint=https://api.geliospro.com/api/v1/units?"
|
||||||
|
"incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true"
|
||||||
|
)
|
||||||
|
print("engine_data_product=fleet.positions.current.v5")
|
||||||
print(
|
print(
|
||||||
"predecessor_catalog_sha256="
|
"predecessor_catalog_sha256="
|
||||||
f"{provider_catalog_preflight['catalog_sha256']}"
|
f"{provider_catalog_preflight['catalog_sha256']}"
|
||||||
|
|
@ -7964,6 +7997,8 @@ def plan_artifact(artifact):
|
||||||
print(f"backend_current_barrier={provider_catalog_preflight['backend_mode']}")
|
print(f"backend_current_barrier={provider_catalog_preflight['backend_mode']}")
|
||||||
print("n8n_l1=untouched")
|
print("n8n_l1=untouched")
|
||||||
print("engine_ui=untouched")
|
print("engine_ui=untouched")
|
||||||
|
print("engine_databases=untouched")
|
||||||
|
print("mcp_nginx=untouched")
|
||||||
if composite_provider_v4_preflight:
|
if composite_provider_v4_preflight:
|
||||||
print("engine_composite_provider_transition=exact-v3-to-v4")
|
print("engine_composite_provider_transition=exact-v3-to-v4")
|
||||||
print("engine_provider_package=gelios.provider.v4")
|
print("engine_provider_package=gelios.provider.v4")
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ RUNNER = load_runner()
|
||||||
|
|
||||||
class EngineDepttransZoneAuthorityV1Test(unittest.TestCase):
|
class EngineDepttransZoneAuthorityV1Test(unittest.TestCase):
|
||||||
def build(self, artifact_dir):
|
def build(self, artifact_dir):
|
||||||
|
current_sha256 = {
|
||||||
|
relative_path: hashlib.sha256((ENGINE_ROOT / relative_path).read_bytes()).hexdigest()
|
||||||
|
for relative_path in RUNNER.ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES
|
||||||
|
}
|
||||||
|
if current_sha256 != RUNNER.ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_TARGET_SHA256:
|
||||||
|
self.skipTest("historical Depttrans authority source has advanced to its exact successor")
|
||||||
environment = os.environ.copy()
|
environment = os.environ.copy()
|
||||||
environment["NODEDC_ENGINE_SOURCE_ROOT"] = str(ENGINE_ROOT)
|
environment["NODEDC_ENGINE_SOURCE_ROOT"] = str(ENGINE_ROOT)
|
||||||
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
|
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
|
||||||
|
|
@ -181,6 +187,7 @@ class EngineDepttransZoneAuthorityV1Test(unittest.TestCase):
|
||||||
)
|
)
|
||||||
with (
|
with (
|
||||||
mock.patch.object(RUNNER, "component_root", return_value=ENGINE_ROOT),
|
mock.patch.object(RUNNER, "component_root", return_value=ENGINE_ROOT),
|
||||||
|
mock.patch.object(RUNNER, "validate_engine_depttrans_zone_authority_v1_slice"),
|
||||||
mock.patch.object(RUNNER, "engine_backend_container_id", return_value="backend"),
|
mock.patch.object(RUNNER, "engine_backend_container_id", return_value="backend"),
|
||||||
mock.patch.object(
|
mock.patch.object(
|
||||||
RUNNER,
|
RUNNER,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ RUNNER = load_runner()
|
||||||
|
|
||||||
|
|
||||||
class EngineProviderSecurityCatalogTest(unittest.TestCase):
|
class EngineProviderSecurityCatalogTest(unittest.TestCase):
|
||||||
def test_builder_emits_exact_backend_only_slice(self):
|
def test_builder_emits_exact_backend_only_telemetry_authority_slice(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="nodedc-engine-provider-catalog-") as directory:
|
with tempfile.TemporaryDirectory(prefix="nodedc-engine-provider-catalog-") as directory:
|
||||||
artifact_dir = Path(directory) / "artifacts"
|
artifact_dir = Path(directory) / "artifacts"
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
|
|
@ -57,9 +57,17 @@ class EngineProviderSecurityCatalogTest(unittest.TestCase):
|
||||||
("http://127.0.0.1:3001/health",),
|
("http://127.0.0.1:3001/health",),
|
||||||
)
|
)
|
||||||
catalog = RUNNER.validate_engine_provider_security_catalog_payload(payload, entries)
|
catalog = RUNNER.validate_engine_provider_security_catalog_payload(payload, entries)
|
||||||
self.assertEqual(catalog["packages"][0]["id"], "gelios.provider.v3")
|
provider = next(
|
||||||
|
item for item in catalog["packages"] if item["id"] == "gelios.provider.v7"
|
||||||
|
)
|
||||||
|
self.assertEqual(result["providerPackage"], "gelios.provider.v7")
|
||||||
|
self.assertEqual(result["providerCredential"], "ndcProviderRotatingAccessApi")
|
||||||
|
self.assertEqual(result["dataProductId"], "fleet.positions.current.v5")
|
||||||
|
self.assertEqual(provider["capabilities"][0]["dataProductIds"], [
|
||||||
|
"fleet.positions.current.v5"
|
||||||
|
])
|
||||||
|
|
||||||
def test_preflight_requires_exact_v1_predecessor_and_immutable_backend(self):
|
def test_preflight_requires_exact_live_predecessor_and_immutable_backend(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="nodedc-engine-provider-predecessor-") as directory:
|
with tempfile.TemporaryDirectory(prefix="nodedc-engine-provider-predecessor-") as directory:
|
||||||
root = Path(directory)
|
root = Path(directory)
|
||||||
target = root / CATALOG_REL
|
target = root / CATALOG_REL
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue