@ingestron/pack-databricks 1.1.0 → 2.0.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 +132 -15
- package/dist/build.d.ts +25 -1
- package/dist/build.js +46 -19
- package/dist/bundle.d.ts +18 -4
- package/dist/bundle.js +48 -13
- package/dist/connect.d.ts +2 -0
- package/dist/connect.js +4 -0
- package/dist/fold-files.d.ts +12 -0
- package/dist/fold-files.js +190 -0
- package/dist/fold-names.d.ts +16 -0
- package/dist/fold-names.js +73 -0
- package/dist/fold-plan.d.ts +15 -0
- package/dist/fold-plan.js +156 -0
- package/dist/fold-problems.d.ts +29 -0
- package/dist/fold-problems.js +113 -0
- package/dist/fold-python.d.ts +17 -0
- package/dist/fold-python.js +108 -0
- package/dist/fold-target.d.ts +29 -0
- package/dist/fold-target.js +97 -0
- package/dist/fold-text.d.ts +17 -0
- package/dist/fold-text.js +87 -0
- package/dist/fold-values.d.ts +54 -0
- package/dist/fold-values.js +98 -0
- package/dist/fold.d.ts +8 -0
- package/dist/fold.js +69 -0
- package/dist/pack.d.ts +3 -2
- package/dist/pack.js +26 -3
- package/dist/python-data.d.ts +3 -3
- package/dist/python-data.js +5 -7
- package/dist/sql-data.d.ts +12 -5
- package/dist/sql-data.js +19 -10
- package/dist/sql.d.ts +5 -4
- package/dist/sql.js +16 -16
- package/dist/view.d.ts +16 -0
- package/dist/view.js +173 -0
- package/dist/walk.d.ts +7 -0
- package/dist/walk.js +22 -0
- package/package.json +4 -4
- package/templates/apply-contract.py.liquid +14 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The Databricks pack: from resolved Ingestron definitions it generates a
|
|
|
4
4
|
[Declarative Automation Bundle](https://docs.databricks.com/aws/en/dev-tools/bundles/)
|
|
5
5
|
(formerly Databricks Asset Bundle) with one
|
|
6
6
|
[Lakeflow Spark Declarative Pipeline](https://docs.databricks.com/aws/en/ldp/concepts/)
|
|
7
|
-
per project. Name it in your standard: `packs: [databricks@
|
|
7
|
+
per project. Name it in your standard: `packs: [databricks@2]`.
|
|
8
8
|
|
|
9
9
|
What it uses on Databricks:
|
|
10
10
|
|
|
@@ -23,24 +23,33 @@ What it uses on Databricks:
|
|
|
23
23
|
- **Serverless pipelines**, a scheduled refresh job, Unity Catalog schemas
|
|
24
24
|
and grants when you ask for them, permissions, tags and notifications.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
One bundle covers every environment, with a target per environment, as
|
|
27
|
+
Databricks teams lay out bundles by hand. A value is a bundle variable only
|
|
28
|
+
where environments differ; everything the same in every environment is written
|
|
29
|
+
as it is. Each target sets its own variables, so the `targets` block shows
|
|
30
|
+
exactly what changes between dev and prod.
|
|
28
31
|
|
|
29
32
|
## Example
|
|
30
33
|
|
|
31
|
-
Most people use this through the [`ingestron`](https://www.npmjs.com/package/ingestron) command
|
|
34
|
+
Most people use this through the [`ingestron`](https://www.npmjs.com/package/ingestron) command, which writes the bundle to `build/databricks/`. In code:
|
|
32
35
|
|
|
33
36
|
```ts
|
|
34
|
-
import { loadDefinitions } from "@ingestron/definitions";
|
|
37
|
+
import { loadDefinitions, type ResolvedModel } from "@ingestron/definitions";
|
|
35
38
|
import { databricks } from "@ingestron/pack-databricks";
|
|
36
39
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const models: Record<string, ResolvedModel> = {};
|
|
41
|
+
for (const environment of ["dev", "prod"]) {
|
|
42
|
+
const result = loadDefinitions("definitions", {
|
|
43
|
+
packs: [databricks],
|
|
44
|
+
environment,
|
|
45
|
+
deployTime: true,
|
|
46
|
+
environmentsTogether: true,
|
|
47
|
+
});
|
|
48
|
+
if (result.model) models[environment] = result.model;
|
|
43
49
|
}
|
|
50
|
+
const { files, problems } = databricks.buildEnvironments!(models);
|
|
51
|
+
for (const problem of problems) console.log(problem.code, problem.message);
|
|
52
|
+
for (const path of Object.keys(files)) console.log(path);
|
|
44
53
|
```
|
|
45
54
|
|
|
46
55
|
For the retail example this prints:
|
|
@@ -54,10 +63,114 @@ src/retail/silver/daily_sales.sql
|
|
|
54
63
|
resources/retail_refresh.job.yml
|
|
55
64
|
```
|
|
56
65
|
|
|
66
|
+
The values that differ between dev and prod are in `databricks.yml`:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
variables:
|
|
70
|
+
catalog:
|
|
71
|
+
description: Catalog of the retail tables.
|
|
72
|
+
shop_path:
|
|
73
|
+
description: Folder the files of connection "shop" are read from.
|
|
74
|
+
targets:
|
|
75
|
+
dev:
|
|
76
|
+
mode: development
|
|
77
|
+
default: true
|
|
78
|
+
workspace:
|
|
79
|
+
host: https://adb-1111111111111111.1.azuredatabricks.net
|
|
80
|
+
# run_as and permissions, as the platform sets them
|
|
81
|
+
presets:
|
|
82
|
+
tags:
|
|
83
|
+
environment: dev
|
|
84
|
+
variables:
|
|
85
|
+
catalog: dev_sales
|
|
86
|
+
shop_path: abfss://landing@retaildev.dfs.core.windows.net/shop
|
|
87
|
+
prod:
|
|
88
|
+
mode: production
|
|
89
|
+
# prod's workspace, run_as, permissions, presets and variables
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The pipeline reads them as `catalog: ${var.catalog}` and as the configuration
|
|
93
|
+
value `ingestron.shop_path`, which its SQL uses as `${ingestron.shop_path}`.
|
|
94
|
+
Tables in the pipeline's catalog are named with two parts, such as
|
|
95
|
+
`bronze.orders`.
|
|
96
|
+
|
|
97
|
+
## Deploy
|
|
98
|
+
|
|
99
|
+
From the bundle folder, deploy one environment at a time, the same files each
|
|
100
|
+
time:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
cd build/databricks
|
|
104
|
+
databricks bundle validate -t prod
|
|
105
|
+
databricks bundle deploy -t prod
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
A value written as `${envvar:NAME}` in the definitions is supplied when
|
|
109
|
+
deploying: the bundle declares it as a variable that the targets leave unset,
|
|
110
|
+
and the deploying CI pipeline sets `BUNDLE_VAR_<name>` (`name` is NAME in lower
|
|
111
|
+
case). A workspace host supplied this way is `DATABRICKS_HOST`.
|
|
112
|
+
|
|
113
|
+
`prerequisites/<environment>.sql` holds the grants an administrator runs once
|
|
114
|
+
in that environment. It is not deployed, and neither are `.generated.json` and
|
|
115
|
+
`.generated.lock`, which `ingestron` keeps in the folder.
|
|
116
|
+
|
|
117
|
+
The after-refresh task (tags, column comments and table checks) is
|
|
118
|
+
`tasks/<pipeline>/apply_contract.py`, outside the pipeline's `src/<pipeline>/`
|
|
119
|
+
sources, so that only the refresh job runs it.
|
|
120
|
+
|
|
121
|
+
## What changed in 2.0
|
|
122
|
+
|
|
123
|
+
- One bundle in `build/databricks/` for every environment, in place of a
|
|
124
|
+
bundle per environment in `build/<environment>/`. Change the standard to
|
|
125
|
+
`databricks@2`.
|
|
126
|
+
- The bundle name, pipeline and job keys, target names and `root_path` are the
|
|
127
|
+
same as in 1.x, so the first `bundle deploy -t dev` or `-t prod` updates
|
|
128
|
+
that environment's resources in place. Run `bundle validate` and
|
|
129
|
+
`bundle summary` first.
|
|
130
|
+
- The mode is `production` for every environment except `dev`, unless the
|
|
131
|
+
platform sets it. In 1.x, environments such as `test`, `uat` and `staging`
|
|
132
|
+
were in `development` mode. On their first 2.0 deploy, jobs and pipelines
|
|
133
|
+
lose the `[dev <identity>]` prefix in their names, pipelines are no longer in
|
|
134
|
+
development, managed schemas lose their `dev_<identity>_` prefix (the old
|
|
135
|
+
schema is deleted after approval, and the pipelines write to a new one), and
|
|
136
|
+
production mode wants `run_as` and `permissions` unless a service principal
|
|
137
|
+
deploys. To keep `development` mode for such an environment, write
|
|
138
|
+
`mode: ${values.mode}` in the platform, `mode: production` in project.yaml's
|
|
139
|
+
`values`, and `mode: development` in that environment's `values`.
|
|
140
|
+
- Managed schemas are keyed by layer (`bronze`), or by schema name when a
|
|
141
|
+
layer has several (`bronze_shop`), not `<catalog>_<schema>`. Each
|
|
142
|
+
environment's deployment state still holds the old key, so with
|
|
143
|
+
`manage.schemas`, before the first 2.0 deploy, for each environment and each
|
|
144
|
+
managed schema:
|
|
145
|
+
1. in the 1.x folder `build/<environment>/`, run
|
|
146
|
+
`databricks bundle deployment unbind <catalog>_<schema> -t <environment>`;
|
|
147
|
+
2. in `build/databricks/`, run
|
|
148
|
+
`databricks bundle deployment bind <key> <catalog>.<schema> -t <environment>`,
|
|
149
|
+
with the key from `resources/schemas.yml`;
|
|
150
|
+
3. run `bundle validate` and `bundle summary`, and check that the deploy
|
|
151
|
+
deletes no schema.
|
|
152
|
+
|
|
153
|
+
Without this, the deploy plans to delete each schema under its old key.
|
|
154
|
+
|
|
155
|
+
- The after-refresh script moved from `src/<pipeline>/apply_contract.py`,
|
|
156
|
+
where the pipeline also loaded it as its own code, to
|
|
157
|
+
`tasks/<pipeline>/apply_contract.py`. The first deploy removes the old file.
|
|
158
|
+
- Pipeline code names tables in the pipeline's catalog with two parts.
|
|
159
|
+
- `prerequisites.sql` is `prerequisites/<environment>.sql`.
|
|
160
|
+
- Environments may differ only in values. A table built in some environments
|
|
161
|
+
only, or code that differs in more than a value, is `ENVIRONMENTS_DIFFER`.
|
|
162
|
+
A PySpark transform reads names that differ from the pipeline's
|
|
163
|
+
configuration with `spark.conf.get("ingestron.<name>")`, into a module-level
|
|
164
|
+
constant for its decorator; values in its own code cannot differ yet.
|
|
165
|
+
- Needs `@ingestron/definitions` 1.9 or later, and Databricks CLI v0.264.0 or
|
|
166
|
+
later, which adds preset tags to pipelines.
|
|
167
|
+
|
|
168
|
+
The rules are in [spec/behaviour.md](spec/behaviour.md).
|
|
169
|
+
|
|
57
170
|
## With the ADF pack
|
|
58
171
|
|
|
59
172
|
Used with the [ADF pack](https://github.com/ingestron/pack-adf)
|
|
60
|
-
(`packs: [databricks@
|
|
173
|
+
(`packs: [databricks@2, adf@2]`), streaming tables read the files ADF lands,
|
|
61
174
|
from its `landing` folder.
|
|
62
175
|
|
|
63
176
|
Columns ADF adds to the landed rows (`audit_columns` in ADF 1.4) are read
|
|
@@ -70,10 +183,14 @@ with the files; timestamps, which ADF writes as text, are cast to `TIMESTAMP`.
|
|
|
70
183
|
`templates/` and the transforms' SQL.
|
|
71
184
|
- [`yaml`](https://eemeli.org/yaml/): writes the bundle's YAML files.
|
|
72
185
|
|
|
73
|
-
## Sources
|
|
186
|
+
## Sources
|
|
187
|
+
|
|
188
|
+
Bundle variables, targets, presets and pipeline configuration:
|
|
189
|
+
[spec/references.md](spec/references.md) (accessed 2026-11-04). Bundle fields
|
|
190
|
+
from Databricks CLI v1.23.0 `databricks bundle schema` (2026-11-04).
|
|
74
191
|
|
|
75
|
-
|
|
76
|
-
|
|
192
|
+
Accessed 2026-10-24: `databricks/bundle-examples` (`lakeflow_pipelines_sql`).
|
|
193
|
+
SQL from
|
|
77
194
|
[CREATE STREAMING TABLE](https://docs.databricks.com/aws/en/ldp/developer/ldp-sql-ref-create-streaming-table.html),
|
|
78
195
|
[CREATE MATERIALIZED VIEW](https://docs.databricks.com/aws/en/ldp/developer/ldp-sql-ref-create-materialized-view),
|
|
79
196
|
[AUTO CDC INTO](https://docs.databricks.com/aws/en/dlt-ref/dlt-sql-ref-apply-changes-into),
|
package/dist/build.d.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import type { ResolvedModel } from "@ingestron/definitions";
|
|
2
|
+
import { type Data } from "./bundle.js";
|
|
3
|
+
/** A YAML file as data, with its header comments. */
|
|
4
|
+
export interface YamlFile {
|
|
5
|
+
headers: string[];
|
|
6
|
+
data: Data;
|
|
7
|
+
}
|
|
8
|
+
/** The bundle's files before they are written: YAML as data, code as text. */
|
|
9
|
+
export interface BundleData {
|
|
10
|
+
/** By path, in the order they are built. */
|
|
11
|
+
files: Record<string, YamlFile | string>;
|
|
12
|
+
/** The definition file each table's code file comes from, by path. */
|
|
13
|
+
origins: Record<string, string>;
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
|
-
* Every file of the bundle for one environment.
|
|
16
|
+
* Every file of the bundle for one environment: one target, every value as resolved.
|
|
4
17
|
* @example
|
|
5
18
|
* const files = build(model); // { "databricks.yml": "...", "src/retail/bronze/orders.sql": "..." }
|
|
6
19
|
*/
|
|
7
20
|
export declare function build(model: ResolvedModel): Record<string, string>;
|
|
21
|
+
/** The files as text. */
|
|
22
|
+
export declare function written(files: BundleData["files"]): Record<string, string>;
|
|
23
|
+
/** The header of the bundle's own files. */
|
|
24
|
+
export declare function headerOf(model: ResolvedModel): string;
|
|
25
|
+
/** Every file of the bundle for one environment, as data. */
|
|
26
|
+
export declare function bundleData(model: ResolvedModel): BundleData;
|
|
27
|
+
/**
|
|
28
|
+
* The after-refresh script's path: in `tasks/`, outside every pipeline's `src/<pipeline>/**`
|
|
29
|
+
* sources whatever the naming standard, so that no pipeline loads it as its own code.
|
|
30
|
+
*/
|
|
31
|
+
export declare function scriptPath(pipeline: string): string;
|
package/dist/build.js
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
|
-
import { bundleFile, jobFile, pipelineExtras, pipelineFile, schemaKeys, schemasFile, yamlText, } from "./bundle.js";
|
|
1
|
+
import { bundleFile, jobFile, pipelineExtras, pipelineFile, pipelineTags, schemaKeys, schemasFile, yamlText, } from "./bundle.js";
|
|
2
2
|
import { connectGroups, gatewayPipeline, ingestionPipeline, previewNote, } from "./connect.js";
|
|
3
3
|
import { applyContract } from "./apply-contract.js";
|
|
4
4
|
import { prerequisites } from "./capabilities.js";
|
|
5
5
|
import { renderApply, renderSql, sqlFile } from "./sql.js";
|
|
6
6
|
const doNotEdit = "Do not edit: change the definitions and generate again.";
|
|
7
7
|
/**
|
|
8
|
-
* Every file of the bundle for one environment.
|
|
8
|
+
* Every file of the bundle for one environment: one target, every value as resolved.
|
|
9
9
|
* @example
|
|
10
10
|
* const files = build(model); // { "databricks.yml": "...", "src/retail/bronze/orders.sql": "..." }
|
|
11
11
|
*/
|
|
12
12
|
export function build(model) {
|
|
13
|
-
|
|
13
|
+
return written(bundleData(model).files);
|
|
14
|
+
}
|
|
15
|
+
/** The files as text. */
|
|
16
|
+
export function written(files) {
|
|
17
|
+
return Object.fromEntries(Object.entries(files).map(([path, file]) => [
|
|
18
|
+
path,
|
|
19
|
+
typeof file === "string" ? file : yamlText(file.headers, file.data),
|
|
20
|
+
]));
|
|
21
|
+
}
|
|
22
|
+
/** The header of the bundle's own files. */
|
|
23
|
+
export function headerOf(model) {
|
|
24
|
+
return `Generated by Ingestron from the ${model.project.id} definitions. ${doNotEdit}`;
|
|
25
|
+
}
|
|
26
|
+
/** Every file of the bundle for one environment, as data. */
|
|
27
|
+
export function bundleData(model) {
|
|
28
|
+
const header = headerOf(model);
|
|
14
29
|
const keys = schemaKeys(model);
|
|
15
30
|
const groups = connectGroups(model);
|
|
16
|
-
const files = {
|
|
17
|
-
|
|
31
|
+
const files = {
|
|
32
|
+
"databricks.yml": { headers: [header], data: bundleFile(model) },
|
|
33
|
+
};
|
|
34
|
+
const output = { header, keys, files, origins: {} };
|
|
18
35
|
const refreshes = [...connectFiles(model, groups, output), ...sqlFiles(model, groups, output)];
|
|
19
36
|
const tasks = [...refreshes, ...applyFiles(model, refreshes, output)];
|
|
20
37
|
const job = jobFile(model, tasks);
|
|
21
38
|
if (job)
|
|
22
|
-
files[`resources/${model.names.job}.job.yml`] =
|
|
39
|
+
files[`resources/${model.names.job}.job.yml`] = { headers: [header], data: job };
|
|
23
40
|
const schemas = schemasFile(model, keys);
|
|
24
41
|
if (schemas)
|
|
25
|
-
files["resources/schemas.yml"] =
|
|
42
|
+
files["resources/schemas.yml"] = { headers: [header], data: schemas };
|
|
26
43
|
const needs = prerequisites(model, header);
|
|
27
44
|
if (needs)
|
|
28
|
-
files[
|
|
29
|
-
return files;
|
|
45
|
+
files[`prerequisites/${model.project.environment}.sql`] = needs;
|
|
46
|
+
return { files, origins: output.origins };
|
|
30
47
|
}
|
|
31
48
|
/** Lakeflow Connect pipelines; returns the job tasks for those the job refreshes. */
|
|
32
49
|
function connectFiles(model, groups, output) {
|
|
@@ -34,14 +51,13 @@ function connectFiles(model, groups, output) {
|
|
|
34
51
|
return groups.flatMap((group) => {
|
|
35
52
|
const headers = [header, previewNote(group)];
|
|
36
53
|
if (group.gateway) {
|
|
37
|
-
const gateway = {
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
files[`resources/${group.gateway}.pipeline.yml`] = yamlText(headers, gateway);
|
|
54
|
+
const gateway = { ...gatewayPipeline(group, keys), ...pipelineTags(model) };
|
|
55
|
+
const data = { resources: { pipelines: { [group.gateway]: gateway } } };
|
|
56
|
+
files[`resources/${group.gateway}.pipeline.yml`] = { headers, data };
|
|
41
57
|
}
|
|
42
58
|
const pipeline = { ...ingestionPipeline(group, keys), ...pipelineExtras(model) };
|
|
43
|
-
const
|
|
44
|
-
files[`resources/${group.ingestion}.pipeline.yml`] =
|
|
59
|
+
const data = { resources: { pipelines: { [group.ingestion]: pipeline } } };
|
|
60
|
+
files[`resources/${group.ingestion}.pipeline.yml`] = { headers, data };
|
|
45
61
|
return group.continuous
|
|
46
62
|
? []
|
|
47
63
|
: [{ key: `ingest_${group.connection.id}`, pipeline: group.ingestion, after: [] }];
|
|
@@ -54,23 +70,34 @@ function sqlFiles(model, groups, output) {
|
|
|
54
70
|
if (tables.length === 0)
|
|
55
71
|
return [];
|
|
56
72
|
const { pipeline } = model.names;
|
|
57
|
-
|
|
73
|
+
const data = pipelineFile(model, tables, keys);
|
|
74
|
+
files[`resources/${pipeline}.pipeline.yml`] = { headers: [output.header], data };
|
|
75
|
+
const catalog = tables[0].fullName.catalog;
|
|
58
76
|
for (const table of tables) {
|
|
59
77
|
const tableHeader = `Generated by Ingestron from ${table.origin.file} (table ${table.name}). ${doNotEdit}`;
|
|
60
|
-
const file = sqlFile(table, tableHeader);
|
|
61
|
-
|
|
78
|
+
const file = sqlFile(table, tableHeader, catalog);
|
|
79
|
+
const path = `src/${pipeline}/${table.layer}/${table.name}.${file.extension}`;
|
|
80
|
+
files[path] = renderSql(file);
|
|
81
|
+
output.origins[path] = table.origin.file;
|
|
62
82
|
}
|
|
63
83
|
const ingestion = groups
|
|
64
84
|
.filter((group) => !group.continuous)
|
|
65
85
|
.map((group) => `ingest_${group.connection.id}`);
|
|
66
86
|
return [{ key: "refresh", pipeline, after: ingestion }];
|
|
67
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The after-refresh script's path: in `tasks/`, outside every pipeline's `src/<pipeline>/**`
|
|
90
|
+
* sources whatever the naming standard, so that no pipeline loads it as its own code.
|
|
91
|
+
*/
|
|
92
|
+
export function scriptPath(pipeline) {
|
|
93
|
+
return `tasks/${pipeline}/apply_contract.py`;
|
|
94
|
+
}
|
|
68
95
|
/** The Python file that applies tags, comments and table checks after every refresh. */
|
|
69
96
|
function applyFiles(model, refreshes, output) {
|
|
70
97
|
const { statements, checks } = applyContract(model);
|
|
71
98
|
if (statements.length === 0 && checks.length === 0)
|
|
72
99
|
return [];
|
|
73
|
-
const path =
|
|
100
|
+
const path = scriptPath(model.names.pipeline);
|
|
74
101
|
output.files[path] = renderApply({ header: output.header, statements, checks });
|
|
75
102
|
return [
|
|
76
103
|
{ key: "apply_contract", python: `../${path}`, after: refreshes.map((task) => task.key) },
|
package/dist/bundle.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { ResolvedModel, ResolvedTable } from "@ingestron/definitions";
|
|
2
|
-
|
|
2
|
+
/** A YAML document as data. */
|
|
3
|
+
export type Data = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Files in the bundle folder that are not deployed: the generation manifest, its lock
|
|
6
|
+
* (left behind if writing stops part way), and the grants an administrator runs by hand.
|
|
7
|
+
*/
|
|
8
|
+
export declare const notDeployed: string[];
|
|
3
9
|
/** `databricks.yml`: one target, named after the environment. */
|
|
4
10
|
export declare function bundleFile(model: ResolvedModel): Data;
|
|
11
|
+
/** The settings Databricks takes per target: mode, workspace, run_as and permissions. */
|
|
12
|
+
export declare function targetOf(model: ResolvedModel): Data;
|
|
5
13
|
/** The pipeline that builds the tables with SQL files under `src/`. */
|
|
6
14
|
export declare function pipelineFile(model: ResolvedModel, tables: ResolvedTable[], schemaKeys: Map<string, string>): Data;
|
|
7
15
|
/** A task of the refresh job: a pipeline to refresh, or a Python file to run, after others. */
|
|
@@ -17,14 +25,20 @@ export interface Task {
|
|
|
17
25
|
* With a Python task it is built without a schedule too, to run on demand.
|
|
18
26
|
*/
|
|
19
27
|
export declare function jobFile(model: ResolvedModel, tasks: Task[]): Data | undefined;
|
|
20
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Bundle keys of the schemas this bundle manages, by `catalog.schema`, the same in every
|
|
30
|
+
* environment where only the catalog differs: the layer when the layer has one schema,
|
|
31
|
+
* else the schema's name. When two catalogs hold a schema of that name, or the key is
|
|
32
|
+
* taken, it is `<catalog>_<schema>`. Empty unless `manage.schemas`.
|
|
33
|
+
*/
|
|
21
34
|
export declare function schemaKeys(model: ResolvedModel): Map<string, string>;
|
|
22
35
|
/** A schema's name, or a reference to it when the bundle manages it, so the bundle creates it first. */
|
|
23
36
|
export declare function schemaRef(keys: Map<string, string>, catalog: string, schema: string): string;
|
|
24
37
|
/** Managed schemas, with the platform's grants for their layer. */
|
|
25
38
|
export declare function schemasFile(model: ResolvedModel, keys: Map<string, string>): Data | undefined;
|
|
26
|
-
/**
|
|
39
|
+
/** The platform's tags, on every pipeline and job. */
|
|
40
|
+
export declare function pipelineTags(model: ResolvedModel): Data;
|
|
41
|
+
/** Tags and failure notifications, the same on every pipeline that builds tables. */
|
|
27
42
|
export declare function pipelineExtras(model: ResolvedModel): Data;
|
|
28
43
|
/** YAML text with header comments, one per line given. */
|
|
29
44
|
export declare function yamlText(headers: string | string[], data: Data): string;
|
|
30
|
-
export {};
|
package/dist/bundle.js
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { stringify } from "yaml";
|
|
2
|
+
/**
|
|
3
|
+
* Files in the bundle folder that are not deployed: the generation manifest, its lock
|
|
4
|
+
* (left behind if writing stops part way), and the grants an administrator runs by hand.
|
|
5
|
+
*/
|
|
6
|
+
export const notDeployed = [".generated.json", ".generated.lock", "prerequisites/**"];
|
|
2
7
|
/** `databricks.yml`: one target, named after the environment. */
|
|
3
8
|
export function bundleFile(model) {
|
|
9
|
+
return {
|
|
10
|
+
bundle: { name: model.project.id },
|
|
11
|
+
include: ["resources/*.yml"],
|
|
12
|
+
sync: { exclude: notDeployed },
|
|
13
|
+
targets: { [model.project.environment]: { ...targetOf(model), default: true } },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** The settings Databricks takes per target: mode, workspace, run_as and permissions. */
|
|
17
|
+
export function targetOf(model) {
|
|
4
18
|
const { platform } = model;
|
|
5
|
-
const target = { mode: platform.mode
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
const target = { mode: platform.mode };
|
|
20
|
+
const workspace = Object.fromEntries(Object.entries(platform.workspace).filter(([, value]) => value));
|
|
21
|
+
if (Object.keys(workspace).length > 0)
|
|
22
|
+
target.workspace = workspace;
|
|
8
23
|
if (platform.run_as)
|
|
9
24
|
target.run_as = platform.run_as;
|
|
10
25
|
if (platform.permissions.length > 0)
|
|
11
26
|
target.permissions = platform.permissions;
|
|
12
|
-
return
|
|
13
|
-
bundle: { name: model.project.id },
|
|
14
|
-
include: ["resources/*.yml"],
|
|
15
|
-
targets: { [model.project.environment]: target },
|
|
16
|
-
};
|
|
27
|
+
return target;
|
|
17
28
|
}
|
|
18
29
|
/** The pipeline that builds the tables with SQL files under `src/`. */
|
|
19
30
|
export function pipelineFile(model, tables, schemaKeys) {
|
|
@@ -77,12 +88,31 @@ function jobTask(task) {
|
|
|
77
88
|
}),
|
|
78
89
|
};
|
|
79
90
|
}
|
|
80
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Bundle keys of the schemas this bundle manages, by `catalog.schema`, the same in every
|
|
93
|
+
* environment where only the catalog differs: the layer when the layer has one schema,
|
|
94
|
+
* else the schema's name. When two catalogs hold a schema of that name, or the key is
|
|
95
|
+
* taken, it is `<catalog>_<schema>`. Empty unless `manage.schemas`.
|
|
96
|
+
*/
|
|
81
97
|
export function schemaKeys(model) {
|
|
82
98
|
if (!model.platform.manage.schemas)
|
|
83
99
|
return new Map();
|
|
84
|
-
const
|
|
85
|
-
|
|
100
|
+
const schemas = new Map();
|
|
101
|
+
for (const { fullName, layer } of model.tables)
|
|
102
|
+
schemas.set(`${fullName.catalog}.${fullName.schema}`, { ...fullName, layer });
|
|
103
|
+
const list = [...schemas.values()];
|
|
104
|
+
const keys = new Map();
|
|
105
|
+
const used = new Set();
|
|
106
|
+
for (const { catalog, schema, layer } of list) {
|
|
107
|
+
const alone = list.filter((other) => other.layer === layer).length === 1;
|
|
108
|
+
const shared = list.filter((other) => other.schema === schema).length > 1;
|
|
109
|
+
const both = `${catalog}_${schema}`;
|
|
110
|
+
const key = alone ? layer : shared ? both : schema;
|
|
111
|
+
const unique = used.has(key) ? both : key;
|
|
112
|
+
used.add(unique);
|
|
113
|
+
keys.set(`${catalog}.${schema}`, unique);
|
|
114
|
+
}
|
|
115
|
+
return keys;
|
|
86
116
|
}
|
|
87
117
|
/** A schema's name, or a reference to it when the bundle manages it, so the bundle creates it first. */
|
|
88
118
|
export function schemaRef(keys, catalog, schema) {
|
|
@@ -116,12 +146,17 @@ function uniqueSchemas(tables) {
|
|
|
116
146
|
return true;
|
|
117
147
|
});
|
|
118
148
|
}
|
|
119
|
-
/**
|
|
149
|
+
/** The platform's tags, on every pipeline and job. */
|
|
150
|
+
export function pipelineTags(model) {
|
|
151
|
+
const { tags } = model.platform;
|
|
152
|
+
return Object.keys(tags).length > 0 ? { tags } : {};
|
|
153
|
+
}
|
|
154
|
+
/** Tags and failure notifications, the same on every pipeline that builds tables. */
|
|
120
155
|
export function pipelineExtras(model) {
|
|
121
156
|
const { platform } = model;
|
|
122
157
|
const alerts = ["on-update-failure", "on-update-fatal-failure", "on-flow-failure"];
|
|
123
158
|
return {
|
|
124
|
-
...(
|
|
159
|
+
...pipelineTags(model),
|
|
125
160
|
...(platform.notifications.on_failure.length > 0 && {
|
|
126
161
|
notifications: [{ email_recipients: platform.notifications.on_failure, alerts }],
|
|
127
162
|
}),
|
package/dist/connect.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface ConnectGroup {
|
|
|
13
13
|
type Keys = Map<string, string>;
|
|
14
14
|
/** Tables ingested by lakeflow-connect, grouped by connection in path order. */
|
|
15
15
|
export declare function connectGroups(model: ResolvedModel): ConnectGroup[];
|
|
16
|
+
/** True when the bundle has a pipeline that runs continuously: a gateway, or Kafka read continuously. */
|
|
17
|
+
export declare function hasContinuous(model: ResolvedModel): boolean;
|
|
16
18
|
/**
|
|
17
19
|
* Problems with settings that one connection's tables share: its gateway, or
|
|
18
20
|
* whether its ingestion runs continuously.
|
package/dist/connect.js
CHANGED
|
@@ -20,6 +20,10 @@ export function connectGroups(model) {
|
|
|
20
20
|
}
|
|
21
21
|
return [...groups.values()];
|
|
22
22
|
}
|
|
23
|
+
/** True when the bundle has a pipeline that runs continuously: a gateway, or Kafka read continuously. */
|
|
24
|
+
export function hasContinuous(model) {
|
|
25
|
+
return connectGroups(model).some((group) => group.gateway !== undefined || group.continuous);
|
|
26
|
+
}
|
|
23
27
|
/**
|
|
24
28
|
* Problems with settings that one connection's tables share: its gateway, or
|
|
25
29
|
* whether its ingestion runs continuously.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Plan } from "./fold-plan.js";
|
|
2
|
+
import { type Targets } from "./fold-target.js";
|
|
3
|
+
import { type Variable } from "./fold-values.js";
|
|
4
|
+
/** The folded files, and each target's host when it is supplied when deploying. */
|
|
5
|
+
export interface Folded {
|
|
6
|
+
files: Record<string, string>;
|
|
7
|
+
hosts: Targets["hosts"];
|
|
8
|
+
/** The variables the files declare. */
|
|
9
|
+
declared: Variable[];
|
|
10
|
+
}
|
|
11
|
+
/** The files for every target, folded from the plan's template. */
|
|
12
|
+
export declare function foldFiles(plan: Plan): Folded;
|