@@ -2,8 +2,11 @@ package app
22
33import (
44 "context"
5+ "fmt"
56
7+ "github.com/databricks/databricks-sdk-go"
68 "github.com/databricks/databricks-sdk-go/apierr"
9+ "github.com/databricks/databricks-sdk-go/retries"
710 "github.com/databricks/databricks-sdk-go/service/apps"
811 "github.com/databricks/terraform-provider-databricks/common"
912 pluginfwcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common"
@@ -14,14 +17,27 @@ import (
1417 "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
1518 "github.com/hashicorp/terraform-plugin-framework/path"
1619 "github.com/hashicorp/terraform-plugin-framework/resource"
20+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
1721 "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
22+ "github.com/hashicorp/terraform-plugin-framework/types"
1823)
1924
2025const (
2126 resourceName = "app"
2227 resourceNamePlural = "apps"
2328)
2429
30+ type appResource struct {
31+ apps_tf.App
32+ NoCompute types.Bool `tfsdk:"no_compute"`
33+ }
34+
35+ func (a appResource ) ApplySchemaCustomizations (s map [string ]tfschema.AttributeBuilder ) map [string ]tfschema.AttributeBuilder {
36+ s ["no_compute" ] = s ["no_compute" ].SetOptional ()
37+ s = apps_tf.App {}.ApplySchemaCustomizations (s )
38+ return s
39+ }
40+
2541func ResourceApp () resource.Resource {
2642 return & resourceApp {}
2743}
@@ -35,14 +51,24 @@ func (a resourceApp) Metadata(ctx context.Context, req resource.MetadataRequest,
3551}
3652
3753func (a resourceApp ) Schema (ctx context.Context , req resource.SchemaRequest , resp * resource.SchemaResponse ) {
38- resp .Schema = tfschema .ResourceStructToSchema (ctx , apps_tf. App {}, func (cs tfschema.CustomizableSchema ) tfschema.CustomizableSchema {
54+ resp .Schema = tfschema .ResourceStructToSchema (ctx , appResource {}, func (cs tfschema.CustomizableSchema ) tfschema.CustomizableSchema {
3955 cs .AddPlanModifier (stringplanmodifier .RequiresReplace (), "name" )
4056 exclusiveFields := []string {"job" , "secret" , "serving_endpoint" , "sql_warehouse" }
4157 paths := path.Expressions {}
4258 for _ , field := range exclusiveFields [1 :] {
4359 paths = append (paths , path .MatchRelative ().AtParent ().AtName (field ))
4460 }
4561 cs .AddValidator (objectvalidator .ExactlyOneOf (paths ... ), "resources" , exclusiveFields [0 ])
62+ for _ , field := range []string {
63+ "create_time" ,
64+ "creator" ,
65+ "service_principal_client_id" ,
66+ "service_principal_name" ,
67+ "url" ,
68+ } {
69+ cs .AddPlanModifier (stringplanmodifier .UseStateForUnknown (), field )
70+ }
71+ cs .AddPlanModifier (int64planmodifier .UseStateForUnknown (), "service_principal_id" )
4672 return cs
4773 })
4874}
@@ -61,7 +87,7 @@ func (a *resourceApp) Create(ctx context.Context, req resource.CreateRequest, re
6187 return
6288 }
6389
64- var app apps_tf. App
90+ var app appResource
6591 resp .Diagnostics .Append (req .Plan .Get (ctx , & app )... )
6692 if resp .Diagnostics .HasError () {
6793 return
@@ -73,30 +99,39 @@ func (a *resourceApp) Create(ctx context.Context, req resource.CreateRequest, re
7399 }
74100
75101 // Create the app
76- waiter , err := w .Apps .Create (ctx , apps.CreateAppRequest {App : & appGoSdk })
102+ var forceSendFields []string
103+ if ! app .NoCompute .IsNull () {
104+ forceSendFields = append (forceSendFields , "NoCompute" )
105+ }
106+ waiter , err := w .Apps .Create (ctx , apps.CreateAppRequest {
107+ App : & appGoSdk ,
108+ NoCompute : app .NoCompute .ValueBool (),
109+ ForceSendFields : forceSendFields ,
110+ })
77111 if err != nil {
78112 resp .Diagnostics .AddError ("failed to create app" , err .Error ())
79113 return
80114 }
81115
82116 // Store the initial version of the app in state
83- var newApp apps_tf. App
117+ var newApp appResource
84118 resp .Diagnostics .Append (converters .GoSdkToTfSdkStruct (ctx , waiter .Response , & newApp )... )
85119 if resp .Diagnostics .HasError () {
86120 return
87121 }
122+ newApp .NoCompute = app .NoCompute
88123 resp .Diagnostics .Append (resp .State .Set (ctx , newApp )... )
89124 if resp .Diagnostics .HasError () {
90125 return
91126 }
92127
93- // Wait for the app to be created
94- finalApp , err := waiter .Get ()
128+ // Wait for the app to be created. If no_compute is specified, the terminal state is
129+ // STOPPED, otherwise it is ACTIVE.
130+ finalApp , err := a .waitForApp (ctx , w , appGoSdk .Name )
95131 if err != nil {
96- resp .Diagnostics .AddError ("error waiting for app to be ready " , err .Error ())
132+ resp .Diagnostics .AddError ("error waiting for app to be active or stopped " , err .Error ())
97133 return
98134 }
99-
100135 // Store the final version of the app in state
101136 resp .Diagnostics .Append (converters .GoSdkToTfSdkStruct (ctx , finalApp , & newApp )... )
102137 if resp .Diagnostics .HasError () {
@@ -108,6 +143,43 @@ func (a *resourceApp) Create(ctx context.Context, req resource.CreateRequest, re
108143 }
109144}
110145
146+ // This is copied from the retries package of the databricks-sdk-go. It should be made public,
147+ // but for now, I'm copying it here.
148+ func shouldRetry (err error ) bool {
149+ if err == nil {
150+ return false
151+ }
152+ e := err .(* retries.Err )
153+ if e == nil {
154+ return false
155+ }
156+ return ! e .Halt
157+ }
158+
159+ // waitForApp waits for the app to reach the target state. The target state is either ACTIVE or STOPPED.
160+ // Apps with no_compute set to true will reach the STOPPED state, otherwise they will reach the ACTIVE state.
161+ func (a * resourceApp ) waitForApp (ctx context.Context , w * databricks.WorkspaceClient , name string ) (* apps.App , error ) {
162+ retrier := retries .New [apps.App ](retries .WithTimeout (- 1 ), retries .WithRetryFunc (shouldRetry ))
163+ return retrier .Run (ctx , func (ctx context.Context ) (* apps.App , error ) {
164+ app , err := w .Apps .GetByName (ctx , name )
165+ if err != nil {
166+ return nil , retries .Halt (err )
167+ }
168+ status := app .ComputeStatus .State
169+ statusMessage := app .ComputeStatus .Message
170+ switch status {
171+ case apps .ComputeStateActive , apps .ComputeStateStopped :
172+ return app , nil
173+ case apps .ComputeStateError :
174+ err := fmt .Errorf ("failed to reach %s or %s, got %s: %s" ,
175+ apps .ComputeStateActive , apps .ComputeStateStopped , status , statusMessage )
176+ return nil , retries .Halt (err )
177+ default :
178+ return nil , retries .Continues (statusMessage )
179+ }
180+ })
181+ }
182+
111183func (a * resourceApp ) Read (ctx context.Context , req resource.ReadRequest , resp * resource.ReadResponse ) {
112184 ctx = pluginfwcontext .SetUserAgentInResourceContext (ctx , resourceName )
113185 w , diags := a .client .GetWorkspaceClient ()
@@ -116,7 +188,7 @@ func (a *resourceApp) Read(ctx context.Context, req resource.ReadRequest, resp *
116188 return
117189 }
118190
119- var app apps_tf. App
191+ var app appResource
120192 resp .Diagnostics .Append (req .State .Get (ctx , & app )... )
121193 if resp .Diagnostics .HasError () {
122194 return
@@ -128,11 +200,12 @@ func (a *resourceApp) Read(ctx context.Context, req resource.ReadRequest, resp *
128200 return
129201 }
130202
131- var newApp apps_tf. App
203+ var newApp appResource
132204 resp .Diagnostics .Append (converters .GoSdkToTfSdkStruct (ctx , appGoSdk , & newApp )... )
133205 if resp .Diagnostics .HasError () {
134206 return
135207 }
208+ newApp .NoCompute = app .NoCompute
136209 resp .Diagnostics .Append (resp .State .Set (ctx , newApp )... )
137210 if resp .Diagnostics .HasError () {
138211 return
@@ -147,7 +220,7 @@ func (a *resourceApp) Update(ctx context.Context, req resource.UpdateRequest, re
147220 return
148221 }
149222
150- var app apps_tf. App
223+ var app appResource
151224 resp .Diagnostics .Append (req .Plan .Get (ctx , & app )... )
152225 if resp .Diagnostics .HasError () {
153226 return
@@ -166,11 +239,13 @@ func (a *resourceApp) Update(ctx context.Context, req resource.UpdateRequest, re
166239 }
167240
168241 // Store the updated version of the app in state
169- var newApp apps_tf. App
242+ var newApp appResource
170243 resp .Diagnostics .Append (converters .GoSdkToTfSdkStruct (ctx , response , & newApp )... )
171244 if resp .Diagnostics .HasError () {
172245 return
173246 }
247+ // Modifying no_compute after creation has no effect.
248+ newApp .NoCompute = app .NoCompute
174249 resp .Diagnostics .Append (resp .State .Set (ctx , newApp )... )
175250 if resp .Diagnostics .HasError () {
176251 return
@@ -185,7 +260,7 @@ func (a *resourceApp) Delete(ctx context.Context, req resource.DeleteRequest, re
185260 return
186261 }
187262
188- var app apps_tf. App
263+ var app appResource
189264 resp .Diagnostics .Append (req .State .Get (ctx , & app )... )
190265 if resp .Diagnostics .HasError () {
191266 return
0 commit comments