Skip to content

Commit

Permalink
feat: add workspace_role datasource (#68)
Browse files Browse the repository at this point in the history
* workspace_role api interfaces

* add http client methods§

* add workspace role datasource

* add tf-plugin-testing and related deps

* fix filter paramter

* acceptance tests for the datasource

* linting
  • Loading branch information
parkedwards authored Oct 24, 2023
1 parent b86c496 commit 40eb98b
Show file tree
Hide file tree
Showing 9 changed files with 648 additions and 6 deletions.
32 changes: 30 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,55 @@ require (
github.com/google/uuid v1.3.1
github.com/hashicorp/terraform-plugin-framework v1.3.5
github.com/hashicorp/terraform-plugin-go v0.18.0
github.com/hashicorp/terraform-plugin-testing v1.5.1
)

require (
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.4.10 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hc-install v0.5.2 // indirect
github.com/hashicorp/hcl/v2 v2.17.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.18.1 // indirect
github.com/hashicorp/terraform-json v0.17.1 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.28.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.1 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zclconf/go-cty v1.13.3 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.1 // indirect
google.golang.org/protobuf v1.31.0 // indirect
Expand Down
111 changes: 107 additions & 4 deletions go.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "github.com/google/uuid"
type PrefectClient interface {
Accounts() (AccountsClient, error)
Workspaces(accountID uuid.UUID) (WorkspacesClient, error)
WorkspaceRoles(accountID uuid.UUID) (WorkspaceRolesClient, error)
WorkPools(accountID uuid.UUID, workspaceID uuid.UUID) (WorkPoolsClient, error)
Variables(accountID uuid.UUID, workspaceID uuid.UUID) (VariablesClient, error)
}
47 changes: 47 additions & 0 deletions internal/api/workspace_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package api

import (
"context"

"github.com/google/uuid"
)

type WorkspaceRolesClient interface {
Create(ctx context.Context, data WorkspaceRoleUpsert) (*WorkspaceRole, error)
Update(ctx context.Context, id uuid.UUID, data WorkspaceRoleUpsert) error
Delete(ctx context.Context, id uuid.UUID) error
List(ctx context.Context, roleNames []string) ([]*WorkspaceRole, error)
Get(ctx context.Context, id uuid.UUID) (*WorkspaceRole, error)
}

// WorkspaceRole is a representation of a workspace role.
type WorkspaceRole struct {
BaseModel
Name string `json:"name"`
Description *string `json:"description"`
Permissions []string `json:"permissions"`
Scopes []string `json:"scopes"`
AccountID *uuid.UUID `json:"account_id"` // this is null for the default roles
InheritedRoleID *uuid.UUID `json:"inherited_role_id"`
}

// WorkspaceRoleUpsert defines the request payload
// when creating or updating a workspace role.
type WorkspaceRoleUpsert struct {
Name string `json:"name"`
Description *string `json:"description"`
Scopes []string `json:"scopes"`
InheritedRoleID *uuid.UUID `json:"inherited_role_id"`
}

// WorkspaceRoleFilter defines the search filter payload
// when searching for workspace roles by name.
// example request payload:
// {"workspace_roles": {"name": {"any_": ["test"]}}}.
type WorkspaceRoleFilter struct {
WorkspaceRoles struct {
Name struct {
Any []string `json:"any_"`
} `json:"name"`
} `json:"workspace_roles"`
}
179 changes: 179 additions & 0 deletions internal/client/workspace_roles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package client

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"

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

// type assertion ensures that this client implements the interface.
var _ = api.WorkspaceRolesClient(&WorkspaceRolesClient{})

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

// WorkspaceRoles is a factory that initializes and returns a WorkspaceRolesClient.
//
//nolint:ireturn // required to support PrefectClient mocking
func (c *Client) WorkspaceRoles(accountID uuid.UUID) (api.WorkspaceRolesClient, error) {
if accountID == uuid.Nil {
accountID = c.defaultAccountID
}

return &WorkspaceRolesClient{
hc: c.hc,
apiKey: c.apiKey,
routePrefix: fmt.Sprintf("%s/api/accounts/%s/workspace_roles", c.endpoint, accountID.String()),
}, nil
}

// Create creates a new workspace role.
func (c *WorkspaceRolesClient) Create(ctx context.Context, data api.WorkspaceRoleUpsert) (*api.WorkspaceRole, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(&data); err != nil {
return nil, fmt.Errorf("failed to encode create payload data: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/", c.routePrefix), &buf)
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.StatusCreated {
return nil, fmt.Errorf("status code %s", resp.Status)
}

var workspaceRole api.WorkspaceRole
if err := json.NewDecoder(resp.Body).Decode(&workspaceRole); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}

return &workspaceRole, nil
}

// Update modifies an existing workspace role by ID.
func (c *WorkspaceRolesClient) Update(ctx context.Context, workspaceRoleID uuid.UUID, data api.WorkspaceRoleUpsert) error {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(&data); err != nil {
return fmt.Errorf("failed to encode update payload data: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/%s", c.routePrefix, workspaceRoleID.String()), &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 {
return fmt.Errorf("status code %s", resp.Status)
}

return nil
}

// Delete removes a workspace role by ID.
func (c *WorkspaceRolesClient) Delete(ctx context.Context, id uuid.UUID) error {
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("%s/%s", c.routePrefix, id.String()), http.NoBody)
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 {
return fmt.Errorf("status code %s", resp.Status)
}

return nil
}

// List returns a list of workspace roles, based on the provided filter.
func (c *WorkspaceRolesClient) List(ctx context.Context, roleNames []string) ([]*api.WorkspaceRole, error) {
var buf bytes.Buffer
filterQuery := api.WorkspaceRoleFilter{}
filterQuery.WorkspaceRoles.Name.Any = roleNames

if err := json.NewEncoder(&buf).Encode(&filterQuery); err != nil {
return nil, fmt.Errorf("failed to encode filter payload data: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/filter", c.routePrefix), &buf)
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 {
return nil, fmt.Errorf("status code %s", resp.Status)
}

var workspaceRoles []*api.WorkspaceRole
if err := json.NewDecoder(resp.Body).Decode(&workspaceRoles); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}

return workspaceRoles, nil
}

// Get returns a workspace role by ID.
func (c *WorkspaceRolesClient) Get(ctx context.Context, id uuid.UUID) (*api.WorkspaceRole, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/%s", c.routePrefix, id.String()), 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 {
return nil, fmt.Errorf("status code %s", resp.Status)
}

var workspaceRole api.WorkspaceRole
if err := json.NewDecoder(resp.Body).Decode(&workspaceRole); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}

return &workspaceRole, nil
}
Loading

0 comments on commit 40eb98b

Please sign in to comment.