Skip to content

Commit 0649acf

Browse files
authored
Clarify empty host error. Fixes #836 (#847)
1 parent 6689055 commit 0649acf

File tree

5 files changed

+112
-79
lines changed

5 files changed

+112
-79
lines changed

common/client.go

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -178,24 +178,6 @@ func ClientAttributes() (attrs []ConfigAttribute) {
178178
return
179179
}
180180

181-
// envVariablesUsed returns coma-separated list of the used relevant variable names
182-
func envVariablesUsed() string {
183-
names := []string{}
184-
for _, attr := range ClientAttributes() {
185-
if len(attr.EnvVars) == 0 {
186-
continue
187-
}
188-
for _, envVar := range attr.EnvVars {
189-
value := os.Getenv(envVar)
190-
if value == "" {
191-
continue
192-
}
193-
names = append(names, envVar)
194-
}
195-
}
196-
return strings.Join(names, ", ")
197-
}
198-
199181
// Configure client to work, optionally specifying configuration attributes used
200182
func (c *DatabricksClient) Configure(attrsUsed ...string) error {
201183
c.configAttributesUsed = attrsUsed
@@ -224,20 +206,24 @@ func (c *DatabricksClient) Authenticate(ctx context.Context) error {
224206
if c.authVisitor != nil {
225207
return nil
226208
}
227-
authorizers := []func(context.Context) (func(*http.Request) error, error){
228-
c.configureWithDirectParams,
229-
c.configureWithAzureClientSecret,
230-
c.configureWithAzureManagedIdentity,
231-
c.configureWithAzureCLI,
232-
c.configureWithGoogleForAccountsAPI,
233-
c.configureWithGoogleForWorkspace,
234-
c.configureWithDatabricksCfg,
209+
type auth struct {
210+
configure func(context.Context) (func(*http.Request) error, error)
211+
name string
212+
}
213+
providers := []auth{
214+
{c.configureWithDirectParams, "direct"},
215+
{c.configureWithAzureClientSecret, "Azure Service Principal"},
216+
{c.configureWithAzureManagedIdentity, "Azure MSI"},
217+
{c.configureWithAzureCLI, "Azure CLI"},
218+
{c.configureWithGoogleForAccountsAPI, "Databricks Account on GCP"},
219+
{c.configureWithGoogleForWorkspace, "Databricks on GCP"},
220+
{c.configureWithDatabricksCfg, "Databricks CLI"},
235221
}
236222
// try configuring authentication with different methods
237-
for _, authProvider := range authorizers {
238-
authorizer, err := authProvider(ctx)
223+
for _, auth := range providers {
224+
authorizer, err := auth.configure(ctx)
239225
if err != nil {
240-
return c.niceError(fmt.Sprintf("cannot configure auth: %s", err))
226+
return c.niceAuthError(fmt.Sprintf("cannot configure %s auth: %s", auth.name, err))
241227
}
242228
if authorizer == nil {
243229
continue
@@ -246,21 +232,51 @@ func (c *DatabricksClient) Authenticate(ctx context.Context) error {
246232
c.fixHost()
247233
return nil
248234
}
249-
return c.niceError("authentication is not configured for provider.")
235+
if c.Host == "" && IsData.GetOrUnknown(ctx) == "yes" {
236+
return c.niceAuthError("workspace is most likely not created yet, because the `host` " +
237+
"is empty. Please add `depends_on = [databricks_mws_workspaces.this]` or " +
238+
"`depends_on = [azurerm_databricks_workspace.this]` to every data resource. See " +
239+
"https://www.terraform.io/docs/language/resources/behavior.html more info")
240+
}
241+
return c.niceAuthError("authentication is not configured for provider.")
250242
}
251243

252-
func (c *DatabricksClient) niceError(message string) error {
244+
func (c *DatabricksClient) niceAuthError(message string) error {
253245
info := ""
254246
if len(c.configAttributesUsed) > 0 {
255-
// TODO: first show env vars and filter out the attrs after
256-
info = fmt.Sprintf(" Attributes used: %s", strings.Join(c.configAttributesUsed, ", "))
257-
envVars := envVariablesUsed()
258-
if envVars != "" {
259-
info = fmt.Sprintf("%s. Environment variables used: %s", info, envVars)
247+
envs := []string{}
248+
attrs := []string{}
249+
usedAsEnv := map[string]bool{}
250+
for _, attr := range ClientAttributes() {
251+
if len(attr.EnvVars) == 0 {
252+
continue
253+
}
254+
for _, envVar := range attr.EnvVars {
255+
value := os.Getenv(envVar)
256+
if value == "" {
257+
continue
258+
}
259+
usedAsEnv[attr.Name] = true
260+
envs = append(envs, envVar)
261+
}
262+
}
263+
for _, attr := range c.configAttributesUsed {
264+
if usedAsEnv[attr] {
265+
continue
266+
}
267+
attrs = append(attrs, attr)
268+
}
269+
infos := []string{}
270+
if len(attrs) > 0 {
271+
infos = append(infos, fmt.Sprintf("Attributes used: %s", strings.Join(attrs, ", ")))
272+
}
273+
if len(envs) > 0 {
274+
infos = append(infos, fmt.Sprintf("Environment variables used: %s", strings.Join(envs, ", ")))
260275
}
276+
info = ". " + strings.Join(infos, ". ")
261277
}
262278
docUrl := "https://registry.terraform.io/providers/databrickslabs/databricks/latest/docs#authentication"
263-
return fmt.Errorf("%s%s Please check %s for details", message, info, docUrl)
279+
return fmt.Errorf("%s%s. Please check %s for details", message, info, docUrl)
264280
}
265281

266282
func (c *DatabricksClient) fixHost() {

common/client_test.go

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestDatabricksClientConfigure_BasicAuth_NoHost(t *testing.T) {
3535
Password: "bar",
3636
})
3737

38-
AssertErrorStartsWith(t, err, "cannot configure auth: host is empty, but is required by basic_auth")
38+
AssertErrorStartsWith(t, err, "cannot configure direct auth: host is empty, but is required by basic_auth")
3939
assert.Equal(t, "Zm9vOmJhcg==", dc.Token)
4040
}
4141

@@ -66,7 +66,7 @@ func TestDatabricksClientConfigure_Token_NoHost(t *testing.T) {
6666
Token: "dapi345678",
6767
})
6868

69-
AssertErrorStartsWith(t, err, "cannot configure auth: host is empty, but is required by token")
69+
AssertErrorStartsWith(t, err, "cannot configure direct auth: host is empty, but is required by token")
7070
assert.Equal(t, "dapi345678", dc.Token)
7171
}
7272

@@ -144,15 +144,6 @@ func TestDatabricksClientConfigure_InvalidConfigFilePath(t *testing.T) {
144144
assert.Error(t, err)
145145
}
146146

147-
// func TestDatabricksClientConfigure_InvalidHome(t *testing.T) {
148-
// defer CleanupEnvironment()()
149-
// os.Setenv("HOME", "whatever")
150-
// _, err := configureAndAuthenticate(&DatabricksClient{
151-
// Profile: "invalidhost",
152-
// })
153-
// assert.EqualError(t, err, ".")
154-
// }
155-
156147
func TestDatabricksClient_FormatURL(t *testing.T) {
157148
client := DatabricksClient{Host: "https://some.host"}
158149
assert.Equal(t, "https://some.host/#job/123", client.FormatURL("#job/123"))
@@ -163,15 +154,31 @@ func TestClientAttributes(t *testing.T) {
163154
assert.Len(t, ca, 25)
164155
}
165156

166-
func TestEnvVarsUsed(t *testing.T) {
167-
ResetCommonEnvironmentClient()
157+
func TestDatabricksClient_Authenticate(t *testing.T) {
158+
defer CleanupEnvironment()()
159+
dc := DatabricksClient{}
160+
err := dc.Configure("account_id", "username", "password")
161+
os.Setenv("DATABRICKS_PASSWORD", ".")
162+
assert.NoError(t, err)
163+
err = dc.Authenticate(context.WithValue(context.Background(), IsData, "yes"))
164+
assert.EqualError(t, err, "workspace is most likely not created yet, because the `host` is empty. "+
165+
"Please add `depends_on = [databricks_mws_workspaces.this]` or `depends_on = [azurerm_databricks"+
166+
"_workspace.this]` to every data resource. See https://www.terraform.io/docs/language/resources/behavior.html more info. "+
167+
"Attributes used: account_id, username. Environment variables used: DATABRICKS_PASSWORD. "+
168+
"Please check https://registry.terraform.io/providers/databrickslabs/databricks/latest/docs#authentication for details")
169+
}
170+
171+
func TestDatabricksClient_AuthenticateAzure(t *testing.T) {
168172
defer CleanupEnvironment()()
169-
os.Setenv("SUPER_SECRET", ".")
170-
os.Setenv("DATABRICKS_HOST", ".")
171-
os.Setenv("ARM_SUBSCRIPTION_ID", ".")
172-
os.Setenv("DATABRICKS_ACCOUNT_ID", ".")
173-
os.Setenv("DATABRICKS_GOOGLE_SERVICE_ACCOUNT", ".")
174-
175-
names := envVariablesUsed()
176-
assert.Equal(t, "DATABRICKS_HOST, DATABRICKS_ACCOUNT_ID, DATABRICKS_GOOGLE_SERVICE_ACCOUNT, ARM_SUBSCRIPTION_ID", names)
173+
os.Setenv("ARM_CLIENT_SECRET", ".")
174+
os.Setenv("ARM_CLIENT_ID", ".")
175+
dc := DatabricksClient{}
176+
err := dc.Configure("azure_client_id", "azure_client_secret")
177+
assert.NoError(t, err)
178+
err = dc.Authenticate(context.WithValue(context.Background(), IsData, "yes"))
179+
assert.EqualError(t, err, "workspace is most likely not created yet, because the `host` is empty. "+
180+
"Please add `depends_on = [databricks_mws_workspaces.this]` or `depends_on = [azurerm_databricks"+
181+
"_workspace.this]` to every data resource. See https://www.terraform.io/docs/language/resources/"+
182+
"behavior.html more info. Environment variables used: ARM_CLIENT_SECRET, ARM_CLIENT_ID. "+
183+
"Please check https://registry.terraform.io/providers/databrickslabs/databricks/latest/docs#authentication for details")
177184
}

common/context.go

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,41 @@ import (
1111
// AddContextToAllResources ...
1212
func AddContextToAllResources(p *schema.Provider, prefix string) {
1313
for k, r := range p.DataSourcesMap {
14-
k = strings.ReplaceAll(k, prefix+"_", "")
15-
addContextToResource(k, r)
14+
name := strings.ReplaceAll(k, prefix+"_", "")
15+
wrap := op(r.ReadContext).addContext(ResourceName, name).addContext(IsData, "yes")
16+
r.ReadContext = schema.ReadContextFunc(wrap)
1617
}
1718
for k, r := range p.ResourcesMap {
1819
k = strings.ReplaceAll(k, prefix+"_", "")
1920
addContextToResource(k, r)
2021
}
2122
}
2223

24+
// any of TF resource CRUD operation, that may need context enhancement
25+
type op func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics
26+
27+
// wrap operation invokations with additional context
28+
func (f op) addContext(k contextKey, v string) op {
29+
return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
30+
ctx = context.WithValue(ctx, k, v)
31+
return f(ctx, d, m)
32+
}
33+
}
34+
2335
func addContextToResource(name string, r *schema.Resource) {
36+
addName := func(a op) func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
37+
return a.addContext(ResourceName, name)
38+
}
2439
if r.CreateContext != nil {
25-
r.CreateContext = addContextToStage(name, r.CreateContext)
40+
r.CreateContext = addName(op(r.CreateContext))
2641
}
2742
if r.ReadContext != nil {
28-
r.ReadContext = addContextToStage(name, r.ReadContext)
43+
r.ReadContext = addName(op(r.ReadContext))
2944
}
3045
if r.UpdateContext != nil {
31-
r.UpdateContext = addContextToStage(name, r.UpdateContext)
46+
r.UpdateContext = addName(op(r.UpdateContext))
3247
}
3348
if r.DeleteContext != nil {
34-
r.DeleteContext = addContextToStage(name, r.DeleteContext)
35-
}
36-
}
37-
38-
func addContextToStage(name string,
39-
f func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics) func(
40-
ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
41-
return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
42-
ctx = context.WithValue(ctx, ResourceName, name)
43-
return f(ctx, d, m)
49+
r.DeleteContext = addName(op(r.DeleteContext))
4450
}
4551
}

common/version.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ var (
1010
Provider contextKey = 2
1111
// Current is the current name of integration test
1212
Current contextKey = 3
13+
// If current resource is data
14+
IsData contextKey = 4
1315
)
1416

1517
type contextKey int

provider/provider_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func TestConfig_TokenEnv(t *testing.T) {
119119
env: map[string]string{
120120
"DATABRICKS_TOKEN": "x",
121121
},
122-
assertError: "cannot configure auth: host is empty, but is required by token",
122+
assertError: "cannot configure direct auth: host is empty, but is required by token. Environment variables used: DATABRICKS_TOKEN",
123123
}.apply(t)
124124
}
125125

@@ -151,7 +151,9 @@ func TestConfig_UserPasswordEnv(t *testing.T) {
151151
"DATABRICKS_USERNAME": "x",
152152
"DATABRICKS_PASSWORD": "x",
153153
},
154-
assertError: "cannot configure auth: host is empty, but is required by basic_auth",
154+
assertError: "cannot configure direct auth: host is empty, but is required by basic_auth. " +
155+
"Environment variables used: DATABRICKS_USERNAME, DATABRICKS_PASSWORD. " +
156+
"Please check https://registry.terraform.io/providers/databrickslabs/databricks/latest/docs#authentication for details",
155157
assertToken: "x",
156158
assertHost: "https://x",
157159
}.apply(t)
@@ -254,7 +256,7 @@ func TestConfig_PatFromDatabricksCfg_NohostProfile(t *testing.T) {
254256
"HOME": "../common/testdata",
255257
"DATABRICKS_CONFIG_PROFILE": "nohost",
256258
},
257-
assertError: "cannot configure auth: config file ../common/testdata/.databrickscfg is corrupt: cannot find host in nohost profile",
259+
assertError: "cannot configure Databricks CLI auth: config file ../common/testdata/.databrickscfg is corrupt: cannot find host in nohost profile",
258260
}.apply(t)
259261
}
260262

