Skip to content

Commit a9edc4a

Browse files
authored
Merge pull request #717 from njdart/group-env-scope
Add environment scope to gitlab_group_variable
2 parents e29dfdd + 12feb4b commit a9edc4a

File tree

3 files changed

+196
-37
lines changed

3 files changed

+196
-37
lines changed

docs/resources/group_variable.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ documentation](https://docs.gitlab.com/ce/ci/variables/README.html#variables).
88

99
```hcl
1010
resource "gitlab_group_variable" "example" {
11-
group = "12345"
12-
key = "group_variable_key"
13-
value = "group_variable_value"
14-
protected = false
15-
masked = false
11+
group = "12345"
12+
key = "group_variable_key"
13+
value = "group_variable_value"
14+
protected = false
15+
masked = false
16+
environment_scope = "*"
1617
}
1718
```
1819

@@ -32,10 +33,12 @@ The following arguments are supported:
3233

3334
* `masked` - (Optional, boolean) If set to `true`, the value of the variable will be hidden in job logs. The value must meet the [masking requirements](https://docs.gitlab.com/ee/ci/variables/#masked-variables). Defaults to `false`.
3435

36+
* `environment_scope` - (Optional, string) The environment scope of the variable. Defaults to all environment (`*`). Note that in Community Editions of Gitlab, values other than `*` will cause inconsistent plans. See https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-group
37+
3538
## Import
3639

37-
GitLab group variables can be imported using an id made up of `groupid:variablename`, e.g.
40+
GitLab group variables can be imported using an id made up of `groupid:variablename:scope`, e.g.
3841

3942
```
40-
$ terraform import gitlab_group_variable.example 12345:group_variable_key
43+
$ terraform import gitlab_group_variable.example 12345:group_variable_key:*
4144
```

gitlab/resource_gitlab_group_variable.go

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,31 @@ package gitlab
22

33
import (
44
"context"
5+
"fmt"
56
"log"
7+
"strings"
68

9+
"github.com/hashicorp/go-retryablehttp"
710
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
811
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
912
gitlab "github.com/xanzy/go-gitlab"
1013
)
1114

15+
// modifyRequestAddEnvironmentFilter returns a RequestOptionFunc function that
16+
// can be passed to the go-gitlab library calls to add the environment scope to
17+
// requests to lookup, modification, and deletion requests. Since gitlab 13.11,
18+
// an environment variable key is no longer unique and is composit-keyed with
19+
// the scope.
20+
// See https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-group
21+
func modifyRequestAddEnvironmentFilter(scope string) gitlab.RequestOptionFunc {
22+
return func(r *retryablehttp.Request) error {
23+
queryParams := r.URL.Query()
24+
queryParams.Add("filter[environment_scope]", scope)
25+
r.URL.RawQuery = queryParams.Encode()
26+
return nil
27+
}
28+
}
29+
1230
func resourceGitlabGroupVariable() *schema.Resource {
1331
return &schema.Resource{
1432
CreateContext: resourceGitlabGroupVariableCreate,
@@ -52,6 +70,12 @@ func resourceGitlabGroupVariable() *schema.Resource {
5270
Optional: true,
5371
Default: false,
5472
},
73+
"environment_scope": {
74+
Type: schema.TypeString,
75+
Optional: true,
76+
ForceNew: true,
77+
Default: "*",
78+
},
5579
},
5680
}
5781
}
@@ -65,13 +89,15 @@ func resourceGitlabGroupVariableCreate(ctx context.Context, d *schema.ResourceDa
6589
variableType := stringToVariableType(d.Get("variable_type").(string))
6690
protected := d.Get("protected").(bool)
6791
masked := d.Get("masked").(bool)
92+
environmentScope := d.Get("environment_scope").(string)
6893

6994
options := gitlab.CreateGroupVariableOptions{
70-
Key: &key,
71-
Value: &value,
72-
VariableType: variableType,
73-
Protected: &protected,
74-
Masked: &masked,
95+
Key: &key,
96+
Value: &value,
97+
VariableType: variableType,
98+
Protected: &protected,
99+
Masked: &masked,
100+
EnvironmentScope: &environmentScope,
75101
}
76102
log.Printf("[DEBUG] create gitlab group variable %s/%s", group, key)
77103

@@ -80,7 +106,9 @@ func resourceGitlabGroupVariableCreate(ctx context.Context, d *schema.ResourceDa
80106
return diag.FromErr(err)
81107
}
82108

83-
d.SetId(buildTwoPartID(&group, &key))
109+
keyScope := fmt.Sprintf("%s:%s", key, environmentScope)
110+
111+
d.SetId(buildTwoPartID(&group, &keyScope))
84112

85113
return resourceGitlabGroupVariableRead(ctx, d, meta)
86114
}
@@ -93,9 +121,21 @@ func resourceGitlabGroupVariableRead(ctx context.Context, d *schema.ResourceData
93121
return diag.FromErr(err)
94122
}
95123

96-
log.Printf("[DEBUG] read gitlab group variable %s/%s", group, key)
124+
keyScope := strings.SplitN(key, ":", 2)
125+
scope := "*"
126+
if len(keyScope) == 2 {
127+
key = keyScope[0]
128+
scope = keyScope[1]
129+
}
130+
131+
log.Printf("[DEBUG] read gitlab group variable %s/%s/%s", group, key, scope)
97132

98-
v, _, err := client.GroupVariables.GetVariable(group, key, gitlab.WithContext(ctx))
133+
v, _, err := client.GroupVariables.GetVariable(
134+
group,
135+
key,
136+
gitlab.WithContext(ctx),
137+
modifyRequestAddEnvironmentFilter(scope),
138+
)
99139
if err != nil {
100140
if is404(err) {
101141
log.Printf("[DEBUG] gitlab group variable not found %s/%s", group, key)
@@ -111,6 +151,7 @@ func resourceGitlabGroupVariableRead(ctx context.Context, d *schema.ResourceData
111151
d.Set("group", group)
112152
d.Set("protected", v.Protected)
113153
d.Set("masked", v.Masked)
154+
d.Set("environment_scope", v.EnvironmentScope)
114155
return nil
115156
}
116157

@@ -123,16 +164,24 @@ func resourceGitlabGroupVariableUpdate(ctx context.Context, d *schema.ResourceDa
123164
variableType := stringToVariableType(d.Get("variable_type").(string))
124165
protected := d.Get("protected").(bool)
125166
masked := d.Get("masked").(bool)
167+
environmentScope := d.Get("environment_scope").(string)
126168

127169
options := &gitlab.UpdateGroupVariableOptions{
128-
Value: &value,
129-
Protected: &protected,
130-
VariableType: variableType,
131-
Masked: &masked,
170+
Value: &value,
171+
Protected: &protected,
172+
VariableType: variableType,
173+
Masked: &masked,
174+
EnvironmentScope: &environmentScope,
132175
}
133-
log.Printf("[DEBUG] update gitlab group variable %s/%s", group, key)
134-
135-
_, _, err := client.GroupVariables.UpdateVariable(group, key, options, gitlab.WithContext(ctx))
176+
log.Printf("[DEBUG] update gitlab group variable %s/%s/%s", group, key, environmentScope)
177+
178+
_, _, err := client.GroupVariables.UpdateVariable(
179+
group,
180+
key,
181+
options,
182+
gitlab.WithContext(ctx),
183+
modifyRequestAddEnvironmentFilter(environmentScope),
184+
)
136185
if err != nil {
137186
return diag.FromErr(err)
138187
}
@@ -143,9 +192,15 @@ func resourceGitlabGroupVariableDelete(ctx context.Context, d *schema.ResourceDa
143192
client := meta.(*gitlab.Client)
144193
group := d.Get("group").(string)
145194
key := d.Get("key").(string)
146-
log.Printf("[DEBUG] Delete gitlab group variable %s/%s", group, key)
147-
148-
_, err := client.GroupVariables.RemoveVariable(group, key, gitlab.WithContext(ctx))
195+
environmentScope := d.Get("environment_scope").(string)
196+
log.Printf("[DEBUG] Delete gitlab group variable %s/%s/%s", group, key, environmentScope)
197+
198+
_, err := client.GroupVariables.RemoveVariable(
199+
group,
200+
key,
201+
gitlab.WithContext(ctx),
202+
modifyRequestAddEnvironmentFilter(environmentScope),
203+
)
149204
if err != nil {
150205
return diag.FromErr(err)
151206
}

gitlab/resource_gitlab_group_variable_test.go

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ func TestAccGitlabGroupVariable_basic(t *testing.T) {
2525
Check: resource.ComposeTestCheckFunc(
2626
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.foo", &groupVariable),
2727
testAccCheckGitlabGroupVariableAttributes(&groupVariable, &testAccGitlabGroupVariableExpectedAttributes{
28-
Key: fmt.Sprintf("key_%s", rString),
29-
Value: fmt.Sprintf("value-%s", rString),
28+
Key: fmt.Sprintf("key_%s", rString),
29+
Value: fmt.Sprintf("value-%s", rString),
30+
EnvironmentScope: "*",
3031
}),
3132
),
3233
},
@@ -36,9 +37,10 @@ func TestAccGitlabGroupVariable_basic(t *testing.T) {
3637
Check: resource.ComposeTestCheckFunc(
3738
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.foo", &groupVariable),
3839
testAccCheckGitlabGroupVariableAttributes(&groupVariable, &testAccGitlabGroupVariableExpectedAttributes{
39-
Key: fmt.Sprintf("key_%s", rString),
40-
Value: fmt.Sprintf("value-inverse-%s", rString),
41-
Protected: true,
40+
Key: fmt.Sprintf("key_%s", rString),
41+
Value: fmt.Sprintf("value-inverse-%s", rString),
42+
Protected: true,
43+
EnvironmentScope: "*",
4244
}),
4345
),
4446
},
@@ -48,9 +50,80 @@ func TestAccGitlabGroupVariable_basic(t *testing.T) {
4850
Check: resource.ComposeTestCheckFunc(
4951
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.foo", &groupVariable),
5052
testAccCheckGitlabGroupVariableAttributes(&groupVariable, &testAccGitlabGroupVariableExpectedAttributes{
51-
Key: fmt.Sprintf("key_%s", rString),
52-
Value: fmt.Sprintf("value-%s", rString),
53-
Protected: false,
53+
Key: fmt.Sprintf("key_%s", rString),
54+
Value: fmt.Sprintf("value-%s", rString),
55+
Protected: false,
56+
EnvironmentScope: "*",
57+
}),
58+
),
59+
},
60+
},
61+
})
62+
}
63+
64+
func TestAccGitlabGroupVariable_scope(t *testing.T) {
65+
var groupVariableA, groupVariableB gitlab.GroupVariable
66+
rString := acctest.RandString(5)
67+
68+
resource.Test(t, resource.TestCase{
69+
PreCheck: func() { testAccPreCheck(t) },
70+
Providers: testAccProviders,
71+
CheckDestroy: testAccCheckGitlabGroupVariableDestroy,
72+
Steps: []resource.TestStep{
73+
// Create a group and variables with same keys, different scopes
74+
{
75+
Config: testAccGitlabGroupVariableScopeConfig(rString, "*", "review/*"),
76+
SkipFunc: isRunningInCE,
77+
Check: resource.ComposeTestCheckFunc(
78+
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.a", &groupVariableA),
79+
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.b", &groupVariableB),
80+
testAccCheckGitlabGroupVariableAttributes(&groupVariableA, &testAccGitlabGroupVariableExpectedAttributes{
81+
Key: fmt.Sprintf("key_%s", rString),
82+
Value: fmt.Sprintf("value-%s-a", rString),
83+
EnvironmentScope: "*",
84+
}),
85+
testAccCheckGitlabGroupVariableAttributes(&groupVariableB, &testAccGitlabGroupVariableExpectedAttributes{
86+
Key: fmt.Sprintf("key_%s", rString),
87+
Value: fmt.Sprintf("value-%s-b", rString),
88+
EnvironmentScope: "review/*",
89+
}),
90+
),
91+
},
92+
// Change a variable's scope
93+
{
94+
Config: testAccGitlabGroupVariableScopeConfig(rString, "my-new-scope", "review/*"),
95+
SkipFunc: isRunningInCE,
96+
Check: resource.ComposeTestCheckFunc(
97+
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.a", &groupVariableA),
98+
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.b", &groupVariableB),
99+
testAccCheckGitlabGroupVariableAttributes(&groupVariableA, &testAccGitlabGroupVariableExpectedAttributes{
100+
Key: fmt.Sprintf("key_%s", rString),
101+
Value: fmt.Sprintf("value-%s-a", rString),
102+
EnvironmentScope: "my-new-scope",
103+
}),
104+
testAccCheckGitlabGroupVariableAttributes(&groupVariableB, &testAccGitlabGroupVariableExpectedAttributes{
105+
Key: fmt.Sprintf("key_%s", rString),
106+
Value: fmt.Sprintf("value-%s-b", rString),
107+
EnvironmentScope: "review/*",
108+
}),
109+
),
110+
},
111+
// Change both variables scopes at the same time
112+
{
113+
Config: testAccGitlabGroupVariableScopeConfig(rString, "my-new-new-scope", "review/hello-world"),
114+
SkipFunc: isRunningInCE,
115+
Check: resource.ComposeTestCheckFunc(
116+
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.a", &groupVariableA),
117+
testAccCheckGitlabGroupVariableExists("gitlab_group_variable.b", &groupVariableB),
118+
testAccCheckGitlabGroupVariableAttributes(&groupVariableA, &testAccGitlabGroupVariableExpectedAttributes{
119+
Key: fmt.Sprintf("key_%s", rString),
120+
Value: fmt.Sprintf("value-%s-a", rString),
121+
EnvironmentScope: "my-new-new-scope",
122+
}),
123+
testAccCheckGitlabGroupVariableAttributes(&groupVariableB, &testAccGitlabGroupVariableExpectedAttributes{
124+
Key: fmt.Sprintf("key_%s", rString),
125+
Value: fmt.Sprintf("value-%s-b", rString),
126+
EnvironmentScope: "review/hello-world",
54127
}),
55128
),
56129
},
@@ -85,10 +158,11 @@ func testAccCheckGitlabGroupVariableExists(n string, groupVariable *gitlab.Group
85158
}
86159

87160
type testAccGitlabGroupVariableExpectedAttributes struct {
88-
Key string
89-
Value string
90-
Protected bool
91-
Masked bool
161+
Key string
162+
Value string
163+
Protected bool
164+
Masked bool
165+
EnvironmentScope string
92166
}
93167

94168
func testAccCheckGitlabGroupVariableAttributes(variable *gitlab.GroupVariable, want *testAccGitlabGroupVariableExpectedAttributes) resource.TestCheckFunc {
@@ -109,6 +183,10 @@ func testAccCheckGitlabGroupVariableAttributes(variable *gitlab.GroupVariable, w
109183
return fmt.Errorf("got masked %t; want %t", variable.Masked, want.Masked)
110184
}
111185

186+
if variable.EnvironmentScope != want.EnvironmentScope {
187+
return fmt.Errorf("got environment_scope %s; want %s", variable.EnvironmentScope, want.EnvironmentScope)
188+
}
189+
112190
return nil
113191
}
114192
}
@@ -170,3 +248,26 @@ resource "gitlab_group_variable" "foo" {
170248
}
171249
`, rString, rString, rString, rString)
172250
}
251+
252+
func testAccGitlabGroupVariableScopeConfig(rString, scopeA, scopeB string) string {
253+
return fmt.Sprintf(`
254+
resource "gitlab_group" "foo" {
255+
name = "foo%v"
256+
path = "foo%v"
257+
}
258+
259+
resource "gitlab_group_variable" "a" {
260+
group = "${gitlab_group.foo.id}"
261+
key = "key_%s"
262+
value = "value-%s-a"
263+
environment_scope = "%s"
264+
}
265+
266+
resource "gitlab_group_variable" "b" {
267+
group = "${gitlab_group.foo.id}"
268+
key = "key_%s"
269+
value = "value-%s-b"
270+
environment_scope = "%s"
271+
}
272+
`, rString, rString, rString, rString, scopeA, rString, rString, scopeB)
273+
}

0 commit comments

Comments
 (0)