Skip to content

Commit 906e721

Browse files
committed
resource/gitlab_branch_protection: Allow to manage branch protection for default branch
This change to the `gitlab_branch_protection` resource will allow to protect the default branch of a project without importing. It basically works by unprotecting and protecting the default branch again in case it already exists when the resource is created. This allows the following code to properly work: ```hcl resource "gitlab_project" "example" { name = "example" initialize_with_readme = true } resource "gitlab_branch_protection" "default_branch" { project = gitlab_project.example.id branch = gitlab_project.example.default_branch // ... whatever settings ... } ``` The drawback of this is that it quickly unprotects the default branch to protect it again in the above scenario. However, it already does that for pretty much any update to the resource attributes, because of the lack of a proper upstream update API. In addition, it's just a special case and maintenance effort - but I guess that's what docs are for and the suppose that the benefits for the users outweigh the maintenance burden. If tried to implement something along the lines of https://github.com/gitlabhq/terraform-provider-gitlab/issues/792#issuecomment-1029148287 but it turns out it's harder to implement than expected. The problems are: * lack of diff / plan customization on the schema: https://discuss.hashicorp.com/t/ignore-the-order-of-a-complex-typed-list/42242 * when implementing the possibility to protect branches in the `gitlab_project` resource we'd also need to allow to share the projects with groups in add members in the resource, because those groups or users maybe used in the branch protection. There again, we need better plan / diff control then what the provider sdk provides us at the moment. * it basically would mean an integration / duplication / re-use of the `gitlab_project_share_group`, `gitlab_project_membership` and `gitlab_branch_protection` in the `gitlab_project` resource. Which I guess would be okay with the proper implementation, but then again the currently proposed "solution" in this PR may be good enough. Refs: #792 Closes: #671
1 parent 630a88e commit 906e721

File tree

3 files changed

+79
-6
lines changed

3 files changed

+79
-6
lines changed

docs/resources/branch_protection.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ page_title: "gitlab_branch_protection Resource - terraform-provider-gitlab"
44
subcategory: ""
55
description: |-
66
The gitlab_branch_protection resource allows to manage the lifecycle of a protected branch of a repository.
7+
~> Branch Protection Behavior for the default branch
8+
Depending on the GitLab instance, group or project setting the default branch of a project is created automatically by GitLab behind the scenes.
9+
Due to some https://github.com/gitlabhq/terraform-provider-gitlab/issues/792 limitations https://discuss.hashicorp.com/t/ignore-the-order-of-a-complex-typed-list/42242 in the Terraform Provider SDK and the GitLab API,
10+
when creating a new project and trying to manage the branch protection setting for its default branch the gitlab_branch_protection resource will
11+
automatically take ownership of the default branch without an explicit import by unprotecting and properly protecting it again.
12+
Having multiple gitlab_branch_protection resources for the same project and default branch will result in them overriding each other - make sure to only have a single one.
13+
This behavior might change in the future.
714
~> The allowed_to_push, allowed_to_merge, allowed_to_unprotect, unprotect_access_level and code_owner_approval_required attributes require a GitLab Enterprise instance.
815
Upstream API: GitLab REST API docs https://docs.gitlab.com/ee/api/protected_branches.html
916
---
@@ -12,6 +19,14 @@ description: |-
1219

1320
The `gitlab_branch_protection` resource allows to manage the lifecycle of a protected branch of a repository.
1421

22+
~> **Branch Protection Behavior for the default branch**
23+
Depending on the GitLab instance, group or project setting the default branch of a project is created automatically by GitLab behind the scenes.
24+
Due to [some](https://github.com/gitlabhq/terraform-provider-gitlab/issues/792) [limitations](https://discuss.hashicorp.com/t/ignore-the-order-of-a-complex-typed-list/42242) in the Terraform Provider SDK and the GitLab API,
25+
when creating a new project and trying to manage the branch protection setting for its default branch the `gitlab_branch_protection` resource will
26+
automatically take ownership of the default branch without an explicit import by unprotecting and properly protecting it again.
27+
Having multiple `gitlab_branch_protection` resources for the same project and default branch will result in them overriding each other - make sure to only have a single one.
28+
This behavior might change in the future.
29+
1530
~> The `allowed_to_push`, `allowed_to_merge`, `allowed_to_unprotect`, `unprotect_access_level` and `code_owner_approval_required` attributes require a GitLab Enterprise instance.
1631

1732
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/protected_branches.html)

internal/provider/resource_gitlab_branch_protection.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"log"
7-
"net/http"
87

