From cd232fe85af7548dea3bcd7f72b64f7e8d399c32 Mon Sep 17 00:00:00 2001 From: Patrick Rice Date: Fri, 11 Feb 2022 02:32:20 +0000 Subject: [PATCH 1/3] Add a WaitForState to project creation This fixes an issue where branch protection running in the background, causing the default branch to occasionally not be protected. --- internal/provider/resource_gitlab_project.go | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/internal/provider/resource_gitlab_project.go b/internal/provider/resource_gitlab_project.go index d724cb92e..3d363425c 100644 --- a/internal/provider/resource_gitlab_project.go +++ b/internal/provider/resource_gitlab_project.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net/http" + "strconv" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -620,6 +621,27 @@ func resourceGitlabProjectCreate(ctx context.Context, d *schema.ResourceData, me } } + // Branch protection for a newly created branch is an async action, so use WaitForState to ensure it's protected + // before we continue. Note this check should only be required when there is a custom default branch set + // See issue 800: https://github.com/gitlabhq/terraform-provider-gitlab/issues/800 + stateConf := &resource.StateChangeConf{ + Pending: []string{"false"}, + Target: []string{"true"}, + Timeout: 2 * time.Minute, //The async action usually completes very quickly, within seconds. Don't wait too long. + Refresh: func() (interface{}, string, error) { + branch, _, err := client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx)) + if err != nil { + return nil, "", err + } + + return branch, strconv.FormatBool(branch.Protected), nil + }, + } + + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return diag.Errorf("error while waiting for branch %s to reach 'protected' status, %s", project.DefaultBranch, err) + } + var editProjectOptions gitlab.EditProjectOptions if v, ok := d.GetOk("mirror_overwrites_diverged_branches"); ok { From 98c5fb82376f863b6918a34632ae58e5022a4afc Mon Sep 17 00:00:00 2001 From: Patrick Rice Date: Fri, 11 Feb 2022 04:32:45 +0000 Subject: [PATCH 2/3] Catch 404 status for when default branch doesn't exist This can happen when default_branch or initialize_with_readme are not set (or the initialize is set to false explicitly) as the branch doesn't exist until first commit. --- internal/provider/resource_gitlab_project.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/provider/resource_gitlab_project.go b/internal/provider/resource_gitlab_project.go index 3d363425c..460521f77 100644 --- a/internal/provider/resource_gitlab_project.go +++ b/internal/provider/resource_gitlab_project.go @@ -629,8 +629,16 @@ func resourceGitlabProjectCreate(ctx context.Context, d *schema.ResourceData, me Target: []string{"true"}, Timeout: 2 * time.Minute, //The async action usually completes very quickly, within seconds. Don't wait too long. Refresh: func() (interface{}, string, error) { - branch, _, err := client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx)) + branch, response, err := client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx)) if err != nil { + if response.StatusCode == 404 { + // When we hit a 404 here, it means the default branch wasn't created at all as part of the project + // this will happen when "default_branch" isn't set, or "initialize_with_readme" is set to false. + // We don't need to wait anymore, so return "true" to exist the wait loop. + return branch, "true", nil + } + + //This is legit error, return the error. return nil, "", err } From 3dfbdd823279387304b95ccb961bd40cdd1aae8e Mon Sep 17 00:00:00 2001 From: Patrick Rice Date: Sun, 13 Feb 2022 01:27:49 +0000 Subject: [PATCH 3/3] Update 404 errors to use the is404 helper method --- internal/provider/resource_gitlab_project.go | 31 ++++++++------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/internal/provider/resource_gitlab_project.go b/internal/provider/resource_gitlab_project.go index 460521f77..fca48a052 100644 --- a/internal/provider/resource_gitlab_project.go +++ b/internal/provider/resource_gitlab_project.go @@ -2,10 +2,8 @@ package provider import ( "context" - "errors" "fmt" "log" - "net/http" "strconv" "time" @@ -565,12 +563,11 @@ func resourceGitlabProjectCreate(ctx context.Context, d *schema.ResourceData, me if _, ok := d.GetOk("push_rules"); ok { err := editOrAddPushRules(ctx, client, d.Id(), d) - var httpError *gitlab.ErrorResponse - if errors.As(err, &httpError) && httpError.Response.StatusCode == http.StatusNotFound { - log.Printf("[DEBUG] Failed to edit push rules for project %q: %v", d.Id(), err) - return diag.Errorf("Project push rules are not supported in your version of GitLab") - } if err != nil { + if is404(err) { + log.Printf("[DEBUG] Failed to edit push rules for project %q: %v", d.Id(), err) + return diag.Errorf("Project push rules are not supported in your version of GitLab") + } return diag.Errorf("Failed to edit push rules for project %q: %s", d.Id(), err) } } @@ -629,9 +626,9 @@ func resourceGitlabProjectCreate(ctx context.Context, d *schema.ResourceData, me Target: []string{"true"}, Timeout: 2 * time.Minute, //The async action usually completes very quickly, within seconds. Don't wait too long. Refresh: func() (interface{}, string, error) { - branch, response, err := client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx)) + branch, _, err := client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx)) if err != nil { - if response.StatusCode == 404 { + if is404(err) { // When we hit a 404 here, it means the default branch wasn't created at all as part of the project // this will happen when "default_branch" isn't set, or "initialize_with_readme" is set to false. // We don't need to wait anymore, so return "true" to exist the wait loop. @@ -700,8 +697,7 @@ func resourceGitlabProjectRead(ctx context.Context, d *schema.ResourceData, meta log.Printf("[DEBUG] read gitlab project %q push rules", d.Id()) pushRules, _, err := client.Projects.GetProjectPushRules(d.Id(), gitlab.WithContext(ctx)) - var httpError *gitlab.ErrorResponse - if errors.As(err, &httpError) && httpError.Response.StatusCode == http.StatusNotFound { + if is404(err) { log.Printf("[DEBUG] Failed to get push rules for project %q: %v", d.Id(), err) } else if err != nil { return diag.Errorf("Failed to get push rules for project %q: %s", d.Id(), err) @@ -889,12 +885,11 @@ func resourceGitlabProjectUpdate(ctx context.Context, d *schema.ResourceData, me if d.HasChange("push_rules") { err := editOrAddPushRules(ctx, client, d.Id(), d) - var httpError *gitlab.ErrorResponse - if errors.As(err, &httpError) && httpError.Response.StatusCode == http.StatusNotFound { - log.Printf("[DEBUG] Failed to get push rules for project %q: %v", d.Id(), err) - return diag.Errorf("Project push rules are not supported in your version of GitLab") - } if err != nil { + if is404(err) { + log.Printf("[DEBUG] Failed to get push rules for project %q: %v", d.Id(), err) + return diag.Errorf("Project push rules are not supported in your version of GitLab") + } return diag.Errorf("Failed to edit push rules for project %q: %s", d.Id(), err) } } @@ -918,9 +913,9 @@ func resourceGitlabProjectDelete(ctx context.Context, d *schema.ResourceData, me Pending: []string{"Deleting"}, Target: []string{"Deleted"}, Refresh: func() (interface{}, string, error) { - out, response, err := client.Projects.GetProject(d.Id(), nil, gitlab.WithContext(ctx)) + out, _, err := client.Projects.GetProject(d.Id(), nil, gitlab.WithContext(ctx)) if err != nil { - if response.StatusCode == 404 { + if is404(err) { return out, "Deleted", nil } log.Printf("[ERROR] Received error: %#v", err)