Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #971 from PatrickRice-KSC/add-project-file-templat…
…e-resource Add new resource for linking Project to Group as file_project_template
- Loading branch information
Showing
6 changed files
with
326 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "gitlab_group_project_file_template Resource - terraform-provider-gitlab" | ||
subcategory: "" | ||
description: |- | ||
The gitlab_group_project_file_template resource allows setting a project from which | ||
custom file templates will be loaded. The project selected must be a direct child of the group identified. | ||
For more information about which file types are available as templates, view | ||
GitLab's documentation https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html#supported-file-types-and-locations | ||
-> This resource requires a GitLab Enterprise instance with a Premium license. | ||
Upstream API: GitLab REST API docs https://docs.gitlab.com/ee/api/groups.html#update-group | ||
--- | ||
|
||
# gitlab_group_project_file_template (Resource) | ||
|
||
The `gitlab_group_project_file_template` resource allows setting a project from which | ||
custom file templates will be loaded. The project selected must be a direct child of the group identified. | ||
For more information about which file types are available as templates, view | ||
[GitLab's documentation](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html#supported-file-types-and-locations) | ||
|
||
-> This resource requires a GitLab Enterprise instance with a Premium license. | ||
|
||
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/groups.html#update-group) | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
resource "gitlab_group" "foo" { | ||
name = "group" | ||
path = "group" | ||
description = "An example group" | ||
} | ||
resource "gitlab_project" "bar" { | ||
name = "template project" | ||
description = "contains file templates" | ||
visibility_level = "public" | ||
namespace_id = gitlab_group.foo.id | ||
} | ||
resource "gitlab_group_project_file_template" "template_link" { | ||
group_id = gitlab_group.foo.id | ||
project = gitlab_project.bar.id | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `file_template_project_id` (Number) The ID of the project that will be used for file templates. This project must be the direct | ||
child of the project defined by the group_id | ||
- `group_id` (Number) The ID of the group that will use the file template project. This group must be the direct | ||
parent of the project defined by project_id | ||
|
||
### Optional | ||
|
||
- `id` (String) The ID of this resource. | ||
|
||
|
18 changes: 18 additions & 0 deletions
18
examples/resources/gitlab_group_project_file_template/resource.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
resource "gitlab_group" "foo" { | ||
name = "group" | ||
path = "group" | ||
description = "An example group" | ||
} | ||
|
||
resource "gitlab_project" "bar" { | ||
name = "template project" | ||
description = "contains file templates" | ||
visibility_level = "public" | ||
|
||
namespace_id = gitlab_group.foo.id | ||
} | ||
|
||
resource "gitlab_group_project_file_template" "template_link" { | ||
group_id = gitlab_group.foo.id | ||
project = gitlab_project.bar.id | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
internal/provider/resource_gitlab_group_project_file_template.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/hashicorp/go-retryablehttp" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
gitlab "github.com/xanzy/go-gitlab" | ||
"log" | ||
) | ||
|
||
var _ = registerResource("gitlab_group_project_file_template", func() *schema.Resource { | ||
return &schema.Resource{ | ||
Description: `The ` + "`gitlab_group_project_file_template`" + ` resource allows setting a project from which | ||
custom file templates will be loaded. The project selected must be a direct child of the group identified. | ||
For more information about which file types are available as templates, view | ||
[GitLab's documentation](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html#supported-file-types-and-locations) | ||
-> This resource requires a GitLab Enterprise instance with a Premium license. | ||
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/groups.html#update-group)`, | ||
|
||
// Since this resource updates an in-place resource, the update method is the same as the create method | ||
CreateContext: resourceGitLabGroupProjectFileTemplateCreateOrUpdate, | ||
UpdateContext: resourceGitLabGroupProjectFileTemplateCreateOrUpdate, | ||
ReadContext: resourceGitLabGroupProjectFileTemplateRead, | ||
DeleteContext: resourceGitLabGroupProjectFileTemplateDelete, | ||
// Since this resource updates an in-place resource, importing doesn't make much sense. Simply add the resource | ||
// to the config and terraform will overwrite what's already in place and manage it from there. | ||
Schema: map[string]*schema.Schema{ | ||
"group_id": { | ||
Description: `The ID of the group that will use the file template project. This group must be the direct | ||
parent of the project defined by project_id`, | ||
Type: schema.TypeInt, | ||
|
||
// Even though there is no traditional resource to create, leave "ForceNew" as "true" so that if someone | ||
// changes a configuration to a different group, the old group gets "deleted" (updated to have a value | ||
// of 0). | ||
ForceNew: true, | ||
Required: true, | ||
}, | ||
"file_template_project_id": { | ||
Description: `The ID of the project that will be used for file templates. This project must be the direct | ||
child of the project defined by the group_id`, | ||
Type: schema.TypeInt, | ||
Required: true, | ||
}, | ||
}, | ||
} | ||
}) | ||
|
||
func resourceGitLabGroupProjectFileTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*gitlab.Client) | ||
|
||
groupID := d.Get("group_id").(int) | ||
group, _, err := client.Groups.GetGroup(groupID, nil, gitlab.WithContext(ctx)) | ||
if err != nil { | ||
if is404(err) { | ||
log.Printf("[DEBUG] gitlab group %d not found, removing from state", groupID) | ||
d.SetId("") | ||
return nil | ||
} | ||
return diag.FromErr(err) | ||
} | ||
if group.MarkedForDeletionOn != nil { | ||
log.Printf("[DEBUG] gitlab group %s is marked for deletion, removing from state", d.Id()) | ||
d.SetId("") | ||
return nil | ||
} | ||
|
||
d.SetId(fmt.Sprintf("%d", group.ID)) | ||
d.Set("file_template_project_id", group.FileTemplateProjectID) | ||
|
||
return nil | ||
} | ||
|
||
func resourceGitLabGroupProjectFileTemplateCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*gitlab.Client) | ||
|
||
groupID := d.Get("group_id").(int) | ||
projectID := gitlab.Int(d.Get("file_template_project_id").(int)) | ||
|
||
// Creating the resource means updating the existing group to link the project to the group. | ||
options := &gitlab.UpdateGroupOptions{} | ||
if d.HasChanges("file_template_project_id") { | ||
options.FileTemplateProjectID = projectID | ||
} | ||
|
||
_, _, err := client.Groups.UpdateGroup(groupID, options) | ||
if err != nil { | ||
return diag.Errorf("unable to update group %d with `file_template_project_id` set to %d: %s", groupID, projectID, err) | ||
} | ||
return resourceGitLabGroupProjectFileTemplateRead(ctx, d, meta) | ||
} | ||
|
||
func resourceGitLabGroupProjectFileTemplateDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*gitlab.Client) | ||
groupID := d.Get("group_id").(int) | ||
options := &gitlab.UpdateGroupOptions{} | ||
|
||
_, _, err := updateGroupWithOverwrittenFileTemplateOption(client, groupID, options) | ||
if err != nil { | ||
return diag.Errorf("could not update group %d to remove file template ID: %s", groupID, err) | ||
} | ||
return resourceGitLabGroupProjectFileTemplateRead(ctx, d, meta) | ||
} | ||
|
||
func updateGroupWithOverwrittenFileTemplateOption(client *gitlab.Client, groupID int, options *gitlab.UpdateGroupOptions) (*gitlab.Group, *gitlab.Response, error) { | ||
return client.Groups.UpdateGroup(groupID, options, func(request *retryablehttp.Request) error { | ||
//Overwrite the GroupUpdateOptions struct to remove the "omitempty", which forces the client to send an empty | ||
//string in just this request. | ||
removeOmitEmptyOptions := struct { | ||
FileTemplateProjectID *string `url:"file_template_project_id" json:"file_template_project_id"` | ||
}{ | ||
FileTemplateProjectID: nil, | ||
} | ||
|
||
//Create the new body request with the above struct | ||
newBody, err := json.Marshal(removeOmitEmptyOptions) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
//Set the request body to have the newly updated body | ||
err = request.SetBody(newBody) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
}) | ||
} |
95 changes: 95 additions & 0 deletions
95
internal/provider/resource_gitlab_group_project_file_template_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package provider | ||
|
||
import ( | ||
"fmt" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform" | ||
"github.com/xanzy/go-gitlab" | ||
"strconv" | ||
"testing" | ||
) | ||
|
||
func TestAccGitlabGroupProjectFileTemplate_basic(t *testing.T) { | ||
// Since we do some manual setup in this test, we need to handle the test skip first. | ||
testAccCheck(t) | ||
baseGroup := testAccCreateGroups(t, 1)[0] | ||
firstProject := testAccCreateProjectWithNamespace(t, baseGroup.ID) | ||
secondProject := testAccCreateProjectWithNamespace(t, baseGroup.ID) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
ProviderFactories: providerFactories, | ||
CheckDestroy: testAccCheckProjectFileTemplateDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
SkipFunc: isRunningInCE, | ||
Config: testAccGroupProjectFileTemplateConfig(baseGroup.ID, firstProject.ID), | ||
Check: resource.ComposeTestCheckFunc( | ||
// Note - we can't use the testAccCheckGitlabGroupAttributes, because that checks the TF | ||
// state attributes, and file project template explicitly doesn't exist there. | ||
testAccCheckGitlabGroupFileTemplateValue(baseGroup, firstProject), | ||
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "group_id", strconv.Itoa(baseGroup.ID)), | ||
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "file_template_project_id", strconv.Itoa(firstProject.ID)), | ||
), | ||
}, | ||
{ | ||
//Test that when we update the project name, it re-links the group to the new project | ||
SkipFunc: isRunningInCE, | ||
Config: testAccGroupProjectFileTemplateConfig(baseGroup.ID, secondProject.ID), | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckGitlabGroupFileTemplateValue(baseGroup, secondProject), | ||
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "group_id", strconv.Itoa(baseGroup.ID)), | ||
resource.TestCheckResourceAttr("gitlab_group_project_file_template.linking_template", "file_template_project_id", strconv.Itoa(secondProject.ID)), | ||
), | ||
}, | ||
}, | ||
}, | ||
) | ||
} | ||
|
||
func testAccCheckGitlabGroupFileTemplateValue(g *gitlab.Group, p *gitlab.Project) resource.TestCheckFunc { | ||
return func(s *terraform.State) error { | ||
//Re-retrieve the group to ensure we have the most up-to-date group info | ||
g, _, err := testGitlabClient.Groups.GetGroup(g.ID, &gitlab.GetGroupOptions{}) | ||
if is404(err) { | ||
return fmt.Errorf("Group no longer exists, expected group to exist with a file_template_project_id") | ||
} | ||
|
||
if g.FileTemplateProjectID == p.ID { | ||
return nil | ||
} | ||
return fmt.Errorf("Group file_template_project_id doesn't match. Wanted %d, received %d", p.ID, g.FileTemplateProjectID) | ||
} | ||
} | ||
|
||
func testAccCheckProjectFileTemplateDestroy(state *terraform.State) error { | ||
for _, rs := range state.RootModule().Resources { | ||
if rs.Type != "gitlab_group_project_file_template" { | ||
continue | ||
} | ||
|
||
// To test if the resource was destroyed, we need to retrieve the group. | ||
gid := rs.Primary.ID | ||
group, _, err := testGitlabClient.Groups.GetGroup(gid, nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// the test should succeed if the group is still present and has a 0 file_template_project_id value | ||
if group != nil && group.FileTemplateProjectID != 0 { | ||
return fmt.Errorf("Group still has a template project attached") | ||
} | ||
return nil | ||
} | ||
return nil | ||
} | ||
|
||
func testAccGroupProjectFileTemplateConfig(groupID int, projectID int) string { | ||
return fmt.Sprintf( | ||
` | ||
resource "gitlab_group_project_file_template" "linking_template" { | ||
group_id = %d | ||
file_template_project_id = %d | ||
} | ||
`, groupID, projectID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters