Skip to content

Commit

Permalink
Added exporter for IP Access Lists
Browse files Browse the repository at this point in the history
  • Loading branch information
alexott committed Feb 16, 2022
1 parent c6ac616 commit ef8a897
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 31 deletions.
27 changes: 13 additions & 14 deletions access/resource_ipaccesslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"`
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -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{
Expand Down
37 changes: 27 additions & 10 deletions access/resource_ipaccesslist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion docs/resources/ip_access_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion exporter/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions exporter/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 73 additions & 0 deletions exporter/exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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?",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
})
}
78 changes: 73 additions & 5 deletions exporter/importables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down Expand Up @@ -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
},
},
}

0 comments on commit ef8a897

Please sign in to comment.