diff --git a/acceptance/bundle/telemetry/deploy-git/databricks.yml b/acceptance/bundle/telemetry/deploy-git/databricks.yml new file mode 100644 index 00000000000..576d7a9ef25 --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-git/databricks.yml @@ -0,0 +1,2 @@ +bundle: + name: test-bundle diff --git a/acceptance/bundle/telemetry/deploy-git/out.test.toml b/acceptance/bundle/telemetry/deploy-git/out.test.toml new file mode 100644 index 00000000000..19b5b15d11f --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-git/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = [""] diff --git a/acceptance/bundle/telemetry/deploy-git/output.txt b/acceptance/bundle/telemetry/deploy-git/output.txt new file mode 100644 index 00000000000..8196d77e7b3 --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-git/output.txt @@ -0,0 +1,15 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Files: 1 uploaded, 0 deleted +Resources: 0 created, 0 changed, 0 deleted, 0 unchanged + +>>> cat [OUTPUT_DIR]/out.requests.txt +{ + "git": { + "origin_url": "https://github.com/databricks/cli", + "branch": "main", + "commit": "[COMMIT_SHA]" + }, + "from_workspace_folder": null +} diff --git a/acceptance/bundle/telemetry/deploy-git/script b/acceptance/bundle/telemetry/deploy-git/script new file mode 100644 index 00000000000..c3f3975c610 --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-git/script @@ -0,0 +1,8 @@ +git-repo-init +git remote add origin https://github.com/databricks/cli + +trace $CLI bundle deploy + +trace cat "$OUT_REQUESTS" | jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event | {git, from_workspace_folder}' + +rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/telemetry/deploy-git/test.toml b/acceptance/bundle/telemetry/deploy-git/test.toml new file mode 100644 index 00000000000..59966cb8183 --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-git/test.toml @@ -0,0 +1,9 @@ +# git-repo-init creates a repo in the test directory so the deploy has git metadata. +Ignore = [ + '.git', +] + +# The commit is different on every run. +[[Repls]] +Old = '\b[0-9a-f]{40}\b' +New = '[COMMIT_SHA]' diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 0806e13ae66..263be88925e 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -14,6 +14,7 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/bundle/metrics" + "github.com/databricks/cli/libs/dbr" "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/telemetry" @@ -195,6 +196,29 @@ func aiRuntimeTaskMetrics(jobs map[string]*resources.Job) (present, scheduled, m return present, scheduled, multitask } +// bundleGitInfo builds the git metadata for the deploy event, or nil when the +// bundle root is not inside a git repository. OriginURL already has credentials +// stripped by the load_git_details mutator. +func bundleGitInfo(b *bundle.Bundle) *protos.BundleGitInfo { + git := b.Config.Bundle.Git + if git.Branch == "" && git.Commit == "" && git.OriginURL == "" { + return nil + } + return &protos.BundleGitInfo{ + OriginURL: git.OriginURL, + Branch: git.Branch, + Commit: git.Commit, + } +} + +// fromWorkspaceFolder reports whether the bundle was deployed from a workspace +// folder: the CLI running on a Databricks cluster with the bundle source under +// /Workspace/, as opposed to a local checkout. Same signal as the source-linked +// deployment preset uses to detect a Databricks workspace. +func fromWorkspaceFolder(ctx context.Context, b *bundle.Bundle) bool { + return dbr.RunsOnRuntime(ctx) && strings.HasPrefix(b.SyncRootPath, "/Workspace/") +} + // LogDeployTelemetry logs a telemetry event for a bundle deploy command. func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { errMsg = telemetry.ScrubErrorMessage(errMsg) @@ -386,6 +410,9 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { ResourcesMetadata: collectResourcesMetadata(ctx, b), + Git: bundleGitInfo(b), + FromWorkspaceFolder: fromWorkspaceFolder(ctx, b), + Experimental: &protos.BundleDeployExperimental{ BundleMode: mode, ConfigurationFileCount: b.Metrics.ConfigurationFileCount, diff --git a/libs/telemetry/protos/bundle_deploy.go b/libs/telemetry/protos/bundle_deploy.go index ed5a79690a3..8a08eff6d27 100644 --- a/libs/telemetry/protos/bundle_deploy.go +++ b/libs/telemetry/protos/bundle_deploy.go @@ -38,9 +38,34 @@ type BundleDeployEvent struct { // Per-resource-type metadata (counts and state-size statistics). ResourcesMetadata *BundleResourcesMetadata `json:"resources_metadata,omitempty"` + // Git metadata of the source the bundle was deployed from. Nil when the + // bundle root is not inside a git repository. + Git *BundleGitInfo `json:"git,omitempty"` + + // True when the bundle was deployed from a workspace folder: the CLI ran on a + // Databricks cluster with the bundle source under /Workspace/, rather than + // from a local checkout. A deployment attribute, not git metadata, so it sits + // beside Git rather than inside it. + FromWorkspaceFolder bool `json:"from_workspace_folder,omitempty"` + Experimental *BundleDeployExperimental `json:"experimental,omitempty"` } +// BundleGitInfo mirrors the universe proto. Git provenance the CLI resolves during +// deploy: from `.git` locally, or from the workspace API when deploying from a +// Databricks Git folder. Values set under `bundle.git` in the configuration take +// precedence. Mirrors the git_info sent to the Deployment Metadata Service. +type BundleGitInfo struct { + // URL of the "origin" remote, with any userinfo (credentials) stripped. + OriginURL string `json:"origin_url,omitempty"` + + // Branch deployed from. + Branch string `json:"branch,omitempty"` + + // Full SHA of the HEAD commit deployed. + Commit string `json:"commit,omitempty"` +} + // These metrics are experimental and are often added in an adhoc manner. There // are no guarantees for these metrics and they maybe removed in the future without // any notice.