Skip to content

Commit ddc1818

Browse files
committed
Implement support for the Custom Attribute APIs. Closes #722
This includes the Custom Attributes for Users, Groups and Projects.
1 parent 79a17cb commit ddc1818

11 files changed

+650
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# gitlab\_group\_custom\_attribute
2+
3+
This resource allows you to set custom attributes for a group.
4+
5+
## Example Usage
6+
7+
```hcl
8+
resource "gitlab_group_custom_attribute" "attr" {
9+
group = "42"
10+
key = "location"
11+
value = "Greenland"
12+
}
13+
```
14+
15+
## Argument Reference
16+
17+
The following arguments are supported:
18+
19+
* `group` - (Required) The id of the group.
20+
21+
* `key` - (Required) Key for the Custom Attribute.
22+
23+
* `value` - (Required) Value for the Custom Attribute.
24+
25+
## Import
26+
27+
You can import a group custom attribute using the following id pattern:
28+
29+
```shell
30+
$ terraform import gitlab_group_custom_attribute.attr <group-id>:<key>
31+
```
32+
33+
For the example above this would be:
34+
35+
```shell
36+
$ terraform import gitlab_group_custom_attribute.attr 42:location
37+
```
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# gitlab\_project\_custom\_attribute
2+
3+
This resource allows you to set custom attributes for a project.
4+
5+
## Example Usage
6+
7+
```hcl
8+
resource "gitlab_project_custom_attribute" "attr" {
9+
project = "42"
10+
key = "location"
11+
value = "Greenland"
12+
}
13+
```
14+
15+
## Argument Reference
16+
17+
The following arguments are supported:
18+
19+
* `project` - (Required) The id of the project.
20+
21+
* `key` - (Required) Key for the Custom Attribute.
22+
23+
* `value` - (Required) Value for the Custom Attribute.
24+
25+
## Import
26+
27+
You can import a project custom attribute using the following id pattern:
28+
29+
```shell
30+
$ terraform import gitlab_project_custom_attribute.attr <project-id>:<key>
31+
```
32+
33+
For the example above this would be:
34+
35+
```shell
36+
$ terraform import gitlab_project_custom_attribute.attr 42:location
37+
```
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# gitlab\_user\_custom\_attribute
2+
3+
This resource allows you to set custom attributes for a user.
4+
5+
## Example Usage
6+
7+
```hcl
8+
resource "gitlab_user_custom_attribute" "attr" {
9+
user = "42"
10+
key = "location"
11+
value = "Greenland"
12+
}
13+
```
14+
15+
## Argument Reference
16+
17+
The following arguments are supported:
18+
19+
* `user` - (Required) The id of the user.
20+
21+
* `key` - (Required) Key for the Custom Attribute.
22+
23+
* `value` - (Required) Value for the Custom Attribute.
24+
25+
## Import
26+
27+
You can import a user custom attribute using the following id pattern:
28+
29+
```shell
30+
$ terraform import gitlab_user_custom_attribute.attr <user-id>:<key>
31+
```
32+
33+
For the example above this would be:
34+
35+
```shell
36+
$ terraform import gitlab_user_custom_attribute.attr 42:location
37+
```

