deploy

package
v0.8.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package deploy implements generic Fabric item deployment: it reads items from a repo's origin/main, applies parameter.yml substitution, compares against a target workspace, and publishes in dependency order. It mirrors the core of Microsoft's fabric-cicd, fronted by the interactive TUI in package cmd. All Fabric access goes through the narrow FabricClient interface so the logic is unit-testable with a fake.

Index

Constants

View Source
const (
	ReasonNameUnknown = "name-unknown"  // baseline GUID not in baseline index — no name to match by
	ReasonNotInTarget = "not-in-target" // name known but absent from every registered target workspace
	ReasonAmbiguous   = "ambiguous"     // name appears in 2+ target workspaces
)
View Source
const LocationReportBinding = "report dataset binding"

LocationReportBinding is the UnresolvedRef.Location for a report's definition.pbir dataset binding. Exported because the cmd layer uses it to route ownership: binding refs are collected by the dedicated report-binding pass, everything else by the per-item substitution pass.

Variables

This section is empty.

Functions

func DefinitionFormat added in v0.8.0

func DefinitionFormat(item LocalItem) string

DefinitionFormat returns the definition-envelope format flag for item types with several definition variants: an .ipynb notebook must declare "ipynb" (the API otherwise parses the payload as the .py git form), and SparkJobDefinition always uses "SparkJobDefinitionV2" (mirrors fabric-cicd's API_FORMAT_MAPPING). Empty for everything else. Exported because the compare must FETCH deployed definitions in the same format it publishes them — an ipynb repo diffed against the default .py form is a phantom full diff on every part.

func DeployedDescription

func DeployedDescription(deployed *fabric.Definition) string

DeployedDescription returns the item description stored in the deployed definition's .platform part (empty if there is none or it can't be parsed). futils excludes .platform from the part-by-part diff, so description drift is surfaced separately as a field-level change.

func Execute

func Execute(client FabricClient, token string, target fabric.Workspace, plan []PlannedItem, rb *Rebinder, modelsByWS map[string]map[string]string, done *int64) ([]Result, []PendingReportRebind, error)

Execute publishes a plan against the target workspace. For each item, in order, it applies logicalId substitution to every part; when rb is non-nil, it also auto-rebinds notebook lakehouse references by name. It then encodes parts to base64 and creates or updates the item.

modelsByWS is a caller-owned accumulator (targetWorkspaceID → model displayName → deployed GUID): Execute records every published SemanticModel into it so the SAME map, threaded through every group's Execute call, is complete by the time report rebinds run. Report rebinds are NOT done inline (that made them order-dependent); instead each published Report is returned as a PendingReportRebind for the post-deploy RebindReports pass.

A per-item error is captured in its Result (the run continues); Execute only returns a top-level error for a setup failure that aborts everything.

done, when non-nil, is incremented atomically once per plan item processed (success or failure) so a spinner can show live "Publishing X/Y" progress. The counter advances even for items that error out, matching the publish loop's "we're done with this item, on to the next" semantics.

func IsShellOnly added in v0.8.0

func IsShellOnly(itemType string) bool

IsShellOnly reports whether an item type publishes as a shell — no definition through the item APIs; see shellOnlyPublish.

func ListRemoteBranches added in v0.8.0

func ListRemoteBranches(repoPath string) ([]string, error)

ListRemoteBranches returns origin's branch names (without any origin/ prefix), sorted. It asks the remote directly (git ls-remote) so branches pushed from elsewhere — e.g. a Fabric workspace committing straight to DevOps — show up without a local fetch; when the remote is unreachable it falls back to the locally-known origin refs.

func ReplaceLogicalIds

func ReplaceLogicalIds(content []byte, idMap map[string]string) []byte

