Skip to content

Commit 29e45e7

Browse files
Add gitlab_group_project_file_template resource
Add tests for gitlab_group_project_file_template Fix bad destroy test, add API workaround, update documentation Fix formatting on Upstream API documentation to pass Regex Update examples/resources/gitlab_group_project_file_template/resource.tf Co-authored-by: Timo Furrer <[email protected]> Refactor tests to manage group and project externally Also update the method used to overwrite the omitempty and regenerate documents Removed Dead Code Fix Linting Issues Update internal/provider/resource_gitlab_group_project_file_template.go Co-authored-by: Timo Furrer <[email protected]> Update internal/provider/resource_gitlab_group_project_file_template.go Co-authored-by: Timo Furrer <[email protected]> Update internal/provider/resource_gitlab_group_project_file_template.go Co-authored-by: Timo Furrer <[email protected]> Refactor test data to use helper_test
1 parent d18aee4 commit 29e45e7

File tree

6 files changed

+326
-9
lines changed

6 files changed

+326
-9
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "gitlab_group_project_file_template Resource - terraform-provider-gitlab"
4+
subcategory: ""
5+
description: |-
6+
The gitlab_group_project_file_template resource allows setting a project from which
7+
custom file templates will be loaded. The project selected must be a direct child of the group identified.
8+
For more information about which file types are available as templates, view
9+
GitLab's documentation https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html#supported-file-types-and-locations
10+
-> This resource requires a GitLab Enterprise instance with a Premium license.
11+
Upstream API: GitLab REST API docs https://docs.gitlab.com/ee/api/groups.html#update-group
12+
---
13+
14+
# gitlab_group_project_file_template (Resource)
15+
16+
The `gitlab_group_project_file_template` resource allows setting a project from which
17+
custom file templates will be loaded. The project selected must be a direct child of the group identified.
18+
For more information about which file types are available as templates, view
19+
[GitLab's documentation](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html#supported-file-types-and-locations)
20+
21+
-> This resource requires a GitLab Enterprise instance with a Premium license.
22+
23+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/groups.html#update-group)
24+
25+
## Example Usage
26+
27+
```terraform
28+
resource "gitlab_group" "foo" {
29+
name = "group"
30+
path = "group"
31+
description = "An example group"
32+
}
33+
34+
resource "gitlab_project" "bar" {
35+
name = "template project"
36+
description = "contains file templates"
37+
visibility_level = "public"
38+
39+
namespace_id = gitlab_group.foo.id
40+
}
41+
42+
resource "gitlab_group_project_file_template" "template_link" {
43+
group_id = gitlab_group.foo.id
44+
project = gitlab_project.bar.id
45+
}
46+
```
47+
48+
<!-- schema generated by tfplugindocs -->
49+
## Schema
50+
51+
### Required
52+
53+
- `file_template_project_id` (Number) The ID of the project that will be used for file templates. This project must be the direct
54+
child of the project defined by the group_id
55+
- `group_id` (Number) The ID of the group that will use the file template project. This group must be the direct
56+
parent of the project defined by project_id
57+
58+
### Optional
59+
60+
- `id` (String) The ID of this resource.
61+
62+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
resource "gitlab_group" "foo" {
2+
name = "group"
3+
path = "group"
4+
description = "An example group"
5+
}
6+
7+
resource "gitlab_project" "bar" {
8+
name = "template project"
9+
description = "contains file templates"
10+
visibility_level = "public"
11+
12+
namespace_id = gitlab_group.foo.id
13+
}
14+
15+
resource "gitlab_group_project_file_template" "template_link" {
16+
group_id = gitlab_group.foo.id
17+
project = gitlab_project.bar.id
18+
}

internal/provider/helper_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,29 @@ func testAccCurrentUser(t *testing.T) *gitlab.User {
118118

119119
// testAccCreateProject is a test helper for creating a project.
120120
func testAccCreateProject(t *testing.T) *gitlab.Project {
121+
return testAccCreateProjectWithNamespace(t, 0)
122+
}
123+
124+
// testAccCreateProject is a test helper for creating a project. This method accepts a namespace to great a project
125+
// within a group
126+
func testAccCreateProjectWithNamespace(t *testing.T, namespaceID int) *gitlab.Project {
121127
t.Helper()
122128

123-
project, _, err := testGitlabClient.Projects.CreateProject(&gitlab.CreateProjectOptions{
129+
options := &gitlab.CreateProjectOptions{
124130
Name: gitlab.String(acctest.RandomWithPrefix("acctest")),
125131
Description: gitlab.String("Terraform acceptance tests"),
126132
// So that acceptance tests can be run in a gitlab organization with no billing.
127133
Visibility: gitlab.Visibility(gitlab.PublicVisibility),
128134
// So that a branch is created.
129135
InitializeWithReadme: gitlab.Bool(true),
130-
})
136+
}
137+
138+
//Apply a namespace if one is passed in.
139+
if namespaceID != 0 {
140+
options.NamespaceID = gitlab.Int(namespaceID)
141+
}
142+
143+
project, _, err := testGitlabClient.Projects.CreateProject(options)
131144
if err != nil {
132145
t.Fatalf("could not create test project: %v", err)
133146
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"github.com/hashicorp/go-retryablehttp"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
gitlab "github.com/xanzy/go-gitlab"
11+
"log"
12+
)
13+
14+
var _ = registerResource("gitlab_group_project_file_template", func() *schema.Resource {
15+
return &schema.Resource{
16+
Description: `The ` + "`gitlab_group_project_file_template`" + ` resource allows setting a project from which
17+
custom file templates will be loaded. The project selected must be a direct child of the group identified.
18+
For more information about which file types are available as templates, view
19+
[GitLab's documentation](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html#supported-file-types-and-locations)
20+
21+
-> This resource requires a GitLab Enterprise instance with a Premium license.
22+
23+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/groups.html#update-group)`,
24+
25+
// Since this resource updates an in-place resource, the update method is the same as the create method
26+
CreateContext: resourceGitLabGroupProjectFileTemplateCreateOrUpdate,
27+
UpdateContext: resourceGitLabGroupProjectFileTemplateCreateOrUpdate,
28+
ReadContext: resourceGitLabGroupProjectFileTemplateRead,
29+
DeleteContext: resourceGitLabGroupProjectFileTemplateDelete,
30+
// Since this resource updates an in-place resource, importing doesn't make much sense. Simply add the resource
31+
// to the config and terraform will overwrite what's already in place and manage it from there.
32+
Schema: map[string]*schema.Schema{
33+
"group_id": {
34+
Description: `The ID of the group that will use the file template project. This group must be the direct
35+
parent of the project defined by project_id`,
36+
Type: schema.TypeInt,
37+
38+
// Even though there is no traditional resource to create, leave "ForceNew" as "true" so that if someone
39+
// changes a configuration to a different group, the old group gets "deleted" (updated to have a value
40+
// of 0).
41+
ForceNew: true,
42+
Required: true,
43+
},
44+
"file_template_project_id": {
45+
Description: `The ID of the project that will be used for file templates. This project must be the direct
46+
child of the project defined by the group_id`,
47+
Type: schema.TypeInt,
48+
Required: true,
49+
},
50+
},
51+
}
52+
})
53+
54+
func resourceGitLabGroupProjectFileTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
55+
client := meta.(*gitlab.Client)
56+
57+
groupID := d.Get("group_id").(int)
58+
group, _, err := client.Groups.GetGroup(groupID, nil, gitlab.WithContext(ctx))
59+
if err != nil {
60+
if is404(err) {
61+
log.Printf("[DEBUG] gitlab group %d not found, removing from state", groupID)
62+
d.SetId("")
63+
return nil
64+
}
65+
return diag.FromErr(err)
66+
}
67+
if group.MarkedForDeletionOn != nil {
68+
log.Printf("[DEBUG] gitlab group %s is marked for deletion, removing from state", d.Id())
69+
d.SetId("")
70+
return nil
71+
}
72+
73+
d.SetId(fmt.Sprintf("%d", group.ID))
74+
d.Set("file_template_project_id", group.FileTemplateProjectID)
75+
76+
return nil
77+
}
78+
79+
func resourceGitLabGroupProjectFileTemplateCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
80+
client := meta.(*gitlab.Client)
81+
82+
groupID := d.Get("group_id").(int)
83+
projectID := gitlab.Int(d.Get("file_template_project_id").(int))
84+
85+
// Creating the resource means updating the existing group to link the project to the group.
86+
options := &gitlab.UpdateGroupOptions{}
87+
if d.HasChanges("file_template_project_id") {
88+
options.FileTemplateProjectID = gitlab.Int(d.Get("file_template_project_id").(int))
89+
}
90+
91+
_, _, err := client.Groups.UpdateGroup(groupID, options)
92+
if err != nil {
93+
return diag.Errorf("unable to update group %d with `file_template_project_id` set to %d: %s", groupID, projectID, err)
94+
}
95+
return resourceGitLabGroupProjectFileTemplateRead(ctx, d, meta)
96+
}
97+
98+
func resourceGitLabGroupProjectFileTemplateDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
99+
client := meta.(*gitlab.Client)
100+
groupID := d.Get("group_id").(int)
101+
options := &gitlab.UpdateGroupOptions{}
102+
103+
_, _, err := updateGroupWithOverwrittenFileTemplateOption(client, groupID, options)
104+
if err != nil {
105+
return diag.Errorf("could not update group %d to remove file template ID: %s", groupID, err)
106+
}
107+
return resourceGitLabGroupProjectFileTemplateRead(ctx, d, meta)
108+
}
109+
110+
func updateGroupWithOverwrittenFileTemplateOption(client *gitlab.Client, groupID int, options *gitlab.UpdateGroupOptions) (*gitlab.Group, *gitlab.Response, error) {
111+
return client.Groups.UpdateGroup(groupID, options, func(request *retryablehttp.Request) error {
112+
//Overwrite the GroupUpdateOptions struct to remove the "omitempty", which forces the client to send an empty
113+
//string in just this request.
114+
removeOmitEmptyOptions := struct {
115+
FileTemplateProjectID *string `url:"file_template_project_id" json:"file_template_project_id"`
116+
}{
117+
FileTemplateProjectID: nil,
118+
}
119+
120+
//Create the new body request with the above struct
121+
newBody, err := json.Marshal(removeOmitEmptyOptions)
122+
if err != nil {
123+
return err
124+
}
125+
126+
//Set the request body to have the newly updated body
127+
err = request.SetBody(newBody)
128+
if err != nil {
129+
return err
130+
}
131+
132+
return nil
133+
})
134+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package provider
2+
3+
import (
4+
"fmt"
5+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
7+
"github.com/xanzy/go-gitlab"
8+
"strconv"
9+
"testing"
10+
)
11+
12+
func TestAccGitlabGroupProjectFileTemplate_basic(t *testing.T) {
13+
// Since we do some manual setup in this test, we need to handle the test skip first.
14+
testAccCheck(t)
15+
baseGroup := testAccCreateGroups(t, 1)[0]
16+
firstProject := testAccCreateProjectWithNamespace(t, baseGroup.ID)
17+
secondProject := testAccCreateProjectWithNamespace(t, baseGroup.ID)
18+
19+
resource.Test(t, resource.TestCase{
20+
PreCheck: func() { testAccPreCheck(t) },
21+
ProviderFactories: providerFactories,
22+
CheckDestroy: testAccCheckProjectFileTemplateDestroy,
23+
Steps: []resource.TestStep{
24+
{
25+
SkipFunc: isRunningInCE,
26+
Config: testAccGroupProjectFileTemplateConfig(baseGroup.ID, firstProject.ID),
27+
Check: resource.ComposeTestCheckFunc(
28+
// Note - we can't use the testAccCheckGitlabGroupAttributes, because that checks the TF
29+
// state attributes, and file project template explicitly doesn't exist there.
30+
testAccCheckGitlabGroupFileTemplateValue(baseGroup, firstProject),
31+
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "group_id", strconv.Itoa(baseGroup.ID)),
32+
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "file_template_project_id", strconv.Itoa(firstProject.ID)),
33+
),
34+
},
35+
{
36+
//Test that when we update the project name, it re-links the group to the new project
37+
SkipFunc: isRunningInCE,
38+
Config: testAccGroupProjectFileTemplateConfig(baseGroup.ID, secondProject.ID),
39+
Check: resource.ComposeTestCheckFunc(
40+
testAccCheckGitlabGroupFileTemplateValue(baseGroup, secondProject),
41+
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "group_id", strconv.Itoa(baseGroup.ID)),
42+
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "file_template_project_id", strconv.Itoa(secondProject.ID)),
43+
),
44+
},
45+
},
46+
},
47+
)
48+
}
49+
50+
func testAccCheckGitlabGroupFileTemplateValue(g *gitlab.Group, p *gitlab.Project) resource.TestCheckFunc {
51+
return func(s *terraform.State) error {
52+
//Re-retrieve the group to ensure we have the most up-to-date group info
53+
g, _, err := testGitlabClient.Groups.GetGroup(g.ID, &gitlab.GetGroupOptions{})
54+
if is404(err) {
55+
return fmt.Errorf("Group no longer exists, expected group to exist with a file_template_project_id")
56+
}
57+
58+
if g.FileTemplateProjectID == p.ID {
59+
return nil
60+
}
61+
return fmt.Errorf("Group file_template_project_id doesn't match. Wanted %d, received %d", p.ID, g.FileTemplateProjectID)
62+
}
63+
}
64+
65+
func testAccCheckProjectFileTemplateDestroy(state *terraform.State) error {
66+
for _, rs := range state.RootModule().Resources {
67+
if rs.Type != "gitlab_group_project_file_template" {
68+
continue
69+
}
70+
71+
// To test if the resource was destroyed, we need to retrieve the group.
72+
gid := rs.Primary.ID
73+
group, _, err := testGitlabClient.Groups.GetGroup(gid, nil)
74+
if err != nil {
75+
return err
76+
}
77+
78+
// the test should succeed if the group is still present and has a 0 file_template_project_id value
79+
if group != nil && group.FileTemplateProjectID != 0 {
80+
return fmt.Errorf("Group still has a template project attached")
81+
}
82+
return nil
83+
}
84+
return nil
85+
}
86+
87+
func testAccGroupProjectFileTemplateConfig(groupID int, projectID int) string {
88+
return fmt.Sprintf(
89+
`
90+
resource "gitlab_group_project_file_template" "linking_template" {
91+
group_id = %d
92+
file_template_project_id = %d
93+
}
94+
`, groupID, projectID)
95+
}

