diff --git a/access/resource_ipaccesslist.go b/access/resource_ipaccesslist.go index e178ada20f..ae15959f5d 100644 --- a/access/resource_ipaccesslist.go +++ b/access/resource_ipaccesslist.go @@ -9,8 +9,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -type listIPAccessListsResponse struct { - ListIPAccessListsResponse []ipAccessListStatus `json:"ip_access_lists,omitempty"` +type ListIPAccessListsResponse struct { + ListIPAccessListsResponse []IpAccessListStatus `json:"ip_access_lists,omitempty"` } type createIPAccessListRequest struct { @@ -19,7 +19,7 @@ type createIPAccessListRequest struct { IPAddresses []string `json:"ip_addresses"` } -type ipAccessListStatus struct { +type IpAccessListStatus struct { ListID string `json:"list_id"` Label string `json:"label"` ListType string `json:"list_type"` @@ -32,15 +32,15 @@ type ipAccessListStatus struct { Enabled bool `json:"enabled,omitempty"` } -type ipAccessListStatusWrapper struct { - IPAccessList ipAccessListStatus `json:"ip_access_list,omitempty"` +type IpAccessListStatusWrapper struct { + IPAccessList IpAccessListStatus `json:"ip_access_list,omitempty"` } type ipAccessListUpdateRequest struct { Label string `json:"label"` ListType string `json:"list_type"` IPAddresses []string `json:"ip_addresses"` - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled,omitempty" tf:"default:true"` } // Preview feature: https://docs.databricks.com/security/network/ip-access-list.html @@ -59,8 +59,8 @@ func NewIPAccessListsAPI(ctx context.Context, m interface{}) ipAccessListsAPI { } // Create creates the IP Access List to given the instance pool configuration -func (a ipAccessListsAPI) Create(cr createIPAccessListRequest) (status ipAccessListStatus, err error) { - wrapper := ipAccessListStatusWrapper{} +func (a ipAccessListsAPI) Create(cr createIPAccessListRequest) (status IpAccessListStatus, err error) { + wrapper := IpAccessListStatusWrapper{} err = a.client.Post(a.context, "/ip-access-lists", cr, &wrapper) if err != nil { return @@ -78,16 +78,16 @@ func (a ipAccessListsAPI) Delete(objectID string) (err error) { return } -func (a ipAccessListsAPI) Read(objectID string) (status ipAccessListStatus, err error) { - wrapper := ipAccessListStatusWrapper{} +func (a ipAccessListsAPI) Read(objectID string) (status IpAccessListStatus, err error) { + wrapper := IpAccessListStatusWrapper{} err = a.client.Get(a.context, "/ip-access-lists/"+objectID, nil, &wrapper) status = wrapper.IPAccessList return } -func (a ipAccessListsAPI) List() (listResponse listIPAccessListsResponse, err error) { - listResponse = listIPAccessListsResponse{} - err = a.client.Get(a.context, "/ip-access-lists", &listResponse, nil) +func (a ipAccessListsAPI) List() (listResponse ListIPAccessListsResponse, err error) { + listResponse = ListIPAccessListsResponse{} + err = a.client.Get(a.context, "/ip-access-lists", nil, &listResponse) return } @@ -100,7 +100,6 @@ func ResourceIPAccessList() *schema.Resource { Type: schema.TypeString, ValidateFunc: validation.Any(validation.IsIPv4Address, validation.IsCIDR), } - s["enabled"].Default = true return s }) return common.Resource{ diff --git a/access/resource_ipaccesslist_test.go b/access/resource_ipaccesslist_test.go index abc54978e0..8f5641020c 100644 --- a/access/resource_ipaccesslist_test.go +++ b/access/resource_ipaccesslist_test.go @@ -66,8 +66,8 @@ func TestIPACLCreate(t *testing.T) { ListType: TestingListType, IPAddresses: TestingIPAddresses, }, - Response: ipAccessListStatusWrapper{ - IPAccessList: ipAccessListStatus{ + Response: IpAccessListStatusWrapper{ + IPAccessList: IpAccessListStatus{ ListID: TestingID, Label: TestingLabel, ListType: TestingListType, @@ -84,8 +84,8 @@ func TestIPACLCreate(t *testing.T) { { Method: http.MethodGet, Resource: "/api/2.0/ip-access-lists/" + TestingID, - Response: ipAccessListStatusWrapper{ - IPAccessList: ipAccessListStatus{ + Response: IpAccessListStatusWrapper{ + IPAccessList: IpAccessListStatus{ ListID: TestingID, Label: TestingLabel, ListType: TestingListType, @@ -148,8 +148,8 @@ func TestIPACLUpdate(t *testing.T) { { Method: http.MethodGet, Resource: "/api/2.0/ip-access-lists/" + TestingID, - Response: ipAccessListStatusWrapper{ - IPAccessList: ipAccessListStatus{ + Response: IpAccessListStatusWrapper{ + IPAccessList: IpAccessListStatus{ ListID: TestingID, Label: TestingLabel, ListType: TestingListType, @@ -166,8 +166,8 @@ func TestIPACLUpdate(t *testing.T) { { Method: http.MethodPut, Resource: "/api/2.0/ip-access-lists/" + TestingID, - Response: ipAccessListStatusWrapper{ - IPAccessList: ipAccessListStatus{ + Response: IpAccessListStatusWrapper{ + IPAccessList: IpAccessListStatus{ ListID: TestingID, Label: TestingLabel, ListType: TestingListType, @@ -225,8 +225,8 @@ func TestIPACLRead(t *testing.T) { { Method: http.MethodGet, Resource: "/api/2.0/ip-access-lists/" + TestingID, - Response: ipAccessListStatusWrapper{ - IPAccessList: ipAccessListStatus{ + Response: IpAccessListStatusWrapper{ + IPAccessList: IpAccessListStatus{ ListID: TestingID, Label: TestingLabel, ListType: TestingListType, @@ -333,3 +333,20 @@ func TestIPACLDelete_Error(t *testing.T) { qa.AssertErrorStartsWith(t, err, "IP access list is not available in ") assert.Equal(t, TestingID, d.Id()) } + +func TestListIpAccessLists(t *testing.T) { + client, server, err := qa.HttpFixtureClient(t, []qa.HTTPFixture{ + { + Method: "GET", + Resource: "/api/2.0/ip-access-lists", + Response: map[string]interface{}{}, + }, + }) + defer server.Close() + require.NoError(t, err) + + ctx := context.Background() + ipLists, err := NewIPAccessListsAPI(ctx, client).List() + require.NoError(t, err) + assert.Equal(t, len(ipLists.ListIPAccessListsResponse), 0) +} diff --git a/docs/resources/ip_access_list.md b/docs/resources/ip_access_list.md index 9863e24dd5..602648e897 100644 --- a/docs/resources/ip_access_list.md +++ b/docs/resources/ip_access_list.md @@ -32,7 +32,7 @@ The following arguments are supported: * `list_type` - Can only be "ALLOW" or "BLOCK" * `ip_addresses` - This is a field to allow the group to have instance pool create priviliges. -* `label` - (Optional) This is the display name for the given IP ACL List. +* `label` - This is the display name for the given IP ACL List. * `enabled` - (Optional) Boolean `true` or `false` indicating whether this list should be active. Defaults to `true` ## Attribute Reference diff --git a/exporter/command.go b/exporter/command.go index 987000333f..f912d64cd5 100644 --- a/exporter/command.go +++ b/exporter/command.go @@ -65,7 +65,7 @@ func Run(args ...string) error { "Items with older than activity specified won't be imported.") flags.BoolVar(&ic.debug, "debug", false, "Print extra debug information.") flags.BoolVar(&ic.mounts, "mounts", false, "List DBFS mount points.") - flags.BoolVar(&ic.generateDeclaration, "generateProviderDeclaration", false, + flags.BoolVar(&ic.generateDeclaration, "generateProviderDeclaration", true, "Generate Databricks provider declaration (for Terraform >= 0.13).") services, listing := ic.allServicesAndListing() flags.StringVar(&ic.services, "services", services, diff --git a/exporter/context.go b/exporter/context.go index f16b69cab7..7ce75021f5 100644 --- a/exporter/context.go +++ b/exporter/context.go @@ -491,6 +491,17 @@ func (ic *importContext) variable(name, desc string) hclwrite.Tokens { }) } +func generateSingleDependsOn(resource, name string) hclwrite.Tokens { + tokens := hclwrite.Tokens{ + &hclwrite.Token{Type: hclsyntax.TokenOQuote, Bytes: []byte{'['}}, + } + tokens = append(tokens, hclwrite.TokensForTraversal(hcl.Traversal{ + hcl.TraverseRoot{Name: resource}, + hcl.TraverseAttr{Name: name}, + })...) + return append(tokens, &hclwrite.Token{Type: hclsyntax.TokenOQuote, Bytes: []byte{']'}}) +} + type fieldTuple struct { Field string Schema *schema.Schema diff --git a/exporter/exporter_test.go b/exporter/exporter_test.go index b08785aa9b..72167dc4c9 100644 --- a/exporter/exporter_test.go +++ b/exporter/exporter_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/databrickslabs/terraform-provider-databricks/access" "github.com/databrickslabs/terraform-provider-databricks/aws" "github.com/databrickslabs/terraform-provider-databricks/clusters" "github.com/databrickslabs/terraform-provider-databricks/commands" @@ -210,12 +211,19 @@ var emptyGitCredentials = qa.HTTPFixture{ Response: repos.GitCredentialList{}, } +var emptyIpAccessLIst = qa.HTTPFixture{ + Method: http.MethodGet, + Resource: "/api/2.0/ip-access-lists", + Response: map[string]interface{}{}, +} + func TestImportingUsersGroupsSecretScopes(t *testing.T) { qa.HTTPFixturesApply(t, []qa.HTTPFixture{ meAdminFixture, repoListFixture, emptyGitCredentials, + emptyIpAccessLIst, { Method: "GET", Resource: "/api/2.0/preview/scim/v2/Groups?", @@ -375,6 +383,7 @@ func TestImportingNoResourcesError(t *testing.T) { Response: scim.GroupList{Resources: []scim.Group{}}, }, emptyGitCredentials, + emptyIpAccessLIst, { Method: "GET", Resource: "/api/2.0/global-init-scripts", @@ -1069,3 +1078,67 @@ func TestImportingGitCredentials_Error(t *testing.T) { assert.Error(t, err) }) } + +func TestImportingIPAccessLists(t *testing.T) { + resp := access.IpAccessListStatus{ + ListID: "123", + Label: "block_list", + ListType: "BLOCK", + IPAddresses: []string{"1.2.3.4"}, + AddressCount: 2, + Enabled: true, + } + resp2 := resp + resp2.IPAddresses = []string{} + resp2.ListID = "124" + qa.HTTPFixturesApply(t, + []qa.HTTPFixture{ + meAdminFixture, + repoListFixture, + { + Method: "GET", + Resource: "/api/2.0/global-init-scripts", + Response: map[string]interface{}{}, + }, + { + Method: "GET", + Resource: "/api/2.0/ip-access-lists", + Response: access.ListIPAccessListsResponse{ + ListIPAccessListsResponse: []access.IpAccessListStatus{resp, resp2}, + }, + }, + { + Method: "GET", + Resource: "/api/2.0/ip-access-lists/123", + Response: access.IpAccessListStatusWrapper{ + IPAccessList: resp, + }, + }, + { + Method: "GET", + Resource: "/api/2.0/ip-access-lists/124", + Response: access.IpAccessListStatusWrapper{ + IPAccessList: resp2, + }, + }, + { + Method: "GET", + Resource: "/api/2.0/workspace-conf?keys=enableIpAccessLists", + Response: map[string]interface{}{ + "enableIpAccessLists": "true", + }, + }, + }, + func(ctx context.Context, client *common.DatabricksClient) { + tmpDir := fmt.Sprintf("/tmp/tf-%s", qa.RandomName()) + defer os.RemoveAll(tmpDir) + + ic := newImportContext(client) + ic.Directory = tmpDir + ic.listing = "workspace,access" + ic.services = "workspace,access" + + err := ic.Run() + assert.NoError(t, err) + }) +} diff --git a/exporter/importables.go b/exporter/importables.go index 2b98dbc711..b9b0cfbdc9 100644 --- a/exporter/importables.go +++ b/exporter/importables.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/databrickslabs/terraform-provider-databricks/access" "github.com/databrickslabs/terraform-provider-databricks/clusters" "github.com/databrickslabs/terraform-provider-databricks/common" "github.com/databrickslabs/terraform-provider-databricks/jobs" @@ -28,11 +29,12 @@ import ( ) var ( - adlsGen2Regex = regexp.MustCompile(`^(abfss?)://([^@]+)@([^.]+)\.(?:[^/]+)(/.*)?$`) - adlsGen1Regex = regexp.MustCompile(`^(adls?)://([^.]+)\.(?:[^/]+)(/.*)?$`) - wasbsRegex = regexp.MustCompile(`^(wasbs?)://([^@]+)@([^.]+)\.(?:[^/]+)(/.*)?$`) - s3Regex = regexp.MustCompile(`^(s3a?)://([^/]+)(/.*)?$`) - gsRegex = regexp.MustCompile(`^gs://([^/]+)(/.*)?$`) + adlsGen2Regex = regexp.MustCompile(`^(abfss?)://([^@]+)@([^.]+)\.(?:[^/]+)(/.*)?$`) + adlsGen1Regex = regexp.MustCompile(`^(adls?)://([^.]+)\.(?:[^/]+)(/.*)?$`) + wasbsRegex = regexp.MustCompile(`^(wasbs?)://([^@]+)@([^.]+)\.(?:[^/]+)(/.*)?$`) + s3Regex = regexp.MustCompile(`^(s3a?)://([^/]+)(/.*)?$`) + gsRegex = regexp.MustCompile(`^gs://([^/]+)(/.*)?$`) + globalWorkspaceConfName = "global_workspace_conf" ) func generateMountBody(ic *importContext, body *hclwrite.Body, r *resource) error { @@ -845,4 +847,70 @@ var resourcesMap map[string]importable = map[string]importable{ return nil }, }, + "databricks_workspace_conf": { + Service: "workspace", + Name: func(d *schema.ResourceData) string { + return globalWorkspaceConfName + }, + Import: func(ic *importContext, r *resource) error { + wsConfAPI := workspace.NewWorkspaceConfAPI(ic.Context, ic.Client) + keys := map[string]interface{}{"enableIpAccessLists": false} + err := wsConfAPI.Read(&keys) + if err != nil { + return err + } + r.Data.Set("custom_config", keys) + return nil + }, + }, + "databricks_ip_access_list": { + Service: "access", + Name: func(d *schema.ResourceData) string { + return d.Get("list_type").(string) + "_" + d.Get("label").(string) + }, + List: func(ic *importContext) error { + ipListsResp, err := access.NewIPAccessListsAPI(ic.Context, ic.Client).List() + if err != nil { + return err + } + ipLists := ipListsResp.ListIPAccessListsResponse + for offset, ipList := range ipLists { + ic.Emit(&resource{ + Resource: "databricks_ip_access_list", + ID: ipList.ListID, + }) + log.Printf("[INFO] Scanned %d of %d IP Access Lists", offset+1, len(ipLists)) + } + if len(ipLists) > 0 { + ic.Emit(&resource{ + Resource: "databricks_workspace_conf", + ID: globalWorkspaceConfName, + Data: ic.Resources["databricks_workspace_conf"].Data( + &terraform.InstanceState{ + ID: globalWorkspaceConfName, + Attributes: map[string]string{}, + }), + }) + } + return nil + }, + Body: func(ic *importContext, body *hclwrite.Body, r *resource) error { + b := body.AppendNewBlock("resource", []string{r.Resource, r.Name}).Body() + b.SetAttributeRaw("depends_on", generateSingleDependsOn("databricks_workspace_conf", globalWorkspaceConfName)) + b.SetAttributeValue("list_type", cty.StringVal(r.Data.Get("list_type").(string))) + b.SetAttributeValue("label", cty.StringVal(r.Data.Get("label").(string))) + b.SetAttributeValue("enabled", cty.BoolVal(r.Data.Get("enabled").(bool))) + list := r.Data.Get("ip_addresses").([]interface{}) + if len(list) > 0 { + ctyList := []cty.Value{} + for _, item := range list { + ctyList = append(ctyList, cty.StringVal(item.(string))) + } + b.SetAttributeValue("ip_addresses", cty.ListVal(ctyList)) + } else { + b.SetAttributeValue("ip_addresses", cty.ListValEmpty(cty.String)) + } + return nil + }, + }, }