ReplaceLogicalIds rewrites every known source logicalId in content with its deployed target GUID. This is how cross-item references (e.g. a report pointing at a semantic model's logicalId) are repointed at the items that were just created in the target workspace. Requires that referenced items are published first (see order.go).

func RepoItemNames

func RepoItemNames(repoPath, itemType string) ([]string, error)

RepoItemNames scans repoPath (the working tree) for Fabric items of the given type and returns their display names, sorted and de-duplicated. Mirrors RepoItemTypes: unparseable .platform files are skipped, a missing path yields an empty result rather than an error.

func RepoItemNamesMulti added in v0.5.0

func RepoItemNamesMulti(repoPaths []string, itemTypes ...string) ([]string, error)

RepoItemNamesMulti unions RepoItemNames of the given types across several repos — one walk per repo regardless of how many types are asked for.

func RepoItemTypes

func RepoItemTypes(repoPath string) ([]string, error)

RepoItemTypes scans a repo directory for Fabric item folders (those containing a .platform file) and returns the distinct item types found, sorted. Used by the edit-customer "exclude item types" picker — a cheap local scan, no API. A missing or empty repoPath yields an empty slice, not an error.

func RepoItemTypesMulti added in v0.5.0

func RepoItemTypesMulti(repoPaths []string) ([]string, error)

RepoItemTypesMulti unions RepoItemTypes across several repos (empty paths and missing paths skipped), sorted and de-duplicated.

func TopLevelFolders

func TopLevelFolders(items []LocalItem) []string

TopLevelFolders returns the distinct first path segments of the items' FolderPaths (e.g. "FabricBackEnd" from "FabricBackEnd/NB_X.Notebook"), sorted. Items sitting directly at the repo root (no separator in FolderPath) have no grouping folder and are skipped. Used to offer a pick-list of mappable folders during deploy setup.

func WorkspaceFolderPath added in v0.8.0

func WorkspaceFolderPath(itemFolderPath, mappingRoot string) string

WorkspaceFolderPath derives the target workspace folder an item should land in from its repo path and the mapping root it deploys under. The mapping root is stripped, then the item's own folder segment is dropped — what's left is the workspace folder path. Examples (root "Backend"):

Backend/X.Notebook                → ""            (workspace root)
Backend/Sub/X.Notebook            → "Sub"
Backend/Notebooks/Config/X.Notebook → "Notebooks/Config"

An empty root (whole-repo mapping) treats the full path minus the item segment as the folder path. Slashes are normalized.

Types

type Action

type Action int
const (
	ActionCreate Action = iota
	ActionUpdate
	ActionDelete
)

func (Action) String

func (a Action) String() string

type Class

type Class int
const (
	ClassNew       Class = iota // local item not present in target
	ClassExists                 // local item present in target (pre-content-diff / unverified)
	ClassOrphan                 // target item with no local counterpart
	ClassChanged                // exists in target and content differs
	ClassUnchanged              // exists in target and content matches
)

func (Class) String

func (c Class) String() string

type CompareRow

type CompareRow struct {
	Class      Class
	Local      LocalItem
	Deployed   fabric.Item
	DeployedID string
}

CompareRow is one line in the compare view. Local is set for New/Exists; Deployed is set for Exists/Orphan.

func Compare

func Compare(local []LocalItem, deployed []fabric.Item, scope map[string]bool) []CompareRow

Compare classifies local items against the deployed set by displayName+type. scope limits which deployed types can be flagged as orphans (so a workspace full of out-of-scope items isn't reported as orphaned).

func (CompareRow) ItemType

func (r CompareRow) ItemType() string

ItemType returns the type regardless of side.

func (CompareRow) Name

func (r CompareRow) Name() string

Name returns the displayName regardless of which side populated the row.

type DeleteTarget

type DeleteTarget struct {
	ID   string
	Name string
	Type string
}

DeleteTarget is a target-only item (an orphan) the user chose to delete.

type FabricClient

type FabricClient interface {
	ListItems(token, workspaceID string) ([]fabric.Item, error)
	ListItemsByType(token, workspaceID, itemType string) ([]fabric.Item, error)
	ListWorkspaces(token string) ([]fabric.Workspace, error)
	GetItemDefinition(token, workspaceID, itemID, format string) (*fabric.Definition, error)
	CreateItem(token, workspaceID, displayName, itemType string, def *fabric.Definition, creationPayload json.RawMessage, folderID string) (fabric.Item, error)
	ListFolders(token, workspaceID string) ([]fabric.Folder, error)
	CreateFolder(token, workspaceID, displayName, parentFolderID string) (fabric.Folder, error)
	UpdateItemDefinition(token, workspaceID, itemID string, def *fabric.Definition) error
	UpdateItem(token, workspaceID, itemID, displayName, description string) error
	DeleteItem(token, workspaceID, itemID string) error
	RebindReport(token, workspaceID, reportID, datasetID string) error
	BulkImportDefinitions(token, workspaceID string, parts []fabric.DefinitionPart, opts fabric.BulkImportOptions) (*fabric.BulkImportResult, error)
	GetLakehouseSqlEndpoint(token, workspaceID, lakehouseID string) (host, id string, err error)
}

FabricClient is the narrow set of Fabric operations the deploy package needs. It is satisfied structurally by cmd.APIClient, so the TUI passes its existing client straight through without an adapter.

type FindReplace

type FindReplace struct {
	FindValue    string            `yaml:"find_value"`
	ReplaceValue map[string]string `yaml:"replace_value"`
	IsRegex      string            `yaml:"is_regex"`
	IgnoreCase   string            `yaml:"ignore_case"`
	ItemType     StringOrSlice     `yaml:"item_type"`
	ItemName     StringOrSlice     `yaml:"item_name"`
	FilePath     StringOrSlice     `yaml:"file_path"`
}

FindReplace is one literal/regex substitution rule.

type IndexedItem

type IndexedItem struct {
	Name        string
	Type        string
	GUID        string
	WorkspaceID string
}

IndexedItem is one item located in an environment's name index.

type LocalItem

type LocalItem struct {
	Type        string
	DisplayName string
	LogicalID   string
	Description string
	FolderPath  string
	Parts       []Part
	Platform    []byte // raw .platform bytes; retained for the bulk backend (not a Part)
	// CreationPayload is .platform's raw metadata.creationPayload — one-time
	// create settings for shell types (Warehouse collation, Lakehouse
	// enableSchemas). Sent on create only; nil when .platform has none.
	CreationPayload json.RawMessage
	// ShellParts counts the definition files discovery dropped because the
	// type publishes as a shell (see shellOnlyPublish) — surfaced as compare
	// notes and publish warnings so a git-side schema change is never
	// silently "deployed".
	ShellParts int
}

func ItemsInFolder

func ItemsInFolder(items []LocalItem, folder string) []LocalItem

ItemsInFolder returns the items whose FolderPath is inside the given repo subfolder. An empty folder matches everything (whole-repo fallback). Slashes are normalized so "Backend", "/Backend", and "Backend/" behave identically. Matching is prefix-on-a-path-boundary: "Backend" matches "Backend/x" but not "BackendExtra/x".

func SortForPublish

func SortForPublish(items []LocalItem) []LocalItem

SortForPublish returns a new slice ordered by publish priority. Stable, so items of the same type keep their discovery order — except DataPipelines, which get a dependency-ordering pass within the type (see sortPipelinesByDependency).

func StripScheduleParts added in v0.7.0

func StripScheduleParts(items []LocalItem) []LocalItem

StripScheduleParts returns items with any ".schedules" definition part removed. Schedules are optional in the definition APIs — omitting the part on update leaves the target item's schedules untouched — so filtering here lets schedules be managed per environment instead of overwritten from git.

type LookupStatus

type LookupStatus int

LookupStatus distinguishes the three forward-lookup outcomes so callers can explain WHY a name didn't resolve.

const (
	LookupFound LookupStatus = iota
	LookupAbsent
	LookupAmbiguous
)

type NameIndex

type NameIndex struct {
	// contains filtered or unexported fields
}

NameIndex maps a single environment's items both ways: GUID->item (to learn the name of a baseline GUID) and name+type->item (to find that name's GUID in a target env). A name+type appearing in more than one of the env's workspaces is recorded as ambiguous and will not resolve forward — matching by name is only safe when names are unique within the env.

func BuildNameIndex

func BuildNameIndex(client FabricClient, token string, workspaces []fabric.Workspace) (*NameIndex, error)

BuildNameIndex enumerates every workspace's items (one ListItems call each) and indexes them. Each item's WorkspaceID is set to the workspace it was listed from, since the items endpoint does not reliably echo it back.

func (*NameIndex) Add added in v0.8.0

func (i *NameIndex) Add(ent IndexedItem)

Add indexes one more item after the initial build — the publish path uses it to register items it just created so later rebinds in the same run resolve them. Mirrors BuildNameIndex's ambiguity rule: a name+type already indexed under a DIFFERENT GUID becomes ambiguous and stops resolving forward.

func (*NameIndex) ItemByGUID

func (i *NameIndex) ItemByGUID(guid string) (IndexedItem, bool)

ItemByGUID returns the indexed item for a GUID (reverse lookup).

func (*NameIndex) ItemByName

func (i *NameIndex) ItemByName(name, typ string) (IndexedItem, bool)

ItemByName returns the indexed item for a name+type (forward lookup). Returns false when the name is absent, or ambiguous across the env's workspaces.

func (*NameIndex) LookupName

func (i *NameIndex) LookupName(name, typ string) (IndexedItem, LookupStatus)

LookupName resolves a name+type forward, reporting whether it was found, absent, or ambiguous across the env's workspaces.

type Override

type Override struct {
	ItemType string
	ItemName string
}

Override is a futils-native reference override resolved by name, keyed by the baseline GUID as it appears in git. It mirrors config.ReferenceOverride without the deploy package depending on config — the cmd layer converts.

type Parameters

type Parameters struct {
	FindReplace     []FindReplace    `yaml:"find_replace"`
	KeyValueReplace []map[string]any `yaml:"key_value_replace"`
	SparkPool       []map[string]any `yaml:"spark_pool"`
}

Parameters is the parsed parameter.yml. KeyValueReplace and SparkPool are parsed (forward-compat) but not applied until Phase 3.

func ParseParameters

func ParseParameters(raw []byte) (Parameters, error)

ParseParameters parses parameter.yml bytes. Empty input yields an empty (no-op) Parameters, since a repo may legitimately have no parameter file.

func (Parameters) ApplyFindReplace

func (p Parameters) ApplyFindReplace(env string, item LocalItem, filePath string, content []byte, resolve func(string) (string, error)) ([]byte, error)

ApplyFindReplace runs every matching find_replace rule against one file's content for the chosen environment. resolve expands dynamic variables (e.g. "$items.Lakehouse.X.$id") in replacement values; pass an identity function when there are none. filePath is the item-relative path of the file.

type Part

type Part struct {
	Path    string
	Content []byte
}

type PartDiff

type PartDiff struct {
	Path string
	Old  string
	New  string
}

PartDiff is the normalized old (deployed) vs new (substituted-local) text of one item part that differs. Old is empty when the part is new locally; New is empty when the part exists only in the deployed definition.

func DiffParts

func DiffParts(localParts map[string][]byte, deployed *fabric.Definition) []PartDiff

DiffParts returns, for each part whose normalized content differs between the substituted local parts and the deployed definition, the normalized old (deployed) and new (local) text. It is the single source of the content verdict (len == 0 means unchanged).

type PendingReportRebind

type PendingReportRebind struct {
	WorkspaceID string
	ReportID    string // the report's deployed GUID
	ReportName  string
	Ref         datasetRef
}

PendingReportRebind defers a single report's rebind to the post-deploy pass. It carries everything RebindReports needs without re-reading the plan: which workspace the report landed in, its deployed GUID + name, and the parsed dataset reference. Because the rebind runs AFTER every group has deployed (and against a model map accumulated across all of them), it no longer depends on whether the model's group ran before or after the report's.

type PlannedItem

type PlannedItem struct {
	Item       LocalItem
	Action     Action
	ExistingID string // set when Action == ActionUpdate
	// WorkspaceFolder is the target workspace folder path a newly-created item
	// should land in, derived from its repo path under the mapping root ("" =
	// workspace root). Only consulted for ActionCreate — existing items keep
	// their current placement.
	WorkspaceFolder string
}

PlannedItem is one item to publish, with its resolved create/update action.

func BuildPlan

func BuildPlan(selected []LocalItem, deployed []fabric.Item, mappingRoot string) []PlannedItem

BuildPlan turns the user-selected local items into an ordered publish plan. An item already present in the target (matched by type+name) becomes an Update against its existing ID; otherwise a Create. The plan is sorted by publish priority so dependencies (e.g. semantic models) go first. mappingRoot is the repo folder this group deploys from; it's stripped when deriving each new item's target workspace folder (empty = whole-repo mapping).

type RebindChange

type RebindChange struct {
	Kind string
	Name string
	Old  string
	New  string
}

RebindChange records one applied baseline→target rewrite, for the deploy summary. Kind is "Lakehouse", "Workspace", or "SQL endpoint". Name is the resolved item/workspace display name, shown alongside the GUIDs so the user can tell which reference is which.

type RebindOutcome

type RebindOutcome struct {
	Changes        []RebindChange
	Unresolved     []UnresolvedRef
	ReportBindings []ReportBinding
}

RebindOutcome bundles what a rebind pass produced: the applied changes (for the summary, deduped by Old within a pass) and the references it could not resolve (surfaced to the user).

func SubstituteParts

func SubstituteParts(item LocalItem, idMap map[string]string, resolver *Resolver, rb *Rebinder) (map[string][]byte, RebindOutcome, error)

SubstituteParts applies logicalId substitution, then (when rb is non-nil) auto-rebind of notebook lakehouse references, to each of an item's parts. Returns path -> substituted raw bytes (not base64), plus a RebindOutcome with any changes applied and references the rebinder could not resolve (tagged with the item name). Shared by the publish path (which base64-encodes the result) and the content-diff. A nil rb skips rebinding entirely.

func (*RebindOutcome) AddUnresolved added in v0.6.0

func (o *RebindOutcome) AddUnresolved(ref UnresolvedRef)

AddUnresolved records an unresolved reference on the outcome, deduplicated on (GUID, ItemType, Location): a model carrying the same broken reference in 80 table expressions reports it once with Count=80, not as 80 identical lines. Merging pre-counted refs (outcome aggregation) sums the counts.

type Rebinder

type Rebinder struct {
	// contains filtered or unexported fields
}

Rebinder translates baseline-environment GUIDs to a target env by item name.

The lazy SQL-endpoint cache (targetEndpoint) is guarded by mu so the rebinder is safe to share across the concurrent per-item compare workers. Every other field is set in NewRebinder / SetSubstitutions and never mutated during the CONCURRENT phase (the compare): substitutions is installed once before any concurrent use, so only the endpoint cache needs the lock there. The target NameIndex additionally grows during the SEQUENTIAL publish loop — RegisterTargetItem adds each just-created item so later rebinds in the same run can resolve it; publish never runs concurrently with a compare.

func NewRebinder

func NewRebinder(client FabricClient, token string, baselineWS, targetWS []fabric.Workspace, overrides map[string]Override) (*Rebinder, error)

NewRebinder builds the baseline and target name indices and returns a Rebinder. baselineWS / targetWS are each env's full workspace set (deploy targets plus reference-only workspaces). A nil overrides map is treated as empty.

func (*Rebinder) ApplyCustomSubstitutions

func (rb *Rebinder) ApplyCustomSubstitutions(item LocalItem, partPath string, content []byte) ([]byte, RebindOutcome)

ApplyCustomSubstitutions runs the customer's find→replace rules over one part. Each rule whose optional item/file filters match is applied: the replacement is a literal (Literal) or the resolved target attribute (by name in the target env). Applied rewrites are recorded as RebindChange{Kind:"Substitution"}; rules whose target can't be resolved are left unapplied and surfaced as UnresolvedRef. Runs in the explicit tier (before auto-rebind).

func (*Rebinder) RebindNotebookLakehouses

func (rb *Rebinder) RebindNotebookLakehouses(content []byte) ([]byte, RebindOutcome)

RebindNotebookLakehouses rewrites the lakehouse dependency GUIDs in a Fabric notebook part from baseline to target, by name. It only touches GUIDs found in the dependencies.lakehouse metadata block (never arbitrary UUIDs in code), records each applied rewrite as a RebindChange, and string-replaces those exact GUIDs throughout the content. GUIDs it cannot resolve become UnresolvedRef and are left unchanged. Content with no lakehouse block is returned unchanged with an empty outcome.

func (*Rebinder) RebindPart

func (rb *Rebinder) RebindPart(item LocalItem, partPath string, content []byte) ([]byte, RebindOutcome)

RebindPart dispatches a single item part to the right rebind pass by item type and part name, returning the rewritten bytes and the outcome. Parts with no recognized reference location are returned unchanged.

func (*Rebinder) RebindReportConnection

func (rb *Rebinder) RebindReportConnection(item LocalItem, content []byte) ([]byte, RebindOutcome)

func (*Rebinder) RebindSemanticModel

func (rb *Rebinder) RebindSemanticModel(content []byte) ([]byte, RebindOutcome)

RebindSemanticModel rewrites a Direct Lake semantic model part's data-source connection from baseline to target. It handles both on-disk shapes: Direct Lake on OneLake (workspace+lakehouse GUID URL) and Direct Lake on SQL (Sql.Database host+endpoint-id). Only the values inside a matched connection expression are rewritten. Content with neither shape is returned unchanged.

func (*Rebinder) RebindShortcuts added in v0.8.0

func (rb *Rebinder) RebindShortcuts(content []byte) ([]byte, RebindOutcome)

RebindShortcuts rewrites the OneLake target GUIDs in a lakehouse's shortcuts.metadata.json from baseline to target, by name. shortcuts.metadata.json is a Lakehouse definition part, so it already deploys with the item — but the items API stores the targets verbatim (unlike Fabric's own deployment pipelines, it does NOT auto-remap internal shortcuts), so a shortcut pointing at a baseline lakehouse would keep pointing there after deploy. For each OneLake target we resolve itemId (a baseline GUID) to the same-named item in the target env and rewrite that shortcut's itemId and workspaceId in place. Self-references (empty/zero itemId GUIDs, which Fabric maps to the current lakehouse), external targets (ADLS/S3/etc.), and unparseable content are left untouched; an unresolvable OneLake target is surfaced as UnresolvedRef and left as-is.

