Skip to content

Commit

Permalink
Added databricks_service_principal_role resource (databricks#1340)
Browse files Browse the repository at this point in the history
This PR adds a new resource `databricks_service_principal_role` that grants Databricks Service Principals access to Databricks Instance Profiles. At the moment, this has to be done as a manual step via the Databricks UI.

Similar attachment/pairing resources already exist for Databricks users (`databricks_user_instance_profile` superseded by `databricks_user_role`) and groups (`databricks_group_instance_profile`).
  • Loading branch information
neinkeinkaffee authored May 30, 2022
1 parent 44a683e commit ec15230
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 3 deletions.
33 changes: 33 additions & 0 deletions aws/resource_service_principal_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package aws

import (
"context"
"fmt"

"github.com/databrickslabs/terraform-provider-databricks/common"
"github.com/databrickslabs/terraform-provider-databricks/scim"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// ResourceServicePrincipalRole binds service principal and instance profile
func ResourceServicePrincipalRole() *schema.Resource {
r := common.NewPairID("service_principal_id", "role").BindResource(common.BindResource{
CreateContext: func(ctx context.Context, servicePrincipalID, role string, c *common.DatabricksClient) error {
return scim.NewServicePrincipalsAPI(ctx, c).Patch(servicePrincipalID, scim.PatchRequest("add", "roles", role))
},
ReadContext: func(ctx context.Context, servicePrincipalID, roleARN string, c *common.DatabricksClient) error {
servicePrincipal, err := scim.NewServicePrincipalsAPI(ctx, c).Read(servicePrincipalID)
hasRole := scim.ComplexValues(servicePrincipal.Roles).HasValue(roleARN)
if err == nil && !hasRole {
return common.NotFound("Service Principal has no role")
}
return err
},
DeleteContext: func(ctx context.Context, servicePrincipalID, roleARN string, c *common.DatabricksClient) error {
return scim.NewServicePrincipalsAPI(ctx, c).Patch(servicePrincipalID, scim.PatchRequest(
"remove", fmt.Sprintf(`roles[value eq "%s"]`, roleARN), ""))
},
})
return r
}
134 changes: 134 additions & 0 deletions aws/resource_service_principal_role_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package aws

import (
"github.com/databrickslabs/terraform-provider-databricks/common"
"testing"

"github.com/databrickslabs/terraform-provider-databricks/scim"

"github.com/databrickslabs/terraform-provider-databricks/qa"
)

func TestResourceServicePrincipalRoleCreate(t *testing.T) {
qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
{
Method: "PATCH",
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals/abc",
ExpectedRequest: scim.PatchRequest(
"add",
"roles",
"arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile"),
Response: scim.User{
ID: "abc",
},
},
{
Method: "GET",
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals/abc",
Response: scim.User{
Schemas: []scim.URN{scim.ServicePrincipalSchema},
DisplayName: "ABC SP",
Roles: []scim.ComplexValue{
{
Value: "arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile",
},
},
ID: "abc",
},
},
},
Resource: ResourceServicePrincipalRole(),
State: map[string]interface{}{
"service_principal_id": "abc",
"role": "arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile",
},
Create: true,
}.ApplyAndExpectData(t, map[string]interface{}{"id": "abc|arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile"})
}

func TestResourceServicePrincipalRoleCreate_Error(t *testing.T) {
qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
{
Method: "PATCH",
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals/abc",
Response: common.APIErrorBody{
ErrorCode: "INVALID_REQUEST",
Message: "Internal error happened",
},
Status: 400,
},
},
Resource: ResourceServicePrincipalRole(),
State: map[string]interface{}{
"service_principal_id": "abc",
"role": "arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile",
},
Create: true,
}.ExpectError(t, "Internal error happened")
}

func TestResourceServicePrincipalRoleRead(t *testing.T) {
qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
{
Method: "GET",
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals/abc",
Response: scim.User{
Schemas: []scim.URN{scim.ServicePrincipalSchema},
DisplayName: "ABC SP",
Roles: []scim.ComplexValue{
{
Value: "arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile",
},
},
ID: "abc",
},
},
},
Resource: ResourceServicePrincipalRole(),
Read: true,
ID: "abc|arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile",
}.ApplyAndExpectData(t, map[string]interface{}{"id": "abc|arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile"})
}

func TestResourceServicePrincipalRoleRead_NoRole(t *testing.T) {
qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
{
Method: "GET",
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals/abc",
Response: scim.User{
Schemas: []scim.URN{scim.ServicePrincipalSchema},
DisplayName: "ABC SP",
ID: "abc",
},
},
},
Resource: ResourceServicePrincipalRole(),
Read: true,
Removed: true,
ID: "abc|arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile",
}.ApplyNoError(t)
}

