Skip to content

Commit

Permalink
Handling environmentScope as a key for project variables
Browse files Browse the repository at this point in the history
  • Loading branch information
techthumb committed Jun 4, 2020
1 parent c7cf214 commit 7a01879
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 35 deletions.
182 changes: 147 additions & 35 deletions gitlab/resource_gitlab_project_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func resourceGitlabProjectVariable() *schema.Resource {
Read: resourceGitlabProjectVariableRead,
Update: resourceGitlabProjectVariableUpdate,
Delete: resourceGitlabProjectVariableDelete,
Exists: resourceGitlabProjectVariableExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Expand Down Expand Up @@ -59,6 +60,35 @@ func resourceGitlabProjectVariable() *schema.Resource {
}
}

func resourceGitlabProjectVariableExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*gitlab.Client)

project := d.Get("project")

log.Printf("[DEBUG] list gitlab project variable %s", project)

options := gitlab.ListProjectVariablesOptions{Page: 1, PerPage: 9999}
projectVariables, _, err := client.ProjectVariables.ListVariables(project, &options)
if err != nil {
return false, err
}

log.Printf("[DEBUG] gitlab project variables: %s", projectVariables)

key := d.Get("key")
environmentScope := d.Get("environment_scope")

for _, projectVariable := range projectVariables {
if projectVariable.Key == key && projectVariable.EnvironmentScope == environmentScope {
log.Printf("[DEBUG] Variable matching key and environment scope exists: %s:%s", key, environmentScope)
return true, nil
}
}

log.Printf("[DEBUG] Variable matching key and environment scope does NOT exist: %s:%s", key, environmentScope)
return false, nil
}

func resourceGitlabProjectVariableCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gitlab.Client)

Expand All @@ -78,80 +108,162 @@ func resourceGitlabProjectVariableCreate(d *schema.ResourceData, meta interface{
Masked: &masked,
EnvironmentScope: &environmentScope,
}
log.Printf("[DEBUG] create gitlab project variable %s/%s", project, key)
log.Printf("[DEBUG] create gitlab project variable %s/%s/%s", project, key, environmentScope)

_, _, err := client.ProjectVariables.CreateVariable(project, &options)
if err != nil {
return err
}

d.SetId(buildTwoPartID(&project, &key))
d.SetId(buildThreePartID(&project, &key, &environmentScope))

return resourceGitlabProjectVariableRead(d, meta)
}

func resourceGitlabProjectVariableRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gitlab.Client)

project, key, err := parseTwoPartID(d.Id())
project, key, environmentScope, err := parseThreePartID(d.Id())
if err != nil {
return err
}

log.Printf("[DEBUG] read gitlab project variable %s/%s", project, key)

v, _, err := client.ProjectVariables.GetVariable(project, key)
options := gitlab.ListProjectVariablesOptions{Page: 1, PerPage: 9999}
projectVariables, _, err := client.ProjectVariables.ListVariables(project, &options)
if err != nil {
return err
}

d.Set("key", v.Key)
d.Set("value", v.Value)
d.Set("variable_type", v.VariableType)
d.Set("project", project)
d.Set("protected", v.Protected)
d.Set("masked", v.Masked)
//For now I'm ignoring environment_scope when reading back data. (this can cause configuration drift so it is bad).
//However I'm unable to stop terraform from gratuitously updating this to values that are unacceptable by Gitlab)
//I don't have an enterprise license to properly test this either.
d.Set("environment_scope", v.EnvironmentScope)
log.Printf("[DEBUG] gitlab project variables: %s", projectVariables)

for _, projectVariable := range projectVariables {
if projectVariable.Key == key && projectVariable.EnvironmentScope == environmentScope {
d.Set("key", projectVariable.Key)
d.Set("value", projectVariable.Value)
d.Set("variable_type", projectVariable.VariableType)
d.Set("project", project)
d.Set("protected", projectVariable.Protected)
d.Set("masked", projectVariable.Masked)
d.Set("environment_scope", projectVariable.EnvironmentScope)
return nil
}
}
return nil
}

func resourceGitlabProjectVariableUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gitlab.Client)

project := d.Get("project").(string)
project := d.Get("project")
key := d.Get("key").(string)
value := d.Get("value").(string)
variableType := stringToVariableType(d.Get("variable_type").(string))
protected := d.Get("protected").(bool)
masked := d.Get("masked").(bool)
environmentScope := d.Get("environment_scope").(string)
environmentScopeOfProjectVariableBeingUpdated := d.Get("environment_scope")

