Skip to content

Commit 2e98354

Browse files
committed
resource/gitlab_topic: use explicit avatar_hash attribute to control updates
1 parent 743fa2e commit 2e98354

File tree

4 files changed

+151
-73
lines changed

4 files changed

+151
-73
lines changed

docs/resources/topic.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ 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."
2828
avatar = "${path.module}/avatar.png"
29+
avatar_hash = filesha256("${path.module}/avatar.png")
2930
}
3031
```
3132

@@ -38,14 +39,14 @@ resource "gitlab_topic" "functional_programming" {
3839

3940
### Optional
4041

41-
- `avatar` (String) A local path to the avatar image to upload. **Note**: not available for imported resources
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.
4244
- `description` (String) A text describing the topic.
4345
- `id` (String) The ID of this resource.
4446
- `soft_destroy` (Boolean, Deprecated) Empty the topics fields instead of deleting it.
4547

4648
### Read-Only
4749

48-
- `_avatar_hash` (String) The hash of the avatar image. **Note**: this is an internally used attribute to track the avatar image.
4950
- `avatar_url` (String) The URL of the avatar image.
5051

5152
## Import

examples/resources/gitlab_topic/resource.tf

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ 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."
44
avatar = "${path.module}/avatar.png"
5+
avatar_hash = filesha256("${path.module}/avatar.png")
56
}

internal/provider/resource_gitlab_topic.go

+20-56
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package provider
22

33
import (
4-
"bytes"
54
"context"
6-
"crypto/sha256"
75
"fmt"
8-
"io"
96
"log"
107
"os"
118
"strconv"
@@ -52,14 +49,16 @@ var _ = registerResource("gitlab_topic", func() *schema.Resource {
5249
Optional: true,
5350
},
5451
"avatar": {
55-
Description: "A local path to the avatar image to upload. **Note**: not available for imported resources",
52+
Description: "A local path to the avatar image to upload. **Note**: not available for imported resources.",
5653
Type: schema.TypeString,
5754
Optional: true,
5855
},
59-
"_avatar_hash": {
60-
Description: "The hash of the avatar image. **Note**: this is an internally used attribute to track the avatar image.",
61-
Type: schema.TypeString,
62-
Computed: true,
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"},
6362
},
6463
"avatar_url": {
6564
Description: "The URL of the avatar image.",
@@ -68,21 +67,10 @@ var _ = registerResource("gitlab_topic", func() *schema.Resource {
6867
},
6968
},
7069
CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i interface{}) error {
71-
if v, ok := rd.GetOk("avatar"); ok {
72-
avatarPath := v.(string)
73-
avatarFile, err := os.Open(avatarPath)
74-
if err != nil {
75-
return fmt.Errorf("Unable to open avatar file %s: %s", avatarPath, err)
76-
}
77-
78-
avatarHash, err := getSha256(avatarFile)
79-
if err != nil {
80-
return fmt.Errorf("Unable to get hash from avatar file %s: %s", avatarPath, err)
81-
}
82-
83-
if rd.Get("_avatar_hash").(string) != avatarHash {
84-
if err := rd.SetNew("_avatar_hash", avatarHash); err != nil {
85-
return fmt.Errorf("Unable to set _avatar_hash: %s", err)
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
8674
}
8775
}
8876
}
@@ -101,14 +89,12 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
10189
options.Description = gitlab.String(v.(string))
10290
}
10391

104-
avatarHash := ""
10592
if v, ok := d.GetOk("avatar"); ok {
106-
avatar, hash, err := resourceGitlabTopicGetAvatar(v.(string))
93+
avatar, err := resourceGitlabTopicGetAvatar(v.(string))
10794
if err != nil {
10895
return diag.FromErr(err)
10996
}
11097
options.Avatar = avatar
111-
avatarHash = hash
11298
}
11399

114100
log.Printf("[DEBUG] create gitlab topic %s", *options.Name)
@@ -119,9 +105,6 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
119105
}
120106

121107
d.SetId(fmt.Sprintf("%d", topic.ID))
122-
if options.Avatar != nil {
123-
d.Set("_avatar_hash", avatarHash)
124-
}
125108
return resourceGitlabTopicRead(ctx, d, meta)
126109
}
127110

@@ -148,7 +131,6 @@ func resourceGitlabTopicRead(ctx context.Context, d *schema.ResourceData, meta i
148131
d.Set("name", topic.Name)
149132
d.Set("description", topic.Description)
150133
d.Set("avatar_url", topic.AvatarURL)
151-
152134
return nil
153135
}
154136

@@ -164,24 +146,22 @@ func resourceGitlabTopicUpdate(ctx context.Context, d *schema.ResourceData, meta
164146
options.Description = gitlab.String(d.Get("description").(string))
165147
}
166148

167-
if d.HasChanges("avatar", "_avatar_hash") {
149+
if d.HasChanges("avatar", "avatar_hash") || d.Get("avatar_hash").(string) == "" {
168150
avatarPath := d.Get("avatar").(string)
169151
var avatar *gitlab.TopicAvatar
170-
var avatarHash string
171152
// NOTE: the avatar should be removed
172153
if avatarPath == "" {
173154
avatar = &gitlab.TopicAvatar{}
174-
avatarHash = ""
155+
// terraform doesn't care to remove this from state, thus, we do.
156+
d.Set("avatar_hash", "")
175157
} else {
176-
changedAvatar, changedAvatarHash, err := resourceGitlabTopicGetAvatar(avatarPath)
158+
changedAvatar, err := resourceGitlabTopicGetAvatar(avatarPath)
177159
if err != nil {
178160
return diag.FromErr(err)
179161
}
180162
avatar = changedAvatar
181-
avatarHash = changedAvatarHash
182163
}
183164
options.Avatar = avatar
184-
d.Set("_avatar_hash", avatarHash)
185165
}
186166

187167
log.Printf("[DEBUG] update gitlab topic %s", d.Id())
@@ -239,30 +219,14 @@ func resourceGitlabTopicDelete(ctx context.Context, d *schema.ResourceData, meta
239219
return nil
240220
}
241221

242-
func resourceGitlabTopicGetAvatar(avatarPath string) (*gitlab.TopicAvatar, string, error) {
222+
func resourceGitlabTopicGetAvatar(avatarPath string) (*gitlab.TopicAvatar, error) {
243223
avatarFile, err := os.Open(avatarPath)
244224
if err != nil {
245-
return nil, "", fmt.Errorf("Unable to open avatar file %s: %s", avatarPath, err)
246-
}
247-
248-
avatarImageBuf := &bytes.Buffer{}
249-
teeReader := io.TeeReader(avatarFile, avatarImageBuf)
250-
avatarHash, err := getSha256(teeReader)
251-
if err != nil {
252-
return nil, "", fmt.Errorf("Unable to get hash from avatar file %s: %s", avatarPath, err)
225+
return nil, fmt.Errorf("Unable to open avatar file %s: %s", avatarPath, err)
253226
}
254227

255-
avatarImageReader := bytes.NewReader(avatarImageBuf.Bytes())
256228
return &gitlab.TopicAvatar{
257229
Filename: avatarPath,
258-
Image: avatarImageReader,
259-
}, avatarHash, nil
260-
}
261-
262-
func getSha256(r io.Reader) (string, error) {
263-
h := sha256.New()
264-
if _, err := io.Copy(h, r); err != nil {
265-
return "", fmt.Errorf("Unable to get hash %s", err)
266-
}
267-
return fmt.Sprintf("%x", h.Sum(nil)), nil
230+
Image: avatarFile,
231+
}, nil
268232
}

0 commit comments

Comments
 (0)