The rewrite edits the parsed JSON per shortcut instead of string-replacing the whole file: a whole-file replace cannot express one baseline workspace GUID resolving to two different target workspaces (two shortcuts sharing a baseline workspace whose items deploy to different target workspaces), and a zero-GUID workspaceId used as a replace key would also rewrite every OTHER self-reference's zero GUIDs in the file. Content is re-marshalled only when something actually changed — an untouched file stays byte-identical — and the diff view normalizes JSON before comparing, so the formatting round-trip never reads as a change.

func (*Rebinder) RegisterTargetItem added in v0.8.0

func (rb *Rebinder) RegisterTargetItem(name, itemType, guid, workspaceID string)

RegisterTargetItem adds a just-created item to the target name index so later rebinds in the same publish run resolve it. Without this, an item created earlier in the run (e.g. the lakehouse a later lakehouse's shortcut points at) stays unresolvable, because the index was built before the run started — and the shortcut would silently keep its baseline GUID.

func (*Rebinder) ResolveTargetAttr

func (rb *Rebinder) ResolveTargetAttr(itemType, itemName, attr string) (string, bool)

ResolveTargetAttr resolves a target item by name in the target env and returns the requested attribute: "id"/"" → the item GUID; "sqlendpoint"/"sqlendpointid" → the lakehouse's SQL endpoint host/id (via the cached endpoint lookup).

func (*Rebinder) SetSubstitutions

func (rb *Rebinder) SetSubstitutions(subs []Substitution)

SetSubstitutions installs the customer's custom find→replace rules. Called by the cmd layer after NewRebinder (config→engine conversion lives there). Each IsRegex rule has its pattern compiled once here; a rule whose pattern is invalid is stored with a nil compiled field and silently skipped at apply time.

type ReportBinding

type ReportBinding struct {
	Report    string // report display name
	Model     string // resolved semantic-model name
	Workspace string // target workspace the model lives in
}

ReportBinding is a resolved report→semantic-model binding, surfaced in the compare step so the user sees which model a report will bind to (and in which target workspace) before deploying.

type ReportRebindOutcome

type ReportRebindOutcome struct {
	ReportID string
	Err      error
	Warning  string
}

ReportRebindOutcome is the result of attempting one pending report rebind. Exactly one of Err / Warning is set when something noteworthy happened; an empty outcome (both blank) means the rebind succeeded cleanly. ReportID matches the report's deployed GUID so callers can fold the outcome back into its Result. (Distinct from RebindOutcome, which is the lakehouse/workspace substitution summary from the rebind package.)

func RebindReports

func RebindReports(client FabricClient, token string, modelsByWS map[string]map[string]string, pending []PendingReportRebind, rebinderActive bool) []ReportRebindOutcome

RebindReports runs AFTER every group has deployed, repointing each published report at the matching semantic model. It is order-independent: modelsByWS is the union of models published across every group, keyed by target workspace, so a model and its report can sit in different folders/groups and still bind — while a model named "X" in one workspace can never bind a report in another (the workspace key isolates them).

rebinderActive signals whether a Rebinder is configured for this run (rb != nil at the call site). This governs the byConnection warning:

  • byPath, model found in the report's workspace → RebindReport; an API failure becomes an Err outcome.
  • byPath, model NOT in modelsByWS → silent skip. The most common case is an incremental deploy where only the report changed and its SemanticModel was Unchanged (never published this run); the model is already live and the report is already correctly bound — emitting a Warning here is a false alarm.
  • byConnection, model CREATED this run (in modelsByWS) → RebindReport; the payload kept the baseline GUID because the model wasn't in the pre-deploy target index at diff time, so we bind it now (mirrors byPath).
  • byConnection, rebinderActive=false, model not this run → warn; the payload is unchanged (no rebinder ran the in-payload rewrite) so the report retains its stale dev binding — the operator must verify manually.
  • byConnection, rebinderActive=true, model not this run → no-op; the in-payload rewrite (RebindReportConnection) already resolved it, or it was surfaced as unresolved pre-deploy.
  • no dataset reference → no outcome (nothing to rebind).

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver expands fabric-cicd dynamic variables against the TARGET workspace at deploy time. It caches workspace and item lookups so repeated variables don't re-hit the API.

The lazy caches (wsByName, itemsWS) are guarded by mu so the resolver is safe to share across the concurrent per-item compare workers. client/token/target are set at construction and never mutated, so they need no lock.

func NewResolver

func NewResolver(client FabricClient, token string, target fabric.Workspace) *Resolver

func (*Resolver) Resolve

func (r *Resolver) Resolve(value string) (string, error)

Resolve expands a single dynamic-variable value. A value that doesn't begin with "$" is returned unchanged (literal replacement value).

type Result

type Result struct {
	Name        string
	Type        string
	Action      Action
	ID          string // deployed item ID
	WorkspaceID string // target workspace the item was deployed to / deleted from
	Err         error
	Warning     string // non-fatal issue after a successful publish (e.g. description not synced)
}

Result is the per-item outcome of a deploy run.

func BulkImport

func BulkImport(client FabricClient, token string, target fabric.Workspace, items []LocalItem, rb *Rebinder) ([]Result, error)

BulkImport publishes every item in items into target in ONE bulkImportDefinitions call. Callers pass items already grouped by target workspace (the API is workspace-scoped). For each item it runs SubstituteParts (logicalId is a no-op here — idMap is empty; rb applies cross-env rebind of notebook/semantic-model references and custom substitutions, exactly as the per-item backend does), base64-encodes each part under its workspace-absolute path, and appends the item's .platform (with logicalId stripped, so pairing is by name+type).

Report→model byPath references and overall dependency order are resolved by Fabric within the payload, so the per-item BuildPlan/publishOrder and the post-deploy RebindReports byPath pass are NOT needed here. (resolver is passed nil: SubstituteParts does not use it.)

func DeleteItems

func DeleteItems(client FabricClient, token string, target fabric.Workspace, targets []DeleteTarget) []Result

DeleteItems removes each target from the workspace, returning a per-target Result (Action=ActionDelete; Err set on failure, like Execute). It is kept separate from Execute so the destructive path is isolated and auditable. Callers MUST confirm before invoking — deletions are irreversible.

type Source

type Source struct {
	// contains filtered or unexported fields
}

Source reads Fabric items from a single git ref (always origin/<default>).

func NewSource

func NewSource(repo string) (*Source, error)

NewSource validates the repo and resolves the deploy ref to origin/<default>.

func NewSourceAt added in v0.8.0

func NewSourceAt(repo, branch string) (*Source, error)

NewSourceAt is NewSource with an explicit branch: a non-empty branch pins the deploy ref to origin/<branch> (a customer deploying from origin/dev, a release branch, or a default branch that isn't main/master), skipping default-branch detection entirely. The pinned ref may not exist locally yet — Fetch verifies it once the remote has been consulted.

func (*Source) DiscoverItems

func (s *Source) DiscoverItems() ([]LocalItem, error)

DiscoverItems lists every tree path at the deploy ref, treats each folder containing a .platform as an item, and reads that folder's files (excluding .platform) as definition parts. Part paths are relative to the item folder.

Efficiency: files are bucketed into item folders in a single O(files×folders) pass, and all blob content is fetched in one git cat-file --batch subprocess call, eliminating the N+1 git show pattern.

func (*Source) Fetch

func (s *Source) Fetch() error

Fetch updates remote-tracking refs so the deploy ref reflects the latest merged state, then verifies the ref actually exists — the clear error beats the git-flavored one discovery would produce for a mistyped or deleted branch. Always run before discovery.

func (*Source) Head added in v0.8.0

func (s *Source) Head() (string, error)

Head returns the short commit SHA the deploy ref currently points at — the "what exactly was deployed" line for reports. Requires the ref to be known locally (any deploy has already fetched it).

func (*Source) ReadFile

func (s *Source) ReadFile(p string) ([]byte, error)

ReadFile returns the bytes of a repo-relative path at the deploy ref.

func (*Source) Ref

func (s *Source) Ref() string

Ref returns the resolved deploy ref, e.g. "origin/main".

func (*Source) Repo

func (s *Source) Repo() string

Repo returns the resolved repository root (git top-level) this source reads from. Used to persist the path so the picker can be skipped next time.

type StringOrSlice

type StringOrSlice []string

StringOrSlice unmarshals a YAML field that may be either a scalar string or a sequence of strings (parameter.yml allows both for item_type/item_name/ file_path). It always presents as a []string.

func (*StringOrSlice) UnmarshalYAML

func (s *StringOrSlice) UnmarshalYAML(value *yaml.Node) error

type Substitution

type Substitution struct {
	FindValue  string
	IsRegex    bool
	ItemType   string
	ItemName   string
	FilePath   string
	TargetType string
	TargetName string
	Attr       string
	Literal    string
	// contains filtered or unexported fields
}

Substitution is a futils-native find→replace rule, the engine-side mirror of config.Substitution (kept config-free). Replacement is a literal (Literal) or the resolved attribute of a target item looked up by name in the target env. For IsRegex rules, compiled holds the pre-compiled *regexp.Regexp set by SetSubstitutions so the pattern is compiled once instead of once per part.

type UnresolvedRef

type UnresolvedRef struct {
	GUID     string
	ItemType string
	Location string // "default_lakehouse" | "known_lakehouses"
	ItemName string
	Reason   string // ReasonNameUnknown | ReasonNotInTarget | ReasonAmbiguous
	// Count is how many occurrences collapsed into this ref (a model can carry
	// the same broken reference in every table expression). 0 means 1 — set
	// only by AddUnresolved.
	Count int
}

UnresolvedRef is a baseline GUID the rebinder could not translate. Surfaced to the user so they can register an override (or ignore/strip it). ItemName is the notebook the GUID was found in, filled in by the substitution pass.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL