Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contract-toolkit/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ demonstrates distinct feature surface, verified by `tests/*.pkl`.
diverse widgets, UIRules, extraNodes.
- `bundle`: multi-entrypoint app (crawler + miner); shared credential configmap;
per-entrypoint artifact subfolders.
- `credential-configs`: `additionalCredentialConfigs` — more than one credential
configmap per entrypoint, including a bare (wrapper-less) one; hoisted and deduped.
- `deploy`: single-pool migration example — KEDA, resources, env, pool-level `overrides`; shows v0.16.x → v0.17.0 migration path.
- `pools`: `pools` map (preferred) — named hot/cold worker pools with per-pool `cooldownPeriod` and resources.
- `system-app`: `type = "system"` — no `entrypoints` block or card, no credential config, no `uiConfig`; typed `inputs` render `_input.py` with no manifest, and `contract/generated/input.pkl`, synced into the toolkit as `src/system/<name>.pkl` for callers.
Expand Down
3 changes: 2 additions & 1 deletion contract-toolkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ The `examples/` directory contains executable contracts that teach stable toolki
- [`examples/tree-filters/`](examples/tree-filters/) — extraction app whose include/exclude filters are tree pickers with `treeSelection = true`; `AppInputContract` extends `TreeSelectionExtractionInput`.
- [`examples/full/`](examples/full/) — every overridable feature: JDBC URL auth, all pipeline steps, diverse widgets, UIRules, extraNodes.
- [`examples/bundle/`](examples/bundle/) — multi-entrypoint app (crawler + miner); shared credential configmap; per-entrypoint artifact subfolders.
- [`examples/credential-configs/`](examples/credential-configs/) — `additionalCredentialConfigs`: an entrypoint that emits more than one credential configmap (primary + wrapped delivery + bare object-store), and one whose only credential is the shared bare config. The bundle hoists and dedupes them. See [CredentialConfig](docs/reference.md#credentialconfig--additional-credential-configmaps).
- [`examples/card-split/`](examples/card-split/) — two entrypoints where only one is a marketplace UI card (`packageId` on the card entrypoint; route-only entrypoint has none).
- [`examples/behind-the-scenes/`](examples/behind-the-scenes/) — single-entrypoint app with `marketplaceCard = false`; routable but no marketplace card or `package_id` emitted.
- [`examples/system-app/`](examples/system-app/) — `type = "system"`: an in-tenant app started by other apps' DAG nodes. No `entrypoints` block, no card, no credential config, no manifest. Typed `inputs` (string, number, enum, bounded list/map, nested object) render `_input.py` without a `uiConfig`. They also render `contract/generated/input.pkl`, the typed DAG node that is synced into the toolkit as `src/system/<name>.pkl` for callers to import.
Expand Down Expand Up @@ -234,7 +235,7 @@ The single entry point for all new native app contracts. Amend this module and d

- App metadata: `name`, `displayName`, `connector`, `icon`
- Marketplace metadata: `shortDescription`, `longDescription`, `tags` (emitted top-level into `atlan.yaml`; Build & Publish syncs `tags` to the Global Marketplace App row after each publish, where marketplace search reads them as keywords; `atlan app register` sends all three when it creates the app); `docsUrl` (emitted top-level into `atlan.yaml`)
- Credential config: `credentialCommonFields`, `credentialAuthOptions`, `credentialConnectorType`
- Credential config: `credentialCommonFields`, `credentialAuthOptions`, `credentialConnectorType`; extra or bare (wrapper-less) credential configmaps via `additionalCredentialConfigs`
- Workflow config: `uiConfig` (form steps and fields using `Widgets.*` types)
- Pipeline: typed `pipeline` block (extract → parseQueries → popularity → lineage → publish)
- Deployment: `deploy` (typed `DeployConfig?` — app-level `dapr` / `overrides` plus a named `pools` map; pool keys must match `@task(pool="…")` strings) — see `Deployment.pkl`. `dapr.objectstore` defaults to `true` and must stay on: the SDK requires the deployment object store and fails at startup without it (conformance rule K029 flags `false`)
Expand Down
99 changes: 99 additions & 0 deletions contract-toolkit/docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ class LineagePublishStep { // wraps LineagePublishNode
| `credentialAuthHiddenEnumListForCreating` | Listing<String>? | null | Auth-type enum values hidden during credential creation. |
| `credentialNamePlaceholder` | String | `"Host Name"` | Placeholder for hidden credential `name` field. |
| `credentialUrlGroup` | AdvancedJDBCUrlGroup? | null | Opt-in JDBC Host↔URL credential form. Requires all `credentialAuthOptions` to be `JDBCUrlAuthOption`. |
| `additionalCredentialConfigs` | Listing<CredentialConfig> | `[]` | Extra credential configmaps beyond `<connectorConfigName>.json`; `wrapper` / `identityProperties` reproduce legacy shapes. See [CredentialConfig](#credentialconfig--additional-credential-configmaps). |

The auth-type radio's `ui.hidden` is auto-derived from `credentialAuthOptions.length == 1` — when the connector declares a single auth option, the radio is hidden and the default value is still emitted. No manual flag is required.

Expand Down Expand Up @@ -1438,6 +1439,7 @@ Developers amend this module. It defines the app's identity, credentials, workfl
| `credentialNamePlaceholder` | String | `"Host Name"` | Placeholder for the hidden credential `name` field. |
| `credentialConnectorDefault` | String? | null | Optional default for the hidden credential `connector` field. |
| `credentialUrlGroup` | AdvancedJDBCUrlGroup? | null | Opt-in JDBC Host↔URL credential form. When set, every `credentialAuthOptions` entry must be a `JDBCUrlAuthOption`. See [AdvancedJDBCUrlGroup](#advancedjdbcurlgroup--hostrlarrowurl-jdbc-credential-form). |
| `additionalCredentialConfigs` | Listing<CredentialConfig> | `[]` | Extra credential configmaps beyond `<connectorConfigName>.json`; `wrapper` / `identityProperties` reproduce legacy shapes. See [CredentialConfig](#credentialconfig--additional-credential-configmaps). |

The auth-type radio's `ui.hidden` is auto-derived from `credentialAuthOptions.length == 1` — when the connector declares a single auth option, the radio is hidden and the default value is still emitted. No manual flag is required.

Expand Down Expand Up @@ -1827,6 +1829,8 @@ open class AuthOption {
nestedLabel: String = "" // Custom label for nested section wrapper
nestedPlaceholder: String? // Custom placeholder for nested section wrapper
nestedHidden: Boolean = true // Hide nested auth pane by default
nestedHelp: String? // `ui.help` on the nested pane; omitted when null
nestedWidth: Int? // `ui.grid` on the nested pane; omitted when null
nestedExtraLabel: String = "" // Label on the per-option `extra` nested section
nestedExtraHeader: String? = "Advanced" // Header on `extra`; null omits it
}
Expand Down Expand Up @@ -1932,6 +1936,95 @@ credentialAuthOptions {

---

## CredentialConfig — Additional Credential Configmaps

Each contract emits at most one primary credential configmap,
`<connectorConfigName>.json`, built from the top-level `credential*` properties.
List further configmaps in `additionalCredentialConfigs` when one entrypoint's
form selects more than one credential type (`CredentialInput { credType = "<name>" }`),
or selects one that is not its own connection credential, such as an
object-store delivery target.

```pkl
class CredentialConfig {
name: String // Configmap name = file stem = credType: (atlan|csa)-connectors-<kebab-case>
wrapper: Boolean = true // false: no icon/helpdeskLink/logo/connector/defaultConnectorType
identityProperties: Boolean = true // false: no hidden name/connector/connectorType properties
connector: String? // Envelope `connector`; defaults to the app's. Refused when !wrapper
connectorType: String? // Envelope `defaultConnectorType` (default "rest"). Refused when !wrapper
icon: String?; logo: String?; helpdeskLink: String? // Wrapper keys; default to the app's
authOptions: Mapping<String, AuthOption> // Radio options + nested panes + anyOf rules
commonFields: Listing<CredentialFieldEntry> = []
sharedExtraFields: Listing<CredentialFieldEntry> = []
authTitle: String = "Authentication"
authDefault: String? // Defaults to the first authOptions key
authRules: Listing<Dynamic>?; authPlaceholder: String?; authWidth: Int?
authHiddenEnumListForCreating: Listing<String>?
namePlaceholder: String? // Hidden `name` placeholder (default "Host Name"). Refused when !identityProperties
connectorDefault: String? // Hidden `connector` default; refused when !identityProperties
}
```

Each entry renders through the same renderer as the primary config, so the
auth-type radio, panes and `anyOf` rules have the same shape. The knobs mirror the
top-level properties of the same name. `credentialUrlGroup` (JDBC Host↔URL) applies
only to the primary config.

**`wrapper` and `identityProperties`.** By default an entry carries what the
primary carries:

- the envelope: top-level `icon`, `helpdeskLink`, `logo`, `connector`,
`defaultConnectorType`;
- hidden `name` / `connector` / `connectorType` form properties.

Turn either off only to reproduce a legacy credential type whose form never had
it. Neither is neutral for such a type:

- the SDK configmap route returns `defaultConnectorType` as
`data.defaultConnectorType`;
- the hidden properties are submitted in every credential body the form creates.

| Legacy shape | `wrapper` | `identityProperties` |
|---|---|---|
| `@atlan/connectors` `csa-connectors-objectstore` (bare `{"config": ...}`) | `false` | `false` |
| Credential.pkl configs, e.g. generic-miner's object-store form (envelope, no identity properties) | `true` | `false` |

A field that a flag turns off is refused rather than ignored: `connector` /
`connectorType` / `icon` / `logo` / `helpdeskLink` with `wrapper = false`, and
`connectorDefault` / `namePlaceholder` with `identityProperties = false`.

**Output.** A single-entrypoint contract writes `app/generated/<name>.json` per
entry. A bundle hoists these files to `app/generated/` and dedupes them by name,
as it does the primary. Two entrypoints may declare the same name only if the
rendered content is identical; otherwise generation fails.

**Unchanged.** The primary config, `credentialFieldName`, `_input.py` and the e2e
credential codegen (`_e2e_credential.py`) describe only the primary credential.
`hasCredentialConfig = false` suppresses only the primary, so a contract whose only
credentials are additional ones still emits them.

**Refused at generation:**

- a multi-entrypoint bundle root, which emits no form or credential of its own:
declare the entry on each entrypoint's `contract`;
- no `uiConfig`, since nothing could select the config;
- a name that is not `atlan-connectors-` / `csa-connectors-` plus lowercase
kebab-case (the SDK treats only those stems as credential templates, so any other
stem can be served as the setup form; `/` or `..` would write outside
`app/generated/`);
- duplicate names, or a name that collides with the primary, workflow config or
manifest;
- an entry with `wrapper = true` and no connector on the entry or the app;
- a field the entry's flags make unused (see above).

Example: `examples/credential-configs/` is a bundle. Its `export` entrypoint emits
a primary, a wrapped delivery config and a shared bare object-store config
(`wrapper = false`, `identityProperties = false`). Its
`report` entrypoint has no primary, only the shared object-store config, which the
bundle writes once.

---

## AdvancedJDBCUrlGroup — Host↔URL JDBC Credential Form

Toolkit primitive for SQL-family connectors whose credential form exposes a
Expand Down Expand Up @@ -4251,6 +4344,12 @@ deploy = new DeployConfig {
For Argo-era apps only. Do not use for new native apps (use `FieldSpec` instead). Still used
by CSA connector credential configs.

An App.pkl contract that needs a second credential configmap, the job a
`NativeApp.pkl` `additionalOutputFiles` + Credential.pkl pairing used to do, lists it in
`additionalCredentialConfigs` instead. Set `identityProperties = false` to keep
Credential.pkl's output shape (envelope, no hidden identity properties). See
[CredentialConfig](#credentialconfig--additional-credential-configmaps).

Widget classes come from the modern `Widgets.pkl` module (not the older `Config.pkl`), and
Credential.pkl re-exports them as unqualified typealiases — mirroring App.pkl — so a credential
contract writes `new TextInput { }`, `new PasswordInput { }`, `new NestedInput { }`, etc.
Expand Down
41 changes: 41 additions & 0 deletions contract-toolkit/examples/credential-configs/app.pkl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// Credential-configs example: entrypoints that emit more than one credential
/// configmap, including a bare (wrapper-less) one, via `additionalCredentialConfigs`.
///
/// - `export` owns a primary credential (`atlan-connectors-credential-configs`)
/// and also emits two additional ones: the shared bare object-store config and
/// a wrapped delivery config.
/// - `report` has no primary credential (`hasCredentialConfig = false`). Its only
/// credential config is the shared bare object-store one.
///
/// Credential configmaps are hoisted to `app/generated/` and deduped by name, so
/// the object-store config both entrypoints declare is written once.
///
/// Generated layout:
/// app/generated/atlan-connectors-credential-configs.json ← export primary
/// app/generated/atlan-connectors-credential-configs-delivery.json ← export, wrapped
/// app/generated/csa-connectors-objectstore.json ← both, bare, deduped
/// app/generated/export/{export.json,manifest.json,_input.py,...}
/// app/generated/report/{report.json,manifest.json,_input.py,...}
amends "../../src/App.pkl"

import "export.pkl" as Export
import "report.pkl" as Report

name = "credential-configs"
displayName = "Credential Configs Example"
icon = "https://assets.atlan.com/assets/download.svg"
// The bundle root owns no credential config; its entrypoints do.
hasCredentialConfig = false

entrypoints {
new Entrypoint {
name = "export"
displayName = "Export"
contract = Export
}
new Entrypoint {
name = "report"
displayName = "Report"
contract = Report
}
}
5 changes: 5 additions & 0 deletions contract-toolkit/examples/credential-configs/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# AUTO-GENERATED from contract/app.pkl — DO NOT EDIT MANUALLY.
# To regenerate: pkl eval -m . contract/app.pkl
app_name: credential-configs
app_image: ${APP_IMAGE}
app_port: 8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
{
"icon": "https://assets.atlan.com/assets/download.svg",
"helpdeskLink": "",
"logo": "https://assets.atlan.com/assets/download.svg",
"connector": "s3",
"defaultConnectorType": "rest",
"config": {
"properties": {
"name": {
"type": "string",
"required": false,
"ui": {
"label": "Name",
"hidden": true,
"placeholder": "Host Name"
}
},
"connector": {
"type": "string",
"required": false,
"ui": {
"label": "Connector",
"hidden": true,
"placeholder": "Connector"
}
},
"connectorType": {
"type": "string",
"required": false,
"ui": {
"key": "_host",
"label": "connectorType",
"placeholder": "connectorType",
"hidden": true
}
},
"auth-type": {
"type": "string",
"enum": [
"s3",
"email"
],
"default": "s3",
"required": true,
"enumNames": [
"S3",
"Email"
],
"ui": {
"widget": "radio",
"hidden": false,
"label": "Output destination"
}
},
"s3": {
"type": "object",
"ui": {
"widget": "nested",
"label": "S3 bucket",
"hidden": false,
"nestedValue": false
},
"properties": {
"username": {
"type": "string",
"ui": {
"widget": "input",
"label": "AWS access key",
"hidden": false,
"grid": 6
}
},
"password": {
"type": "string",
"ui": {
"widget": "password",
"label": "AWS secret key",
"hidden": false,
"grid": 6
}
},
"extra": {
"type": "object",
"ui": {
"widget": "nested",
"label": "",
"header": "Advanced",
"hidden": false
},
"properties": {
"s3-bucket-name": {
"type": "string",
"required": true,
"ui": {
"widget": "input",
"label": "S3 bucket name",
"hidden": false,
"placeholder": "my-bucket",
"grid": 4
}
}
}
}
}
},
"email": {
"type": "object",
"ui": {
"widget": "nested",
"label": "Email",
"hidden": false,
"nestedValue": false
},
"properties": {
"extra": {
"type": "object",
"ui": {
"widget": "nested",
"label": "",
"header": "Advanced",
"hidden": false
},
"properties": {
"recipients": {
"type": "string",
"required": true,
"ui": {
"widget": "input",
"label": "Recipients",
"hidden": false,
"placeholder": "a@example.com",
"grid": 8
}
}
}
}
}
}
},
"anyOf": [
{
"properties": {
"auth-type": {
"const": "s3"
}
},
"required": [
"s3"
]
},
{
"properties": {
"auth-type": {
"const": "email"
}
},
"required": [
"email"
]
}
]
}
}
Loading
Loading