Skip to content

Commit

Permalink
feat(work pools): support work pool access (#389)
Browse files Browse the repository at this point in the history
* feat(work pools): support work pool access

Similar to deployment_access and workspace_access, adds work_pool_access
resource to manage access to work pools.

Closes #366

Closes https://linear.app/prefect/issue/PLA-985/add-support-for-work-pool-access

* Add example usage

* Update Admin, Owner account role counts

* Replace 'Deployment' refs with 'Work Pool'

copypasta
  • Loading branch information
mitchnielsen authored Feb 28, 2025
1 parent f4c1db5 commit 05cbc49
Show file tree
Hide file tree
Showing 9 changed files with 808 additions and 1 deletion.
95 changes: 95 additions & 0 deletions docs/resources/work_pool_access.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "prefect_work_pool_access Resource - prefect"
subcategory: ""
description: |-
The resource work_pool_access represents a connection between an accessor (User, Service Account or Team) with a Work Pool. This resource specifies an actor's access level to a specific Work Pool in the Account. For more information, see object access control lists https://docs.prefect.io/v3/manage/cloud/manage-users/object-access-control-lists.
---

# prefect_work_pool_access (Resource)

The resource `work_pool_access` represents a connection between an accessor (User, Service Account or Team) with a Work Pool. This resource specifies an actor's access level to a specific Work Pool in the Account. For more information, see [object access control lists](https://docs.prefect.io/v3/manage/cloud/manage-users/object-access-control-lists).

## Example Usage

```terraform
provider "prefect" {}
# All work pools are scoped to a Workspace.
data "prefect_workspace" "test" {
handle = "my-workspace"
}
# Be sure to grant all Actors/Teams who need Work Pool access to first be
# invited to the Workspace (with a role).
data "prefect_workspace_role" "developer" {
name = "Developer"
}
# Example: invite a Service Account to the Workspace and grant it Developer access
resource "prefect_service_account" "test" {
name = "my-service-account"
}
resource "prefect_workspace_access" "test" {
accessor_type = "SERVICE_ACCOUNT"
accessor_id = prefect_service_account.test.id
workspace_role_id = data.prefect_workspace_role.developer.id
workspace_id = data.prefect_workspace.test.id
}
# Example: invite a Team to the Workspace and grant it Developer access
data "prefect_team" "test" {
name = "my-team"
}
resource "prefect_workspace_access" "test_team" {
accessor_type = "TEAM"
accessor_id = data.prefect_team.test.id
workspace_role_id = data.prefect_workspace_role.developer.id
workspace_id = data.prefect_workspace.test.id
}
# Define the Work Pool and grant access to the Work Pool
resource "prefect_work_pool" "test" {
name = "my-work-pool"
workspace_id = data.prefect_workspace.test.id
}
resource "prefect_work_pool_access" "test" {
workspace_id = data.prefect_workspace.test.id
work_pool_name = prefect_work_pool.test.name
manage_actor_ids = [prefect_service_account.test.actor_id]
run_actor_ids = [prefect_service_account.test.actor_id]
view_actor_ids = [prefect_service_account.test.actor_id]
manage_team_ids = [data.prefect_team.test.id]
run_team_ids = [data.prefect_team.test.id]
view_team_ids = [data.prefect_team.test.id]
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `work_pool_name` (String) The name of the Work Pool

### Optional

- `account_id` (String) Account ID (UUID)
- `manage_actor_ids` (List of String) List of actor IDs with manage access to the Work Pool
- `manage_team_ids` (List of String) List of team IDs with manage access to the Work Pool
- `run_actor_ids` (List of String) List of actor IDs with run access to the Work Pool
- `run_team_ids` (List of String) List of team IDs with run access to the Work Pool
- `view_actor_ids` (List of String) List of actor IDs with view access to the Work Pool
- `view_team_ids` (List of String) List of team IDs with view access to the Work Pool
- `workspace_id` (String) Workspace ID (UUID)
61 changes: 61 additions & 0 deletions examples/resources/prefect_work_pool_access/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
provider "prefect" {}

# All work pools are scoped to a Workspace.
data "prefect_workspace" "test" {
handle = "my-workspace"
}

# Be sure to grant all Actors/Teams who need Work Pool access to first be
# invited to the Workspace (with a role).
data "prefect_workspace_role" "developer" {
name = "Developer"
}


# Example: invite a Service Account to the Workspace and grant it Developer access

resource "prefect_service_account" "test" {
name = "my-service-account"
}

resource "prefect_workspace_access" "test" {
accessor_type = "SERVICE_ACCOUNT"
accessor_id = prefect_service_account.test.id
workspace_role_id = data.prefect_workspace_role.developer.id
workspace_id = data.prefect_workspace.test.id
}


# Example: invite a Team to the Workspace and grant it Developer access

data "prefect_team" "test" {
name = "my-team"
}

resource "prefect_workspace_access" "test_team" {
accessor_type = "TEAM"
accessor_id = data.prefect_team.test.id
workspace_role_id = data.prefect_workspace_role.developer.id
workspace_id = data.prefect_workspace.test.id
}


# Define the Work Pool and grant access to the Work Pool

resource "prefect_work_pool" "test" {
name = "my-work-pool"
workspace_id = data.prefect_workspace.test.id
}

resource "prefect_work_pool_access" "test" {
workspace_id = data.prefect_workspace.test.id
work_pool_name = prefect_work_pool.test.name

manage_actor_ids = [prefect_service_account.test.actor_id]
run_actor_ids = [prefect_service_account.test.actor_id]
view_actor_ids = [prefect_service_account.test.actor_id]

manage_team_ids = [data.prefect_team.test.id]
run_team_ids = [data.prefect_team.test.id]
view_team_ids = [data.prefect_team.test.id]
}
1 change: 1 addition & 0 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type PrefectClient interface {
WorkspaceAccess(accountID uuid.UUID, workspaceID uuid.UUID) (WorkspaceAccessClient, error)
WorkspaceRoles(accountID uuid.UUID) (WorkspaceRolesClient, error)
WorkPools(accountID uuid.UUID, workspaceID uuid.UUID) (WorkPoolsClient, error)
WorkPoolAccess(accountID uuid.UUID, workspaceID uuid.UUID) (WorkPoolAccessClient, error)
WorkQueues(accountID uuid.UUID, workspaceID uuid.UUID, workPoolName string) (WorkQueuesClient, error)
Variables(accountID uuid.UUID, workspaceID uuid.UUID) (VariablesClient, error)
ServiceAccounts(accountID uuid.UUID) (ServiceAccountsClient, error)
Expand Down
44 changes: 44 additions & 0 deletions internal/api/work_pool_access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package api

import (
"context"

"github.com/google/uuid"
)

// WorkPoolAccessClient is a client for working with work pool access.
type WorkPoolAccessClient interface {
Read(ctx context.Context, workPoolName string) (*WorkPoolAccessControl, error)
Set(ctx context.Context, workPoolName string, accessControl WorkPoolAccessSet) error
}

// WorkPoolAccess is a representation of a work pool access.
type WorkPoolAccess struct {
BaseModel
AccountID uuid.UUID `json:"account_id"`
WorkspaceID uuid.UUID `json:"workspace_id"`
WorkPoolName string `json:"work_pool_name"`
AccessControl WorkPoolAccessControl `json:"access_control"`
}

// WorkPoolAccessSet is a subset of WorkPoolAccess used when setting work pool access control.
type WorkPoolAccessSet struct {
AccessControl WorkPoolAccessControlSet `json:"access_control"`
}

// WorkPoolAccessControlSet is a definition of work pool access control.
type WorkPoolAccessControlSet struct {
ManageActorIDs []string `json:"manage_actor_ids"`
RunActorIDs []string `json:"run_actor_ids"`
ViewActorIDs []string `json:"view_actor_ids"`
ManageTeamIDs []string `json:"manage_team_ids"`
RunTeamIDs []string `json:"run_team_ids"`
ViewTeamIDs []string `json:"view_team_ids"`
}

// WorkPoolAccessControl is a representation of a work pool access control.
type WorkPoolAccessControl struct {
ManageActors []ObjectActorAccess `json:"manage_actors"`
RunActors []ObjectActorAccess `json:"run_actors"`
ViewActors []ObjectActorAccess `json:"view_actors"`
}
79 changes: 79 additions & 0 deletions internal/client/work_pool_access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package client

import (
"context"
"fmt"
"net/http"

"github.com/google/uuid"
"github.com/prefecthq/terraform-provider-prefect/internal/api"
)

type WorkPoolAccessClient struct {
hc *http.Client
routePrefix string
apiKey string
basicAuthKey string
}

// WorkPoolAccess returns a WorkPoolAccessClient.
//
//nolint:ireturn // required to support PrefectClient mocking
func (c *Client) WorkPoolAccess(accountID uuid.UUID, workspaceID uuid.UUID) (api.WorkPoolAccessClient, error) {
if accountID == uuid.Nil {
accountID = c.defaultAccountID
}

if workspaceID == uuid.Nil {
workspaceID = c.defaultWorkspaceID
}

if err := validateCloudEndpoint(c.endpoint, accountID, workspaceID); err != nil {
return nil, err
}

return &WorkPoolAccessClient{
hc: c.hc,
routePrefix: getWorkspaceScopedURL(c.endpoint, accountID, workspaceID, "work_pools"),
apiKey: c.apiKey,
basicAuthKey: c.basicAuthKey,
}, nil
}

func (c *WorkPoolAccessClient) Read(ctx context.Context, workPoolName string) (*api.WorkPoolAccessControl, error) {
cfg := requestConfig{
method: http.MethodGet,
url: fmt.Sprintf("%s/%s/access", c.routePrefix, workPoolName),
body: http.NoBody,
apiKey: c.apiKey,
basicAuthKey: c.basicAuthKey,
successCodes: successCodesStatusOK,
}

var accessControl api.WorkPoolAccessControl
if err := requestWithDecodeResponse(ctx, c.hc, cfg, &accessControl); err != nil {
return nil, fmt.Errorf("failed to get work pool access control: %w", err)
}

return &accessControl, nil
}

func (c *WorkPoolAccessClient) Set(ctx context.Context, workPoolName string, accessControl api.WorkPoolAccessSet) error {
cfg := requestConfig{
method: http.MethodPut,
url: fmt.Sprintf("%s/%s/access", c.routePrefix, workPoolName),
body: &accessControl,
apiKey: c.apiKey,
basicAuthKey: c.basicAuthKey,
successCodes: successCodesStatusNoContent,
}

resp, err := request(ctx, c.hc, cfg)
if err != nil {
return fmt.Errorf("failed to set work pool access control: %w", err)
}

defer resp.Body.Close()

return nil
}
2 changes: 1 addition & 1 deletion internal/provider/datasources/account_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestAccDatasource_account_role_defaults(t *testing.T) {
}

// Default account role names - these exist in every account
defaultAccountRoles := []defaultAccountRole{{"Admin", 42}, {"Member", 12}, {"Owner", 44}}
defaultAccountRoles := []defaultAccountRole{{"Admin", 43}, {"Member", 12}, {"Owner", 45}}

testSteps := []resource.TestStep{}

Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ func (p *PrefectProvider) Resources(_ context.Context) []func() resource.Resourc
resources.NewVariableResource,
resources.NewWebhookResource,
resources.NewWorkPoolResource,
resources.NewWorkPoolAccessResource,
resources.NewWorkspaceAccessResource,
resources.NewWorkspaceResource,
resources.NewWorkspaceRoleResource,
Expand Down
Loading

0 comments on commit 05cbc49

Please sign in to comment.