-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Deployments): support access control (#294)
* feat(Deployments): support access control Adds support for access control on Deployments. Closes https://linear.app/prefect/issue/PLA-439/deployments-support-access * Expected sucecss response is 204 * Adhere to the example set by block access * Reuse ObjectActorAccess object * Get tests working * Reduce verbosity in tests * Add docs, example * Generate Terraform Docs * Require cloud endpoint for deployment access --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
0beb8cd
commit 9bf9b8b
Showing
9 changed files
with
882 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "prefect_deployment_access Resource - prefect" | ||
subcategory: "" | ||
description: |- | ||
The resource deployment_access represents a connection between an accessor (User, Service Account or Team) with a Deployment. This resource specifies an actor's access level to a specific Deployment in the Account. | ||
--- | ||
|
||
# prefect_deployment_access (Resource) | ||
|
||
The resource `deployment_access` represents a connection between an accessor (User, Service Account or Team) with a Deployment. This resource specifies an actor's access level to a specific Deployment in the Account. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
provider "prefect" {} | ||
# All Deployments are scoped to a Workspace. | ||
data "prefect_workspace" "test" { | ||
handle = "my-workspace" | ||
} | ||
# Be sure to grant all Actors/Teams who need Deployment 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 Flow and Deployment, and grant access to the Deployment | ||
resource "prefect_flow" "test" { | ||
name = "my-flow" | ||
workspace_id = data.prefect_workspace.test.id | ||
tags = ["test"] | ||
} | ||
resource "prefect_deployment" "test" { | ||
name = "my-deployment" | ||
workspace_id = data.prefect_workspace.test.id | ||
flow_id = prefect_flow.test.id | ||
} | ||
resource "prefect_deployment_access" "test" { | ||
workspace_id = data.prefect_workspace.test.id | ||
deployment_id = prefect_deployment.test.id | ||
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 | ||
|
||
- `deployment_id` (String) Deployment ID (UUID) | ||
|
||
### Optional | ||
|
||
- `account_id` (String) Account ID (UUID) | ||
- `manage_actor_ids` (List of String) List of actor IDs with manage access to the Deployment | ||
- `manage_team_ids` (List of String) List of team IDs with manage access to the Deployment | ||
- `run_actor_ids` (List of String) List of actor IDs with run access to the Deployment | ||
- `run_team_ids` (List of String) List of team IDs with run access to the Deployment | ||
- `view_actor_ids` (List of String) List of actor IDs with view access to the Deployment | ||
- `view_team_ids` (List of String) List of team IDs with view access to the Deployment | ||
- `workspace_id` (String) Workspace ID (UUID) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
provider "prefect" {} | ||
|
||
# All Deployments are scoped to a Workspace. | ||
data "prefect_workspace" "test" { | ||
handle = "my-workspace" | ||
} | ||
|
||
# Be sure to grant all Actors/Teams who need Deployment 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 Flow and Deployment, and grant access to the Deployment | ||
|
||
resource "prefect_flow" "test" { | ||
name = "my-flow" | ||
workspace_id = data.prefect_workspace.test.id | ||
tags = ["test"] | ||
} | ||
|
||
resource "prefect_deployment" "test" { | ||
name = "my-deployment" | ||
workspace_id = data.prefect_workspace.test.id | ||
flow_id = prefect_flow.test.id | ||
} | ||
|
||
resource "prefect_deployment_access" "test" { | ||
workspace_id = data.prefect_workspace.test.id | ||
deployment_id = prefect_deployment.test.id | ||
|
||
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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
// DeploymentAccessClient is a client for working with deployment access. | ||
type DeploymentAccessClient interface { | ||
Read(ctx context.Context, deploymentID uuid.UUID) (*DeploymentAccessControl, error) | ||
Set(ctx context.Context, deploymentID uuid.UUID, accessControl DeploymentAccessSet) error | ||
} | ||
|
||
// DeploymentAccess is a representation of a deployment access. | ||
type DeploymentAccess struct { | ||
BaseModel | ||
AccountID uuid.UUID `json:"account_id"` | ||
WorkspaceID uuid.UUID `json:"workspace_id"` | ||
DeploymentID uuid.UUID `json:"deployment_id"` | ||
AccessControl DeploymentAccessControl `json:"access_control"` | ||
} | ||
|
||
// DeploymentAccessSet is a subset of DeploymentAccess used when setting deployment access control. | ||
type DeploymentAccessSet struct { | ||
AccessControl DeploymentAccessControlSet `json:"access_control"` | ||
} | ||
|
||
// DeploymentAccessControlSet is a definition of deployment access control. | ||
type DeploymentAccessControlSet 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"` | ||
} | ||
|
||
// DeploymentAccessControl is a definition of deployment access control. | ||
type DeploymentAccessControl struct { | ||
ManageActors []ObjectActorAccess `json:"manage_actors"` | ||
RunActors []ObjectActorAccess `json:"run_actors"` | ||
ViewActors []ObjectActorAccess `json:"view_actors"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package client | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/google/uuid" | ||
"github.com/prefecthq/terraform-provider-prefect/internal/api" | ||
"github.com/prefecthq/terraform-provider-prefect/internal/provider/helpers" | ||
) | ||
|
||
var _ = api.DeploymentAccessClient(&DeploymentAccessClient{}) | ||
|
||
type DeploymentAccessClient struct { | ||
hc *http.Client | ||
routePrefix string | ||
apiKey string | ||
} | ||
|
||
// DeploymentAccess returns a DeploymentAccessClient. | ||
// | ||
//nolint:ireturn // required to support PrefectClient mocking | ||
func (c *Client) DeploymentAccess(accountID uuid.UUID, workspaceID uuid.UUID) (api.DeploymentAccessClient, error) { | ||
if accountID == uuid.Nil { | ||
accountID = c.defaultAccountID | ||
} | ||
|
||
if workspaceID == uuid.Nil { | ||
workspaceID = c.defaultWorkspaceID | ||
} | ||
|
||
if helpers.IsCloudEndpoint(c.endpoint) && (accountID == uuid.Nil || workspaceID == uuid.Nil) { | ||
return nil, fmt.Errorf("prefect Cloud endpoints require an account_id and workspace_id to be set on either the provider or the resource") | ||
} | ||
|
||
return &DeploymentAccessClient{ | ||
hc: c.hc, | ||
routePrefix: getWorkspaceScopedURL(c.endpoint, accountID, workspaceID, "deployments"), | ||
apiKey: c.apiKey, | ||
}, nil | ||
} | ||
|
||
func (c *DeploymentAccessClient) Read(ctx context.Context, deploymentID uuid.UUID) (*api.DeploymentAccessControl, error) { | ||
url := fmt.Sprintf("%s/%s/access", c.routePrefix, deploymentID.String()) | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating request: %w", err) | ||
} | ||
|
||
setDefaultHeaders(req, c.apiKey) | ||
|
||
resp, err := c.hc.Do(req) | ||
if err != nil { | ||
return nil, fmt.Errorf("http error: %w", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
errorBody, _ := io.ReadAll(resp.Body) | ||
|
||
return nil, fmt.Errorf("status code %s, error=%s", resp.Status, errorBody) | ||
} | ||
|
||
var accessControl api.DeploymentAccessControl | ||
if err := json.NewDecoder(resp.Body).Decode(&accessControl); err != nil { | ||
return nil, fmt.Errorf("failed to decode response: %w", err) | ||
} | ||
|
||
return &accessControl, nil | ||
} | ||
|
||
func (c *DeploymentAccessClient) Set(ctx context.Context, deploymentID uuid.UUID, accessControl api.DeploymentAccessSet) error { | ||
var buf bytes.Buffer | ||
if err := json.NewEncoder(&buf).Encode(&accessControl); err != nil { | ||
return fmt.Errorf("failed to encode access control: %w", err) | ||
} | ||
|
||
url := fmt.Sprintf("%s/%s/access", c.routePrefix, deploymentID.String()) | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, &buf) | ||
if err != nil { | ||
return fmt.Errorf("error creating request: %w", err) | ||
} | ||
|
||
setDefaultHeaders(req, c.apiKey) | ||
|
||
resp, err := c.hc.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("http error: %w", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusNoContent { | ||
errorBody, _ := io.ReadAll(resp.Body) | ||
|
||
return fmt.Errorf("status code %s, error=%s", resp.Status, errorBody) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.