feat(dbt): add keboola_snowflake profile target and keboola adapter env vars#2570
feat(dbt): add keboola_snowflake profile target and keboola adapter env vars#2570
Conversation
…nv vars
Generate a second dbt profile output alongside the existing direct-Snowflake
target so users can switch between direct access and the Keboola Query Service
adapter with a single flag:
dbt run # uses {target_name} (direct Snowflake)
dbt run --target keboola_{target_name} # uses keboola_snowflake adapter
Changes:
- profile/operation.go: append keboola_{target_name} output to profiles.yml
with type: keboola_snowflake and env-var refs for base_url, token,
branch_id, workspace_id, database, schema, warehouse
- env/operation.go: introduce WorkspaceDetails struct (replaces *sandbox.SandboxWorkspace)
with BaseURL / BranchID / WorkspaceID fields; write DBT_KBC_*_BASE_URL,
_BRANCH_ID, _WORKSPACE_ID to .env.local when both BaseURL and WorkspaceID are set
- dbt init/operation.go: construct WorkspaceDetails from StorageWorkspace
credentials, pass BaseURL (derived from API host) and WorkspaceID from
EditorSession through to env generation
- dbt init cmd: set BaseURL via baseURLFromHost(d.StorageAPIHost())
- dbt generate-env cmd + dialog: construct WorkspaceDetails inline from
StorageWorkspace; set BranchID and WorkspaceID from matched EditorSession;
set BaseURL in cmd after AskGenerateEnv returns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@claude review |
Phase 1 — CreateEditorSession provides workspace coordinates (WorkspaceID, BranchID, BaseURL) for the keboola_snowflake dbt profile. No credential rotation at this step. Phase 2 — StorageWorkspaceCreateCredentialsRequest generates a server-side keypair (pushes public key to workspace, returns private key) together with all connection details (Host, User, DB, Schema, Warehouse) for the direct-Snowflake dbt profile. Password auth is deprecated; keypair is the only supported authentication method. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Review summary
Overall the approach is clean — introducing WorkspaceDetails to decouple the operation layer from *sandbox.SandboxWorkspace is a good call, and the two-phase comment block in init/operation.go makes the workflow much easier to follow.
A few issues worth addressing before merge:
Blocking
keboola_snowflake profile target causes dbt startup failures when env vars are absent
profiles.yml now always includes a keboola_{target} output block with bare env_var() references (no defaults). dbt evaluates all env_var() calls at startup regardless of the active target, so any environment that lacks DBT_KBC_*_BASE_URL, _BRANCH_ID, _WORKSPACE_ID (Python/R workspaces, CI environments that source .env.local selectively, etc.) will abort on dbt run. See inline comment on profile/operation.go:120-133.
keboola_snowflake target written for all workspace types
BigQuery (and any future non-Snowflake) workspace will get a keboola_snowflake output in profiles.yml. The adapter name makes it non-functional there. See inline on profile/operation.go:113.
Non-blocking but should fix
baseURLFromHost duplicated
Identical function defined in both dbtinit/cmd.go and generate/env/cmd.go. Should live in one place. See inline on dbtinit/cmd.go:81.
deref closure duplicated
Same inline closure in dialog.go and init/operation.go. Minor but worth consolidating.
Observation
The baseURLFromHost logic (strip connection., prepend query.) produces a wrong URL for hosts that do not start with connection. (e.g. staging or on-premise stacks). This may be intentional if all Keboola stacks follow the connection.* convention, but it should be covered by a unit test to document and lock in the expected inputs/outputs.
There was a problem hiding this comment.
Pull request overview
Adds support for generating a second dbt profile target for the keboola_snowflake adapter (Query Service) alongside the existing direct Snowflake target, and extends .env.local generation to emit the Query Service adapter environment variables (base URL, branch ID, workspace ID) for editor-session (SQL) workspaces.
Changes:
dbt generate profile/dbt initnow generate an additionalkeboola_{target_name}output inprofiles.ymlfor thekeboola_snowflakeadapter.dbt generate env/dbt initnow populateDBT_KBC_{TARGET}_BASE_URL,DBT_KBC_{TARGET}_BRANCH_ID, andDBT_KBC_{TARGET}_WORKSPACE_IDin.env.localwhen editor-session coordinates are available.- Introduces a new
WorkspaceDetailsstruct for env generation, removing the dependency on*sandbox.SandboxWorkspacein the dbt env/profile flow.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/lib/operation/dbt/init/operation.go | Builds combined workspace details (editor session coords + storage creds) and passes them into profile/env generation. |
| pkg/lib/operation/dbt/generate/profile/operation.go | Adds the keboola_{target} profile output using keboola_snowflake and env-var based configuration. |
| pkg/lib/operation/dbt/generate/env/operation.go | Switches to WorkspaceDetails and writes additional Query Service adapter env vars when available. |
| internal/pkg/service/cli/cmd/dbt/generate/env/dialog.go | Updates CLI option gathering to populate WorkspaceDetails for both Python/R and SQL workspaces. |
| internal/pkg/service/cli/cmd/dbt/generate/env/cmd.go | Derives and injects Query Service base URL into generated env options. |
| internal/pkg/service/cli/cmd/dbt/dbtinit/cmd.go | Derives and injects Query Service base URL into dbt init options. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ter env vars Add expected keboola_target1 section to profiles.yml fixtures and DBT_KBC_*_BASE_URL / _BRANCH_ID / _WORKSPACE_ID vars to .env.local fixtures for all dbt init and generate-env/profile test cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract baseURLFromHost to dbtutil.BaseURLFromHost, shared by both
dbtinit and generate/env commands; removes duplication (3 review comments)
- Preserve input http/https scheme in BaseURLFromHost instead of hardcoding
https:// (Copilot + claude[bot] comments)
- Add unit tests for BaseURLFromHost covering 5 representative inputs
- Add IncludeKeboolaTarget bool to profile.Options; gate the keboola_snowflake
output block so it is only written when explicitly requested (claude[bot])
- Add env_var("...", "") empty defaults for all keboola_snowflake-only vars
(base_url, token, branch_id, workspace_id) so dbt does not abort at startup
when those vars are absent (claude[bot])
- Skip writing empty-value vars to .env.local; Python/R workspaces no longer
produce confusing DBT_KBC_*_ACCOUNT= placeholder lines (claude[bot])
- Update test fixtures for env_var defaults in keboola_snowflake block
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Password auth is deprecated — the Storage API now always returns a keypair from StorageWorkspaceCreateCredentialsRequest. Removed the --key-pair=false escape hatch, UseKeyPair field, and the `password` profile entry that caused dbt to abort when the var was missing. Deleted the ok-legacy E2E fixture that covered the removed flag path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes in env generation: 1. KEBOOLA_TOKEN is now written to .env.local alongside the other keboola adapter vars when BaseURL and WorkspaceID are present, so the keboola_snowflake profile target works out of the box. 2. Private keys (and any multiline value) are now written with ANSI-C $'...' quoting instead of double-quoted \\n sequences. After `source .env.local`, bash/zsh expand \n to real newlines, so the PEM parser no longer sees byte 92 (\) and rejects the key. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inlining a PEM private key in dotenv format requires shell-specific quoting ($'...') that breaks on Windows cmd/PowerShell, direnv, and all dotenv libraries. Instead, write the key to .dbt_private_key_<target>.p8, add it to .gitignore, and set DBT_KBC_<TARGET>_PRIVATE_KEY_PATH to the filename. profiles.yml now uses private_key_path which dbt-snowflake reads directly from the file — no encoding involved, works on all platforms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Review
Good overall structure — the two-target design (direct Snowflake + keboola_snowflake adapter), private key extraction to a .p8 file, and automatic .gitignore entries are all solid improvements.
Issues found
Bug – non-deterministic linked-bucket log message (pkg/lib/operation/dbt/generate/env/operation.go, lines 185-188)
The linkedBucketEnvsMap keys are iterated in map order before being joined into the log message. This produces a different message each run, violating the codebase's "Deterministic output" rule (CLAUDE.md). These lines predate this PR but the PR reshapes the surrounding code — worth fixing here.
var linkedBucketEnvs []string
for envName := range linkedBucketEnvsMap {
linkedBucketEnvs = append(linkedBucketEnvs, envName)
}
sort.Strings(linkedBucketEnvs) // add this
l.Infof(ctx, " have been generated: \"%s\"", strings.Join(linkedBucketEnvs, `", "`))See also the three inline comments for:
database/schema/warehousemissing empty-string defaults in thekeboola_snowflakeprofile target (would cause dbt startup failures when direct-Snowflake vars are absent)strings.Replace→strings.ReplaceAll- Table-driven test missing
t.Runsub-tests
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 35 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- fixtures/profiles.yml: add empty-string default to private_key_path env_var (5 files) - fixtures/out/.gitignore: add expected .gitignore output to 4 test dirs (env and init) - env/operation.go: strings.Replace → strings.ReplaceAll for snowflakecomputing.com strip - dbtutil/util_test.go: use t.Run sub-tests so failures include the input host as test name Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 39 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Review: feat(dbt) keboola_snowflake profile target + keboola adapter env vars
Overall the change is well-structured and the motivations are clear. A few issues to address.
Non-deterministic log output (pre-existing, worth fixing here)
In pkg/lib/operation/dbt/generate/env/operation.go lines 185–188, the linked-bucket env var names are collected from a map and joined without sorting. CLAUDE.md requires sorting map iterations before output. Add sort.Strings(linkedBucketEnvs) before the l.Infof call.
Inline comments posted
dbtutil/util.go:21— Doc comment references a--base-urloverride flag that doesn't exist on either command. Fix or remove the sentence.operation.go:6—"os"imported forO_*constants; check whether thefilesystempackage has a higher-level write-with-mode API to avoid this.operation.go:119–129— File handle not managed with defer; current pattern leaks on panic. Suggested alternative collapses both error paths cleanly.operation.go:137–144—KEBOOLA_TOKENis an unscoped global key; if a user manages multiple targets or stacks in the same project, repeateddbt generate envruns will silently overwrite it. Consider target-scoping it or at minimum documenting the intentional collision behaviour.dialog.go:87–112— Identical nil-unpacking block duplicated ininit/operation.go:80–95. Two-site reuse warrants a small conversion helper.profile/operation.go:128–139—database/schema/warehousein the keboola_ target lack default values while all other keboola_-specific vars have""defaults; add a comment explaining the intentional asymmetry (they are shared with the direct-Snowflake target so are always populated when.env.localis sourced).
Minor
linkedBucketEnvsMap is maintained in parallel with envVars solely to drive the final warning message. Collecting just the names ([]string) as entries are added to envVars removes the duplicate map.
- dbtutil/util.go: remove stale --base-url override hint (flag does not exist) - env/operation.go: write private key with write-then-close pattern to avoid fd leak on write failure - profile/operation.go: add comment explaining why database/schema/warehouse in keboola_ target intentionally have no empty-string default Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jachym-tousek-keboola
left a comment
There was a problem hiding this comment.
No issues with the code but this really isn't my area of expertise. 😅
Release Notes
dbt generate profileanddbt initnow generate two outputs inprofiles.yml:{target_name}(direct Snowflake access, default) andkeboola_{target_name}(Keboolakeboola_snowflakeadapter via Query Service)dbt generate envanddbt initnow writeDBT_KBC_*_BASE_URL,_BRANCH_ID,_WORKSPACE_ID, andKEBOOLA_TOKENto.env.localfor use with thekeboola_snowflakeadapter target (only for SQL/editor-session workspaces).dbt_private_key_<target>.p8file (mode0600) referenced viaDBT_KBC_*_PRIVATE_KEY_PATH;.env.localand the key file are both added to.gitignoreautomaticallyWorkspaceDetailsstruct in thedbt/generate/envpackage, replacing the dependency on*sandbox.SandboxWorkspacedbt initremoves the--key-pairflag. Key-pair auth is now always used (password auth removed); Snowflake deprecated password auth for service accounts.Plans for customer communication
None.
Impact analysis
pkg/lib/operation/dbt/generate/profile/operation.go— newkeboola_{target_name}output added toprofiles.yml; existing{target_name}output is unchanged;private_key→private_key_path(⚠ profile format change)pkg/lib/operation/dbt/generate/env/operation.go—Options.Workspacetype changed from*sandbox.SandboxWorkspaceto newWorkspaceDetailsstruct; callers updated accordingly;.env.localgains keboola adapter vars andKEBOOLA_TOKENwhen workspace has an editor session; private key written to.p8file instead of inlinepkg/lib/operation/dbt/init/operation.go— newBaseURLfield onDbtInitOptions;--key-pairflag removed (key-pair always used)internal/pkg/keboola/sandboxbridge package — not touched; workspace list/create/delete operations unchanged--key-pairflag removed fromdbt initChange type
Feature — Enables the
keboola_snowflakedbt adapter target alongside the existing direct-Snowflake profileJustification
The
keboola_snowflakedbt adapter routes queries through the Keboola Query Service instead of connecting directly to Snowflake. To use it, dbt needsbase_url,branch_id,workspace_id, andKEBOOLA_TOKENenv vars alongside the existing connection vars. This PR adds those vars to.env.localand a second profile target toprofiles.ymlso users can switch adapters withdbt run --target keboola_{target_name}without any manual config changes.The
--key-pairflag is removed because Snowflake has deprecated password authentication for service accounts; key-pair auth is always used now.Extracted from PR #2562 — only the dbt profile/env generation changes, without the workspace/data-science refactoring.
Deployment
Merge & automatic deploy.
Rollback plan
Revert of this PR.
Post release support plan
None.