internal/provider/resource_gitlab_project_test.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package provider
55
import (
66
"errors"
77
"fmt"
8-
"os"
98
"regexp"
109
"strings"
1110
"testing"
@@ -773,9 +772,7 @@ func TestAccGitlabProject_transfer(t *testing.T) {
773772
// lintignore: AT002 // not a Terraform import test
774773
func TestAccGitlabProject_importURL(t *testing.T) {
775774
// Since we do some manual setup in this test, we need to handle the test skip first.
776-
if os.Getenv(resource.EnvTfAcc) == "" {
777-
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", resource.EnvTfAcc))
778-
}
775+
testAccCheck(t)
779776

780777
rInt := acctest.RandInt()
781778

@@ -901,9 +898,7 @@ func testAccCheckGitlabProjectMirroredAttributes(project *gitlab.Project, want *
901898
// lintignore: AT002 // not a Terraform import test
902899
func TestAccGitlabProject_importURLMirrored(t *testing.T) {
903900
// Since we do some manual setup in this test, we need to handle the test skip first.
904-
if os.Getenv(resource.EnvTfAcc) == "" {
905-
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", resource.EnvTfAcc))
906-
}
901+
testAccCheck(t)
907902

908903
var mirror gitlab.Project
909904
rInt := acctest.RandInt()

0 commit comments

Comments
 (0)