Skip to content

Commit e2bd728

Browse files
authored
Add support for Team Members (#238)
Data Source & Resource Closes #131.
1 parent aec74a0 commit e2bd728

31 files changed

+1175
-28
lines changed

.github/workflows/test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ jobs:
102102
VERCEL_TERRAFORM_TESTING_GITLAB_REPO: "dglsparsons/test"
103103
VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO: "dglsparsons-test/test"
104104
VERCEL_TERRAFORM_TESTING_DOMAIN: "dgls.dev"
105+
VERCEL_TERRAFORM_TESTING_ADDITIONAL_USER: ${{ secrets.VERCEL_TERRAFORM_TESTING_ADDITIONAL_USER }}
105106
run: |
106107
go test ./...
107108

client/request.go

-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ func (c *Client) _doRequest(req *http.Request, v interface{}, errorOnNoContent b
111111

112112
defer resp.Body.Close()
113113
responseBody, err := io.ReadAll(resp.Body)
114-
115114
if err != nil {
116115
return fmt.Errorf("error reading response body: %w", err)
117116
}

client/team_member.go

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-log/tflog"
8+
)
9+
10+
type ProjectRole struct {
11+
ProjectID string `json:"projectId"`
12+
Role string `json:"role"`
13+
}
14+
15+
type TeamMemberInviteRequest struct {
16+
UserID string `json:"uid,omitempty"`
17+
Email string `json:"email,omitempty"`
18+
Role string `json:"role,omitempty"`
19+
Projects []ProjectRole `json:"projects,omitempty"`
20+
AccessGroups []string `json:"accessGroups,omitempty"`
21+
TeamID string `json:"-"`
22+
}
23+
24+
func (c *Client) InviteTeamMember(ctx context.Context, request TeamMemberInviteRequest) error {
25+
url := fmt.Sprintf("%s/v1/teams/%s/members", c.baseURL, request.TeamID)
26+
tflog.Info(ctx, "inviting user", map[string]interface{}{
27+
"url": url,
28+
"user": request.UserID,
29+
"email": request.Email,
30+
"role": request.Role,
31+
})
32+
33+
err := c.doRequest(clientRequest{
34+
ctx: ctx,
35+
method: "POST",
36+
url: url,
37+
body: string(mustMarshal(request)),
38+
}, nil)
39+
return err
40+
}
41+
42+
type TeamMemberRemoveRequest struct {
43+
UserID string
44+
TeamID string
45+
}
46+
47+
func (c *Client) RemoveTeamMember(ctx context.Context, request TeamMemberRemoveRequest) error {
48+
url := fmt.Sprintf("%s/v2/teams/%s/members/%s", c.baseURL, request.TeamID, request.UserID)
49+
tflog.Info(ctx, "removing user", map[string]interface{}{
50+
"url": url,
51+
"user": request.UserID,
52+
})
53+
err := c.doRequest(clientRequest{
54+
ctx: ctx,
55+
method: "DELETE",
56+
url: url,
57+
body: "",
58+
}, nil)
59+
return err
60+
}
61+
62+
type TeamMemberUpdateRequest struct {
63+
UserID string `json:"-"`
64+
Role string `json:"role"`
65+
TeamID string `json:"-"`
66+
Projects []ProjectRole `json:"projects,omitempty"`
67+
AccessGroupsToAdd []string `json:"accessGroupsToAdd,omitempty"`
68+
AccessGroupsToRemove []string `json:"accessGroupsToRemove,omitempty"`
69+
}
70+
71+
func (c *Client) UpdateTeamMember(ctx context.Context, request TeamMemberUpdateRequest) error {
72+
url := fmt.Sprintf("%s/v1/teams/%s/members/%s", c.baseURL, request.TeamID, request.UserID)
73+
tflog.Info(ctx, "updating team member", map[string]interface{}{
74+
"url": url,
75+
"user": request.UserID,
76+
"role": request.Role,
77+
})
78+
err := c.doRequest(clientRequest{
79+
ctx: ctx,
80+
method: "PATCH",
81+
url: url,
82+
body: string(mustMarshal(request)),
83+
}, nil)
84+
return err
85+
}
86+
87+
type GetTeamMemberRequest struct {
88+
TeamID string
89+
UserID string
90+
}
91+
92+
type TeamMember struct {
93+
Confirmed bool `json:"confirmed"`
94+
Role string `json:"role"`
95+
UserID string `json:"uid"`
96+
Projects []ProjectRole `json:"projects"`
97+
AccessGroups []struct {
98+
ID string `json:"id"`
99+
Name string `json:"name"`
100+
} `json:"accessGroups"`
101+
}
102+
103+
func (c *Client) GetTeamMember(ctx context.Context, request GetTeamMemberRequest) (TeamMember, error) {
104+
url := fmt.Sprintf("%s/v2/teams/%s/members?limit=1&filterByUserIds=%s", c.baseURL, request.TeamID, request.UserID)
105+
tflog.Info(ctx, "getting team member", map[string]interface{}{
106+
"url": url,
107+
})
108+
109+
var response struct {
110+
Members []TeamMember `json:"members"`
111+
}
112+
err := c.doRequest(clientRequest{
113+
ctx: ctx,
114+
method: "GET",
115+
url: url,
116+
body: "",
117+
}, &response)
118+
if err != nil {
119+
return TeamMember{}, err
120+
}
121+
if len(response.Members) == 0 {
122+
return TeamMember{}, APIError{
123+
StatusCode: 404,
124+
Message: "Team member not found",
125+
Code: "not_found",
126+
}
127+
}
128+
129+
// Now look up the projects for the member, but only if we need to.
130+
if !response.Members[0].Confirmed {
131+
return response.Members[0], nil
132+
}
133+
url = fmt.Sprintf("%s/v1/teams/%s/members/%s/projects?limit=100", c.baseURL, request.TeamID, request.UserID)
134+
var response2 struct {
135+
Projects []ProjectRole `json:"projects"`
136+
}
137+
err = c.doRequest(clientRequest{
138+
ctx: ctx,
139+
method: "GET",
140+
url: url,
141+
body: "",
142+
}, &response2)
143+
if err != nil {
144+
return TeamMember{}, err
145+
}
146+
response.Members[0].Projects = response2.Projects
147+
return response.Members[0], err
148+
}

docs/data-sources/access_group.md

+6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ Provides information about an existing Access Group.
1313

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

16+
## Example Usage
1617

18+
```terraform
19+
data "vercel_access_group" "example" {
20+
id = "ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
21+
}
22+
```
1723

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

docs/data-sources/access_group_project.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@ Provides information about an existing Access Group Project Assignment.
1313

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

16-
16+
## Example Usage
17+
18+
```terraform
19+
data "vercel_project" "example" {
20+
name = "my-existing-project"
21+
}
22+
23+
data "vercel_access_group_project" "example" {
24+
access_group_id = "ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
25+
project_id = vercel_project.example.id
26+
}
27+
```
1728

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

docs/data-sources/project.md

+1-6
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,9 @@ For more detailed information, please see the [Vercel documentation](https://ver
1919
## Example Usage
2020

2121
```terraform
22-
data "vercel_project" "foo" {
22+
data "vercel_project" "example" {
2323
name = "my-existing-project"
2424
}
25-
26-
# Outputs prj_xxxxxx
27-
output "project_id" {
28-
value = data.vercel_project.foo.id
29-
}
3025
```
3126

3227
<!-- schema generated by tfplugindocs -->

docs/data-sources/team_member.md

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "vercel_team_member Data Source - terraform-provider-vercel"
4+
subcategory: ""
5+
description: |-
6+
Provider a datasource for managing a team member.
7+
---
8+
9+
# vercel_team_member (Data Source)
10+
11+
Provider a datasource for managing a team member.
12+
13+
## Example Usage
14+
15+
```terraform
16+
data "vercel_team_member" "example" {
17+
user_id = "uuuuuuuuuuuuuuuuuuuuuuuuuu"
18+
team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxx"
19+
}
20+
```
21+
22+
<!-- schema generated by tfplugindocs -->
23+
## Schema
24+
25+
### Required
26+
27+
- `team_id` (String) The ID of the existing Vercel Team.
28+
- `user_id` (String) The ID of the existing Vercel Team Member.
29+
30+
### Read-Only
31+
32+
- `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.
33+
- `id` (String) The ID of this resource.
34+
- `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))
35+
- `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.
36+
37+
<a id="nestedatt--projects"></a>
38+
### Nested Schema for `projects`
39+
40+
Required:
41+
42+
- `project_id` (String) The ID of the project that the user should be granted access to.
43+
- `role` (String) The role that the user should have in the project.

docs/resources/access_group.md

+19
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ Access Groups provide a way to manage groups of Vercel users across projects on
1616

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

19+
## Example Usage
1920

21+
```terraform
22+
resource "vercel_access_group" "example" {
23+
name = "example-access-group"
24+
}
25+
```
2026

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

3440
- `id` (String) The ID of the Access Group.
41+
42+
## Import
43+
44+
Import is supported using the following syntax:
45+
46+
```shell
47+
# If importing into a personal account, or with a team configured on
48+
# the provider, simply use the access_group_id.
49+
terraform import vercel_access_group.example ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
50+
51+
# If importing to a team, use the team_id and access_group_id.
52+
terraform import vercel_access_group.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
53+
```

docs/resources/access_group_project.md

+29
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,23 @@ An Access Group Project resource defines the relationship between a `vercel_acce
1616

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