func TestResourceServicePrincipalRoleRead_NotFound(t *testing.T) {
qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
{
Method: "GET",
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals/abc",
Response: common.APIErrorBody{
ErrorCode: "NOT_FOUND",
Message: "Item not found",
},
Status: 404,
},
},
Resource: ResourceServicePrincipalRole(),
Read: true,
Removed: true,
ID: "abc|arn:aws:iam::999999999999:instance-profile/my-fake-instance-profile",
}.ApplyNoError(t)
}
51 changes: 51 additions & 0 deletions docs/resources/service_principal_role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
subcategory: "Security"
---
# databricks_service_principal_role Resource

This resource allows you to attach a role or [databricks_instance_profile](instance_profile.md) (AWS) to a [databricks_service_principal](service_principal.md).

## Example Usage

Granting a service principal access to an instance profile

```hcl
resource "databricks_instance_profile" "instance_profile" {
instance_profile_arn = "my_instance_profile_arn"
}
resource "databricks_service_principal" "this" {
display_name = "My Service Principal"
}
resource "databricks_service_principal_role" "my_service_principal_instance_profile" {
service_principal_id = databricks_service_principal.this.id
role = databricks_instance_profile.instance_profile.id
}
```
## Argument Reference

The following arguments are supported:

* `service_principal_id` - (Required) This is the id of the [service principal](service_principal.md) resource.
* `role` - (Required) This is the id of the role or [instance profile](instance_profile.md) resource.

## Attribute Reference

In addition to all arguments above, the following attributes are exported:

* `id` - The id in the format `<service_principal_id>|<role>`.

## Import

-> **Note** Importing this resource is not currently supported.

## Related Resources

The following resources are often used in the same context:

* [End to end workspace management](../guides/workspace-management.md) guide.
* [databricks_user_role](user_instance_profile.md) to attach role or [databricks_instance_profile](instance_profile.md) (AWS) to [databricks_user](user.md).
* [databricks_group_instance_profile](group_instance_profile.md) to attach [databricks_instance_profile](instance_profile.md) (AWS) to [databricks_group](group.md).
* [databricks_group_member](group_member.md) to attach [users](user.md) and [groups](group.md) as group members.
* [databricks_instance_profile](instance_profile.md) to manage AWS EC2 instance profiles that users can launch [databricks_cluster](cluster.md) and access data, like [databricks_mount](mount.md).
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func DatabricksProvider() *schema.Provider {
"databricks_secret_scope": secrets.ResourceSecretScope(),
"databricks_secret_acl": secrets.ResourceSecretACL(),
"databricks_service_principal": scim.ResourceServicePrincipal(),
"databricks_service_principal_role": aws.ResourceServicePrincipalRole(),
"databricks_sql_dashboard": sql.ResourceDashboard(),
"databricks_sql_endpoint": sql.ResourceSQLEndpoint(),
"databricks_sql_global_config": sql.ResourceSQLGlobalConfig(),
Expand Down
11 changes: 8 additions & 3 deletions scim/resource_service_principal.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (a ServicePrincipalsAPI) Create(rsp User) (sp User, err error) {
return sp, err
}

func (a ServicePrincipalsAPI) read(servicePrincipalID string) (sp User, err error) {
func (a ServicePrincipalsAPI) Read(servicePrincipalID string) (sp User, err error) {
servicePrincipalPath := fmt.Sprintf("/preview/scim/v2/ServicePrincipals/%v", servicePrincipalID)
err = a.client.Scim(a.context, "GET", servicePrincipalPath, nil, &sp)
return
Expand All @@ -51,9 +51,14 @@ func (a ServicePrincipalsAPI) filter(filter string) (u []User, err error) {
return
}

// Patch updates resource-friendly entity
func (a ServicePrincipalsAPI) Patch(servicePrincipalID string, r patchRequest) error {
return a.client.Scim(a.context, http.MethodPatch, fmt.Sprintf("/preview/scim/v2/ServicePrincipals/%v", servicePrincipalID), r, nil)
}

// Update replaces resource-friendly-entity
func (a ServicePrincipalsAPI) Update(servicePrincipalID string, updateRequest User) error {
servicePrincipal, err := a.read(servicePrincipalID)
servicePrincipal, err := a.Read(servicePrincipalID)
if err != nil {
return err
}
Expand Down Expand Up @@ -114,7 +119,7 @@ func ResourceServicePrincipal() *schema.Resource {
return nil
},
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
sp, err := NewServicePrincipalsAPI(ctx, c).read(d.Id())
sp, err := NewServicePrincipalsAPI(ctx, c).Read(d.Id())
if err != nil {
return err
}
Expand Down

0 comments on commit ec15230

Please sign in to comment.