Skip to content

Commit 0176da5

Browse files
committed
resource/gitlab_project: Fix admin token requirement to check default branch protection
This bugfix change set gracefully handles checking for the instance-level default branch protection setting if the provider token is not from an administrator user. This bug was introduced in #1128 in an attempt to check the instance-level wide settings - which unfortunately, and not to my knowledge at the time requires an admin token. In case the token is not from an admin the project resource won't check for the settings and by default restore the faulty behavior before #1128 which was to wait for the default branch protection to be created. However, if this won't happen because it's disabled on the instance-level the user can set `skip_wait_for_default_branch_protection = true` which skips waiting for the default branch protection. Closes: #1167
1 parent d4b7e10 commit 0176da5

7 files changed

+161
-61
lines changed

CHANGELOG.md

+24-16
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,39 @@
1+
## 3.16.1 (2022-07-11)
2+
3+
This release was tested against GitLab 14.10, 15.0 and 15.1 for both CE and EE.
4+
5+
BUG FIXES:
6+
7+
* resource/gitlab_project: Fix admin token requirement to check default branch protection ([#1169](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1169))
8+
19
## 3.16.0 (2022-07-07)
210

311
This release was tested against GitLab 14.10, 15.0 and 15.1 for both CE and EE.
412

513
FEATURES:
614

7-
* **New Data Source:** `gitlab_current_user` ([#1118])
8-
* **New Data Source:** `gitlab_release_link` ([#1131])
9-
* **New Data Source:** `gitlab_release_links` ([#1131])
10-
* **New Resource:** `gitlab_release_link` ([#1131])
11-
* **New Resource:** `gitlab_cluster_agent_token` ([#1147])
15+
* **New Data Source:** `gitlab_current_user` ([#1118](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1118))
16+
* **New Data Source:** `gitlab_release_link` ([#1131](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1131))
17+
* **New Data Source:** `gitlab_release_links` ([#1131](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1131))
18+
* **New Resource:** `gitlab_release_link` ([#1131](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1131))
19+
* **New Resource:** `gitlab_cluster_agent_token` ([#1147](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1147))
1220

1321
IMPROVEMENTS:
1422

15-
* resource/gitlab_project_protected_environment: Add `required_approval_count` attribute ([#1097])
16-
* resource/gitlab_project_access_token: Add `owner` as possible value to `access_level` ([#1145])
17-
* resource/gitlab_project_membership: Add `owner` as possible value to `access_level` ([#1145])
18-
* resource/gitlab_project_share_group: Add `owner` as possible value to `access_level` ([#1145])
19-
* resource/gitlab_project: Add `ci_default_git_depth` attribute ([#1146])
20-
* datasource/gitlab_project: Add `ci_default_git_depth` attribute ([#1146])
21-
* datasource/gitlab_projects: Add `ci_default_git_depth` attribute ([#1146])
23+
* resource/gitlab_project_protected_environment: Add `required_approval_count` attribute ([#1097](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1097))
24+
* resource/gitlab_project_access_token: Add `owner` as possible value to `access_level` ([#1145](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1145))
25+
* resource/gitlab_project_membership: Add `owner` as possible value to `access_level` ([#1145](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1145))
26+
* resource/gitlab_project_share_group: Add `owner` as possible value to `access_level` ([#1145](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1145))
27+
* resource/gitlab_project: Add `ci_default_git_depth` attribute ([#1146](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1146))
28+
* datasource/gitlab_project: Add `ci_default_git_depth` attribute ([#1146](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1146))
29+
* datasource/gitlab_projects: Add `ci_default_git_depth` attribute ([#1146](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1146))
2230

2331
BUG FIXES:
2432

25-
* resource/gitlab_project: Fix project creation when default branch protection is disabled on instance-level (#[1128])
26-
* resource/gitlab_project: Fix a case where a change to a project in terraform can never apply when certain fields are modified ([#1158])
27-
* resource/gitlab_project: Fix passing `false` to API for explicitly set optional attributes ([#1152])
28-
* resource/gitlab_group: Fix passing false to API for explicitly set optional attributes ([#1152])
33+
* resource/gitlab_project: Fix project creation when default branch protection is disabled on instance-level ([#1128](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1128))
34+
* resource/gitlab_project: Fix a case where a change to a project in terraform can never apply when certain fields are modified ([#1158](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1158))
35+
* resource/gitlab_project: Fix passing `false` to API for explicitly set optional attributes ([#1152](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1152))
36+
* resource/gitlab_group: Fix passing false to API for explicitly set optional attributes ([#1152](https://github.com/gitlabhq/terraform-provider-gitlab/pull/1152))
2937

3038
## 3.15.1 (2022-06-08)
3139

docs/resources/personal_access_token.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ description: |-
1212

1313
The `gitlab_personal_access_token` resource allows to manage the lifecycle of a personal access token for a specified user.
1414

15-
-> This resource requires administration privileges.
15+
-> This resource requires administration privileges.
1616

1717
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/personal_access_tokens.html)
1818

docs/resources/project.md

+4
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ resource "gitlab_project" "peters_repo" {
136136
- `resolve_outdated_diff_discussions` (Boolean) Automatically resolve merge request diffs discussions on lines changed with a push.
137137
- `security_and_compliance_access_level` (String) Set the security and compliance access level. Valid values are `disabled`, `private`, `enabled`.
138138
- `shared_runners_enabled` (Boolean) Enable shared runners for this project.
139+
- `skip_wait_for_default_branch_protection` (Boolean) If `true`, the default behavior to wait for the default branch protection to be created is skipped.
140+
This is necessary if the current user is not an admin and the default branch protection is disabled on an instance-level.
141+
There is currently no known way to determine if the default branch protection is disabled on an instance-level for non-admin users.
142+
This attribute is only used during resource creation, thus changes are suppressed and the attribute cannot be imported.
139143
- `snippets_access_level` (String) Set the snippets access level. Valid values are `disabled`, `private`, `enabled`.
140144
- `snippets_enabled` (Boolean) Enable snippets for the project.
141145
- `squash_commit_template` (String) Template used to create squash commit message in merge requests. (Introduced in GitLab 14.6.)

internal/provider/resource_gitlab_personal_access_tokens.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ var _ = registerResource("gitlab_personal_access_token", func() *schema.Resource
2929
return &schema.Resource{
3030
Description: `The ` + "`gitlab_personal_access_token`" + ` resource allows to manage the lifecycle of a personal access token for a specified user.
3131
32-
-> This resource requires administration privileges.
32+
-> This resource requires administration privileges.
3333
3434
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/personal_access_tokens.html)`,
3535

@@ -97,7 +97,7 @@ var _ = registerResource("gitlab_personal_access_token", func() *schema.Resource
9797
func resourceGitlabPersonalAccessTokenCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
9898
client := meta.(*gitlab.Client)
9999

100-
currentUserAdmin, err := isCurrentUserAdmin(client)
100+
currentUserAdmin, err := isCurrentUserAdmin(ctx, client)
101101
if err != nil {
102102
return diag.Errorf("[ERROR] cannot query the user API for current user: %v", err)
103103
}

internal/provider/resource_gitlab_project.go

+66-35
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,20 @@ branch using a ` + "`DELETE`" + ` request. Then define the desired branch protec
671671
Importer: &schema.ResourceImporter{
672672
StateContext: schema.ImportStatePassthroughContext,
673673
},
674-
Schema: resourceGitLabProjectSchema,
674+
Schema: constructSchema(resourceGitLabProjectSchema, map[string]*schema.Schema{
675+
"skip_wait_for_default_branch_protection": {
676+
Description: `If ` + "`true`" + `, the default behavior to wait for the default branch protection to be created is skipped.
677+
This is necessary if the current user is not an admin and the default branch protection is disabled on an instance-level.
678+
There is currently no known way to determine if the default branch protection is disabled on an instance-level for non-admin users.
679+
This attribute is only used during resource creation, thus changes are suppressed and the attribute cannot be imported.
680+
`,
681+
Type: schema.TypeBool,
682+
Optional: true,
683+
DiffSuppressFunc: func(string, string, string, *schema.ResourceData) bool {
684+
return true
685+
},
686+
},
687+
}),
675688
CustomizeDiff: customdiff.All(
676689
customdiff.ComputedIf("path_with_namespace", namespaceOrPathChanged),
677690
customdiff.ComputedIf("ssh_url_to_repo", namespaceOrPathChanged),
@@ -1106,41 +1119,48 @@ func resourceGitlabProjectCreate(ctx context.Context, d *schema.ResourceData, me
11061119
}
11071120
}
11081121

1109-
// If the project is assigned to a group namespace and the group has *default branch protection*
1110-
// disabled (`default_branch_protection = 0`) then we don't have to wait for one.
1111-
waitForDefaultBranchProtection, err := expectDefaultBranchProtection(ctx, client, project)
1112-
if err != nil {
1113-
return diag.Errorf("Failed to discover if branch protection is enabled by default or not for project %d: %+v", project.ID, err)
1122+
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
1123+
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
1124+
if v, ok := d.GetOkExists("skip_wait_for_default_branch_protection"); ok {
1125+
d.Set("skip_wait_for_default_branch_protection", v.(bool))
11141126
}
1127+
if !d.Get("skip_wait_for_default_branch_protection").(bool) {
1128+
// If the project is assigned to a group namespace and the group has *default branch protection*
1129+
// disabled (`default_branch_protection = 0`) then we don't have to wait for one.
1130+
waitForDefaultBranchProtection, err := expectDefaultBranchProtection(ctx, client, project)
1131+
if err != nil {
1132+
return diag.Errorf("Failed to discover if branch protection is enabled by default or not for project %d: %+v", project.ID, err)
1133+
}
11151134

1116-
if waitForDefaultBranchProtection {
1117-
// Branch protection for a newly created branch is an async action, so use WaitForState to ensure it's protected
1118-
// before we continue. Note this check should only be required when there is a custom default branch set
1119-
// See issue 800: https://github.com/gitlabhq/terraform-provider-gitlab/issues/800
1120-
stateConf := &resource.StateChangeConf{
1121-
Pending: []string{"false"},
1122-
Target: []string{"true"},
1123-
Timeout: 2 * time.Minute, //The async action usually completes very quickly, within seconds. Don't wait too long.
1124-
Refresh: func() (interface{}, string, error) {
1125-
branch, _, err := client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx))
1126-
if err != nil {
1127-
if is404(err) {
1128-
// When we hit a 404 here, it means the default branch wasn't created at all as part of the project
1129-
// this will happen when "default_branch" isn't set, or "initialize_with_readme" is set to false.
1130-
// We don't need to wait anymore, so return "true" to exist the wait loop.
1131-
return branch, "true", nil
1135+
if waitForDefaultBranchProtection {
1136+
// Branch protection for a newly created branch is an async action, so use WaitForState to ensure it's protected
1137+
// before we continue. Note this check should only be required when there is a custom default branch set
1138+
// See issue 800: https://github.com/gitlabhq/terraform-provider-gitlab/issues/800
1139+
stateConf := &resource.StateChangeConf{
1140+
Pending: []string{"false"},
1141+
Target: []string{"true"},
1142+
Timeout: 2 * time.Minute, //The async action usually completes very quickly, within seconds. Don't wait too long.
1143+
Refresh: func() (interface{}, string, error) {
1144+
branch, _, err := client.Branches.GetBranch(project.ID, project.DefaultBranch, gitlab.WithContext(ctx))
1145+
if err != nil {
1146+
if is404(err) {
1147+
// When we hit a 404 here, it means the default branch wasn't created at all as part of the project
1148+
// this will happen when "default_branch" isn't set, or "initialize_with_readme" is set to false.
1149+
// We don't need to wait anymore, so return "true" to exist the wait loop.
1150+
return branch, "true", nil
1151+
}
1152+
1153+
//This is legit error, return the error.
1154+
return nil, "", err
11321155
}
11331156

1134-
//This is legit error, return the error.
1135-
return nil, "", err
1136-
}
1137-
1138-
return branch, strconv.FormatBool(branch.Protected), nil
1139-
},
1140-
}
1157+
return branch, strconv.FormatBool(branch.Protected), nil
1158+
},
1159+
}
11411160

1142-
if _, err := stateConf.WaitForStateContext(ctx); err != nil {
1143-
return diag.Errorf("error while waiting for branch %s to reach 'protected' status, %s", project.DefaultBranch, err)
1161+
if _, err := stateConf.WaitForStateContext(ctx); err != nil {
1162+
return diag.Errorf("error while waiting for branch %s to reach 'protected' status, %s", project.DefaultBranch, err)
1163+
}
11441164
}
11451165
}
11461166

@@ -1824,11 +1844,22 @@ func expectDefaultBranchProtection(ctx context.Context, client *gitlab.Client, p
18241844
return group.DefaultBranchProtection != 0, nil
18251845
}
18261846

1827-
// // If the project is not part of a group it may have default branch protection disabled because of the instance-wide application settings
1828-
settings, _, err := client.Settings.GetSettings(nil, gitlab.WithContext(ctx))
1847+
isAdmin, err := isCurrentUserAdmin(ctx, client)
18291848
if err != nil {
1830-
return false, err
1849+
return false, fmt.Errorf("failed to check if user is admin to verify is default branch protection is enabled on instance-level: %w", err)
1850+
}
1851+
1852+
if isAdmin {
1853+
// If the project is not part of a group it may have default branch protection disabled because of the instance-wide application settings
1854+
settings, _, err := client.Settings.GetSettings(nil, gitlab.WithContext(ctx))
1855+
if err != nil {
1856+
return false, err
1857+
}
1858+
1859+
return settings.DefaultBranchProtection != 0, nil
18311860
}
18321861

1833-
return settings.DefaultBranchProtection != 0, nil
1862+
// NOTE: for the lack of a better solution (at least for now), we assume that the default branch protection is NOT disabled on instance-level.
1863+
// To override this behavior it's best to set `skip_wait_for_default_branch_protection = true` in the resource config.
1864+
return true, nil
18341865
}

internal/provider/resource_gitlab_project_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,67 @@ func TestAccGitlabProject_InstanceBranchProtectionDisabled(t *testing.T) {
977977
ImportStateVerify: true,
978978
ImportStateVerifyIgnore: []string{"initialize_with_readme"},
979979
},
980+
// Force a destroy for the project so that it can be recreated as the same resource
981+
{
982+
Config: ` `, // requires a space for empty config
983+
},
984+
// With `skip_wait_for_default_branch_protection` enabled
985+
{
986+
Config: fmt.Sprintf(`
987+
resource "gitlab_project" "foo" {
988+
name = "foo-%d-custom-default-branch"
989+
description = "Terraform acceptance tests"
990+
visibility_level = "public"
991+
initialize_with_readme = true
992+
993+
skip_wait_for_default_branch_protection = true
994+
}
995+
`, rInt),
996+
},
997+
// Verify Import
998+
{
999+
ResourceName: "gitlab_project.foo",
1000+
ImportState: true,
1001+
ImportStateVerify: true,
1002+
ImportStateVerifyIgnore: []string{"initialize_with_readme", "skip_wait_for_default_branch_protection"},
1003+
},
1004+
// Force a destroy for the project so that it can be recreated as the same resource
1005+
{
1006+
Config: ` `, // requires a space for empty config
1007+
},
1008+
{
1009+
Config: fmt.Sprintf(`
1010+
resource "gitlab_project" "foo" {
1011+
name = "foo-%d-custom-default-branch"
1012+
description = "Terraform acceptance tests"
1013+
visibility_level = "public"
1014+
initialize_with_readme = true
1015+
1016+
skip_wait_for_default_branch_protection = false
1017+
}
1018+
`, rInt),
1019+
},
1020+
// Check if plan is empty after changing `skip_wait_for_default_branch_protection` attribute
1021+
{
1022+
Config: fmt.Sprintf(`
1023+
resource "gitlab_project" "foo" {
1024+
name = "foo-%d-custom-default-branch"
1025+
description = "Terraform acceptance tests"
1026+
visibility_level = "public"
1027+
initialize_with_readme = true
1028+
1029+
skip_wait_for_default_branch_protection = true
1030+
}
1031+
`, rInt),
1032+
PlanOnly: true,
1033+
},
1034+
// Verify Import
1035+
{
1036+
ResourceName: "gitlab_project.foo",
1037+
ImportState: true,
1038+
ImportStateVerify: true,
1039+
ImportStateVerifyIgnore: []string{"initialize_with_readme", "skip_wait_for_default_branch_protection"},
1040+
},
9801041
},
9811042
})
9821043
}

internal/provider/util.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -312,17 +312,13 @@ func is404(err error) bool {
312312
return false
313313
}
314314

315-
func isCurrentUserAdmin(client *gitlab.Client) (bool, error) {
316-
currentUser, _, err := client.Users.CurrentUser()
315+
func isCurrentUserAdmin(ctx context.Context, client *gitlab.Client) (bool, error) {
316+
currentUser, _, err := client.Users.CurrentUser(gitlab.WithContext(ctx))
317317
if err != nil {
318318
return false, err
319319
}
320320

321-
if currentUser.IsAdmin {
322-
return true, nil
323-
} else {
324-
return false, nil
325-
}
321+
return currentUser.IsAdmin, nil
326322
}
327323

328324
// ISO 8601 date format

0 commit comments

Comments
 (0)