Skip to content

Commit 359ec9c

Browse files
WodansSonkatbyte
andauthored
include fix for #9785
* Current progress * Enable CMK working * Add validation for cmk * Add nil check for amlWorkspaceID * Attempt to fix complete test case * remove maxitems from storage_account_identity * Fix lint errors * Modify test TestAccDatabricksWorkspace_update * Remove the set for ui_definition_uri * Update test cases * Add test cases for new attributes * Fixing test again * Commenting out unused test code for now * Update azurerm/internal/services/databricks/databricks_workspace_resource.go Co-authored-by: kt <[email protected]> * Update azurerm/internal/services/databricks/databricks_workspace_resource.go Co-authored-by: kt <[email protected]> * Update azurerm/internal/services/databricks/databricks_workspace_resource.go Co-authored-by: kt <[email protected]> * Address PR comments... * Update validation checks * Fix lint error * Refactoring validation for readability * Refactor the world * Fixing my dyslexia in my uber comment... * Update tests * Bug fix * Moved managed CMK code * Remove computed from custom_parameters * Revert update to custom_parameters * Enable CMK tests * Middle of massive refactor to new resource * Moved to new resource * terrafmt documentation * Fix test case * Add new step to test case * Update import test * Update import test * Update importer setId * Additional test updates * Fix lint errors * Import fix * moved cmk out of custom params * terrafmt * Another terrafmt * Update tests to validate parent resource * Update CMK to always pass all custom params * Fixing my dyslexia again * Update delete to pass all params * Add dependency on CMK for access policy * Wait a minute... Strike that. Reverse it. * no_public_ip cannot be changed once set * Fix invalid update and infra tests * Fix test cleanup configurations * Fix update test as everything is force new * Trying unhook the associations so destroy works * Remove just the subnet delegation * Delete the NSG first the the delegation * Remove assoc order * Modify step one of cleanup * Disable no public ip in clean up * remove deprecated attributes from tests * Documentation update only... * Remove the local specific URL from the docs... * Address PR comments * Force databricks deletion first * Remove fix for broken tests * add workspace dependency on nsg * Strike that, reverse it... * Not the NSG the NSGA Co-authored-by: kt <[email protected]>
1 parent de534c6 commit 359ec9c

12 files changed

