Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling environmentScope as a key for project variables #324

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
techthumb marked this conversation as resolved.
Show resolved Hide resolved
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