Skip to content

Commit 2309b2d

Browse files
authored
Merge pull request #968 from timofurrer/feature/topic-avatars
resource/gitlab_topic: Support avatars
2 parents 331e8dc + 2e98354 commit 2309b2d

File tree

7 files changed

+268
-19
lines changed

7 files changed

+268
-19
lines changed

docs/resources/topic.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ The `gitlab_topic` resource allows to manage the lifecycle of topics that are th
2525
resource "gitlab_topic" "functional_programming" {
2626
name = "Functional Programming"
2727
description = "In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions."
28+
avatar = "${path.module}/avatar.png"
29+
avatar_hash = filesha256("${path.module}/avatar.png")
2830
}
2931
```
3032

@@ -33,13 +35,19 @@ resource "gitlab_topic" "functional_programming" {
3335

3436
### Required
3537

36-
- `name` (String) The topic's name
38+
- `name` (String) The topic's name.
3739

3840
### Optional
3941

40-
- `description` (String) A text describing the topic
42+
- `avatar` (String) A local path to the avatar image to upload. **Note**: not available for imported resources.
43+
- `avatar_hash` (String) The hash of the avatar image. Use `filesha256("path/to/avatar.png")` whenever possible. **Note**: this is used to trigger an update of the avatar. If it's not given, but an avatar is given, the avatar will be updated each time.
44+
- `description` (String) A text describing the topic.
4145
- `id` (String) The ID of this resource.
42-
- `soft_destroy` (Boolean, Deprecated) Empty the topics fields instead of deleting it
46+
- `soft_destroy` (Boolean, Deprecated) Empty the topics fields instead of deleting it.
47+
48+
### Read-Only
49+
50+
- `avatar_url` (String) The URL of the avatar image.
4351

4452
## Import
4553

Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
resource "gitlab_topic" "functional_programming" {
22
name = "Functional Programming"
33
description = "In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions."
4+
avatar = "${path.module}/avatar.png"
5+
avatar_hash = filesha256("${path.module}/avatar.png")
46
}

internal/provider/helper_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package provider
22

33
import (
44
"fmt"
5+
"io"
56
"os"
67
"strings"
78
"testing"
@@ -394,3 +395,23 @@ func testCheckResourceAttrLazy(name string, key string, value func() string) res
394395
return resource.TestCheckResourceAttr(name, key, value())(s)
395396
}
396397
}
398+
399+
func copyFile(src, dst string) error {
400+
in, err := os.Open(src)
401+
if err != nil {
402+
return err
403+
}
404+
defer in.Close()
405+
406+
out, err := os.Create(dst)
407+
if err != nil {
408+
return err
409+
}
410+
defer out.Close()
411+
412+
_, err = io.Copy(out, in)
413+
if err != nil {
414+
return err
415+
}
416+
return out.Close()
417+
}

internal/provider/resource_gitlab_topic.go

+70-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log"
7+
"os"
78
"strconv"
89

910
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -32,21 +33,48 @@ var _ = registerResource("gitlab_topic", func() *schema.Resource {
3233

3334
Schema: map[string]*schema.Schema{
3435
"name": {
35-
Description: "The topic's name",
36+
Description: "The topic's name.",
3637
Type: schema.TypeString,
3738
Required: true,
3839
},
3940
"soft_destroy": {
40-
Description: "Empty the topics fields instead of deleting it",
41+
Description: "Empty the topics fields instead of deleting it.",
4142
Type: schema.TypeBool,
4243
Optional: true,
4344
Deprecated: "GitLab 14.9 introduced the proper deletion of topics. This field is no longer needed.",
4445
},
4546
"description": {
46-
Description: "A text describing the topic",
47+
Description: "A text describing the topic.",
4748
Type: schema.TypeString,
4849
Optional: true,
4950
},
51+
"avatar": {
52+
Description: "A local path to the avatar image to upload. **Note**: not available for imported resources.",
53+
Type: schema.TypeString,
54+
Optional: true,
55+
},
56+
"avatar_hash": {
57+
Description: "The hash of the avatar image. Use `filesha256(\"path/to/avatar.png\")` whenever possible. **Note**: this is used to trigger an update of the avatar. If it's not given, but an avatar is given, the avatar will be updated each time.",
58+
Type: schema.TypeString,
59+
Optional: true,
60+
Computed: true,
61+
RequiredWith: []string{"avatar"},
62+
},
63+
"avatar_url": {
64+
Description: "The URL of the avatar image.",
65+
Type: schema.TypeString,
66+
Computed: true,
67+
},
68+
},
69+
CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i interface{}) error {
70+
if _, ok := rd.GetOk("avatar"); ok {
71+
if v, ok := rd.GetOk("avatar_hash"); !ok || v.(string) == "" {
72+
if err := rd.SetNewComputed("avatar_hash"); err != nil {
73+
return err
74+
}
75+
}
76+
}
77+
return nil
5078
},
5179
}
5280
})
@@ -61,6 +89,14 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
6189
options.Description = gitlab.String(v.(string))
6290
}
6391

