@@ -2,10 +2,13 @@ package gitlab
22
33import (
44 "errors"
5+ "fmt"
56 "log"
67 "net/http"
8+ "net/url"
79 "strings"
810
11+ retryablehttp "github.com/hashicorp/go-retryablehttp"
912 "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1013 gitlab "github.com/xanzy/go-gitlab"
1114)
@@ -56,7 +59,9 @@ func resourceGitlabProjectVariable() *schema.Resource {
5659 "environment_scope" : {
5760 Type : schema .TypeString ,
5861 Optional : true ,
59- Default : false ,
62+ Default : "*" ,
63+ // Versions of GitLab prior to 13.4 cannot update environment_scope.
64+ ForceNew : true ,
6065 },
6166 },
6267 }
@@ -81,30 +86,55 @@ func resourceGitlabProjectVariableCreate(d *schema.ResourceData, meta interface{
8186 Masked : & masked ,
8287 EnvironmentScope : & environmentScope ,
8388 }
84- log .Printf ("[DEBUG] create gitlab project variable %s/%s" , project , key )
89+
90+ id := strings .Join ([]string {project , key , environmentScope }, ":" )
91+
92+ log .Printf ("[DEBUG] create gitlab project variable %q" , id )
8593
8694 _ , _ , err := client .ProjectVariables .CreateVariable (project , & options )
8795 if err != nil {
8896 return augmentProjectVariableClientError (d , err )
8997 }
9098
91- d .SetId (buildTwoPartID ( & project , & key ) )
99+ d .SetId (id )
92100
93101 return resourceGitlabProjectVariableRead (d , meta )
94102}
95103
96104func resourceGitlabProjectVariableRead (d * schema.ResourceData , meta interface {}) error {
97105 client := meta .(* gitlab.Client )
98106
99- project , key , err := parseTwoPartID (d .Id ())
100- if err != nil {
101- return err
107+ var (
108+ project string
109+ key string
110+ environmentScope string
111+ )
112+
113+ // An older version of this resource used the ID format "project:key".
114+ // For backwards compatibility we still support the old format.
115+ parts := strings .SplitN (d .Id (), ":" , 4 )
116+ switch len (parts ) {
117+ case 2 :
118+ project = parts [0 ]
119+ key = parts [1 ]
120+ environmentScope = d .Get ("environment_scope" ).(string )
121+ case 3 :
122+ project = parts [0 ]
123+ key = parts [1 ]
124+ environmentScope = parts [2 ]
125+ default :
126+ return fmt .Errorf (`Failed to parse project variable ID %q: expected format project:key or project:key:environment_scope` , d .Id ())
102127 }
103128
104- log .Printf ("[DEBUG] read gitlab project variable %s/%s " , project , key )
129+ log .Printf ("[DEBUG] read gitlab project variable %q " , d . Id () )
105130
106- v , _ , err := client . ProjectVariables . GetVariable ( project , key )
131+ v , err := getProjectVariable ( client , project , key , environmentScope )
107132 if err != nil {
133+ if errors .Is (err , errProjectVariableNotExist ) {
134+ log .Printf ("[DEBUG] read gitlab project variable %q was not found" , d .Id ())
135+ d .SetId ("" )
136+ return nil
137+ }
108138 return augmentProjectVariableClientError (d , err )
109139 }
110140
@@ -114,9 +144,6 @@ func resourceGitlabProjectVariableRead(d *schema.ResourceData, meta interface{})
114144 d .Set ("project" , project )
115145 d .Set ("protected" , v .Protected )
116146 d .Set ("masked" , v .Masked )
117- //For now I'm ignoring environment_scope when reading back data. (this can cause configuration drift so it is bad).
118- //However I'm unable to stop terraform from gratuitously updating this to values that are unacceptable by Gitlab)
119- //I don't have an enterprise license to properly test this either.
120147 d .Set ("environment_scope" , v .EnvironmentScope )
121148 return nil
122149}
@@ -139,9 +166,9 @@ func resourceGitlabProjectVariableUpdate(d *schema.ResourceData, meta interface{
139166 Masked : & masked ,
140167 EnvironmentScope : & environmentScope ,
141168 }
142- log .Printf ("[DEBUG] update gitlab project variable %s/%s " , project , key )
169+ log .Printf ("[DEBUG] update gitlab project variable %q " , d . Id () )
143170
144- _ , _ , err := client .ProjectVariables .UpdateVariable (project , key , options )
171+ _ , _ , err := client .ProjectVariables .UpdateVariable (project , key , options , withEnvironmentScopeFilter ( environmentScope ) )
145172 if err != nil {
146173 return augmentProjectVariableClientError (d , err )
147174 }
@@ -153,9 +180,14 @@ func resourceGitlabProjectVariableDelete(d *schema.ResourceData, meta interface{
153180 client := meta .(* gitlab.Client )
154181 project := d .Get ("project" ).(string )
155182 key := d .Get ("key" ).(string )
156- log .Printf ("[DEBUG] Delete gitlab project variable %s/%s" , project , key )
183+ environmentScope := d .Get ("environment_scope" ).(string )
184+ log .Printf ("[DEBUG] Delete gitlab project variable %q" , d .Id ())
157185
158- _ , err := client .ProjectVariables .RemoveVariable (project , key )
186+ // Note that the environment_scope filter is added here to support GitLab versions >= 13.4,
187+ // but it will be ignored in prior versions, causing nondeterministic destroy behavior when
188+ // destroying or updating scoped variables.
189+ // ref: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39209
190+ _ , err := client .ProjectVariables .RemoveVariable (project , key , withEnvironmentScopeFilter (environmentScope ))
159191 return augmentProjectVariableClientError (d , err )
160192}
161193
@@ -177,3 +209,43 @@ func isInvalidValueError(err error) bool {
177209 strings .Contains (httpErr .Message , "value" ) &&
178210 strings .Contains (httpErr .Message , "invalid" )
179211}
212+
213+ func withEnvironmentScopeFilter (environmentScope string ) gitlab.RequestOptionFunc {
214+ return func (req * retryablehttp.Request ) error {
215+ query , err := url .ParseQuery (req .Request .URL .RawQuery )
216+ if err != nil {
217+ return err
218+ }
219+ query .Set ("filter[environment_scope]" , environmentScope )
220+ req .Request .URL .RawQuery = query .Encode ()
221+ return nil
222+ }
223+ }
224+
225+ var errProjectVariableNotExist = errors .New ("project variable does not exist" )
226+
227+ func getProjectVariable (client * gitlab.Client , project interface {}, key , environmentScope string ) (* gitlab.ProjectVariable , error ) {
228+ // List and filter variables manually to support GitLab versions < v13.4 (2020-08-22)
229+ // ref: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39209
230+
231+ page := 1
232+
233+ for {
234+ projectVariables , resp , err := client .ProjectVariables .ListVariables (project , & gitlab.ListProjectVariablesOptions {Page : page })
235+ if err != nil {
236+ return nil , err
237+ }
238+
239+ for _ , v := range projectVariables {
240+ if v .Key == key && v .EnvironmentScope == environmentScope {
241+ return v , nil
242+ }
243+ }
244+
245+ if resp .NextPage == 0 {
246+ return nil , errProjectVariableNotExist
247+ }
248+
249+ page ++
250+ }
251+ }
0 commit comments