+1414
-75
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package databricks
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strings"
8+
"time"
9+
10+
"github.com/Azure/azure-sdk-for-go/services/databricks/mgmt/2018-04-01/databricks"
11+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
12+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
13+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks"
14+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/databricks/parse"
15+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/databricks/validate"
16+
keyVaultParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/parse"
17+
keyVaultValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/validate"
18+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk"
19+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
20+
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
21+
)
22+
23+
func resourceDatabricksWorkspaceCustomerManagedKey() *pluginsdk.Resource {
24+
return &pluginsdk.Resource{
25+
Create: DatabricksWorkspaceCustomerManagedKeyCreateUpdate,
26+
Read: DatabricksWorkspaceCustomerManagedKeyRead,
27+
Update: DatabricksWorkspaceCustomerManagedKeyCreateUpdate,
28+
Delete: DatabricksWorkspaceCustomerManagedKeyDelete,
29+
30+
Timeouts: &pluginsdk.ResourceTimeout{
31+
Create: pluginsdk.DefaultTimeout(30 * time.Minute),
32+
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
33+
Update: pluginsdk.DefaultTimeout(30 * time.Minute),
34+
Delete: pluginsdk.DefaultTimeout(30 * time.Minute),
35+
},
36+
37+
Importer: pluginsdk.ImporterValidatingResourceIdThen(func(id string) error {
38+
_, err := parse.CustomerManagedKeyID(id)
39+
return err
40+
}, func(ctx context.Context, d *pluginsdk.ResourceData, meta interface{}) ([]*pluginsdk.ResourceData, error) {
41+
client := meta.(*clients.Client).DataBricks.WorkspacesClient
42+
43+
// validate that the passed ID is a valid CMK configuration ID
44+
customManagedKey, err := parse.CustomerManagedKeyID(d.Id())
45+
if err != nil {
46+
return []*pluginsdk.ResourceData{d}, fmt.Errorf("parsing Databricks workspace customer managed key ID %q for import: %v", d.Id(), err)
47+
}
48+
49+
// convert the passed custom Managed Key ID to a valid workspace ID
50+
workspace := parse.NewWorkspaceID(customManagedKey.SubscriptionId, customManagedKey.ResourceGroup, customManagedKey.CustomerMangagedKeyName)
51+
52+
// validate that the workspace exists
53+
if _, err = client.Get(ctx, workspace.ResourceGroup, workspace.Name); err != nil {
54+
return []*pluginsdk.ResourceData{d}, fmt.Errorf("retrieving the Databricks workspace customer managed key configuration(ID: %q) for workspace (ID: %q): %s", customManagedKey.ID(), workspace.ID(), err)
55+
}
56+
57+
// set the new values for the CMK resource
58+
d.SetId(customManagedKey.ID())
59+
d.Set("workspace_id", workspace.ID())
60+
61+
return []*pluginsdk.ResourceData{d}, nil
62+
}),
63+
64+
Schema: map[string]*pluginsdk.Schema{
65+
"workspace_id": {
66+
Type: pluginsdk.TypeString,
67+
Required: true,
68+
ValidateFunc: validate.WorkspaceID,
69+
},
70+
71+
// Make this key vault key id and abstract everything from the string...
72+
"key_vault_key_id": {
73+
Type: pluginsdk.TypeString,
74+
Required: true,
75+
ValidateFunc: keyVaultValidate.KeyVaultChildID,
76+
},
77+
},
78+
}
79+
}
80+
81+
func DatabricksWorkspaceCustomerManagedKeyCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
82+
workspaceClient := meta.(*clients.Client).DataBricks.WorkspacesClient
83+
keyVaultsClient := meta.(*clients.Client).KeyVault
84+
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
85+
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
86+
defer cancel()
87+
88+
workspaceIDRaw := d.Get("workspace_id").(string)
89+
workspaceID, err := parse.WorkspaceID(workspaceIDRaw)
90+
if err != nil {
91+
return err
92+
}
93+
94+
keyIdRaw := d.Get("key_vault_key_id").(string)
95+
key, err := keyVaultParse.ParseNestedItemID(keyIdRaw)
96+
if err != nil {
97+
return err
98+
}
99+
100+
// Not sure if I should also lock the key vault here too
101+
// or at the very least the key?
102+
locks.ByName(workspaceID.Name, "azurerm_databricks_workspace")
103+
defer locks.UnlockByName(workspaceID.Name, "azurerm_databricks_workspace")
104+
var encryptionEnabled, infrastructureEnabled bool
105+
106+
workspace, err := workspaceClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name)
107+
if err != nil {
108+
return fmt.Errorf("retrieving Databricks Workspace %q (Resource Group %q): %+v", workspaceID.Name, workspaceID.ResourceGroup, err)
109+
}
110+
if workspace.Parameters != nil {
111+
if workspace.Parameters.RequireInfrastructureEncryption != nil {
112+
infrastructureEnabled = *workspace.Parameters.RequireInfrastructureEncryption.Value
113+
}
114+
if workspace.Parameters.PrepareEncryption != nil {
115+
encryptionEnabled = *workspace.Parameters.PrepareEncryption.Value
116+
}
117+
} else {
118+
return fmt.Errorf("retrieving Databricks Workspace %q (Resource Group %q): `WorkspaceCustomParameters` was nil", workspaceID.Name, workspaceID.ResourceGroup)
119+
}
120+
121+
if infrastructureEnabled {
122+
return fmt.Errorf("Databricks Workspace %q (Resource Group %q): `infrastructure_encryption_enabled` must be set to `false`", workspaceID.Name, workspaceID.ResourceGroup)
123+
}
124+
if !encryptionEnabled {
125+
return fmt.Errorf("Databricks Workspace %q (Resource Group %q): `customer_managed_key_enabled` must be set to `true`", workspaceID.Name, workspaceID.ResourceGroup)
126+
}
127+
128+
// make sure the key vault exists
129+
keyVaultIdRaw, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, meta.(*clients.Client).Resource, key.KeyVaultBaseUrl)
130+
if err != nil || keyVaultIdRaw == nil {
131+
return fmt.Errorf("retrieving the Resource ID for the Key Vault at URL %q: %+v", key.KeyVaultBaseUrl, err)
132+
}
133+
134+
resourceID := parse.NewCustomerManagedKeyID(subscriptionId, workspaceID.ResourceGroup, workspaceID.Name)
135+
136+
if d.IsNewResource() {
137+
if workspace.Parameters.Encryption != nil {
138+
return tf.ImportAsExistsError("azurerm_databricks_workspace_customer_managed_key", resourceID.ID())
139+
}
140+
}
141+
142+
// We need to pull all of the custom params from the parent
143+
// workspace resource and then add our new encryption values into the
144+
// structure, else the other values set in the parent workspace
145+
// resource will be lost and overwritten as nil. ¯\_(ツ)_/¯
146+
// NOTE: 'workspace.Parameters' will never be nil as 'customer_managed_key_enabled' and 'infrastructure_encryption_enabled'
147+
// fields have a default value in the parent workspace resource.
148+
params := workspace.Parameters
149+
params.Encryption = &databricks.WorkspaceEncryptionParameter{
150+
Value: &databricks.Encryption{
151+
KeySource: databricks.MicrosoftKeyvault,
152+
KeyName: &key.Name,
153+
KeyVersion: &key.Version,
154+
KeyVaultURI: &key.KeyVaultBaseUrl,
155+
},
156+
}
157+
158+
props := databricks.Workspace{
159+
Location: workspace.Location,
160+
Sku: workspace.Sku,
161+
WorkspaceProperties: &databricks.WorkspaceProperties{
162+
ManagedResourceGroupID: workspace.WorkspaceProperties.ManagedResourceGroupID,
163+
Parameters: params,
164+
},
165+
Tags: workspace.Tags,
166+
}
167+
168+
future, err := workspaceClient.CreateOrUpdate(ctx, props, resourceID.ResourceGroup, resourceID.CustomerMangagedKeyName)
169+
if err != nil {
170+
return fmt.Errorf("creating/updating %s: %+v", resourceID, err)
171+
}
172+
173+
if err = future.WaitForCompletionRef(ctx, workspaceClient.Client); err != nil {
174+
return fmt.Errorf("waiting for create/update of %s: %+v", resourceID, err)
175+
}
176+
177+
d.SetId(resourceID.ID())
178+
return DatabricksWorkspaceCustomerManagedKeyRead(d, meta)
179+
}
180+
181+
func DatabricksWorkspaceCustomerManagedKeyRead(d *pluginsdk.ResourceData, meta interface{}) error {
182+
client := meta.(*clients.Client).DataBricks.WorkspacesClient
183+
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
184+
defer cancel()
185+
186+
id, err := parse.CustomerManagedKeyID(d.Id())
187+
if err != nil {
188+
return err
189+
}
190+
191+
workspaceId := parse.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.CustomerMangagedKeyName)
192+
193+
resp, err := client.Get(ctx, id.ResourceGroup, id.CustomerMangagedKeyName)
194+
if err != nil {
195+
if utils.ResponseWasNotFound(resp.Response) {
196+
log.Printf("[DEBUG] %s was not found - removing from state", *id)
197+
d.SetId("")
198+
return nil
199+
}
200+
201+
return fmt.Errorf("retrieving %s: %+v", *id, err)
202+
}
203+
204+
keySource := ""
205+
keyName := ""
206+
keyVersion := ""
207+
keyVaultURI := ""
208+
209+
if resp.WorkspaceProperties.Parameters != nil {
210+
if props := resp.WorkspaceProperties.Parameters.Encryption; props != nil {
211+
if props.Value.KeySource != "" {
212+
keySource = string(props.Value.KeySource)
213+
}
214+
if props.Value.KeyName != nil {
215+
keyName = *props.Value.KeyName
216+
}
217+
if props.Value.KeyVersion != nil {
218+
keyVersion = *props.Value.KeyVersion
219+
}
220+
if props.Value.KeyVaultURI != nil {
221+
keyVaultURI = *props.Value.KeyVaultURI
222+
}
223+
}
224+
}
225+
226+
// I have to get rid of this check due to import if you want to re-cmk your DBFS.
227+
// This is because when you delete this it sets the key source to default
228+
// if !strings.EqualFold(keySource, string(databricks.MicrosoftKeyvault)) {
229+
// return fmt.Errorf("retrieving Databricks Workspace %q (Resource Group %q): `Workspace.WorkspaceProperties.Encryption.Value.KeySource` was expected to be %q, got %q", id.CustomerMangagedKeyName, id.ResourceGroup, string(databricks.MicrosoftKeyvault), keySource)
230+
// }
231+
232+
if strings.EqualFold(keySource, string(databricks.MicrosoftKeyvault)) && (keyName == "" || keyVersion == "" || keyVaultURI == "") {
233+
return fmt.Errorf("Databricks Workspace %q (Resource Group %q): `Workspace.WorkspaceProperties.Encryption.Value(s)` were nil", id.CustomerMangagedKeyName, id.ResourceGroup)
234+
}
235+
236+
d.SetId(id.ID())
237+
d.Set("workspace_id", workspaceId.ID())
238+
if keyVaultURI != "" {
239+
key, err := keyVaultParse.NewNestedItemID(keyVaultURI, "keys", keyName, keyVersion)
240+
if err == nil {
241+
d.Set("key_vault_key_id", key.ID())
242+
}
243+
}
244+
245+
return nil
246+
}
247+
248+
func DatabricksWorkspaceCustomerManagedKeyDelete(d *pluginsdk.ResourceData, meta interface{}) error {
249+
client := meta.(*clients.Client).DataBricks.WorkspacesClient
250+
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
251+
defer cancel()
252+
253+
id, err := parse.CustomerManagedKeyID(d.Id())
254+
if err != nil {
255+
return err
256+
}
257+
258+
workspaceID := parse.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.CustomerMangagedKeyName)
259+
260+
// Not sure if I should also lock the key vault here too
261+
locks.ByName(workspaceID.Name, "azurerm_databricks_workspace")
262+
defer locks.UnlockByName(workspaceID.Name, "azurerm_databricks_workspace")
263+
264+
workspace, err := client.Get(ctx, id.ResourceGroup, id.CustomerMangagedKeyName)
265+
if err != nil {
266+
if utils.ResponseWasNotFound(workspace.Response) {
267+
log.Printf("[DEBUG] %s was not found - removing from state", *id)
268+
d.SetId("")
269+
return nil
270+
}
271+
272+
return fmt.Errorf("retrieving %s: %+v", *id, err)
273+
}
274+
275+
// Since this isn't real and you cannot turn off CMK without destroying the
276+
// workspace and recreating it the best I can do is to set the workspace
277+
// back to using Microsoft managed keys and removing the CMK fields
278+
// also need to pull all of the custom params from the parent
279+
// workspace resource and then add our new encryption values into the
280+
// structure, else the other values set in the parent workspace
281+
// resource will be lost and overwritten as nil. ¯\_(ツ)_/¯
282+
params := workspace.Parameters
283+
params.Encryption = &databricks.WorkspaceEncryptionParameter{
284+
Value: &databricks.Encryption{
285+
KeySource: databricks.Default,
286+
},
287+
}
288+
289+
props := databricks.Workspace{
290+
Location: workspace.Location,
291+
Sku: workspace.Sku,
292+
WorkspaceProperties: &databricks.WorkspaceProperties{
293+
ManagedResourceGroupID: workspace.WorkspaceProperties.ManagedResourceGroupID,
294+
Parameters: params,
295+
},
296+
Tags: workspace.Tags,
297+
}
298+
299+
future, err := client.CreateOrUpdate(ctx, props, workspaceID.ResourceGroup, workspaceID.Name)
300+
if err != nil {
301+
return fmt.Errorf("creating/updating %s: %+v", workspaceID, err)
302+
}
303+
304+
if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
305+
return fmt.Errorf("waiting for create/update of %s: %+v", workspaceID, err)
306+
}
307+
308+
return nil
309+
}

0 commit comments

Comments
 (0)