98
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
109
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -43,6 +42,14 @@ var _ = registerResource("gitlab_branch_protection", func() *schema.Resource {
4342
return &schema.Resource{
4443
Description: `The ` + "`gitlab_branch_protection`" + ` resource allows to manage the lifecycle of a protected branch of a repository.
4544
45+
~> **Branch Protection Behavior for the default branch**
46+
Depending on the GitLab instance, group or project setting the default branch of a project is created automatically by GitLab behind the scenes.
47+
Due to [some](https://github.com/gitlabhq/terraform-provider-gitlab/issues/792) [limitations](https://discuss.hashicorp.com/t/ignore-the-order-of-a-complex-typed-list/42242) in the Terraform Provider SDK and the GitLab API,
48+
when creating a new project and trying to manage the branch protection setting for its default branch the ` + "`gitlab_branch_protection`" + ` resource will
49+
automatically take ownership of the default branch without an explicit import by unprotecting and properly protecting it again.
50+
Having multiple ` + "`gitlab_branch_protection`" + ` resources for the same project and default branch will result in them overriding each other - make sure to only have a single one.
51+
This behavior might change in the future.
52+
4653
~> The ` + "`allowed_to_push`" + `, ` + "`allowed_to_merge`" + `, ` + "`allowed_to_unprotect`" + `, ` + "`unprotect_access_level`" + ` and ` + "`code_owner_approval_required`" + ` attributes require a GitLab Enterprise instance.
4754
4855
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/protected_branches.html)`,
@@ -124,12 +131,25 @@ func resourceGitlabBranchProtectionCreate(ctx context.Context, d *schema.Resourc
124131
log.Printf("[DEBUG] create gitlab branch protection on branch %q for project %s", branch, project)
125132

126133
if d.IsNewResource() {
127-
existing, resp, err := client.ProtectedBranches.GetProtectedBranch(project, branch, gitlab.WithContext(ctx))
128-
if err != nil && resp.StatusCode != http.StatusNotFound {
129-
return diag.Errorf("error looking up protected branch %q on project %q: %v", branch, project, err)
134+
existing, _, err := client.ProtectedBranches.GetProtectedBranch(project, branch, gitlab.WithContext(ctx))
135+
if err == nil {
136+
projectDetails, _, err := client.Projects.GetProject(project, nil, gitlab.WithContext(ctx))
137+
if err != nil {
138+
return diag.Errorf("Failed to get project details for %q to get the name of the default branch: %v", project, err)
139+
}
140+
141+
if projectDetails.DefaultBranch == branch {
142+
log.Printf("[DEBUG] this branch protection is for the default branch %q in project %q, thus we allow configuring it even though this is a new resource! We do this by quickly unprotect it, because it's not editable ...!", branch, project)
143+
_, err := client.ProtectedBranches.UnprotectRepositoryBranches(project, branch, gitlab.WithContext(ctx))
144+
if err != nil {
145+
return diag.Errorf("Failed to unprotect default branch %q in project %q while trying to 'import' it: %v", branch, project, err)
146+
}
147+
} else {
148+
return diag.Errorf("protected branch %q on project %q already exists: %+v", branch, project, *existing)
149+
}
130150
}
131-
if resp.StatusCode != http.StatusNotFound {
132-
return diag.Errorf("protected branch %q on project %q already exists: %+v", branch, project, *existing)
151+
if err != nil && !is404(err) {
152+
return diag.Errorf("error looking up protected branch %q on project %q: %v", branch, project, err)
133153
}
134154
}
135155

internal/provider/resource_gitlab_branch_protection_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,44 @@ func TestAccGitlabBranchProtection_createWithMultipleAccessLevels(t *testing.T)
383383
})
384384
}
385385

386+
func TestAccGitlabBranchProtection_createForProjectDefaultBranch(t *testing.T) {
387+
testProjectName := acctest.RandomWithPrefix("tf-acc-test")
388+
var protectedBranch gitlab.ProtectedBranch
389+
390+
resource.ParallelTest(t, resource.TestCase{
391+
ProviderFactories: providerFactories,
392+
CheckDestroy: testAccCheckGitlabBranchProtectionDestroy,
393+
Steps: []resource.TestStep{
394+
// Create a project and protect its default branch with custom settings
395+
{
396+
Config: fmt.Sprintf(`
397+
resource "gitlab_project" "this" {
398+
name = "%s"
399+
initialize_with_readme = true
400+
}
401+
402+
resource "gitlab_branch_protection" "default_branch" {
403+
project = gitlab_project.this.id
404+
branch = gitlab_project.this.default_branch
405+
406+
// non-default setting
407+
allow_force_push = true
408+
}
409+
`, testProjectName),
410+
Check: resource.ComposeTestCheckFunc(
411+
testAccCheckGitlabBranchProtectionExists("gitlab_branch_protection.default_branch", &protectedBranch),
412+
func(_ *terraform.State) error {
413+
if protectedBranch.AllowForcePush != true {
414+
return fmt.Errorf("allow_force_push is not set to true")
415+
}
416+
return nil
417+
},
418+
),
419+
},
420+
},
421+
})
422+
}
423+
386424
func testAccCheckGitlabBranchProtectionPersistsInStateCorrectly(n string, pb *gitlab.ProtectedBranch) resource.TestCheckFunc {
387425
return func(s *terraform.State) error {
388426
rs, ok := s.RootModule().Resources[n]

0 commit comments

Comments
 (0)