Skip to content

Commit bbbc19d

Browse files
denikIsaac
andcommitted
bundle: look up the deployment-history node alongside the remote state reads
With deployment history configured, plan/deploy/destroy read resources.json and only then get-status resources.deployment.json to find the deployment id. readStates now runs that lookup concurrently with the state reads and passes the result on StateDesc, saving one sequential round trip. Co-authored-by: Isaac <no-reply@databricks.com>
1 parent 96dead2 commit bbbc19d

3 files changed

Lines changed: 72 additions & 29 deletions

File tree

‎bundle/statemgmt/state_pull.go‎

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import (
1717
"github.com/databricks/cli/bundle"
1818
"github.com/databricks/cli/bundle/config/engine"
1919
"github.com/databricks/cli/bundle/deploy"
20+
bundleenv "github.com/databricks/cli/bundle/env"
2021
"github.com/databricks/cli/libs/atomicfile"
2122
"github.com/databricks/cli/libs/diag"
23+
"github.com/databricks/cli/libs/dms"
2224
"github.com/databricks/cli/libs/filer"
2325
"github.com/databricks/cli/libs/log"
2426
"github.com/databricks/cli/libs/logdiag"
@@ -43,6 +45,16 @@ type StateDesc struct {
4345
IsLocal bool `json:"-"`
4446

4547
AllStates []*StateDesc
48+
49+
// Deployment is the deployment-history lookup made alongside the remote state reads; nil when
50+
// none was made (state not pulled, or deployment history not configured).
51+
Deployment *DeploymentLookup `json:"-"`
52+
}
53+
54+
// DeploymentLookup is the result of dms.LookupDeploymentID.
55+
type DeploymentLookup struct {
56+
ID string
57+
Err error
4658
}
4759

4860
func (s *StateDesc) String() string {
@@ -151,7 +163,7 @@ func PullResourcesState(ctx context.Context, b *bundle.Bundle, alwaysPull Always
151163
_, localPathDirect := b.StateFilenameDirect(ctx)
152164
_, localPathTerraform := b.StateFilenameTerraform(ctx)
153165

154-
states := readStates(ctx, b, alwaysPull)
166+
states, deployment := readStates(ctx, b, alwaysPull)
155167

156168
if logdiag.HasError(ctx) {
157169
return nil
@@ -170,6 +182,7 @@ func PullResourcesState(ctx context.Context, b *bundle.Bundle, alwaysPull Always
170182
winner = states[len(states)-1]
171183
winner.AllStates = states
172184
}
185+
winner.Deployment = deployment
173186

174187
log.Infof(ctx, "Available resource state files (from least to most preferred): %v", states)
175188

@@ -206,14 +219,15 @@ func PullResourcesState(ctx context.Context, b *bundle.Bundle, alwaysPull Always
206219
return winner
207220
}
208221

209-
func readStates(ctx context.Context, b *bundle.Bundle, alwaysPull AlwaysPull) []*StateDesc {
222+
func readStates(ctx context.Context, b *bundle.Bundle, alwaysPull AlwaysPull) ([]*StateDesc, *DeploymentLookup) {
210223
var states []*StateDesc
224+
var deployment *DeploymentLookup
211225

212226
remotePathDirect, localPathDirect := b.StateFilenameDirect(ctx)
213227
remotePathTerraform, localPathTerraform := b.StateFilenameTerraform(ctx)
214228

215229
if logdiag.HasError(ctx) {
216-
return nil
230+
return nil, nil
217231
}
218232

219233
directLocalState := localRead(ctx, localPathDirect, engine.EngineDirect)
@@ -223,7 +237,7 @@ func readStates(ctx context.Context, b *bundle.Bundle, alwaysPull AlwaysPull) []
223237
f, err := deploy.StateFiler(ctx, b)
224238
if err != nil {
225239
logdiag.LogError(ctx, err)
226-
return nil
240+
return nil, nil
227241
}
228242

229243
var wg sync.WaitGroup
@@ -237,6 +251,16 @@ func readStates(ctx context.Context, b *bundle.Bundle, alwaysPull AlwaysPull) []
237251
terraformRemoteState = filerRead(ctx, f, remotePathTerraform, engine.EngineTerraform)
238252
})
239253

254+
// Whether the state records deployment history is only known once it is read, but the
255+
// lookup is needed right after; going by the config overlaps it with the reads. A state
256+
// that disagrees with the config is either rejected or looked up again by the caller.
257+
if ConfiguresDeploymentHistory(ctx, b) {
258+
wg.Go(func() {
259+
id, err := dms.LookupDeploymentID(ctx, b.WorkspaceClient(ctx), b.Config.Workspace.StatePath)
260+
deployment = &DeploymentLookup{ID: id, Err: err}
261+
})
262+
}
263+
240264
wg.Wait()
241265

242266
// find highest serial across all state files
@@ -250,7 +274,16 @@ func readStates(ctx context.Context, b *bundle.Bundle, alwaysPull AlwaysPull) []
250274
return a.Serial - b.Serial
251275
})
252276

253-
return states
277+
return states, deployment
278+
}
279+
280+
// ConfiguresDeploymentHistory reports whether the bundle config (or env) turns deployment history on.
281+
func ConfiguresDeploymentHistory(ctx context.Context, b *bundle.Bundle) bool {
282+
var configured *bool
283+
if b.Config.Experimental != nil {
284+
configured = b.Config.Experimental.DeploymentHistory
285+
}
286+
return bundleenv.RecordsDeploymentHistory(ctx, configured)
254287
}
255288

256289
func validateStates(states []*StateDesc) error {

‎cmd/bundle/utils/process.go‎

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"path"
87
"path/filepath"
98
"strconv"
109
"time"
@@ -18,7 +17,6 @@ import (
1817
"github.com/databricks/cli/bundle/deployplan"
1918
"github.com/databricks/cli/bundle/direct"
2019
"github.com/databricks/cli/bundle/direct/dstate"
21-
bundleenv "github.com/databricks/cli/bundle/env"
2220
"github.com/databricks/cli/bundle/phases"
2321
"github.com/databricks/cli/bundle/scripts"
2422
"github.com/databricks/cli/bundle/statemgmt"
@@ -35,7 +33,6 @@ import (
3533
"github.com/databricks/cli/libs/telemetry"
3634
"github.com/databricks/cli/libs/telemetry/protos"
3735
"github.com/databricks/databricks-sdk-go"
38-
"github.com/databricks/databricks-sdk-go/apierr"
3936
"github.com/databricks/databricks-sdk-go/service/bundledeployments"
4037
"github.com/databricks/databricks-sdk-go/useragent"
4138
"github.com/spf13/cobra"
@@ -372,7 +369,7 @@ func ProcessBundleRet(cmd *cobra.Command, opts ProcessOptions) (b *bundle.Bundle
372369

373370
if needDirectState && stateDesc.IsDMS() {
374371
var err error
375-
dmsDeploymentID, dmsDeployment, err = fetchDeploymentFromStatePath(ctx, b.WorkspaceClient(ctx), b.Config.Workspace.StatePath)
372+
dmsDeploymentID, dmsDeployment, err = fetchDeploymentFromStatePath(ctx, b.WorkspaceClient(ctx), b.Config.Workspace.StatePath, stateDesc)
376373
if err != nil {
377374
logdiag.LogError(ctx, err)
378375
return b, stateDesc, root.ErrAlreadyPrinted
@@ -586,6 +583,8 @@ func ValidateEngineSetting(ctx context.Context, b *bundle.Bundle) error {
586583

587584
// Lookup and return the deployment object from ${workspace.state_path}/resources.deployment.json
588585
//
586+
// The id comes from stateDesc.Deployment when the state pull already looked it up.
587+
//
589588
// TODO: a deployment is only usable when both the node and the service's record exist, and a
590589
// half-created one - node present, record missing - blocks the bundle here even though
591590
// CreateDeployment already recovers from it. Move this behind a dms.ReadDeployment(ctx, statePath)
@@ -594,17 +593,17 @@ func ValidateEngineSetting(ctx context.Context, b *bundle.Bundle) error {
594593
//
595594
// TODO: ask the service for a lookup by state path, so this is one round trip rather than two - a
596595
// workspace lookup to turn the node into an id, then a get by that id.
597-
func fetchDeploymentFromStatePath(ctx context.Context, w *databricks.WorkspaceClient, statePath string) (string, *bundledeployments.Deployment, error) {
598-
nodePath := path.Join(statePath, dms.DeploymentNodeName)
599-
600-
obj, err := w.Workspace.GetStatusByPath(ctx, nodePath)
601-
if errors.Is(err, apierr.ErrNotFound) || errors.Is(err, apierr.ErrResourceDoesNotExist) {
602-
return "", nil, nil
596+
func fetchDeploymentFromStatePath(ctx context.Context, w *databricks.WorkspaceClient, statePath string, stateDesc *statemgmt.StateDesc) (string, *bundledeployments.Deployment, error) {
597+
var deploymentID string
598+
var err error
599+
if stateDesc.Deployment != nil {
600+
deploymentID, err = stateDesc.Deployment.ID, stateDesc.Deployment.Err
601+
} else {
602+
deploymentID, err = dms.LookupDeploymentID(ctx, w, statePath)
603603
}
604-
if err != nil {
605-
return "", nil, fmt.Errorf("looking up deployment at %s: %w", nodePath, err)
604+
if err != nil || deploymentID == "" {
605+
return "", nil, err
606606
}
607-
deploymentID := strconv.FormatInt(obj.ObjectId, 10)
608607
deployment, err := w.BundleDeployments.GetDeployment(ctx, bundledeployments.GetDeploymentRequest{
609608
Name: dms.DeploymentName(deploymentID),
610609
})
@@ -639,7 +638,7 @@ func OpenDirectStateForRead(ctx context.Context, b *bundle.Bundle, stateDesc *st
639638
return enforceDeploymentHistorySetting(ctx, b, stateDesc, false)
640639
}
641640

642-
dmsDeploymentID, dmsDeployment, err := fetchDeploymentFromStatePath(ctx, b.WorkspaceClient(ctx), b.Config.Workspace.StatePath)
641+
dmsDeploymentID, dmsDeployment, err := fetchDeploymentFromStatePath(ctx, b.WorkspaceClient(ctx), b.Config.Workspace.StatePath, stateDesc)
643642
if err != nil {
644643
return err
645644
}
@@ -658,7 +657,7 @@ func OpenDirectStateForRead(ctx context.Context, b *bundle.Bundle, stateDesc *st
658657
}
659658

660659
func resolveDeploymentHistory(ctx context.Context, b *bundle.Bundle, stateDesc *statemgmt.StateDesc) bool {
661-
configured := configuresDeploymentHistory(ctx, b)
660+
configured := statemgmt.ConfiguresDeploymentHistory(ctx, b)
662661
if stateDesc.SourcePath == "" {
663662
if configured {
664663
stateDesc.Features = map[string]struct{}{dstate.FeatureDeploymentHistory: {}}
@@ -673,7 +672,7 @@ func enforceDeploymentHistorySetting(ctx context.Context, b *bundle.Bundle, stat
673672
if stateDesc.SourcePath == "" {
674673
return nil
675674
}
676-
configured := configuresDeploymentHistory(ctx, b)
675+
configured := statemgmt.ConfiguresDeploymentHistory(ctx, b)
677676
recorded := stateDesc.IsDMS()
678677
if configured == recorded {
679678
return nil
@@ -692,14 +691,6 @@ Run "databricks bundle destroy" first, then deploy again with deployment history
692691
return nil
693692
}
694693

695-
func configuresDeploymentHistory(ctx context.Context, b *bundle.Bundle) bool {
696-
var configured *bool
697-
if b.Config.Experimental != nil {
698-
configured = b.Config.Experimental.DeploymentHistory
699-
}
700-
return bundleenv.RecordsDeploymentHistory(ctx, configured)
701-
}
702-
703694
// isNewerVersion reports whether the state's recorded CLI version is strictly
704695
// newer than the running build. Both are bare versions without a leading "v".
705696
// An empty stateVersion (state not written by any CLI yet) or an unparseable

‎libs/dms/metadata.go‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package dms
22

33
import (
4+
"context"
5+
"errors"
46
"fmt"
7+
"path"
58
"strconv"
69
"strings"
710

811
"github.com/databricks/cli/libs/structs/structdiff"
12+
"github.com/databricks/databricks-sdk-go"
13+
"github.com/databricks/databricks-sdk-go/apierr"
914
"github.com/databricks/databricks-sdk-go/service/bundledeployments"
1015
)
1116

@@ -85,6 +90,20 @@ func NextVersion(lastVersionID string) (int, error) {
8590
// Keep it equal to the service's DEPLOYMENT_NODE_NAME.
8691
const DeploymentNodeName = "resources.deployment.json"
8792

93+
// LookupDeploymentID returns the id of the deployment whose node sits in statePath, the node's
94+
// object id. It returns "" when there is no node, i.e. nothing was deployed with history yet.
95+
func LookupDeploymentID(ctx context.Context, w *databricks.WorkspaceClient, statePath string) (string, error) {
96+
nodePath := path.Join(statePath, DeploymentNodeName)
97+
obj, err := w.Workspace.GetStatusByPath(ctx, nodePath)
98+
if errors.Is(err, apierr.ErrNotFound) || errors.Is(err, apierr.ErrResourceDoesNotExist) {
99+
return "", nil
100+
}
101+
if err != nil {
102+
return "", fmt.Errorf("looking up deployment at %s: %w", nodePath, err)
103+
}
104+
return strconv.FormatInt(obj.ObjectId, 10), nil
105+
}
106+
88107
// DeploymentName, VersionName and OperationName build the resource names the
89108
// service uses, so callers pass only ids.
90109
func DeploymentName(deploymentID string) string {

0 commit comments

Comments
 (0)