@@ -307,7 +309,7 @@ func TestConfig_AzureCliHost_Fail(t *testing.T) {
307309
"HOME": "../common/testdata",
308310
"FAIL": "yes",
309311
},
310-
assertError: "cannot configure auth: Invoking Azure CLI failed with the following error: This is just a failing script.",
312+
assertError: "cannot configure Azure CLI auth: Invoking Azure CLI failed with the following error: This is just a failing script.",
311313
}.apply(t)
312314
}
313315

@@ -319,7 +321,7 @@ func TestConfig_AzureCliHost_AzNotInstalled(t *testing.T) {
319321
"PATH": "whatever",
320322
"HOME": "../common/testdata",
321323
},
322-
assertError: "cannot configure auth: most likely Azure CLI is not installed.",
324+
assertError: "cannot configure Azure CLI auth: most likely Azure CLI is not installed.",
323325
}.apply(t)
324326
}
325327

@@ -407,7 +409,7 @@ func TestConfig_CorruptConfig(t *testing.T) {
407409
env: map[string]string{
408410
"HOME": "../common/testdata/corrupt",
409411
},
410-
assertError: "cannot configure auth: ../common/testdata/corrupt/.databrickscfg has no DEFAULT profile configured",
412+
assertError: "cannot configure Databricks CLI auth: ../common/testdata/corrupt/.databrickscfg has no DEFAULT profile configured",
411413
}.apply(t)
412414
}
413415

0 commit comments

Comments
 (0)