gitlab/custom_attribute_helper.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package gitlab
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
gitlab "github.com/xanzy/go-gitlab"
11+
)
12+
13+
type CustomAttributeGetter func(int, string, ...gitlab.RequestOptionFunc) (*gitlab.CustomAttribute, *gitlab.Response, error)
14+
type CustomAttributeSetter func(int, gitlab.CustomAttribute, ...gitlab.RequestOptionFunc) (*gitlab.CustomAttribute, *gitlab.Response, error)
15+
type CustomAttributeDeleter func(int, string, ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
16+
17+
type CreateGetter func(*gitlab.Client) CustomAttributeGetter
18+
type CreateSetter func(*gitlab.Client) CustomAttributeSetter
19+
type CreateDeleter func(*gitlab.Client) CustomAttributeDeleter
20+
21+
func CreateCustomAttributeResource(idName string, createGetter CreateGetter, createSetter CreateSetter, createDeleter CreateDeleter) *schema.Resource {
22+
setToState := func(d *schema.ResourceData, userId int, customAttribute *gitlab.CustomAttribute) {
23+
// lintignore:R001
24+
d.Set(idName, userId)
25+
d.Set("key", customAttribute.Key)
26+
d.Set("value", customAttribute.Value)
27+
}
28+
29+
readFunc := func(d *schema.ResourceData, meta interface{}) error {
30+
client := meta.(*gitlab.Client)
31+
getter := createGetter(client)
32+
log.Printf("[DEBUG] read Custom Attribute %s", d.Id())
33+
34+
id, key, err := parseId(d.Id())
35+
if err != nil {
36+
return err
37+
}
38+
39+
customAttribute, _, err := getter(id, key)
40+
if err != nil {
41+
return err
42+
}
43+
44+
setToState(d, id, customAttribute)
45+
return nil
46+
}
47+
48+
setFunc := func(d *schema.ResourceData, meta interface{}) error {
49+
client := meta.(*gitlab.Client)
50+
setter := createSetter(client)
51+
52+
id := d.Get(idName).(int)
53+
options := &gitlab.CustomAttribute{
54+
Key: d.Get("key").(string),
55+
Value: d.Get("value").(string),
56+
}
57+
58+
log.Printf("[DEBUG] set (create or update) Custom Attribute %s with value %s for %s %d", options.Key, options.Value, idName, id)
59+
60+
customAttribute, _, err := setter(id, *options)
61+
if err != nil {
62+
return err
63+
}
64+
65+
d.SetId(buildId(id, customAttribute.Key))
66+
return readFunc(d, meta)
67+
}
68+
69+
deleteFunc := func(d *schema.ResourceData, meta interface{}) error {
70+
client := meta.(*gitlab.Client)
71+
deleter := createDeleter(client)
72+
log.Printf("[DEBUG] delete Custom Attribute %s", d.Id())
73+
74+
id, key, err := parseId(d.Id())
75+
if err != nil {
76+
return err
77+
}
78+
79+
_, err = deleter(id, key)
80+
if err != nil {
81+
return err
82+
}
83+
84+
return nil
85+
}
86+
87+
return &schema.Resource{
88+
Create: setFunc,
89+
Read: readFunc,
90+
Update: setFunc,
91+
Delete: deleteFunc,
92+
Importer: &schema.ResourceImporter{
93+
StateContext: schema.ImportStatePassthroughContext,
94+
},
95+
96+
Schema: map[string]*schema.Schema{
97+
idName: {
98+
Type: schema.TypeInt,
99+
Required: true,
100+
},
101+
"key": {
102+
Type: schema.TypeString,
103+
Required: true,
104+
},
105+
"value": {
106+
Type: schema.TypeString,
107+
Required: true,
108+
},
109+
},
110+
}
111+
}
112+
113+
func parseId(id string) (int, string, error) {
114+
parts := strings.SplitN(id, ":", 2)
115+
if len(parts) != 2 {
116+
return -1, "", fmt.Errorf("unexpected ID format (%q). Expected id:key", id)
117+
}
118+
119+
subjectId, err := strconv.Atoi(parts[0])
120+
if err != nil {
121+
return -1, "", fmt.Errorf("unexpected ID format (%q). Expected id:key whereas `id` must be an integer", id)
122+
}
123+
124+
return subjectId, parts[1], nil
125+
}
126+
127+
func buildId(id int, key string) string {
128+
return fmt.Sprintf("%d:%s", id, key)
129+
}

gitlab/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ func Provider() *schema.Provider {
6666
"gitlab_branch_protection": resourceGitlabBranchProtection(),
6767
"gitlab_tag_protection": resourceGitlabTagProtection(),
6868
"gitlab_group": resourceGitlabGroup(),
69+
"gitlab_group_custom_attribute": resourceGitlabGroupCustomAttribute(),
6970
"gitlab_project": resourceGitlabProject(),
71+
"gitlab_project_custom_attribute": resourceGitlabProjectCustomAttribute(),
7072
"gitlab_label": resourceGitlabLabel(),
7173
"gitlab_group_label": resourceGitlabGroupLabel(),
7274
"gitlab_pipeline_schedule": resourceGitlabPipelineSchedule(),
@@ -77,6 +79,7 @@ func Provider() *schema.Provider {
7779
"gitlab_deploy_key_enable": resourceGitlabDeployEnableKey(),
7880
"gitlab_deploy_token": resourceGitlabDeployToken(),
7981
"gitlab_user": resourceGitlabUser(),
82+
"gitlab_user_custom_attribute": resourceGitlabUserCustomAttribute(),
8083
"gitlab_project_membership": resourceGitlabProjectMembership(),
8184
"gitlab_group_membership": resourceGitlabGroupMembership(),
8285
"gitlab_project_variable": resourceGitlabProjectVariable(),
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package gitlab
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
5+
gitlab "github.com/xanzy/go-gitlab"
6+
)
7+
8+
func resourceGitlabGroupCustomAttribute() *schema.Resource {
9+
return CreateCustomAttributeResource(
10+
"group",
11+
func(client *gitlab.Client) CustomAttributeGetter {
12+
return client.CustomAttribute.GetCustomGroupAttribute
13+
},
14+
func(client *gitlab.Client) CustomAttributeSetter {
15+
return client.CustomAttribute.SetCustomGroupAttribute
16+
},
17+
func(client *gitlab.Client) CustomAttributeDeleter {
18+
return client.CustomAttribute.DeleteCustomGroupAttribute
19+
},
20+
)
21+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package gitlab
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
10+
"github.com/xanzy/go-gitlab"
11+
)
12+
13+
func TestAccGitlabGroupCustomAttribute_basic(t *testing.T) {
14+
var group gitlab.Group
15+
var customAttribute gitlab.CustomAttribute
16+
rInt := acctest.RandInt()
17+
18+
resource.Test(t, resource.TestCase{
19+
PreCheck: func() { testAccPreCheck(t) },
20+
Providers: testAccProviders,
21+
CheckDestroy: testAccCheckGitlabGroupDestroy,
22+
Steps: []resource.TestStep{
23+
{
24+
Config: fmt.Sprintf(`
25+
resource "gitlab_group" "group" {
26+
name = "foo-name-%d"
27+
path = "foo-path-%d"
28+
}
29+
30+
resource "gitlab_group_custom_attribute" "attr" {
31+
group = gitlab_group.group.id
32+
key = "foo"
33+
value = "bar"
34+
}`, rInt, rInt),
35+
Check: resource.ComposeTestCheckFunc(
36+
testAccCheckGitlabGroupExists("gitlab_group.group", &group),
37+
testAccCheckGitlabGroupCustomAttributeExists("gitlab_group_custom_attribute.attr", &customAttribute),
38+
testAccCheckGitlabGroupCustomAttributes(&customAttribute, &testAccGitlabGroupExpectedCustomAttributes{
39+
Key: "foo",
40+
Value: "bar",
41+
}),
42+
),
43+
},
44+
// Update the custom attribute
45+
{
46+
Config: fmt.Sprintf(`
47+
resource "gitlab_group" "group" {
48+
name = "foo-name-%d"
49+
path = "foo-path-%d"
50+
}
51+
52+
resource "gitlab_group_custom_attribute" "attr" {
53+
group = gitlab_group.group.id
54+
key = "foo"
55+
value = "updated"
56+
}`, rInt, rInt),
57+
Check: resource.ComposeTestCheckFunc(
58+
testAccCheckGitlabGroupExists("gitlab_group.group", &group),
59+
testAccCheckGitlabGroupCustomAttributeExists("gitlab_group_custom_attribute.attr", &customAttribute),
60+
testAccCheckGitlabGroupCustomAttributes(&customAttribute, &testAccGitlabGroupExpectedCustomAttributes{
61+
Key: "foo",
62+
Value: "updated",
63+
}),
64+
),
65+
},
66+
{
67+
ResourceName: "gitlab_group_custom_attribute.attr",
68+
ImportState: true,
69+
ImportStateVerify: true,
70+
},
71+
},
72+
})
73+
}
74+
75+
func testAccCheckGitlabGroupCustomAttributeExists(n string, customAttribute *gitlab.CustomAttribute) resource.TestCheckFunc {
76+
return func(s *terraform.State) error {
77+
rs, ok := s.RootModule().Resources[n]
78+
if !ok {
79+
return fmt.Errorf("Not Found: %s", n)
80+
}
81+
82+
id, key, err := parseId(rs.Primary.ID)
83+
if err != nil {
84+
return err
85+
}
86+
87+
client := testAccProvider.Meta().(*gitlab.Client)
88+
gotCustomAttribute, _, err := client.CustomAttribute.GetCustomGroupAttribute(id, key)
89+
if err != nil {
90+
return err
91+
}
92+
*customAttribute = *gotCustomAttribute
93+
return nil
94+
}
95+
}
96+
97+
type testAccGitlabGroupExpectedCustomAttributes struct {
98+
Key string
99+
Value string
100+
}
101+
102+
func testAccCheckGitlabGroupCustomAttributes(got *gitlab.CustomAttribute, want *testAccGitlabGroupExpectedCustomAttributes) resource.TestCheckFunc {
103+
return func(s *terraform.State) error {
104+
if got.Key != want.Key {
105+
return fmt.Errorf("got key %q; want %q", got.Key, want.Key)
106+
}
107+
108+
if got.Value != want.Value {
109+
return fmt.Errorf("got value %q; want %q", got.Value, want.Value)
110+
}
111+
112+
return nil
113+
}
114+
}

0 commit comments

Comments
 (0)