Skip to content

Commit cd90c29

Browse files
authored
Merge pull request #1169 from timofurrer/bugfix/1167
resource/gitlab_project: Fix admin token requirement to check default branch protection
2 parents d4b7e10 + 0176da5 commit cd90c29

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)