Skip to content

Commit d019ff4

Browse files
committed
gitlab_project resource: make default_branch attribute configurable on creation
- Made default_branch a computed attribute - Removed the DiffSuppressFunc from default_branch so that we can use it during creation - Implemented logic to set the default branch in cases where a repository exists (if the project was created with a readme, as a mirror, or from a template) - Refactored the mirror code to be more explicit, instead of calling the update function from inside the create function. There was an error being returned here, which was being ignored.
1 parent 352ae8c commit d019ff4

File tree

3 files changed

+189
-117
lines changed

3 files changed

+189
-117
lines changed

gitlab/resource_gitlab_project.go

Lines changed: 134 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -44,35 +44,7 @@ var resourceGitLabProjectSchema = map[string]*schema.Schema{
4444
"default_branch": {
4545
Type: schema.TypeString,
4646
Optional: true,
47-
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
48-
// `old` is the current value on GitLab side
49-
// `new` is the value that Terraform plans to set there
50-
51-
log.Printf("[DEBUG] default_branch DiffSuppressFunc old new")
52-
log.Printf("[DEBUG] (%T) %#v, (%T) %#v", old, old, new, new)
53-
54-
// If there is no current default branch, it means that the project is
55-
// empty and does not have branches. Setting the default branch will fail
56-
// with 400 error. The check will defer the setting of a default branch
57-
// to a time when the repository is no longer empty.
58-
if old == "" {
59-
if new != "" {
60-
log.Printf("[WARN] not setting default_branch %#v on empty repo", new)
61-
}
62-
return true
63-
}
64-
65-
// For non-empty repositories GitLab automatically sets master as the
66-
// default branch. If the project resource doesn't specify default_branch
67-
// attribute, Terraform will force "master" => "" on the next run. This
68-
// check makes Terraform ignore default branch value until it is set in
69-
// .tf configuration. For schema.TypeString empty is equal to "".
70-
if new == "" {
71-
return true
72-
}
73-
74-
return old == new
75-
},
47+
Computed: true,
7648
},
7749
"import_url": {
7850
Type: schema.TypeString,
@@ -275,25 +247,31 @@ var resourceGitLabProjectSchema = map[string]*schema.Schema{
275247
Default: "private",
276248
ValidateFunc: validation.StringInSlice([]string{"public", "private", "enabled", "disabled"}, true),
277249
},
250+
// The GitLab API requires that import_url is also set when mirror options are used
251+
// Ref: https://github.com/gitlabhq/terraform-provider-gitlab/pull/449#discussion_r549729230
278252
"mirror": {
279-
Type: schema.TypeBool,
280-
Optional: true,
281-
Default: false,
253+
Type: schema.TypeBool,
254+
Optional: true,
255+
Default: false,
256+
RequiredWith: []string{"import_url"},
282257
},
283258
"mirror_trigger_builds": {
284-
Type: schema.TypeBool,
285-
Optional: true,
286-
Default: false,
259+
Type: schema.TypeBool,
260+
Optional: true,
261+
Default: false,
262+
RequiredWith: []string{"import_url"},
287263
},
288264
"mirror_overwrites_diverged_branches": {
289-
Type: schema.TypeBool,
290-
Optional: true,
291-
Default: false,
265+
Type: schema.TypeBool,
266+
Optional: true,
267+
Default: false,
268+
RequiredWith: []string{"import_url"},
292269
},
293270
"only_mirror_protected_branches": {
294-
Type: schema.TypeBool,
295-
Optional: true,
296-
Default: false,
271+
Type: schema.TypeBool,
272+
Optional: true,
273+
Default: false,
274+
RequiredWith: []string{"import_url"},
297275
},
298276
"build_coverage_regex": {
299277
Type: schema.TypeString,
@@ -316,43 +294,44 @@ func resourceGitlabProject() *schema.Resource {
316294

317295
func resourceGitlabProjectSetToState(d *schema.ResourceData, project *gitlab.Project) error {
318296
d.SetId(fmt.Sprintf("%d", project.ID))
319-
values := map[string]interface{}{
320-
"name": project.Name,
321-
"path": project.Path,
322-
"path_with_namespace": project.PathWithNamespace,
323-
"description": project.Description,
324-
"default_branch": project.DefaultBranch,
325-
"request_access_enabled": project.RequestAccessEnabled,
326-
"issues_enabled": project.IssuesEnabled,
327-
"merge_requests_enabled": project.MergeRequestsEnabled,
328-
"pipelines_enabled": project.JobsEnabled,
329-
"approvals_before_merge": project.ApprovalsBeforeMerge,
330-
"wiki_enabled": project.WikiEnabled,
331-
"snippets_enabled": project.SnippetsEnabled,
332-
"container_registry_enabled": project.ContainerRegistryEnabled,
333-
"lfs_enabled": project.LFSEnabled,
334-
"visibility_level": string(project.Visibility),
335-
"merge_method": string(project.MergeMethod),
336-
"only_allow_merge_if_pipeline_succeeds": project.OnlyAllowMergeIfPipelineSucceeds,
337-
"only_allow_merge_if_all_discussions_are_resolved": project.OnlyAllowMergeIfAllDiscussionsAreResolved,
338-
"namespace_id": project.Namespace.ID,
339-
"ssh_url_to_repo": project.SSHURLToRepo,
340-
"http_url_to_repo": project.HTTPURLToRepo,
341-
"web_url": project.WebURL,
342-
"runners_token": project.RunnersToken,
343-
"shared_runners_enabled": project.SharedRunnersEnabled,
344-
"tags": project.TagList,
345-
"archived": project.Archived,
346-
"remove_source_branch_after_merge": project.RemoveSourceBranchAfterMerge,
347-
"packages_enabled": project.PackagesEnabled,
348-
"pages_access_level": string(project.PagesAccessLevel),
349-
"mirror": project.Mirror,
350-
"mirror_trigger_builds": project.MirrorTriggerBuilds,
351-
"mirror_overwrites_diverged_branches": project.MirrorOverwritesDivergedBranches,
352-
"only_mirror_protected_branches": project.OnlyMirrorProtectedBranches,
353-
"build_coverage_regex": project.BuildCoverageRegex,
354-
}
355-
return setResourceData(d, values)
297+
d.Set("name", project.Name)
298+
d.Set("path", project.Path)
299+
d.Set("path_with_namespace", project.PathWithNamespace)
300+
d.Set("description", project.Description)
301+
d.Set("default_branch", project.DefaultBranch)
302+
d.Set("request_access_enabled", project.RequestAccessEnabled)
303+
d.Set("issues_enabled", project.IssuesEnabled)
304+
d.Set("merge_requests_enabled", project.MergeRequestsEnabled)
305+
d.Set("pipelines_enabled", project.JobsEnabled)
306+
d.Set("approvals_before_merge", project.ApprovalsBeforeMerge)
307+
d.Set("wiki_enabled", project.WikiEnabled)
308+
d.Set("snippets_enabled", project.SnippetsEnabled)
309+
d.Set("container_registry_enabled", project.ContainerRegistryEnabled)
310+
d.Set("lfs_enabled", project.LFSEnabled)
311+
d.Set("visibility_level", string(project.Visibility))
312+
d.Set("merge_method", string(project.MergeMethod))
313+
d.Set("only_allow_merge_if_pipeline_succeeds", project.OnlyAllowMergeIfPipelineSucceeds)
314+
d.Set("only_allow_merge_if_all_discussions_are_resolved", project.OnlyAllowMergeIfAllDiscussionsAreResolved)
315+
d.Set("namespace_id", project.Namespace.ID)
316+
d.Set("ssh_url_to_repo", project.SSHURLToRepo)
317+
d.Set("http_url_to_repo", project.HTTPURLToRepo)
318+
d.Set("web_url", project.WebURL)
319+
d.Set("runners_token", project.RunnersToken)
320+
d.Set("shared_runners_enabled", project.SharedRunnersEnabled)
321+
if err := d.Set("tags", project.TagList); err != nil {
322+
return err
323+
}
324+
d.Set("archived", project.Archived)
325+
d.Set("remove_source_branch_after_merge", project.RemoveSourceBranchAfterMerge)
326+
d.Set("packages_enabled", project.PackagesEnabled)
327+
d.Set("pages_access_level", string(project.PagesAccessLevel))
328+
d.Set("mirror", project.Mirror)
329+
d.Set("mirror_trigger_builds", project.MirrorTriggerBuilds)
330+
d.Set("mirror_overwrites_diverged_branches", project.MirrorOverwritesDivergedBranches)
331+
d.Set("only_mirror_protected_branches", project.OnlyMirrorProtectedBranches)
332+
d.Set("build_coverage_regex", project.BuildCoverageRegex)
333+
334+
return nil
356335
}
357336

358337
func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error {
@@ -393,6 +372,10 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error
393372
options.Description = gitlab.String(v.(string))
394373
}
395374

375+
if v, ok := d.GetOk("default_branch"); ok {
376+
options.DefaultBranch = gitlab.String(v.(string))
377+
}
378+
396379
if v, ok := d.GetOk("tags"); ok {
397380
options.TagList = stringSetToStringSlice(v.(*schema.Set))
398381
}
@@ -436,10 +419,8 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error
436419
// is committed to state since we set its ID
437420
d.SetId(fmt.Sprintf("%d", project.ID))
438421

439-
_, importURLSet := d.GetOk("import_url")
440-
_, templateNameSet := d.GetOk("template_name")
441-
_, templateProjectIDSet := d.GetOk("template_project_id")
442-
if importURLSet || templateNameSet || templateProjectIDSet {
422+
// An import can be triggered by import_url or by creating the project from a template.
423+
if project.ImportStatus != "none" {
443424
log.Printf("[DEBUG] waiting for project %q import to finish", *options.Name)
444425

445426
stateConf := &resource.StateChangeConf{
@@ -459,6 +440,12 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error
459440
if _, err := stateConf.WaitForState(); err != nil {
460441
return fmt.Errorf("error while waiting for project %q import to finish: %w", *options.Name, err)
461442
}
443+
444+
// Read the project again, so that we can detect the default branch.
445+
project, _, err = client.Projects.GetProject(project.ID, nil)
446+
if err != nil {
447+
return fmt.Errorf("Failed to get project %q after completing import: %w", d.Id(), err)
448+
}
462449
}
463450

464451
if d.Get("archived").(bool) {
@@ -480,9 +467,69 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error
480467
}
481468
}
482469

483-
// Some project settings can't be set in the Project Create API and have to
484-
// set in a second call after project creation.
485-
resourceGitlabProjectUpdate(d, meta) // nolint // TODO: Resolve this golangci-lint issue: Error return value is not checked (errcheck)
470+
// default_branch cannot always be set during creation.
471+
// If the branch does not exist, the update will fail, so we also create it here.
472+
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/333426
473+
// This logic may be removed when the above issue is resolved.
474+
if v, ok := d.GetOk("default_branch"); ok && project.DefaultBranch != "" && project.DefaultBranch != v.(string) {
475+
oldDefaultBranch := project.DefaultBranch
476+
newDefaultBranch := v.(string)
477+
478+
log.Printf("[DEBUG] create branch %q for project %q", newDefaultBranch, d.Id())
479+
_, _, err := client.Branches.CreateBranch(project.ID, &gitlab.CreateBranchOptions{
480+
Branch: gitlab.String(newDefaultBranch),
481+
Ref: gitlab.String(oldDefaultBranch),
482+
})
483+
if err != nil {
484+
return fmt.Errorf("Failed to create branch %q for project %q: %w", newDefaultBranch, d.Id(), err)
485+
}
486+
487+
log.Printf("[DEBUG] set new default branch to %q for project %q", newDefaultBranch, d.Id())
488+
_, _, err = client.Projects.EditProject(project.ID, &gitlab.EditProjectOptions{
489+
DefaultBranch: gitlab.String(newDefaultBranch),
490+
})
491+
if err != nil {
492+
return fmt.Errorf("Failed to set default branch to %q for project %q: %w", newDefaultBranch, d.Id(), err)
493+
}
494+
495+
log.Printf("[DEBUG] protect new default branch %q for project %q", newDefaultBranch, d.Id())
496+
_, _, err = client.ProtectedBranches.ProtectRepositoryBranches(project.ID, &gitlab.ProtectRepositoryBranchesOptions{
497+
Name: gitlab.String(newDefaultBranch),
498+
})
499+
if err != nil {
500+
return fmt.Errorf("Failed to protect default branch %q for project %q: %w", newDefaultBranch, d.Id(), err)
501+
}
502+
503+
log.Printf("[DEBUG] unprotect old default branch %q for project %q", oldDefaultBranch, d.Id())
504+
_, err = client.ProtectedBranches.UnprotectRepositoryBranches(project.ID, oldDefaultBranch)
505+
if err != nil {
506+
return fmt.Errorf("Failed to unprotect undesired default branch %q for project %q: %w", oldDefaultBranch, d.Id(), err)
507+
}
508+
509+
log.Printf("[DEBUG] delete old default branch %q for project %q", oldDefaultBranch, d.Id())
510+
_, err = client.Branches.DeleteBranch(project.ID, oldDefaultBranch)
511+
if err != nil {
512+
return fmt.Errorf("Failed to clean up undesired default branch %q for project %q: %w", oldDefaultBranch, d.Id(), err)
513+
}
514+
}
515+
516+
var editProjectOptions gitlab.EditProjectOptions
517+
518+
if v, ok := d.GetOk("mirror_overwrites_diverged_branches"); ok {
519+
editProjectOptions.MirrorOverwritesDivergedBranches = gitlab.Bool(v.(bool))
520+
editProjectOptions.ImportURL = gitlab.String(d.Get("import_url").(string))
521+
}
522+
523+
if v, ok := d.GetOk("only_mirror_protected_branches"); ok {
524+
editProjectOptions.OnlyMirrorProtectedBranches = gitlab.Bool(v.(bool))
525+
editProjectOptions.ImportURL = gitlab.String(d.Get("import_url").(string))
526+
}
527+
528+
if (editProjectOptions != gitlab.EditProjectOptions{}) {
529+
if _, _, err := client.Projects.EditProject(d.Id(), &editProjectOptions); err != nil {
530+
return fmt.Errorf("Could not update project %q: %w", d.Id(), err)
531+
}
532+
}
486533

487534
return resourceGitlabProjectRead(d, meta)
488535
}
@@ -501,8 +548,7 @@ func resourceGitlabProjectRead(d *schema.ResourceData, meta interface{}) error {
501548
return nil
502549
}
503550

504-
err = resourceGitlabProjectSetToState(d, project)
505-
if err != nil {
551+
if err := resourceGitlabProjectSetToState(d, project); err != nil {
506552
return err
507553
}
508554

@@ -516,9 +562,7 @@ func resourceGitlabProjectRead(d *schema.ResourceData, meta interface{}) error {
516562
return fmt.Errorf("Failed to get push rules for project %q: %w", d.Id(), err)
517563
}
518564

519-
d.Set("push_rules", flattenProjectPushRules(pushRules)) // lintignore: XR004 // TODO: Resolve this tfproviderlint issue
520-
521-
return nil
565+
return d.Set("push_rules", flattenProjectPushRules(pushRules))
522566
}
523567

524568
func resourceGitlabProjectUpdate(d *schema.ResourceData, meta interface{}) error {
@@ -620,29 +664,21 @@ func resourceGitlabProjectUpdate(d *schema.ResourceData, meta interface{}) error
620664
}
621665

622666
if d.HasChange("mirror") {
623-
// It appears that GitLab API requires that import_url is also set when `mirror` is updated/changed
624-
// Ref: https://github.com/gitlabhq/terraform-provider-gitlab/pull/449#discussion_r549729230
625667
options.ImportURL = gitlab.String(d.Get("import_url").(string))
626668
options.Mirror = gitlab.Bool(d.Get("mirror").(bool))
627669
}
628670

629671
if d.HasChange("mirror_trigger_builds") {
630-
// It appears that GitLab API requires that import_url is also set when `mirror_trigger_builds` is updated/changed
631-
// Ref: https://github.com/gitlabhq/terraform-provider-gitlab/pull/449#discussion_r549729230
632672
options.ImportURL = gitlab.String(d.Get("import_url").(string))
633673
options.MirrorTriggerBuilds = gitlab.Bool(d.Get("mirror_trigger_builds").(bool))
634674
}
635675

636676
if d.HasChange("only_mirror_protected_branches") {
637-
// It appears that GitLab API requires that import_url is also set when `only_mirror_protected_branches` is updated/changed
638-
// Ref: https://github.com/gitlabhq/terraform-provider-gitlab/pull/449#discussion_r549729230
639677
options.ImportURL = gitlab.String(d.Get("import_url").(string))
640678
options.OnlyMirrorProtectedBranches = gitlab.Bool(d.Get("only_mirror_protected_branches").(bool))
641679
}
642680

643681
if d.HasChange("mirror_overwrites_diverged_branches") {
644-
// It appears that GitLab API requires that import_url is also set when `mirror_overwrites_diverged_branches` is updated/changed
645-
// Ref: https://github.com/gitlabhq/terraform-provider-gitlab/pull/449#discussion_r549729230
646682
options.ImportURL = gitlab.String(d.Get("import_url").(string))
647683
options.MirrorOverwritesDivergedBranches = gitlab.Bool(d.Get("mirror_overwrites_diverged_branches").(bool))
648684
}

0 commit comments

Comments
 (0)