Skip to content

Commit c8726af

Browse files
committed
resource/gitlab_group_access_token: support resource for Group Access Tokens. Closes #835
Closes #835
1 parent 4feb656 commit c8726af

File tree

5 files changed

+568
-0
lines changed

5 files changed

+568
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "gitlab_group_access_token Resource - terraform-provider-gitlab"
4+
subcategory: ""
5+
description: |-
6+
This resource allows you to create and manage Group Access Token for your GitLab Groups. (Introduced in GitLab 14.7)
7+
---
8+
9+
# gitlab_group_access_token (Resource)
10+
11+
This resource allows you to create and manage Group Access Token for your GitLab Groups. (Introduced in GitLab 14.7)
12+
13+
## Example Usage
14+
15+
```terraform
16+
resource "gitlab_group_access_token" "example" {
17+
group = "25"
18+
name = "Example project access token"
19+
expires_at = "2020-03-14"
20+
access_level = "developer"
21+
22+
scopes = ["api"]
23+
}
24+
25+
resource "gitlab_group_variable" "example" {
26+
group = "25"
27+
key = "gat"
28+
value = gitlab_group_access_token.example.token
29+
}
30+
```
31+
32+
<!-- schema generated by tfplugindocs -->
33+
## Schema
34+
35+
### Required
36+
37+
- **group** (String) The ID or path of the group to add the group access token to.
38+
- **name** (String) The name of the group access token.
39+
- **scopes** (Set of String) The scope for the group access token. It determines the actions which can be performed when authenticating with this token. Valid values are: `api`, `read_api`, `read_registry`, `write_registry`, `read_repository`, `write_repository`.
40+
41+
### Optional
42+
43+
- **access_level** (String) The access level for the group access token. Valid values are: `guest`, `reporter`, `developer`, `maintainer`.
44+
- **expires_at** (String) The token expires at midnight UTC on that date. The date must be in the format YYYY-MM-DD. Default is never.
45+
- **id** (String) The ID of this resource.
46+
47+
### Read-Only
48+
49+
- **active** (Boolean) True if the token is active.
50+
- **created_at** (String) Time the token has been created, RFC3339 format.
51+
- **revoked** (Boolean) True if the token is revoked.
52+
- **token** (String, Sensitive) The group access token. This is only populated when creating a new group access token. This attribute is not available for imported resources.
53+
- **user_id** (Number) The user id associated to the token.
54+
55+
## Import
56+
57+
Import is supported using the following syntax:
58+
59+
```shell
60+
# A GitLab Group Access Token can be imported using a key composed of `<group-id>:<token-id>`, e.g.
61+
terraform import gitlab_group_access_token.example "12345:1"
62+
63+
# ATTENTION: the `token` resource attribute is not available for imported resources as this information cannot be read from the GitLab API.
64+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# A GitLab Group Access Token can be imported using a key composed of `<group-id>:<token-id>`, e.g.
2+
terraform import gitlab_group_access_token.example "12345:1"
3+
4+
# ATTENTION: the `token` resource attribute is not available for imported resources as this information cannot be read from the GitLab API.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
resource "gitlab_group_access_token" "example" {
2+
group = "25"
3+
name = "Example project access token"
4+
expires_at = "2020-03-14"
5+
access_level = "developer"
6+
7+
scopes = ["api"]
8+
}
9+
10+
resource "gitlab_group_variable" "example" {
11+
group = "25"
12+
key = "gat"
13+
value = gitlab_group_access_token.example.token
14+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strconv"
8+
"time"
9+
10+
"github.com/hashicorp/go-cty/cty"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
14+
gitlab "github.com/xanzy/go-gitlab"
15+
)
16+
17+
var validGroupAccessTokenScopes = []string{
18+
"api",
19+
"read_api",
20+
"read_registry",
21+
"write_registry",
22+
"read_repository",
23+
"write_repository",
24+
}
25+
var validAccessLevels = []string{
26+
"guest",
27+
"reporter",
28+
"developer",
29+
"maintainer",
30+
}
31+
32+
var _ = registerResource("gitlab_group_access_token", func() *schema.Resource {
33+
return &schema.Resource{
34+
Description: "This resource allows you to create and manage Group Access Token for your GitLab Groups. (Introduced in GitLab 14.7)",
35+
36+
CreateContext: resourceGitlabGroupAccessTokenCreate,
37+
ReadContext: resourceGitlabGroupAccessTokenRead,
38+
DeleteContext: resourceGitlabGroupAccessTokenDelete,
39+
Importer: &schema.ResourceImporter{
40+
StateContext: schema.ImportStatePassthroughContext,
41+
},
42+
43+
Schema: map[string]*schema.Schema{
44+
"group": {
45+
Description: "The ID or path of the group to add the group access token to.",
46+
Type: schema.TypeString,
47+
Required: true,
48+
ForceNew: true,
49+
},
50+
"name": {
51+
Description: "The name of the group access token.",
52+
Type: schema.TypeString,
53+
Required: true,
54+
ForceNew: true,
55+
},
56+
"scopes": {
57+
Description: fmt.Sprintf("The scope for the group access token. It determines the actions which can be performed when authenticating with this token. Valid values are: %s.", renderValueListForDocs(validGroupAccessTokenScopes)),
58+
Type: schema.TypeSet,
59+
Required: true,
60+
ForceNew: true,
61+
Elem: &schema.Schema{
62+
Type: schema.TypeString,
63+
ValidateFunc: validation.StringInSlice(validGroupAccessTokenScopes, false),
64+
},
65+
},
66+
"access_level": {
67+
Description: fmt.Sprintf("The access level for the group access token. Valid values are: %s.", renderValueListForDocs(validAccessLevels)),
68+
Type: schema.TypeString,
69+
Optional: true,
70+
ForceNew: true,
71+
Default: accessLevelValueToName[gitlab.MaintainerPermissions],
72+
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(validAccessLevels, false)),
73+
},
74+
"expires_at": {
75+
Description: "The token expires at midnight UTC on that date. The date must be in the format YYYY-MM-DD. Default is never.",
76+
Type: schema.TypeString,
77+
Optional: true,
78+
ForceNew: true,
79+
ValidateDiagFunc: func(i interface{}, p cty.Path) diag.Diagnostics {
80+
v := i.(string)
81+
82+
if _, err := time.Parse("2006-01-02", v); err != nil {
83+
return diag.Errorf("expected %q to be a valid YYYY-MM-DD date, got %q: %+v", p, i, err)
84+
}
85+
86+
return nil
87+
},
88+
},
89+
"token": {
90+
Description: "The group access token. This is only populated when creating a new group access token. This attribute is not available for imported resources.",
91+
Type: schema.TypeString,
92+
Computed: true,
93+
Sensitive: true,
94+
},
95+
"active": {
96+
Description: "True if the token is active.",
97+
Type: schema.TypeBool,
98+
Computed: true,
99+
},
100+
"created_at": {
101+
Description: "Time the token has been created, RFC3339 format.",
102+
Type: schema.TypeString,
103+
Computed: true,
104+
},
105+
"revoked": {
106+
Description: "True if the token is revoked.",
107+
Type: schema.TypeBool,
108+
Computed: true,
109+
},
110+
"user_id": {
111+
Description: "The user id associated to the token.",
112+
Type: schema.TypeInt,
113+
Computed: true,
114+
},
115+
},
116+
}
117+
})
118+
119+
func resourceGitlabGroupAccessTokenCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
120+
client := meta.(*gitlab.Client)
121+
122+
group := d.Get("group").(string)
123+
options := &gitlab.CreateGroupAccessTokenOptions{
124+
Name: gitlab.String(d.Get("name").(string)),
125+
Scopes: stringSetToStringSlice(d.Get("scopes").(*schema.Set)),
126+
}
127+
if v, ok := d.GetOk("access_level"); ok {
128+
accessLevel := accessLevelNameToValue[v.(string)]
129+
options.AccessLevel = &accessLevel
130+
}
131+
132+
log.Printf("[DEBUG] create gitlab GroupAccessToken %s (scopes: %s, access_level: %v) for group ID %s", *options.Name, options.Scopes, options.AccessLevel, group)
133+
134+
if v, ok := d.GetOk("expires_at"); ok {
135+
parsedExpiresAt, err := time.Parse("2006-01-02", v.(string))
136+
if err != nil {
137+
return diag.Errorf("Invalid expires_at date: %v", err)
138+
}
139+
parsedExpiresAtISOTime := gitlab.ISOTime(parsedExpiresAt)
140+
options.ExpiresAt = &parsedExpiresAtISOTime
141+
log.Printf("[DEBUG] create gitlab GroupAccessToken %s with expires_at %s for group ID %s", *options.Name, *options.ExpiresAt, group)
142+
}
143+
144+
groupAccessToken, _, err := client.GroupAccessTokens.CreateGroupAccessToken(group, options, gitlab.WithContext(ctx))
145+
if err != nil {
146+
return diag.FromErr(err)
147+
}
148+
149+
log.Printf("[DEBUG] created gitlab GroupAccessToken %d - %s for group ID %s", groupAccessToken.ID, *options.Name, group)
150+
151+
tokenId := strconv.Itoa(groupAccessToken.ID)
152+
d.SetId(buildTwoPartID(&group, &tokenId))
153+
// NOTE: the token can only be read once after creating it
154+
d.Set("token", groupAccessToken.Token)
155+
156+
return resourceGitlabGroupAccessTokenRead(ctx, d, meta)
157+
}
158+
159+
func resourceGitlabGroupAccessTokenRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
160+
group, tokenId, err := parseTwoPartID(d.Id())
161+
if err != nil {
162+
return diag.Errorf("Error parsing ID: %s", d.Id())
163+
}
164+
165+
client := meta.(*gitlab.Client)
166+
167+
groupAccessTokenId, err := strconv.Atoi(tokenId)
168+
if err != nil {
169+
return diag.Errorf("%s cannot be converted to int", tokenId)
170+
}
171+
172+
log.Printf("[DEBUG] read gitlab GroupAccessToken %d, group ID %s", groupAccessTokenId, group)
173+
174+
//there is a slight possibility to not find an existing item, for example
175+
// 1. item is #101 (ie, in the 2nd page)
176+
// 2. I load first page (ie. I don't find my target item)
177+
// 3. A concurrent operation remove item 99 (ie, my target item shift to 1st page)
178+
// 4. a concurrent operation add an item
179+
// 5: I load 2nd page (ie. I don't find my target item)
180+
// 6. Total pages and total items properties are unchanged (from the perspective of the reader)
181+
182+
page := 1
183+
for page != 0 {
184+
groupAccessTokens, response, err := client.GroupAccessTokens.ListGroupAccessTokens(group, &gitlab.ListGroupAccessTokensOptions{Page: page, PerPage: 100}, gitlab.WithContext(ctx))
185+
if err != nil {
186+
return diag.FromErr(err)
187+
}
188+
189+
for _, groupAccessToken := range groupAccessTokens {
190+
if groupAccessToken.ID == groupAccessTokenId {
191+
192+
d.Set("group", group)
193+
d.Set("name", groupAccessToken.Name)
194+
if groupAccessToken.ExpiresAt != nil {
195+
d.Set("expires_at", groupAccessToken.ExpiresAt.String())
196+
}
197+
d.Set("active", groupAccessToken.Active)
198+
d.Set("created_at", groupAccessToken.CreatedAt.Format(time.RFC3339))
199+
d.Set("access_level", accessLevelValueToName[groupAccessToken.AccessLevel])
200+
d.Set("revoked", groupAccessToken.Revoked)
201+
d.Set("user_id", groupAccessToken.UserID)
202+
203+
err = d.Set("scopes", groupAccessToken.Scopes)
204+
if err != nil {
205+
return diag.FromErr(err)
206+
}
207+
208+
return nil
209+
}
210+
}
211+
212+
page = response.NextPage
213+
}
214+
215+
log.Printf("[DEBUG] failed to read gitlab GroupAccessToken %d, group ID %s", groupAccessTokenId, group)
216+
d.SetId("")
217+
return nil
218+
}
219+
220+
func resourceGitlabGroupAccessTokenDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
221+
222+
group, tokenId, err := parseTwoPartID(d.Id())
223+
if err != nil {
224+
return diag.Errorf("Error parsing ID: %s", d.Id())
225+
}
226+
227+
client := meta.(*gitlab.Client)
228+
229+
groupAccessTokenId, err := strconv.Atoi(tokenId)
230+
if err != nil {
231+
return diag.Errorf("%s cannot be converted to int", tokenId)
232+
}
233+
234+
log.Printf("[DEBUG] Delete gitlab GroupAccessToken %s", d.Id())
235+
_, err = client.GroupAccessTokens.DeleteGroupAccessToken(group, groupAccessTokenId, gitlab.WithContext(ctx))
236+
return diag.FromErr(err)
237+
}

0 commit comments

Comments
 (0)