92+
if v, ok := d.GetOk("avatar"); ok {
93+
avatar, err := resourceGitlabTopicGetAvatar(v.(string))
94+
if err != nil {
95+
return diag.FromErr(err)
96+
}
97+
options.Avatar = avatar
98+
}
99+
64100
log.Printf("[DEBUG] create gitlab topic %s", *options.Name)
65101

66102
topic, _, err := client.Topics.CreateTopic(options, gitlab.WithContext(ctx))
@@ -69,7 +105,6 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
69105
}
70106

71107
d.SetId(fmt.Sprintf("%d", topic.ID))
72-
73108
return resourceGitlabTopicRead(ctx, d, meta)
74109
}
75110

@@ -95,7 +130,7 @@ func resourceGitlabTopicRead(ctx context.Context, d *schema.ResourceData, meta i
95130
d.SetId(fmt.Sprintf("%d", topic.ID))
96131
d.Set("name", topic.Name)
97132
d.Set("description", topic.Description)
98-
133+
d.Set("avatar_url", topic.AvatarURL)
99134
return nil
100135
}
101136

@@ -111,6 +146,24 @@ func resourceGitlabTopicUpdate(ctx context.Context, d *schema.ResourceData, meta
111146
options.Description = gitlab.String(d.Get("description").(string))
112147
}
113148

149+
if d.HasChanges("avatar", "avatar_hash") || d.Get("avatar_hash").(string) == "" {
150+
avatarPath := d.Get("avatar").(string)
151+
var avatar *gitlab.TopicAvatar
152+
// NOTE: the avatar should be removed
153+
if avatarPath == "" {
154+
avatar = &gitlab.TopicAvatar{}
155+
// terraform doesn't care to remove this from state, thus, we do.
156+
d.Set("avatar_hash", "")
157+
} else {
158+
changedAvatar, err := resourceGitlabTopicGetAvatar(avatarPath)
159+
if err != nil {
160+
return diag.FromErr(err)
161+
}
162+
avatar = changedAvatar
163+
}
164+
options.Avatar = avatar
165+
}
166+
114167
log.Printf("[DEBUG] update gitlab topic %s", d.Id())
115168

116169
topicID, err := strconv.Atoi(d.Id())
@@ -121,7 +174,6 @@ func resourceGitlabTopicUpdate(ctx context.Context, d *schema.ResourceData, meta
121174
if err != nil {
122175
return diag.Errorf("Failed to update topic %d: %s", topicID, err)
123176
}
124-
125177
return resourceGitlabTopicRead(ctx, d, meta)
126178
}
127179

@@ -166,3 +218,15 @@ func resourceGitlabTopicDelete(ctx context.Context, d *schema.ResourceData, meta
166218

167219
return nil
168220
}
221+
222+
func resourceGitlabTopicGetAvatar(avatarPath string) (*gitlab.TopicAvatar, error) {
223+
avatarFile, err := os.Open(avatarPath)
224+
if err != nil {
225+
return nil, fmt.Errorf("Unable to open avatar file %s: %s", avatarPath, err)
226+
}
227+
228+
return &gitlab.TopicAvatar{
229+
Filename: avatarPath,
230+
Image: avatarFile,
231+
}, nil
232+
}

0 commit comments

Comments
 (0)