19+
## Example Usage
1920

21+
```terraform
22+
resource "vercel_project" "example" {
23+
name = "example-project"
24+
}
25+
26+
resource "vercel_access_group" "example" {
27+
name = "example-access-group"
28+
}
29+
30+
resource "vercel_access_group_project" "example" {
31+
project_id = vercel_project.example.id
32+
access_group_id = vercel_access_group.example.id
33+
role = "ADMIN"
34+
}
35+
```
2036

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

3248
- `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.
49+
50+
## Import
51+
52+
Import is supported using the following syntax:
53+
54+
```shell
55+
# If importing into a personal account, or with a team configured on
56+
# the provider, use the access_group_id and project_id.
57+
terraform import vercel_access_group.example ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
58+
59+
# If importing to a team, use the team_id, access_group_id and project_id.
60+
terraform import vercel_access_group.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ag_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
61+
```

docs/resources/log_drain.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ Import is supported using the following syntax:
7878
# If importing into a personal account, or with a team configured on
7979
# the provider, simply use the log_drain_id.
8080
# - 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).
81-
terraform import vercel_log_drain.example ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
81+
terraform import vercel_log_drain.example ld_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
8282

8383
# Alternatively, you can import via the team_id and edge_config_id.
8484
# - team_id can be found in the team `settings` tab in the Vercel UI.
8585
# - 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).
86-
terraform import vercel_log_drain.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
86+
terraform import vercel_log_drain.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ld_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
8787
```

docs/resources/team_member.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "vercel_team_member Resource - terraform-provider-vercel"
4+
subcategory: ""
5+
description: |-
6+
Provider a resource for managing a team member.
7+
---
8+
9+
# vercel_team_member (Resource)
10+
11+
Provider a resource for managing a team member.
12+
13+
## Example Usage
14+
15+
```terraform
16+
resource "vercel_team_member" "example" {
17+
team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxx"
18+
user_id = "uuuuuuuuuuuuuuuuuuuuuuuuuu"
19+
role = "MEMBER"
20+
}
21+
```
22+
23+
<!-- schema generated by tfplugindocs -->
24+
## Schema
25+
26+
### Required
27+
28+
- `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.
29+
- `team_id` (String) The ID of the existing Vercel Team.
30+
- `user_id` (String) The ID of the user to add to the team.
31+
32+
### Optional
33+
34+
- `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.
35+
- `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))
36+
37+
<a id="nestedatt--projects"></a>
38+
### Nested Schema for `projects`
39+
40+
Required:
41+
42+
- `project_id` (String) The ID of the project that the user should be granted access to.
43+
- `role` (String) The role that the user should have in the project.
44+
45+
## Import
46+
47+
Import is supported using the following syntax:
48+
49+
```shell
50+
# To import, use the team_id and user_id.
51+
terraform import vercel_access_group.example team_xxxxxxxxxxxxxxxxxxxxxxxx/uuuuuuuuuuuuuuuuuuuuuuuuuu
52+
```

0 commit comments

Comments
 (0)