Skip to content

Commit 4813a03

Browse files
committed
Add release link resource
1 parent 8be3fde commit 4813a03

File tree

6 files changed

+361
-0
lines changed

6 files changed

+361
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Gitlab release link can be imported with a key composed of `<project>:<tag_name>:<link_id>`, e.g.
2+
terraform import gitlab_release_link.example "12345:test:2"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Create a project
2+
resource "gitlab_project" "example" {
3+
name = "example"
4+
description = "An example project"
5+
namespace_id = gitlab_group.example.id
6+
}
7+
8+
# Create a tag
9+
resource "gitlab_project_tag" "example" {
10+
name = "example"
11+
ref = gitlab_project.example.default_branch
12+
project = gitlab_project.example.id
13+
}
14+
15+
resource "gitlab_release_link" "example" {
16+
project = gitlab_project.example.id
17+
tag_name = gitlab_project_tag.example.name
18+
name = "test"
19+
url = "https://test/"
20+
}

internal/provider/helper_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,27 @@ func testAccCreateProtectedBranches(t *testing.T, project *gitlab.Project, n int
267267
return protectedBranches
268268
}
269269

270+
// testAccCreateTags is a test helper for creating a specified number of tags.
271+
// It assumes the project will be destroyed at the end of the test and will not cleanup created tags.
272+
func testAccCreateTags(t *testing.T, project *gitlab.Project, n int) []*gitlab.Tag {
273+
t.Helper()
274+
275+
tags := make([]*gitlab.Tag, n)
276+
277+
for i := range tags {
278+
var err error
279+
tags[i], _, err = testGitlabClient.Tags.CreateTag(project.ID, &gitlab.CreateTagOptions{
280+
TagName: gitlab.String(acctest.RandomWithPrefix("acctest")),
281+
Ref: gitlab.String(project.DefaultBranch),
282+
})
283+
if err != nil {
284+
t.Fatalf("could not create test tags: %v", err)
285+
}
286+
}
287+
288+
return tags
289+
}
290+
270291
// testAccAddProjectMembers is a test helper for adding users as members of a project.
271292
// It assumes the project will be destroyed at the end of the test and will not cleanup members.
272293
func testAccAddProjectMembers(t *testing.T, pid interface{}, users []*gitlab.User) {
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
gitlab "github.com/xanzy/go-gitlab"
13+
)
14+
15+
var _ = registerResource("gitlab_release_link", func() *schema.Resource {
16+
return &schema.Resource{
17+
Description: `The ` + "`gitlab_release_link`" + ` resource allows to manage the lifecycle of a release links.
18+
19+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/releases/links.html)`,
20+
21+
CreateContext: resourceGitlabReleaseLinkCreate,
22+
ReadContext: resourceGitlabReleaseLinkRead,
23+
UpdateContext: resourceGitlabReleaseLinkUpdate,
24+
DeleteContext: resourceGitlabReleaseLinkDelete,
25+
Importer: &schema.ResourceImporter{
26+
StateContext: schema.ImportStatePassthroughContext,
27+
},
28+
Schema: gitlabReleaseLinkGetSchema(),
29+
}
30+
})
31+
32+
func resourceGitlabReleaseLinkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
33+
client := meta.(*gitlab.Client)
34+
project := d.Get("project").(string)
35+
tagName := d.Get("tag_name").(string)
36+
name := d.Get("name").(string)
37+
url := d.Get("url").(string)
38+
39+
options := &gitlab.CreateReleaseLinkOptions{
40+
Name: &name,
41+
URL: &url,
42+
}
43+
if filePath, ok := d.GetOk("file_path"); ok {
44+
options.FilePath = gitlab.String(filePath.(string))
45+
}
46+
if linkType, ok := d.GetOk("link_type"); ok {
47+
linkTypeValue := gitlab.LinkTypeValue(linkType.(string))
48+
options.LinkType = &linkTypeValue
49+
}
50+
51+
log.Printf("[DEBUG] create release link project/tagName/name: %s/%s/%s", project, tagName, name)
52+
releaseLink, resp, err := client.ReleaseLinks.CreateReleaseLink(project, tagName, options, gitlab.WithContext(ctx))
53+
if err != nil {
54+
log.Printf("[WARN] failed to create release link project/tagName/name: %s/%s/%s (response %v)", project, tagName, name, resp)
55+
return diag.FromErr(err)
56+
}
57+
d.SetId(resourceGitLabReleaseLinkBuildId(project, tagName, releaseLink.ID))
58+
59+
return resourceGitlabReleaseLinkRead(ctx, d, meta)
60+
}
61+
62+
func resourceGitlabReleaseLinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
63+
client := meta.(*gitlab.Client)
64+
project, tagName, linkID, err := resourceGitLabReleaseLinkParseId(d.Id())
65+
if err != nil {
66+
return diag.FromErr(err)
67+
}
68+
69+
log.Printf("[DEBUG] read release link project/tagName/linkID: %s/%s/%d", project, tagName, linkID)
70+
releaseLink, resp, err := client.ReleaseLinks.GetReleaseLink(project, tagName, linkID, gitlab.WithContext(ctx))
71+
if err != nil {
72+
if is404(err) {
73+
log.Printf("[WARN] recieved 404 for release link project/tagName/linkID: %s/%s/%d. Removing from state", project, tagName, linkID)
74+
d.SetId("")
75+
return nil
76+
}
77+
log.Printf("[WARN] failed to read release link project/tagName/linkID: %s/%s/%d. Response %v", project, tagName, linkID, resp)
78+
return diag.FromErr(err)
79+
}
80+
81+
stateMap := gitlabReleaseLinkToStateMap(project, tagName, releaseLink)
82+
if err = setStateMapInResourceData(stateMap, d); err != nil {
83+
return diag.FromErr(err)
84+
}
85+
return nil
86+
}
87+
88+
func resourceGitlabReleaseLinkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
89+
client := meta.(*gitlab.Client)
90+
project, tagName, linkID, err := resourceGitLabReleaseLinkParseId(d.Id())
91+
if err != nil {
92+
return diag.FromErr(err)
93+
}
94+
95+
options := &gitlab.UpdateReleaseLinkOptions{}
96+
if d.HasChange("name") {
97+
options.Name = gitlab.String(d.Get("name").(string))
98+
}
99+
if d.HasChange("url") {
100+
options.URL = gitlab.String(d.Get("url").(string))
101+
}
102+
if d.HasChange("file_path") {
103+
options.FilePath = gitlab.String(d.Get("file_path").(string))
104+
}
105+
if d.HasChange("link_type") {
106+
linkTypeValue := gitlab.LinkTypeValue(d.Get("link_type").(string))
107+
options.LinkType = &linkTypeValue
108+
}
109+
110+
log.Printf("[DEBUG] update release link project/tagName/linkID: %s/%s/%d", project, tagName, linkID)
111+
_, _, err = client.ReleaseLinks.UpdateReleaseLink(project, tagName, linkID, options, gitlab.WithContext(ctx))
112+
if err != nil {
113+
log.Printf("[WARN] failed to update release link project/tagName/linkID: %s/%s/%d", project, tagName, linkID)
114+
return diag.FromErr(err)
115+
}
116+
117+
return resourceGitlabReleaseLinkRead(ctx, d, meta)
118+
}
119+
120+
func resourceGitlabReleaseLinkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
121+
client := meta.(*gitlab.Client)
122+
project, tagName, linkID, err := resourceGitLabReleaseLinkParseId(d.Id())
123+
if err != nil {
124+
return diag.FromErr(err)
125+
}
126+
127+
log.Printf("[DEBUG] delete release link project/tagName/linkID: %s/%s/%d", project, tagName, linkID)
128+
_, resp, err := client.ReleaseLinks.DeleteReleaseLink(project, tagName, linkID, gitlab.WithContext(ctx))
129+
if err != nil {
130+
log.Printf("[DEBUG] failed to delete release link project/tagName/linkID: %s/%s/%d. Response %v", project, tagName, linkID, resp)
131+
return diag.FromErr(err)
132+
}
133+
return nil
134+
}
135+
136+
func resourceGitLabReleaseLinkParseId(id string) (string, string, int, error) {
137+
parts := strings.SplitN(id, ":", 3)
138+
if len(parts) != 3 {
139+
return "", "", 0, fmt.Errorf("Unexpected ID format (%q). Expected project:tagName:linkID", id)
140+
}
141+
142+
linkID, err := strconv.Atoi(parts[2])
143+
if err != nil {
144+
return "", "", 0, err
145+
}
146+
147+
return parts[0], parts[1], linkID, nil
148+
}
149+
150+
func resourceGitLabReleaseLinkBuildId(project string, tagName string, linkID int) string {
151+
id := fmt.Sprintf("%s:%s:%d", project, tagName, linkID)
152+
return id
153+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//go:build acceptance
2+
// +build acceptance
3+
4+
package provider
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
"testing"
10+
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
14+
)
15+
16+
func TestAccGitlabReleaseLink_basic(t *testing.T) {
17+
18+
rInt1, rInt2 := acctest.RandInt(), acctest.RandInt()
19+
project := testAccCreateProject(t)
20+
tag := testAccCreateTags(t, project, 1)[0]
21+
22+
resource.Test(t, resource.TestCase{
23+
ProviderFactories: providerFactories,
24+
CheckDestroy: testAccCheckGitlabReleaseLinkDestroy,
25+
Steps: []resource.TestStep{
26+
{
27+
// create Release link with required values only
28+
Config: fmt.Sprintf(`
29+
resource "gitlab_release_link" "this" {
30+
project = "%s"
31+
tag_name = "%s"
32+
name = "test-%d"
33+
url = "https://test/%d"
34+
}`, project.PathWithNamespace, tag.Name, rInt1, rInt1),
35+
Check: resource.ComposeTestCheckFunc(
36+
resource.TestCheckResourceAttrSet("gitlab_release_link.this", "link_id"),
37+
resource.TestCheckResourceAttrSet("gitlab_release_link.this", "external"),
38+
),
39+
},
40+
{
41+
// verify import
42+
ResourceName: "gitlab_release_link.this",
43+
ImportState: true,
44+
ImportStateVerify: true,
45+
},
46+
{
47+
// update some Release link attributes
48+
Config: fmt.Sprintf(`
49+
resource "gitlab_release_link" "this" {
50+
project = "%d"
51+
tag_name = "%s"
52+
name = "test-%d"
53+
url = "https://test/%d"
54+
filepath = "http://test/%d"
55+
link_type = "runbook"
56+
}`, project.ID, tag.Name, rInt2, rInt2, rInt2),
57+
Check: resource.ComposeTestCheckFunc(
58+
resource.TestCheckResourceAttrSet("gitlab_release_link.this", "link_id"),
59+
resource.TestCheckResourceAttrSet("gitlab_release_link.this", "external"),
60+
),
61+
},
62+
{
63+
// verify import
64+
ResourceName: "gitlab_release_link.this",
65+
ImportState: true,
66+
ImportStateVerify: true,
67+
},
68+
},
69+
})
70+
}
71+
72+
func testAccCheckGitlabReleaseLinkDestroy(s *terraform.State) error {
73+
for _, rs := range s.RootModule().Resources {
74+
if rs.Type != "gitlab_release_link" {
75+
continue
76+
}
77+
project, tagName, linkID, err := resourceGitLabReleaseLinkParseId(rs.Primary.ID)
78+
if err != nil {
79+
return err
80+
}
81+
82+
releaseLink, _, err := testGitlabClient.ReleaseLinks.GetReleaseLink(project, tagName, linkID)
83+
if err == nil && releaseLink != nil {
84+
return errors.New("Release link still exists")
85+
}
86+
if !is404(err) {
87+
return err
88+
}
89+
return nil
90+
}
91+
return nil
92+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package provider
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
8+
"github.com/xanzy/go-gitlab"
9+
)
10+
11+
func gitlabReleaseLinkGetSchema() map[string]*schema.Schema {
12+
validLinkTypes := []string{"other", "runbook", "image", "package"}
13+
14+
return map[string]*schema.Schema{
15+
"project": {
16+
Description: "The ID or [URL-encoded path of the project](https://docs.gitlab.com/ee/api/index.html#namespaced-path-encoding).",
17+
Type: schema.TypeString,
18+
ForceNew: true,
19+
Required: true,
20+
},
21+
"tag_name": {
22+
Description: "The tag associated with the Release.",
23+
Type: schema.TypeString,
24+
Required: true,
25+
},
26+
"name": {
27+
Description: "The name of the link. Link names must be unique within the release.",
28+
Type: schema.TypeString,
29+
Required: true,
30+
},
31+
"url": {
32+
Description: "The URL of the link. Link URLs must be unique within the release.",
33+
Type: schema.TypeString,
34+
Required: true,
35+
},
36+
"filepath": {
37+
Description: "Optional path for a [Direct Asset link](https://docs.gitlab.com/ee/user/project/releases/index.html#permanent-links-to-release-assets).",
38+
Type: schema.TypeString,
39+
Optional: true,
40+
},
41+
"link_type": {
42+
Description: fmt.Sprintf("The type of the link: %s. Defaults to %s.", renderValueListForDocs(validLinkTypes), validLinkTypes[0]),
43+
Type: schema.TypeString,
44+
Optional: true,
45+
Default: validLinkTypes[0],
46+
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(validLinkTypes, false)),
47+
},
48+
"link_id": {
49+
Description: "The ID of the link.",
50+
Type: schema.TypeInt,
51+
Computed: true,
52+
},
53+
"external": {
54+
Description: "External or internal link.",
55+
Type: schema.TypeBool,
56+
Computed: true,
57+
},
58+
}
59+
}
60+
61+
func gitlabReleaseLinkToStateMap(project string, tagName string, releaseLink *gitlab.ReleaseLink) map[string]interface{} {
62+
stateMap := make(map[string]interface{})
63+
stateMap["project"] = project
64+
stateMap["tag_name"] = tagName
65+
stateMap["name"] = releaseLink.Name
66+
stateMap["url"] = releaseLink.URL
67+
stateMap["file_path"] = releaseLink.DirectAssetURL
68+
stateMap["link_type"] = releaseLink.LinkType
69+
stateMap["link_id"] = releaseLink.ID
70+
stateMap["external"] = releaseLink.External
71+
72+
return stateMap
73+
}

0 commit comments

Comments
 (0)