options := &gitlab.UpdateProjectVariableOptions{
Value: &value,
VariableType: variableType,
Protected: &protected,
Masked: &masked,
EnvironmentScope: &environmentScope,
projectVariablesWithSameKeyAsTheOneBeingUpdated, fetchErr := projectVariablesMatchingKey(client, project, key)
if fetchErr != nil {
return fetchErr
}
log.Printf("[DEBUG] update gitlab project variable %s/%s", project, key)

_, _, err := client.ProjectVariables.UpdateVariable(project, key, options)
if err != nil {
return err
deleteErr := deleteProjectVariables(projectVariablesWithSameKeyAsTheOneBeingUpdated, project, client)
if deleteErr != nil {
return deleteErr
}

for _, projectVariable := range projectVariablesWithSameKeyAsTheOneBeingUpdated {
environmentScope := projectVariable.EnvironmentScope
var value string
var variableType gitlab.VariableTypeValue
var protected bool
var masked bool
if environmentScope == environmentScopeOfProjectVariableBeingUpdated {
value = d.Get("value").(string)
variableType = *stringToVariableType(d.Get("variable_type").(string))
protected = d.Get("protected").(bool)
masked = d.Get("masked").(bool)
} else {
value = projectVariable.Value
variableType = projectVariable.VariableType
protected = projectVariable.Protected
masked = projectVariable.Masked
}
options := gitlab.CreateProjectVariableOptions{
Key: &key,
Value: &value,
VariableType: &variableType,
Protected: &protected,
Masked: &masked,
EnvironmentScope: &environmentScope,
}
log.Printf("[DEBUG] create gitlab project variable %s/%s/%s", project, key, environmentScope)
_, _, err := client.ProjectVariables.CreateVariable(project, &options)
if err != nil {
return err
}
}
return resourceGitlabProjectVariableRead(d, meta)
}

func resourceGitlabProjectVariableDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gitlab.Client)
project := d.Get("project").(string)
project := d.Get("project")
key := d.Get("key").(string)
log.Printf("[DEBUG] Delete gitlab project variable %s/%s", project, key)
environmentScopeOfProjectVariableBeingDeleted := d.Get("environment_scope")

projectVariablesWithSameKeyAsTheOneBeingDeleted, fetchErr := projectVariablesMatchingKey(client, project, key)
if fetchErr != nil {
return fetchErr
}

deleteErr := deleteProjectVariables(projectVariablesWithSameKeyAsTheOneBeingDeleted, project, client)
if deleteErr != nil {
return deleteErr
}

for _, projectVariable := range projectVariablesWithSameKeyAsTheOneBeingDeleted {
if projectVariable.EnvironmentScope == environmentScopeOfProjectVariableBeingDeleted {
continue // Don't re-create project variable for the environment scope that is marked for deletion
}
options := gitlab.CreateProjectVariableOptions{
Key: &projectVariable.Key,
Value: &projectVariable.Value,
VariableType: &projectVariable.VariableType,
Protected: &projectVariable.Protected,
Masked: &projectVariable.Masked,
EnvironmentScope: &projectVariable.EnvironmentScope,
}
log.Printf("[DEBUG] create gitlab project variable %s/%s/%s", project, key, projectVariable.EnvironmentScope)
_, _, err := client.ProjectVariables.CreateVariable(project, &options)
if err != nil {
return err
}
}
return nil
}

func deleteProjectVariables(projectVariables []*gitlab.ProjectVariable, project interface{}, client *gitlab.Client) error {
for _, projectVariable := range projectVariables {
log.Printf("[DEBUG] Delete gitlab project variable %s/%s", project, projectVariable.Key)
_, err := client.ProjectVariables.RemoveVariable(project, projectVariable.Key)
if err != nil {
return err
}
}
return nil
}

func projectVariablesMatchingKey(client *gitlab.Client, project interface{}, key string) ([]*gitlab.ProjectVariable, error) {
options := gitlab.ListProjectVariablesOptions{Page: 1, PerPage: 9999}
allProjectVariables, _, err := client.ProjectVariables.ListVariables(project, &options)
if err != nil {
return nil, err
}

var projectVariablesWithSameKey []*gitlab.ProjectVariable

_, err := client.ProjectVariables.RemoveVariable(project, key)
return err
for _, projectVariable := range allProjectVariables {
if projectVariable.Key == key {
projectVariablesWithSameKey = append(projectVariablesWithSameKey, projectVariable)
}
}
return projectVariablesWithSameKey, nil
}
15 changes: 15 additions & 0 deletions gitlab/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,21 @@ func buildTwoPartID(a, b *string) string {
return fmt.Sprintf("%s:%s", *a, *b)
}

// format the strings into an id `a:b:c`
func buildThreePartID(a, b *string, c *string) string {
return fmt.Sprintf("%s:%s:%s", *a, *b, *c)
}

// return the pieces of id `a:b:c` as a, b, c
func parseThreePartID(id string) (string, string, string, error) {
parts := strings.SplitN(id, ":", 3)
if len(parts) != 3 {
return "", "", "", fmt.Errorf("Unexpected ID format (%q). Expected project:key:env", id)
}

return parts[0], parts[1], parts[2], nil
}

var accessLevelID = map[string]gitlab.AccessLevelValue{
"no one": gitlab.NoPermissions,
"guest": gitlab.GuestPermissions,
Expand Down

0 comments on commit 7a01879

Please sign in to comment.