@fabricorg/databricks-testkit 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/index.cjs +841 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +257 -1
- package/dist/index.d.ts +257 -1
- package/dist/index.js +821 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -654,7 +654,8 @@ async function runLiveSuite(spec, runtime = {}) {
|
|
|
654
654
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
655
655
|
success,
|
|
656
656
|
required: required2,
|
|
657
|
-
results
|
|
657
|
+
results,
|
|
658
|
+
...spec.targetPack ? { targetPack: spec.targetPack } : {}
|
|
658
659
|
};
|
|
659
660
|
if (spec.evidencePath)
|
|
660
661
|
await writeReport(spec.evidencePath, `${JSON.stringify(evidence, null, 2)}
|
|
@@ -1535,6 +1536,824 @@ function classicComputeCheck() {
|
|
|
1535
1536
|
};
|
|
1536
1537
|
}
|
|
1537
1538
|
|
|
1539
|
+
// src/target-packs.ts
|
|
1540
|
+
var TARGET_PACK_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1541
|
+
function defineTargetPack(spec) {
|
|
1542
|
+
if (!TARGET_PACK_ID.test(spec.id)) {
|
|
1543
|
+
throw new Error(`Target pack id '${spec.id}' must be lowercase kebab-case`);
|
|
1544
|
+
}
|
|
1545
|
+
if (!spec.name.trim()) throw new Error(`Target pack '${spec.id}' requires a name`);
|
|
1546
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(spec.version)) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`Target pack '${spec.id}' version '${spec.version}' is not semantic versioning`
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
if (spec.capabilities.length === 0) {
|
|
1552
|
+
throw new Error(`Target pack '${spec.id}' must declare at least one capability`);
|
|
1553
|
+
}
|
|
1554
|
+
const createChecks = typeof spec.checks === "function" ? spec.checks : () => spec.checks;
|
|
1555
|
+
const validateChecks = () => {
|
|
1556
|
+
const checks = [...createChecks()];
|
|
1557
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1558
|
+
for (const check of checks) {
|
|
1559
|
+
if (!check.id.trim())
|
|
1560
|
+
throw new Error(`Target pack '${spec.id}' contains a check without an id`);
|
|
1561
|
+
if (ids.has(check.id)) {
|
|
1562
|
+
throw new Error(`Target pack '${spec.id}' contains duplicate check '${check.id}'`);
|
|
1563
|
+
}
|
|
1564
|
+
ids.add(check.id);
|
|
1565
|
+
}
|
|
1566
|
+
for (const required2 of spec.requiredChecks ?? []) {
|
|
1567
|
+
if (!ids.has(required2)) {
|
|
1568
|
+
throw new Error(`Target pack '${spec.id}' requires unknown check '${required2}'`);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return checks;
|
|
1572
|
+
};
|
|
1573
|
+
validateChecks();
|
|
1574
|
+
return Object.freeze({
|
|
1575
|
+
...spec,
|
|
1576
|
+
capabilities: Object.freeze([...spec.capabilities]),
|
|
1577
|
+
requiredChecks: Object.freeze([...spec.requiredChecks ?? []]),
|
|
1578
|
+
requirements: Object.freeze([...spec.requirements ?? []]),
|
|
1579
|
+
certificationScopes: Object.freeze([...spec.certificationScopes ?? []]),
|
|
1580
|
+
checks: validateChecks
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
function createTargetPackRegistry(packs) {
|
|
1584
|
+
const registry = /* @__PURE__ */ new Map();
|
|
1585
|
+
for (const pack of packs) {
|
|
1586
|
+
if (registry.has(pack.id)) throw new Error(`Duplicate target pack '${pack.id}'`);
|
|
1587
|
+
registry.set(pack.id, pack);
|
|
1588
|
+
}
|
|
1589
|
+
return registry;
|
|
1590
|
+
}
|
|
1591
|
+
function doctorTargetPack(pack, env = process.env, requiredChecks = pack.requiredChecks ?? []) {
|
|
1592
|
+
const requirements = (pack.requirements ?? []).map((requirement) => ({
|
|
1593
|
+
id: requirement.id,
|
|
1594
|
+
description: requirement.description,
|
|
1595
|
+
satisfied: requirement.anyOf.some((name) => Boolean(env[name]?.trim())),
|
|
1596
|
+
required: requirement.required !== false,
|
|
1597
|
+
variables: [...requirement.anyOf]
|
|
1598
|
+
}));
|
|
1599
|
+
const required2 = new Set(requiredChecks);
|
|
1600
|
+
const checks = pack.checks().map((check) => ({
|
|
1601
|
+
id: check.id,
|
|
1602
|
+
description: check.description,
|
|
1603
|
+
configured: check.configured ? check.configured(env) : true,
|
|
1604
|
+
required: required2.has(check.id)
|
|
1605
|
+
}));
|
|
1606
|
+
const known = new Set(checks.map((check) => check.id));
|
|
1607
|
+
const missingRequirements = requirements.filter((requirement) => requirement.required && !requirement.satisfied).map((requirement) => requirement.id);
|
|
1608
|
+
const missingRequiredChecks = [
|
|
1609
|
+
...checks.filter((check) => check.required && !check.configured).map((check) => check.id),
|
|
1610
|
+
...[...required2].filter((id) => !known.has(id))
|
|
1611
|
+
];
|
|
1612
|
+
return {
|
|
1613
|
+
pack: {
|
|
1614
|
+
id: pack.id,
|
|
1615
|
+
name: pack.name,
|
|
1616
|
+
version: pack.version,
|
|
1617
|
+
maturity: pack.maturity
|
|
1618
|
+
},
|
|
1619
|
+
ready: missingRequirements.length === 0 && missingRequiredChecks.length === 0,
|
|
1620
|
+
requirements,
|
|
1621
|
+
checks,
|
|
1622
|
+
missingRequirements,
|
|
1623
|
+
missingRequiredChecks
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
function defineTargetPackRun(pack, options = {}) {
|
|
1627
|
+
const env = options.env ?? process.env;
|
|
1628
|
+
const requiredChecks = [...options.requiredChecks ?? pack.requiredChecks ?? []];
|
|
1629
|
+
const doctor = doctorTargetPack(pack, env, requiredChecks);
|
|
1630
|
+
if (!doctor.ready) {
|
|
1631
|
+
const missing = [...doctor.missingRequirements, ...doctor.missingRequiredChecks].join(", ");
|
|
1632
|
+
throw new Error(`Target pack '${pack.id}' is not configured: ${missing}`);
|
|
1633
|
+
}
|
|
1634
|
+
const targetPack = evidenceMetadata(pack, env);
|
|
1635
|
+
const suite = defineLiveSuite({
|
|
1636
|
+
checks: pack.checks(),
|
|
1637
|
+
required: requiredChecks,
|
|
1638
|
+
env,
|
|
1639
|
+
evidencePath: options.evidencePath,
|
|
1640
|
+
junitPath: options.junitPath,
|
|
1641
|
+
targetPack
|
|
1642
|
+
});
|
|
1643
|
+
return { pack, run: (runtime) => suite.run(runtime) };
|
|
1644
|
+
}
|
|
1645
|
+
async function runTargetPack(pack, options = {}, runtime) {
|
|
1646
|
+
return defineTargetPackRun(pack, options).run(runtime);
|
|
1647
|
+
}
|
|
1648
|
+
function evidenceMetadata(pack, env) {
|
|
1649
|
+
return {
|
|
1650
|
+
id: pack.id,
|
|
1651
|
+
name: pack.name,
|
|
1652
|
+
version: pack.version,
|
|
1653
|
+
maturity: pack.maturity,
|
|
1654
|
+
capabilities: [...pack.capabilities],
|
|
1655
|
+
...pack.docsUrl ? { docsUrl: pack.docsUrl } : {},
|
|
1656
|
+
certificationScopes: [...pack.certificationScopes ?? []],
|
|
1657
|
+
target: compact({
|
|
1658
|
+
cloud: env.DBX_TEST_CLOUD,
|
|
1659
|
+
region: env.DBX_TEST_REGION,
|
|
1660
|
+
workspace: env.DATABRICKS_HOST,
|
|
1661
|
+
runtime: env.DBX_TEST_RUNTIME_VERSION,
|
|
1662
|
+
compute: env.DBX_TEST_COMPUTE
|
|
1663
|
+
})
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
function compact(value) {
|
|
1667
|
+
return Object.fromEntries(
|
|
1668
|
+
Object.entries(value).filter((entry) => Boolean(entry[1]))
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// src/foundation-packs.ts
|
|
1673
|
+
var DOCS = "https://experiments.fabric.pro/docs/testing/target-packs/";
|
|
1674
|
+
var AZURE_SCOPE = "Azure Databricks live-gated; see certification evidence for exact runtime";
|
|
1675
|
+
var workspaceRequirement = {
|
|
1676
|
+
id: "workspace",
|
|
1677
|
+
description: "Databricks workspace host",
|
|
1678
|
+
anyOf: ["DATABRICKS_HOST"]
|
|
1679
|
+
};
|
|
1680
|
+
var authRequirement = {
|
|
1681
|
+
id: "authentication",
|
|
1682
|
+
description: "Databricks OAuth, OIDC, bearer, or PAT authentication",
|
|
1683
|
+
anyOf: [
|
|
1684
|
+
"DATABRICKS_BEARER",
|
|
1685
|
+
"DATABRICKS_TOKEN",
|
|
1686
|
+
"DATABRICKS_CLIENT_ID",
|
|
1687
|
+
"DATABRICKS_OIDC_TOKEN",
|
|
1688
|
+
"DATABRICKS_OIDC_TOKEN_FILEPATH"
|
|
1689
|
+
],
|
|
1690
|
+
secret: true
|
|
1691
|
+
};
|
|
1692
|
+
var warehouseRequirement = {
|
|
1693
|
+
id: "warehouse",
|
|
1694
|
+
description: "SQL warehouse ID or HTTP path",
|
|
1695
|
+
anyOf: ["DATABRICKS_WAREHOUSE_ID", "DATABRICKS_HTTP_PATH"]
|
|
1696
|
+
};
|
|
1697
|
+
function createSqlDeltaFoundationPack() {
|
|
1698
|
+
return defineTargetPack({
|
|
1699
|
+
id: "sql-delta",
|
|
1700
|
+
name: "SQL and Delta foundation",
|
|
1701
|
+
description: "SQL Statement Execution, Delta contracts, performance, recovery, backup and rollback.",
|
|
1702
|
+
version: "1.0.0",
|
|
1703
|
+
maturity: "live-gated",
|
|
1704
|
+
capabilities: [
|
|
1705
|
+
"sql.statement-execution",
|
|
1706
|
+
"delta.contract",
|
|
1707
|
+
"delta.time-travel",
|
|
1708
|
+
"performance.sql",
|
|
1709
|
+
"recovery.sql",
|
|
1710
|
+
"backup-restore.delta",
|
|
1711
|
+
"rollback.delta"
|
|
1712
|
+
],
|
|
1713
|
+
checks: () => [
|
|
1714
|
+
restrictedPrincipalCheck(),
|
|
1715
|
+
sqlRoundTripCheck(),
|
|
1716
|
+
deltaContractCheck(),
|
|
1717
|
+
serverlessComputeCheck(),
|
|
1718
|
+
performanceBudgetCheck(),
|
|
1719
|
+
failureRecoveryCheck(),
|
|
1720
|
+
backupRestoreCheck(),
|
|
1721
|
+
rollbackCheck()
|
|
1722
|
+
],
|
|
1723
|
+
requiredChecks: ["sql"],
|
|
1724
|
+
requirements: [workspaceRequirement, authRequirement, warehouseRequirement],
|
|
1725
|
+
docsUrl: `${DOCS}#sql-and-delta-foundation`,
|
|
1726
|
+
certificationScopes: [AZURE_SCOPE]
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
function createLakeflowFoundationPack() {
|
|
1730
|
+
return defineTargetPack({
|
|
1731
|
+
id: "lakeflow-ingestion",
|
|
1732
|
+
name: "Lakeflow ingestion foundation",
|
|
1733
|
+
description: "Lakeflow refresh, Volumes, Auto Loader ingestion and rescued-row evidence.",
|
|
1734
|
+
version: "1.0.0",
|
|
1735
|
+
maturity: "live-gated",
|
|
1736
|
+
capabilities: [
|
|
1737
|
+
"lakeflow.refresh",
|
|
1738
|
+
"autoloader.ingestion",
|
|
1739
|
+
"volumes.files",
|
|
1740
|
+
"delta.rescued-data"
|
|
1741
|
+
],
|
|
1742
|
+
checks: () => [
|
|
1743
|
+
restrictedPrincipalCheck(),
|
|
1744
|
+
pipelineRefreshCheck(),
|
|
1745
|
+
volumeIOCheck(),
|
|
1746
|
+
autoLoaderIngestionCheck()
|
|
1747
|
+
],
|
|
1748
|
+
requiredChecks: ["pipeline"],
|
|
1749
|
+
requirements: [workspaceRequirement, authRequirement],
|
|
1750
|
+
docsUrl: `${DOCS}#lakeflow-ingestion-foundation`,
|
|
1751
|
+
certificationScopes: [AZURE_SCOPE]
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
function createJobsCodeFoundationPack() {
|
|
1755
|
+
return defineTargetPack({
|
|
1756
|
+
id: "jobs-code",
|
|
1757
|
+
name: "Jobs and code foundation",
|
|
1758
|
+
description: "Jobs, notebooks, dbt, serverless compute and classic compute probes.",
|
|
1759
|
+
version: "1.0.0",
|
|
1760
|
+
maturity: "live-gated",
|
|
1761
|
+
capabilities: [
|
|
1762
|
+
"jobs.run-now",
|
|
1763
|
+
"jobs.submit.notebook",
|
|
1764
|
+
"jobs.submit.dbt",
|
|
1765
|
+
"compute.serverless",
|
|
1766
|
+
"compute.classic"
|
|
1767
|
+
],
|
|
1768
|
+
checks: () => [
|
|
1769
|
+
restrictedPrincipalCheck(),
|
|
1770
|
+
jobRunCheck(),
|
|
1771
|
+
notebookSubmitCheck(),
|
|
1772
|
+
dbtBuildCheck(),
|
|
1773
|
+
serverlessComputeCheck(),
|
|
1774
|
+
classicComputeCheck()
|
|
1775
|
+
],
|
|
1776
|
+
requiredChecks: ["jobs"],
|
|
1777
|
+
requirements: [workspaceRequirement, authRequirement],
|
|
1778
|
+
docsUrl: `${DOCS}#jobs-and-code-foundation`,
|
|
1779
|
+
certificationScopes: [AZURE_SCOPE]
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
function createUnityStorageFoundationPack() {
|
|
1783
|
+
return defineTargetPack({
|
|
1784
|
+
id: "unity-storage",
|
|
1785
|
+
name: "Unity Catalog and storage foundation",
|
|
1786
|
+
description: "Unity Catalog allow/deny, Volumes, secret metadata and secret rotation.",
|
|
1787
|
+
version: "1.0.0",
|
|
1788
|
+
maturity: "live-gated",
|
|
1789
|
+
capabilities: [
|
|
1790
|
+
"unity-catalog.permissions",
|
|
1791
|
+
"volumes.files",
|
|
1792
|
+
"secrets.metadata",
|
|
1793
|
+
"secrets.rotation"
|
|
1794
|
+
],
|
|
1795
|
+
checks: () => [
|
|
1796
|
+
restrictedPrincipalCheck(),
|
|
1797
|
+
unityCatalogAccessCheck(),
|
|
1798
|
+
volumeIOCheck(),
|
|
1799
|
+
secretScopeCheck(),
|
|
1800
|
+
secretRotationCheck()
|
|
1801
|
+
],
|
|
1802
|
+
requiredChecks: ["uc-grants"],
|
|
1803
|
+
requirements: [workspaceRequirement, authRequirement, warehouseRequirement],
|
|
1804
|
+
docsUrl: `${DOCS}#unity-catalog-and-storage-foundation`,
|
|
1805
|
+
certificationScopes: [AZURE_SCOPE]
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
function createAppsOperationalFoundationPack() {
|
|
1809
|
+
return defineTargetPack({
|
|
1810
|
+
id: "apps-operational",
|
|
1811
|
+
name: "Apps and operational data foundation",
|
|
1812
|
+
description: "Databricks Apps resources and health plus Lakebase OAuth and pool refresh.",
|
|
1813
|
+
version: "1.0.0",
|
|
1814
|
+
maturity: "live-gated",
|
|
1815
|
+
capabilities: [
|
|
1816
|
+
"apps.health",
|
|
1817
|
+
"apps.resources",
|
|
1818
|
+
"lakebase.credentials",
|
|
1819
|
+
"lakebase.round-trip",
|
|
1820
|
+
"lakebase.pool-refresh"
|
|
1821
|
+
],
|
|
1822
|
+
checks: () => [
|
|
1823
|
+
restrictedPrincipalCheck(),
|
|
1824
|
+
appHealthCheck(),
|
|
1825
|
+
lakebaseCredentialCheck(),
|
|
1826
|
+
lakebaseRoundTripCheck()
|
|
1827
|
+
],
|
|
1828
|
+
requiredChecks: ["app"],
|
|
1829
|
+
requirements: [workspaceRequirement, authRequirement],
|
|
1830
|
+
docsUrl: `${DOCS}#apps-and-operational-data-foundation`,
|
|
1831
|
+
certificationScopes: [AZURE_SCOPE]
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
function createFoundationTargetPacks() {
|
|
1835
|
+
return [
|
|
1836
|
+
createSqlDeltaFoundationPack(),
|
|
1837
|
+
createLakeflowFoundationPack(),
|
|
1838
|
+
createJobsCodeFoundationPack(),
|
|
1839
|
+
createUnityStorageFoundationPack(),
|
|
1840
|
+
createAppsOperationalFoundationPack()
|
|
1841
|
+
];
|
|
1842
|
+
}
|
|
1843
|
+
var foundationTargetPacks = createFoundationTargetPacks();
|
|
1844
|
+
var DatabricksJobsAdapter = class {
|
|
1845
|
+
constructor(client) {
|
|
1846
|
+
this.client = client;
|
|
1847
|
+
}
|
|
1848
|
+
client;
|
|
1849
|
+
async submit(request) {
|
|
1850
|
+
validateJobGraph(request.tasks);
|
|
1851
|
+
const response = await this.client.post(
|
|
1852
|
+
"/api/2.1/jobs/runs/submit",
|
|
1853
|
+
request
|
|
1854
|
+
);
|
|
1855
|
+
if (!response.run_id) throw new Error("Databricks Jobs submit returned no run_id");
|
|
1856
|
+
return response.run_id;
|
|
1857
|
+
}
|
|
1858
|
+
async get(runId) {
|
|
1859
|
+
const raw = await this.client.get("/api/2.1/jobs/runs/get", {
|
|
1860
|
+
run_id: runId,
|
|
1861
|
+
include_history: true,
|
|
1862
|
+
include_resolved_values: true
|
|
1863
|
+
});
|
|
1864
|
+
return normalizeJobRun(runId, raw);
|
|
1865
|
+
}
|
|
1866
|
+
async wait(runId, options = {}) {
|
|
1867
|
+
const raw = await databricks.waitForDatabricksRun(this.client, runId, {
|
|
1868
|
+
timeoutMs: options.timeoutMs,
|
|
1869
|
+
pollIntervalMs: options.pollIntervalMs
|
|
1870
|
+
});
|
|
1871
|
+
return normalizeJobRun(runId, raw);
|
|
1872
|
+
}
|
|
1873
|
+
async cancel(runId) {
|
|
1874
|
+
await this.client.post("/api/2.1/jobs/runs/cancel", { run_id: runId });
|
|
1875
|
+
}
|
|
1876
|
+
async waitUntilActive(runId, options = {}) {
|
|
1877
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
1878
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2e3;
|
|
1879
|
+
const deadline = Date.now() + timeoutMs;
|
|
1880
|
+
while (Date.now() < deadline) {
|
|
1881
|
+
const run = await this.get(runId);
|
|
1882
|
+
if (run.lifeCycleState === "RUNNING") return run;
|
|
1883
|
+
if (isTerminal(run.lifeCycleState)) {
|
|
1884
|
+
throw new Error(
|
|
1885
|
+
`Databricks run ${runId} reached ${run.lifeCycleState}/${run.resultState} before it could be canceled`
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
await delay2(pollIntervalMs);
|
|
1889
|
+
}
|
|
1890
|
+
throw new Error(`Databricks run ${runId} did not become active within ${timeoutMs}ms`);
|
|
1891
|
+
}
|
|
1892
|
+
async repair(runId, options = {}) {
|
|
1893
|
+
const response = await this.client.post("/api/2.1/jobs/runs/repair", {
|
|
1894
|
+
run_id: runId,
|
|
1895
|
+
...options.latestRepairId ? { latest_repair_id: options.latestRepairId } : {},
|
|
1896
|
+
...options.rerunDependentTasks ? { rerun_dependent_tasks: true } : {},
|
|
1897
|
+
...options.rerunTasks?.length ? { rerun_tasks: options.rerunTasks } : { rerun_all_failed_tasks: true }
|
|
1898
|
+
});
|
|
1899
|
+
if (!response.repair_id) throw new Error("Databricks Jobs repair returned no repair_id");
|
|
1900
|
+
return response.repair_id;
|
|
1901
|
+
}
|
|
1902
|
+
async waitForRepair(runId, repairId, options = {}) {
|
|
1903
|
+
const timeoutMs = options.timeoutMs ?? 9e5;
|
|
1904
|
+
const pollIntervalMs = options.pollIntervalMs ?? 5e3;
|
|
1905
|
+
const deadline = Date.now() + timeoutMs;
|
|
1906
|
+
while (Date.now() < deadline) {
|
|
1907
|
+
const run = await this.get(runId);
|
|
1908
|
+
const repair = run.repairs.find((candidate) => candidate.repairId === repairId);
|
|
1909
|
+
if (repair && isTerminal(repair.lifeCycleState)) return run;
|
|
1910
|
+
await delay2(pollIntervalMs);
|
|
1911
|
+
}
|
|
1912
|
+
throw new Error(
|
|
1913
|
+
`Databricks repair ${repairId} for run ${runId} timed out after ${timeoutMs}ms`
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
function validateJobGraph(tasks) {
|
|
1918
|
+
if (tasks.length === 0) throw new Error("Jobs orchestration requires at least one task");
|
|
1919
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1920
|
+
for (const task of tasks) {
|
|
1921
|
+
if (!/^[A-Za-z0-9_-]{1,100}$/.test(task.task_key)) {
|
|
1922
|
+
throw new Error(`Invalid task key '${task.task_key}'`);
|
|
1923
|
+
}
|
|
1924
|
+
if (keys.has(task.task_key)) throw new Error(`Duplicate task key '${task.task_key}'`);
|
|
1925
|
+
keys.add(task.task_key);
|
|
1926
|
+
}
|
|
1927
|
+
const edges = /* @__PURE__ */ new Map();
|
|
1928
|
+
for (const task of tasks) {
|
|
1929
|
+
const dependencies = (task.depends_on ?? []).map((dependency) => dependency.task_key);
|
|
1930
|
+
for (const dependency of dependencies) {
|
|
1931
|
+
if (!keys.has(dependency)) {
|
|
1932
|
+
throw new Error(`Task '${task.task_key}' depends on unknown task '${dependency}'`);
|
|
1933
|
+
}
|
|
1934
|
+
if (dependency === task.task_key) {
|
|
1935
|
+
throw new Error(`Task '${task.task_key}' cannot depend on itself`);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
edges.set(task.task_key, dependencies);
|
|
1939
|
+
}
|
|
1940
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1941
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1942
|
+
const visit = (key) => {
|
|
1943
|
+
if (visiting.has(key)) throw new Error(`Jobs task graph contains a cycle at '${key}'`);
|
|
1944
|
+
if (visited.has(key)) return;
|
|
1945
|
+
visiting.add(key);
|
|
1946
|
+
for (const dependency of edges.get(key) ?? []) visit(dependency);
|
|
1947
|
+
visiting.delete(key);
|
|
1948
|
+
visited.add(key);
|
|
1949
|
+
};
|
|
1950
|
+
for (const key of keys) visit(key);
|
|
1951
|
+
}
|
|
1952
|
+
function jobsOrchestrationCheck() {
|
|
1953
|
+
return {
|
|
1954
|
+
id: "jobs-orchestration",
|
|
1955
|
+
description: "Lakeflow Jobs multi-task orchestration reaches expected task outcomes",
|
|
1956
|
+
configured: (env) => Boolean(env.DBX_TEST_JOBS_ORCHESTRATION_JSON),
|
|
1957
|
+
async run({ env, client }) {
|
|
1958
|
+
const configured = env.DBX_TEST_JOBS_ORCHESTRATION_JSON;
|
|
1959
|
+
if (!configured) throw new Error("DBX_TEST_JOBS_ORCHESTRATION_JSON is required");
|
|
1960
|
+
const spec = parseJobOrchestrationLiveSpec(configured);
|
|
1961
|
+
const adapter = new DatabricksJobsAdapter(client);
|
|
1962
|
+
const runId = await adapter.submit(spec.request);
|
|
1963
|
+
let run = await adapter.wait(runId, {
|
|
1964
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
1965
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
1966
|
+
});
|
|
1967
|
+
let repairId;
|
|
1968
|
+
if (spec.repairFailedTasks && run.resultState !== "SUCCESS") {
|
|
1969
|
+
const failed = run.tasks.filter((task) => task.resultState === "FAILED").map((task) => task.taskKey);
|
|
1970
|
+
repairId = await adapter.repair(runId, {
|
|
1971
|
+
rerunTasks: failed.length > 0 ? failed : void 0,
|
|
1972
|
+
rerunDependentTasks: true
|
|
1973
|
+
});
|
|
1974
|
+
run = await adapter.waitForRepair(runId, repairId, {
|
|
1975
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
1976
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
const expectedResult = spec.expectedResult ?? "SUCCESS";
|
|
1980
|
+
if (run.resultState !== expectedResult) {
|
|
1981
|
+
throw new Error(
|
|
1982
|
+
`Job orchestration run ${runId} ended in ${run.resultState}, expected ${expectedResult}`
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
for (const [taskKey, expected] of Object.entries(spec.expectedTasks ?? {})) {
|
|
1986
|
+
const task = run.tasks.find((candidate) => candidate.taskKey === taskKey);
|
|
1987
|
+
if (!task) throw new Error(`Job orchestration result did not include task '${taskKey}'`);
|
|
1988
|
+
if (task.resultState !== expected) {
|
|
1989
|
+
throw new Error(`Task '${taskKey}' ended in ${task.resultState}, expected ${expected}`);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
let cancellation;
|
|
1993
|
+
if (spec.cancellation) {
|
|
1994
|
+
const cancellationRunId = await adapter.submit(spec.cancellation.request);
|
|
1995
|
+
await adapter.waitUntilActive(cancellationRunId, {
|
|
1996
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
1997
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
1998
|
+
});
|
|
1999
|
+
await adapter.cancel(cancellationRunId);
|
|
2000
|
+
const canceled = await adapter.wait(cancellationRunId, {
|
|
2001
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
2002
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
2003
|
+
});
|
|
2004
|
+
const expectedCancellation = spec.cancellation.expectedResult ?? "CANCELED";
|
|
2005
|
+
if (canceled.resultState !== expectedCancellation) {
|
|
2006
|
+
throw new Error(
|
|
2007
|
+
`Cancellation run ${cancellationRunId} ended in ${canceled.resultState}, expected ${expectedCancellation}`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
cancellation = { runId: cancellationRunId, resultState: canceled.resultState };
|
|
2011
|
+
}
|
|
2012
|
+
return {
|
|
2013
|
+
runId,
|
|
2014
|
+
...repairId ? { repairId } : {},
|
|
2015
|
+
resultState: run.resultState,
|
|
2016
|
+
tasks: run.tasks,
|
|
2017
|
+
...cancellation ? { cancellation } : {}
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
function parseJobOrchestrationLiveSpec(value) {
|
|
2023
|
+
let parsed;
|
|
2024
|
+
try {
|
|
2025
|
+
parsed = JSON.parse(value);
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
throw new Error(`DBX_TEST_JOBS_ORCHESTRATION_JSON is invalid JSON: ${String(error)}`);
|
|
2028
|
+
}
|
|
2029
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2030
|
+
throw new Error("Jobs orchestration spec must be an object");
|
|
2031
|
+
}
|
|
2032
|
+
const spec = parsed;
|
|
2033
|
+
if (!spec.request || typeof spec.request !== "object" || !Array.isArray(spec.request.tasks)) {
|
|
2034
|
+
throw new Error("Jobs orchestration spec requires request.tasks");
|
|
2035
|
+
}
|
|
2036
|
+
validateJobGraph(spec.request.tasks);
|
|
2037
|
+
if (spec.cancellation) {
|
|
2038
|
+
if (typeof spec.cancellation !== "object" || !spec.cancellation.request || !Array.isArray(spec.cancellation.request.tasks)) {
|
|
2039
|
+
throw new Error("Jobs orchestration cancellation requires request.tasks");
|
|
2040
|
+
}
|
|
2041
|
+
validateJobGraph(spec.cancellation.request.tasks);
|
|
2042
|
+
}
|
|
2043
|
+
return spec;
|
|
2044
|
+
}
|
|
2045
|
+
function normalizeJobRun(fallbackRunId, raw) {
|
|
2046
|
+
const state = objectOf(raw.state);
|
|
2047
|
+
const repairEntries = Array.isArray(raw.repair_history) ? raw.repair_history.filter(
|
|
2048
|
+
(repair) => Boolean(repair && typeof repair === "object")
|
|
2049
|
+
) : [];
|
|
2050
|
+
const taskRunRanks = /* @__PURE__ */ new Map();
|
|
2051
|
+
for (const [rank, repair] of repairEntries.entries()) {
|
|
2052
|
+
for (const runId of numberArray(repair.task_run_ids)) taskRunRanks.set(runId, rank);
|
|
2053
|
+
}
|
|
2054
|
+
const taskAttempts = Array.isArray(raw.tasks) ? raw.tasks.filter(
|
|
2055
|
+
(task) => Boolean(task && typeof task === "object")
|
|
2056
|
+
).map((task) => {
|
|
2057
|
+
const taskState = objectOf(task.state);
|
|
2058
|
+
return {
|
|
2059
|
+
taskKey: String(task.task_key ?? "unknown"),
|
|
2060
|
+
...Number.isFinite(Number(task.run_id)) ? { runId: Number(task.run_id) } : {},
|
|
2061
|
+
lifeCycleState: String(taskState.life_cycle_state ?? "UNKNOWN"),
|
|
2062
|
+
resultState: String(taskState.result_state ?? "UNKNOWN"),
|
|
2063
|
+
...typeof taskState.state_message === "string" ? { stateMessage: taskState.state_message } : {},
|
|
2064
|
+
...Number.isFinite(Number(task.attempt_number)) ? { attemptNumber: Number(task.attempt_number) } : {}
|
|
2065
|
+
};
|
|
2066
|
+
}) : [];
|
|
2067
|
+
const latestTasks = /* @__PURE__ */ new Map();
|
|
2068
|
+
for (const task of taskAttempts) {
|
|
2069
|
+
const current = latestTasks.get(task.taskKey);
|
|
2070
|
+
const currentAttempt = current?.attemptNumber ?? -1;
|
|
2071
|
+
const candidateAttempt = task.attemptNumber ?? -1;
|
|
2072
|
+
const currentRank = current?.runId ? taskRunRanks.get(current.runId) ?? -1 : -1;
|
|
2073
|
+
const candidateRank = task.runId ? taskRunRanks.get(task.runId) ?? -1 : -1;
|
|
2074
|
+
if (!current || candidateAttempt > currentAttempt || candidateAttempt === currentAttempt && candidateRank >= currentRank) {
|
|
2075
|
+
latestTasks.set(task.taskKey, task);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
const tasks = [...latestTasks.values()];
|
|
2079
|
+
const repairs = repairEntries.map((repair) => {
|
|
2080
|
+
const repairState = objectOf(repair.state);
|
|
2081
|
+
return {
|
|
2082
|
+
repairId: Number(repair.id ?? repair.repair_id ?? 0),
|
|
2083
|
+
type: String(repair.type ?? "UNKNOWN"),
|
|
2084
|
+
lifeCycleState: String(repairState.life_cycle_state ?? "UNKNOWN"),
|
|
2085
|
+
resultState: String(repairState.result_state ?? "UNKNOWN"),
|
|
2086
|
+
taskRunIds: numberArray(repair.task_run_ids)
|
|
2087
|
+
};
|
|
2088
|
+
});
|
|
2089
|
+
return {
|
|
2090
|
+
runId: Number(raw.run_id ?? fallbackRunId),
|
|
2091
|
+
lifeCycleState: String(state.life_cycle_state ?? "UNKNOWN"),
|
|
2092
|
+
resultState: String(state.result_state ?? "UNKNOWN"),
|
|
2093
|
+
tasks,
|
|
2094
|
+
repairs,
|
|
2095
|
+
raw
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
function isTerminal(state) {
|
|
2099
|
+
return ["TERMINATED", "SKIPPED", "INTERNAL_ERROR", "BLOCKED"].includes(state);
|
|
2100
|
+
}
|
|
2101
|
+
function delay2(ms) {
|
|
2102
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2103
|
+
}
|
|
2104
|
+
function objectOf(value) {
|
|
2105
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2106
|
+
}
|
|
2107
|
+
function numberArray(value) {
|
|
2108
|
+
return Array.isArray(value) ? value.map(Number).filter((candidate) => Number.isFinite(candidate)) : [];
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/jobs-certification-check.ts
|
|
2112
|
+
var NOTEBOOKS = {
|
|
2113
|
+
seed: `# Databricks notebook source
|
|
2114
|
+
dbutils.jobs.taskValues.set(key="branch", value="run")
|
|
2115
|
+
dbutils.jobs.taskValues.set(key="items", value=["alpha", "beta"])
|
|
2116
|
+
dbutils.notebook.exit("seeded")
|
|
2117
|
+
`,
|
|
2118
|
+
process: `# Databricks notebook source
|
|
2119
|
+
dbutils.widgets.text("item", "")
|
|
2120
|
+
item = dbutils.widgets.get("item")
|
|
2121
|
+
assert item, "item parameter is required"
|
|
2122
|
+
dbutils.notebook.exit(item)
|
|
2123
|
+
`,
|
|
2124
|
+
retry: `# Databricks notebook source
|
|
2125
|
+
dbutils.widgets.text("execution_count", "0")
|
|
2126
|
+
execution_count = int(dbutils.widgets.get("execution_count"))
|
|
2127
|
+
if execution_count <= 1:
|
|
2128
|
+
raise RuntimeError("intentional retryable failure")
|
|
2129
|
+
dbutils.notebook.exit("retried")
|
|
2130
|
+
`,
|
|
2131
|
+
repair: `# Databricks notebook source
|
|
2132
|
+
dbutils.widgets.text("repair_count", "0")
|
|
2133
|
+
repair_count = int(dbutils.widgets.get("repair_count"))
|
|
2134
|
+
if repair_count == 0:
|
|
2135
|
+
raise RuntimeError("intentional first-run failure")
|
|
2136
|
+
dbutils.notebook.exit("repaired")
|
|
2137
|
+
`,
|
|
2138
|
+
sleep: `# Databricks notebook source
|
|
2139
|
+
import time
|
|
2140
|
+
time.sleep(300)
|
|
2141
|
+
`
|
|
2142
|
+
};
|
|
2143
|
+
function jobsCertificationCheck() {
|
|
2144
|
+
return {
|
|
2145
|
+
id: "jobs-certification",
|
|
2146
|
+
description: "Disposable Lakeflow Jobs branch, loop, failure, repair, cancellation, and cleanup certification",
|
|
2147
|
+
configured: (env) => env.DBX_TEST_JOBS_CERTIFY === "1",
|
|
2148
|
+
async run({ env, client }) {
|
|
2149
|
+
const root = `${env.DBX_TEST_JOBS_FIXTURE_ROOT ?? "/Workspace/Shared/fabric-experiments-target-pack"}/${crypto.randomUUID()}`;
|
|
2150
|
+
const paths = Object.fromEntries(
|
|
2151
|
+
Object.keys(NOTEBOOKS).map((name) => [name, `${root}/${name}`])
|
|
2152
|
+
);
|
|
2153
|
+
try {
|
|
2154
|
+
await importNotebooks(client, paths);
|
|
2155
|
+
const adapter = new DatabricksJobsAdapter(client);
|
|
2156
|
+
const runId = await adapter.submit(certificationRequest(paths));
|
|
2157
|
+
const initial = await adapter.wait(runId, waitOptions(env));
|
|
2158
|
+
if (initial.resultState !== "FAILED") {
|
|
2159
|
+
throw new Error(
|
|
2160
|
+
`Certification run ${runId} expected FAILED before repair, got ${initial.resultState}`
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
const retried = assertTask(initial.tasks, "retry", "SUCCESS");
|
|
2164
|
+
if ((retried.attemptNumber ?? 0) < 1) {
|
|
2165
|
+
throw new Error("Certification retry task did not record a second attempt");
|
|
2166
|
+
}
|
|
2167
|
+
assertTask(initial.tasks, "seed", "SUCCESS");
|
|
2168
|
+
assertTask(initial.tasks, "branch", "SUCCESS");
|
|
2169
|
+
assertTask(initial.tasks, "foreach", "SUCCESS");
|
|
2170
|
+
assertTask(initial.tasks, "repair", "FAILED");
|
|
2171
|
+
const repairId = await adapter.repair(runId, {
|
|
2172
|
+
rerunTasks: ["repair"],
|
|
2173
|
+
rerunDependentTasks: true
|
|
2174
|
+
});
|
|
2175
|
+
const repaired = await adapter.waitForRepair(runId, repairId, waitOptions(env));
|
|
2176
|
+
if (repaired.resultState !== "SUCCESS") {
|
|
2177
|
+
throw new Error(`Certification repair ${repairId} ended in ${repaired.resultState}`);
|
|
2178
|
+
}
|
|
2179
|
+
assertTask(repaired.tasks, "repair", "SUCCESS");
|
|
2180
|
+
const cancellationRunId = await adapter.submit(cancellationRequest(paths));
|
|
2181
|
+
await adapter.waitUntilActive(cancellationRunId, waitOptions(env));
|
|
2182
|
+
await adapter.cancel(cancellationRunId);
|
|
2183
|
+
const canceled = await adapter.wait(cancellationRunId, waitOptions(env));
|
|
2184
|
+
if (canceled.resultState !== "CANCELED") {
|
|
2185
|
+
throw new Error(`Certification cancellation ended in ${canceled.resultState}`);
|
|
2186
|
+
}
|
|
2187
|
+
return {
|
|
2188
|
+
runId,
|
|
2189
|
+
repairId,
|
|
2190
|
+
cancellationRunId,
|
|
2191
|
+
taskValues: true,
|
|
2192
|
+
retry: true,
|
|
2193
|
+
condition: true,
|
|
2194
|
+
forEach: true,
|
|
2195
|
+
repair: true,
|
|
2196
|
+
cancellation: true,
|
|
2197
|
+
fixtureRoot: root
|
|
2198
|
+
};
|
|
2199
|
+
} finally {
|
|
2200
|
+
await client.post("/api/2.0/workspace/delete", { path: root, recursive: true });
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
async function importNotebooks(client, paths) {
|
|
2206
|
+
await client.post("/api/2.0/workspace/mkdirs", {
|
|
2207
|
+
path: paths.seed.slice(0, paths.seed.lastIndexOf("/"))
|
|
2208
|
+
});
|
|
2209
|
+
for (const [name, source] of Object.entries(NOTEBOOKS)) {
|
|
2210
|
+
await client.post("/api/2.0/workspace/import", {
|
|
2211
|
+
path: paths[name],
|
|
2212
|
+
format: "SOURCE",
|
|
2213
|
+
language: "PYTHON",
|
|
2214
|
+
overwrite: true,
|
|
2215
|
+
content: Buffer.from(source).toString("base64")
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
function certificationRequest(paths) {
|
|
2220
|
+
return {
|
|
2221
|
+
run_name: "fabric-experiments-jobs-certification",
|
|
2222
|
+
tasks: [
|
|
2223
|
+
{
|
|
2224
|
+
task_key: "retry",
|
|
2225
|
+
notebook_task: {
|
|
2226
|
+
notebook_path: paths.retry,
|
|
2227
|
+
base_parameters: { execution_count: "{{task.execution_count}}" }
|
|
2228
|
+
},
|
|
2229
|
+
max_retries: 1
|
|
2230
|
+
},
|
|
2231
|
+
{
|
|
2232
|
+
task_key: "seed",
|
|
2233
|
+
depends_on: [{ task_key: "retry" }],
|
|
2234
|
+
notebook_task: { notebook_path: paths.seed }
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
task_key: "branch",
|
|
2238
|
+
depends_on: [{ task_key: "seed" }],
|
|
2239
|
+
condition_task: {
|
|
2240
|
+
left: "{{tasks.seed.values.branch}}",
|
|
2241
|
+
op: "EQUAL_TO",
|
|
2242
|
+
right: "run"
|
|
2243
|
+
}
|
|
2244
|
+
},
|
|
2245
|
+
{
|
|
2246
|
+
task_key: "foreach",
|
|
2247
|
+
depends_on: [{ task_key: "branch", outcome: "true" }],
|
|
2248
|
+
for_each_task: {
|
|
2249
|
+
inputs: "{{tasks.seed.values.items}}",
|
|
2250
|
+
concurrency: 2,
|
|
2251
|
+
task: {
|
|
2252
|
+
task_key: "process_item",
|
|
2253
|
+
notebook_task: {
|
|
2254
|
+
notebook_path: paths.process,
|
|
2255
|
+
base_parameters: { item: "{{input}}" }
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
task_key: "repair",
|
|
2262
|
+
depends_on: [{ task_key: "foreach" }],
|
|
2263
|
+
notebook_task: {
|
|
2264
|
+
notebook_path: paths.repair,
|
|
2265
|
+
base_parameters: { repair_count: "{{job.repair_count}}" }
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
]
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
function cancellationRequest(paths) {
|
|
2272
|
+
return {
|
|
2273
|
+
run_name: "fabric-experiments-jobs-cancellation-certification",
|
|
2274
|
+
tasks: [{ task_key: "sleep", notebook_task: { notebook_path: paths.sleep } }]
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
function waitOptions(env) {
|
|
2278
|
+
return {
|
|
2279
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
2280
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 5e3)
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
function assertTask(tasks, taskKey, resultState) {
|
|
2284
|
+
const task = tasks.find((candidate) => candidate.taskKey === taskKey);
|
|
2285
|
+
if (!task) throw new Error(`Certification result did not contain task '${taskKey}'`);
|
|
2286
|
+
if (task.resultState !== resultState) {
|
|
2287
|
+
throw new Error(
|
|
2288
|
+
`Certification task '${taskKey}' ended in ${task.resultState}, expected ${resultState}`
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
return task;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
// src/jobs-target-pack.ts
|
|
2295
|
+
function createLakeflowJobsTargetPack() {
|
|
2296
|
+
return defineTargetPack({
|
|
2297
|
+
id: "lakeflow-jobs",
|
|
2298
|
+
name: "Lakeflow Jobs orchestration",
|
|
2299
|
+
description: "Typed multi-task DAG, branch/loop, retry, cancellation, repair and task-outcome validation.",
|
|
2300
|
+
version: "0.2.0",
|
|
2301
|
+
maturity: "live-gated",
|
|
2302
|
+
capabilities: [
|
|
2303
|
+
"jobs.multi-task-dag",
|
|
2304
|
+
"jobs.dependencies",
|
|
2305
|
+
"jobs.condition-task",
|
|
2306
|
+
"jobs.for-each-task",
|
|
2307
|
+
"jobs.retry",
|
|
2308
|
+
"jobs.cancel",
|
|
2309
|
+
"jobs.repair",
|
|
2310
|
+
"jobs.task-outcomes"
|
|
2311
|
+
],
|
|
2312
|
+
checks: () => [
|
|
2313
|
+
restrictedPrincipalCheck(),
|
|
2314
|
+
jobRunCheck(),
|
|
2315
|
+
jobsOrchestrationCheck(),
|
|
2316
|
+
jobsCertificationCheck()
|
|
2317
|
+
],
|
|
2318
|
+
requiredChecks: ["jobs-orchestration"],
|
|
2319
|
+
requirements: [
|
|
2320
|
+
{
|
|
2321
|
+
id: "workspace",
|
|
2322
|
+
description: "Databricks workspace host",
|
|
2323
|
+
anyOf: ["DATABRICKS_HOST"]
|
|
2324
|
+
},
|
|
2325
|
+
{
|
|
2326
|
+
id: "authentication",
|
|
2327
|
+
description: "Databricks OAuth, OIDC, bearer, or PAT authentication",
|
|
2328
|
+
anyOf: [
|
|
2329
|
+
"DATABRICKS_BEARER",
|
|
2330
|
+
"DATABRICKS_TOKEN",
|
|
2331
|
+
"DATABRICKS_CLIENT_ID",
|
|
2332
|
+
"DATABRICKS_OIDC_TOKEN",
|
|
2333
|
+
"DATABRICKS_OIDC_TOKEN_FILEPATH"
|
|
2334
|
+
],
|
|
2335
|
+
secret: true
|
|
2336
|
+
},
|
|
2337
|
+
{
|
|
2338
|
+
id: "orchestration-spec",
|
|
2339
|
+
description: "JSON multi-task orchestration scenario",
|
|
2340
|
+
anyOf: ["DBX_TEST_JOBS_ORCHESTRATION_JSON"],
|
|
2341
|
+
required: false
|
|
2342
|
+
}
|
|
2343
|
+
],
|
|
2344
|
+
docsUrl: "https://experiments.fabric.pro/docs/testing/target-packs/#lakeflow-jobs-orchestration",
|
|
2345
|
+
certificationScopes: [
|
|
2346
|
+
"Azure Databricks serverless Jobs, West US, workspace OAuth, public network path"
|
|
2347
|
+
]
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// src/builtin-packs.ts
|
|
2352
|
+
function createBuiltinTargetPacks() {
|
|
2353
|
+
return [...createFoundationTargetPacks(), createLakeflowJobsTargetPack()];
|
|
2354
|
+
}
|
|
2355
|
+
var builtinTargetPacks = createBuiltinTargetPacks();
|
|
2356
|
+
|
|
1538
2357
|
Object.defineProperty(exports, "waitForDatabricksRun", {
|
|
1539
2358
|
enumerable: true,
|
|
1540
2359
|
get: function () { return databricks.waitForDatabricksRun; }
|
|
@@ -1543,36 +2362,55 @@ Object.defineProperty(exports, "waitForDatabricksStatement", {
|
|
|
1543
2362
|
enumerable: true,
|
|
1544
2363
|
get: function () { return databricks.waitForDatabricksStatement; }
|
|
1545
2364
|
});
|
|
2365
|
+
exports.DatabricksJobsAdapter = DatabricksJobsAdapter;
|
|
1546
2366
|
exports.appHealthCheck = appHealthCheck;
|
|
1547
2367
|
exports.assertDeltaContract = assertDeltaContract;
|
|
1548
2368
|
exports.autoLoaderIngestionCheck = autoLoaderIngestionCheck;
|
|
1549
2369
|
exports.backupRestoreCheck = backupRestoreCheck;
|
|
2370
|
+
exports.builtinTargetPacks = builtinTargetPacks;
|
|
1550
2371
|
exports.classicComputeCheck = classicComputeCheck;
|
|
1551
2372
|
exports.compareToGolden = compareToGolden;
|
|
2373
|
+
exports.createAppsOperationalFoundationPack = createAppsOperationalFoundationPack;
|
|
2374
|
+
exports.createBuiltinTargetPacks = createBuiltinTargetPacks;
|
|
1552
2375
|
exports.createClientFromEnv = createClientFromEnv;
|
|
1553
2376
|
exports.createDuckDbContext = createDuckDbContext;
|
|
1554
2377
|
exports.createEphemeralLakebaseBranch = createEphemeralLakebaseBranch;
|
|
1555
2378
|
exports.createExecutionContext = createExecutionContext;
|
|
2379
|
+
exports.createFoundationTargetPacks = createFoundationTargetPacks;
|
|
2380
|
+
exports.createJobsCodeFoundationPack = createJobsCodeFoundationPack;
|
|
1556
2381
|
exports.createLakebaseTestProvider = createLakebaseTestProvider;
|
|
2382
|
+
exports.createLakeflowFoundationPack = createLakeflowFoundationPack;
|
|
2383
|
+
exports.createLakeflowJobsTargetPack = createLakeflowJobsTargetPack;
|
|
1557
2384
|
exports.createMockDatabricksClient = createMockDatabricksClient;
|
|
2385
|
+
exports.createSqlDeltaFoundationPack = createSqlDeltaFoundationPack;
|
|
2386
|
+
exports.createTargetPackRegistry = createTargetPackRegistry;
|
|
2387
|
+
exports.createUnityStorageFoundationPack = createUnityStorageFoundationPack;
|
|
1558
2388
|
exports.createWorkspaceContext = createWorkspaceContext;
|
|
1559
2389
|
exports.customLiveCheck = customLiveCheck;
|
|
1560
2390
|
exports.dbtBuildCheck = dbtBuildCheck;
|
|
1561
2391
|
exports.defineLiveSuite = defineLiveSuite;
|
|
2392
|
+
exports.defineTargetPack = defineTargetPack;
|
|
2393
|
+
exports.defineTargetPackRun = defineTargetPackRun;
|
|
1562
2394
|
exports.deleteEphemeralLakebaseBranch = deleteEphemeralLakebaseBranch;
|
|
1563
2395
|
exports.deleteVolumeFile = deleteVolumeFile;
|
|
1564
2396
|
exports.deltaContractCheck = deltaContractCheck;
|
|
2397
|
+
exports.doctorTargetPack = doctorTargetPack;
|
|
1565
2398
|
exports.ensureVolumeDirectory = ensureVolumeDirectory;
|
|
1566
2399
|
exports.expectQueryToMatchGolden = expectQueryToMatchGolden;
|
|
1567
2400
|
exports.expectTable = expectTable;
|
|
1568
2401
|
exports.failureRecoveryCheck = failureRecoveryCheck;
|
|
2402
|
+
exports.foundationTargetPacks = foundationTargetPacks;
|
|
1569
2403
|
exports.jobRunCheck = jobRunCheck;
|
|
2404
|
+
exports.jobsCertificationCheck = jobsCertificationCheck;
|
|
2405
|
+
exports.jobsOrchestrationCheck = jobsOrchestrationCheck;
|
|
1570
2406
|
exports.lakebaseCredentialCheck = lakebaseCredentialCheck;
|
|
1571
2407
|
exports.lakebaseRoundTripCheck = lakebaseRoundTripCheck;
|
|
2408
|
+
exports.normalizeJobRun = normalizeJobRun;
|
|
1572
2409
|
exports.normalizeRow = normalizeRow;
|
|
1573
2410
|
exports.normalizeVolumeRoot = normalizeVolumeRoot;
|
|
1574
2411
|
exports.notebookSubmitCheck = notebookSubmitCheck;
|
|
1575
2412
|
exports.parseFixtureColumns = parseFixtureColumns;
|
|
2413
|
+
exports.parseJobOrchestrationLiveSpec = parseJobOrchestrationLiveSpec;
|
|
1576
2414
|
exports.parseStatementRows = parseStatementRows;
|
|
1577
2415
|
exports.performanceBudgetCheck = performanceBudgetCheck;
|
|
1578
2416
|
exports.pipelineRefreshCheck = pipelineRefreshCheck;
|
|
@@ -1584,6 +2422,7 @@ exports.resolveTokenProvider = resolveTokenProvider;
|
|
|
1584
2422
|
exports.restrictedPrincipalCheck = restrictedPrincipalCheck;
|
|
1585
2423
|
exports.rollbackCheck = rollbackCheck;
|
|
1586
2424
|
exports.runLiveSuite = runLiveSuite;
|
|
2425
|
+
exports.runTargetPack = runTargetPack;
|
|
1587
2426
|
exports.secretRotationCheck = secretRotationCheck;
|
|
1588
2427
|
exports.secretScopeCheck = secretScopeCheck;
|
|
1589
2428
|
exports.serverlessComputeCheck = serverlessComputeCheck;
|
|
@@ -1592,6 +2431,7 @@ exports.toLakebaseBranchId = toLakebaseBranchId;
|
|
|
1592
2431
|
exports.toNamedParameters = toNamedParameters;
|
|
1593
2432
|
exports.unityCatalogAccessCheck = unityCatalogAccessCheck;
|
|
1594
2433
|
exports.uploadVolumeFile = uploadVolumeFile;
|
|
2434
|
+
exports.validateJobGraph = validateJobGraph;
|
|
1595
2435
|
exports.volumeIOCheck = volumeIOCheck;
|
|
1596
2436
|
exports.waitForPipelineUpdate = waitForPipelineUpdate;
|
|
1597
2437
|
//# sourceMappingURL=index.cjs.map
|