Skip to content

Commit

Permalink
Add support for Team Members
Browse files Browse the repository at this point in the history
Data Source & Resource

Closes #131.
  • Loading branch information
dglsparsons committed Nov 29, 2024
1 parent aec74a0 commit 65cb586
Show file tree
Hide file tree
Showing 30 changed files with 1,182 additions and 28 deletions.
5 changes: 4 additions & 1 deletion client/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,14 @@ func (c *Client) _doRequest(req *http.Request, v interface{}, errorOnNoContent b

defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)

if err != nil {
return fmt.Errorf("error reading response body: %w", err)
}

tflog.Info(req.Context(), "RESPONSE BODY", map[string]interface{}{
"body": string(responseBody),
})

if resp.StatusCode >= 300 {
var errorResponse APIError
if string(responseBody) == "" {
Expand Down
148 changes: 148 additions & 0 deletions client/team_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package client

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"
)

type ProjectRole struct {
ProjectID string `json:"projectId"`
Role string `json:"role"`
}

type TeamMemberInviteRequest struct {
UserID string `json:"uid,omitempty"`
Email string `json:"email,omitempty"`
Role string `json:"role,omitempty"`
Projects []ProjectRole `json:"projects,omitempty"`
AccessGroups []string `json:"accessGroups,omitempty"`
TeamID string `json:"-"`
}

func (c *Client) InviteTeamMember(ctx context.Context, request TeamMemberInviteRequest) error {
url := fmt.Sprintf("%s/v1/teams/%s/members", c.baseURL, request.TeamID)
tflog.Info(ctx, "inviting user", map[string]interface{}{
"url": url,
"user": request.UserID,
"email": request.Email,
"role": request.Role,
})

err := c.doRequest(clientRequest{
ctx: ctx,
method: "POST",
url: url,
body: string(mustMarshal(request)),
}, nil)
return err
}

type TeamMemberRemoveRequest struct {
UserID string
TeamID string
}

func (c *Client) RemoveTeamMember(ctx context.Context, request TeamMemberRemoveRequest) error {
url := fmt.Sprintf("%s/v2/teams/%s/members/%s", c.baseURL, request.TeamID, request.UserID)
tflog.Info(ctx, "removing user", map[string]interface{}{
"url": url,
"user": request.UserID,
})
err := c.doRequest(clientRequest{
ctx: ctx,
method: "DELETE",
url: url,
body: "",
}, nil)
return err
}

type TeamMemberUpdateRequest struct {
UserID string `json:"-"`
Role string `json:"role"`
TeamID string `json:"-"`
Projects []ProjectRole `json:"projects,omitempty"`
AccessGroupsToAdd []string `json:"accessGroupsToAdd,omitempty"`
AccessGroupsToRemove []string `json:"accessGroupsToRemove,omitempty"`
}

func (c *Client) UpdateTeamMember(ctx context.Context, request TeamMemberUpdateRequest) error {
url := fmt.Sprintf("%s/v1/teams/%s/members/%s", c.baseURL, request.TeamID, request.UserID)
tflog.Info(ctx, "updating team member", map[string]interface{}{
"url": url,
"user": request.UserID,
"role": request.Role,
})
err := c.doRequest(clientRequest{
ctx: ctx,
method: "PATCH",
url: url,
body: string(mustMarshal(request)),
}, nil)
return err
}

type GetTeamMemberRequest struct {
TeamID string
UserID string
}

type TeamMember struct {
Confirmed bool `json:"confirmed"`
Role string `json:"role"`
UserID string `json:"uid"`
Projects []ProjectRole `json:"projects"`
AccessGroups []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"accessGroups"`
}

func (c *Client) GetTeamMember(ctx context.Context, request GetTeamMemberRequest) (TeamMember, error) {
url := fmt.Sprintf("%s/v2/teams/%s/members?limit=1&filterByUserIds=%s", c.baseURL, request.TeamID, request.UserID)
tflog.Info(ctx, "getting team member", map[string]interface{}{
"url": url,
})

var response struct {
Members []TeamMember `json:"members"`
}
err := c.doRequest(clientRequest{
ctx: ctx,
method: "GET",
url: url,
body: "",
}, &response)
if err != nil {
return TeamMember{}, err
}
if len(response.Members) == 0 {
return TeamMember{}, APIError{
StatusCode: 404,
Message: "Team member not found",
Code: "not_found",
}
}

// Now look up the projects for the member, but only if we need to.
if !response.Members[0].Confirmed {
return response.Members[0], nil
}
url = fmt.Sprintf("%s/v1/teams/%s/members/%s/projects?limit=100", c.baseURL, request.TeamID, request.UserID)
var response2 struct {
Projects []ProjectRole `json:"projects"`
}
err = c.doRequest(clientRequest{
ctx: ctx,
method: "GET",
url: url,
body: "",
}, &response2)
if err != nil {
return TeamMember{}, err
}
response.Members[0].Projects = response2.Projects
return response.Members[0], err
}
6 changes: 6 additions & 0 deletions docs/data-sources/access_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ Provides information about an existing Access Group.

For more detailed information, please see the [Vercel documentation](https://vercel.com/docs/accounts/team-members-and-roles/access-groups).

## Example Usage

```terraform
data "vercel_access_group" "example" {
id = "ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```

<!-- schema generated by tfplugindocs -->
## Schema
Expand Down
13 changes: 12 additions & 1 deletion docs/data-sources/access_group_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ Provides information about an existing Access Group Project Assignment.

For more detailed information, please see the [Vercel documentation](https://vercel.com/docs/accounts/team-members-and-roles/access-groups).


## Example Usage

```terraform
data "vercel_project" "example" {
name = "my-existing-project"
}
data "vercel_access_group_project" "example" {
access_group_id = "ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
project_id = vercel_project.example.id
}
```

<!-- schema generated by tfplugindocs -->
## Schema
Expand Down
7 changes: 1 addition & 6 deletions docs/data-sources/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,9 @@ For more detailed information, please see the [Vercel documentation](https://ver
## Example Usage

```terraform
data "vercel_project" "foo" {
data "vercel_project" "example" {
name = "my-existing-project"
}
# Outputs prj_xxxxxx
output "project_id" {
value = data.vercel_project.foo.id
}
```

<!-- schema generated by tfplugindocs -->
Expand Down
43 changes: 43 additions & 0 deletions docs/data-sources/team_member.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "vercel_team_member Data Source - terraform-provider-vercel"
subcategory: ""
description: |-
Provider a datasource for managing a team member.
---

# vercel_team_member (Data Source)

Provider a datasource for managing a team member.

## Example Usage

```terraform
data "vercel_team_member" "example" {
user_id = "uuuuuuuuuuuuuuuuuuuuuuuuuu"
team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxx"
}
```

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

### Required

- `team_id` (String) The ID of the existing Vercel Team.
- `user_id` (String) The ID of the existing Vercel Team Member.

### Read-Only

- `access_groups` (Set of String) If access groups are enabled on the team, and the user is a CONTRIBUTOR, `projects`, `access_groups` or both must be specified. A set of access groups IDs that the user should be granted access to.
- `id` (String) The ID of this resource.
- `projects` (Attributes Set) If access groups are enabled on the team, and the user is a CONTRIBUTOR, `projects`, `access_groups` or both must be specified. A set of projects that the user should be granted access to, along with their role in each project. (see [below for nested schema](#nestedatt--projects))
- `role` (String) The role that the user should have in the project. One of 'MEMBER', 'OWNER', 'VIEWER', 'DEVELOPER', 'BILLING' or 'CONTRIBUTOR'. Depending on your Team's plan, some of these roles may be unavailable.

<a id="nestedatt--projects"></a>
### Nested Schema for `projects`

Required:

- `project_id` (String) The ID of the project that the user should be granted access to.
- `role` (String) The role that the user should have in the project.
19 changes: 19 additions & 0 deletions docs/resources/access_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ Access Groups provide a way to manage groups of Vercel users across projects on

For more detailed information, please see the [Vercel documentation](https://vercel.com/docs/accounts/team-members-and-roles/access-groups).

## Example Usage

```terraform
resource "vercel_access_group" "example" {
name = "example-access-group"
}
```

<!-- schema generated by tfplugindocs -->
## Schema
Expand All @@ -32,3 +38,16 @@ For more detailed information, please see the [Vercel documentation](https://ver
### Read-Only

- `id` (String) The ID of the Access Group.

## Import

Import is supported using the following syntax:

```shell
# If importing into a personal account, or with a team configured on
# the provider, simply use the access_group_id.
terraform import vercel_access_group.example ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

# If importing to a team, use the team_id and access_group_id.
terraform import vercel_access_group.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
29 changes: 29 additions & 0 deletions docs/resources/access_group_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,23 @@ An Access Group Project resource defines the relationship between a `vercel_acce

For more detailed information, please see the [Vercel documentation](https://vercel.com/docs/accounts/team-members-and-roles/access-groups).

## Example Usage

```terraform
resource "vercel_project" "example" {
name = "example-project"
}
resource "vercel_access_group" "example" {
name = "example-access-group"
}
resource "vercel_access_group_project" "example" {
project_id = vercel_project.example.id
access_group_id = vercel_access_group.example.id
role = "ADMIN"
}
```

<!-- schema generated by tfplugindocs -->
## Schema
Expand All @@ -30,3 +46,16 @@ For more detailed information, please see the [Vercel documentation](https://ver
### Optional

- `team_id` (String) The ID of the team the access group project should exist under. Required when configuring a team resource if a default team has not been set in the provider.

## Import

Import is supported using the following syntax:

```shell
# If importing into a personal account, or with a team configured on
# the provider, use the access_group_id and project_id.
terraform import vercel_access_group.example ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

# If importing to a team, use the team_id, access_group_id and project_id.
terraform import vercel_access_group.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
4 changes: 2 additions & 2 deletions docs/resources/log_drain.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ Import is supported using the following syntax:
# If importing into a personal account, or with a team configured on
# the provider, simply use the log_drain_id.
# - log_drain_id can be found by querying the Vercel REST API (https://vercel.com/docs/rest-api/endpoints/logDrains#retrieves-a-list-of-all-the-log-drains).
terraform import vercel_log_drain.example ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
terraform import vercel_log_drain.example ld_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Alternatively, you can import via the team_id and edge_config_id.
# - team_id can be found in the team `settings` tab in the Vercel UI.
# - log_drain_id can be found by querying the Vercel REST API (https://vercel.com/docs/rest-api/endpoints/logDrains#retrieves-a-list-of-all-the-log-drains).
terraform import vercel_log_drain.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
terraform import vercel_log_drain.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ld_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
52 changes: 52 additions & 0 deletions docs/resources/team_member.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "vercel_team_member Resource - terraform-provider-vercel"
subcategory: ""
description: |-
Provider a resource for managing a team member.
---

# vercel_team_member (Resource)

Provider a resource for managing a team member.

## Example Usage

```terraform
resource "vercel_team_member" "example" {
team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxx"
user_id = "uuuuuuuuuuuuuuuuuuuuuuuuuu"
role = "MEMBER"
}
```

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

### Required

- `role` (String) The role that the user should have in the project. One of 'MEMBER', 'OWNER', 'VIEWER', 'DEVELOPER', 'BILLING' or 'CONTRIBUTOR'. Depending on your Team's plan, some of these roles may be unavailable.
- `team_id` (String) The ID of the existing Vercel Team.
- `user_id` (String) The ID of the user to add to the team.

### Optional

- `access_groups` (Set of String) If access groups are enabled on the team, and the user is a CONTRIBUTOR, `projects`, `access_groups` or both must be specified. A set of access groups IDs that the user should be granted access to.
- `projects` (Attributes Set) If access groups are enabled on the team, and the user is a CONTRIBUTOR, `projects`, `access_groups` or both must be specified. A set of projects that the user should be granted access to, along with their role in each project. (see [below for nested schema](#nestedatt--projects))

<a id="nestedatt--projects"></a>
### Nested Schema for `projects`

Required:

- `project_id` (String) The ID of the project that the user should be granted access to.
- `role` (String) The role that the user should have in the project.

## Import

Import is supported using the following syntax:

```shell
# To import, use the team_id and user_id.
terraform import vercel_access_group.example team_xxxxxxxxxxxxxxxxxxxxxxxx/uuuuuuuuuuuuuuuuuuuuuuuuuu
```
Loading

0 comments on commit 65cb586

Please sign in to comment.