11package provider
22
33import (
4+ "bytes"
45 "context"
6+ "crypto/sha256"
57 "fmt"
8+ "io"
69 "log"
10+ "os"
711 "strconv"
812
913 "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -32,21 +36,57 @@ var _ = registerResource("gitlab_topic", func() *schema.Resource {
3236
3337 Schema : map [string ]* schema.Schema {
3438 "name" : {
35- Description : "The topic's name" ,
39+ Description : "The topic's name. " ,
3640 Type : schema .TypeString ,
3741 Required : true ,
3842 },
3943 "soft_destroy" : {
40- Description : "Empty the topics fields instead of deleting it" ,
44+ Description : "Empty the topics fields instead of deleting it. " ,
4145 Type : schema .TypeBool ,
4246 Optional : true ,
4347 Deprecated : "GitLab 14.9 introduced the proper deletion of topics. This field is no longer needed." ,
4448 },
4549 "description" : {
46- Description : "A text describing the topic" ,
50+ Description : "A text describing the topic. " ,
4751 Type : schema .TypeString ,
4852 Optional : true ,
4953 },
54+ "avatar" : {
55+ Description : "A local path to the avatar image to upload. **Note**: not available for imported resources" ,
56+ Type : schema .TypeString ,
57+ Optional : true ,
58+ },
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 ,
63+ },
64+ "avatar_url" : {
65+ Description : "The URL of the avatar image." ,
66+ Type : schema .TypeString ,
67+ Computed : true ,
68+ },
69+ },
70+ 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 )
86+ }
87+ }
88+ }
89+ return nil
5090 },
5191 }
5292})
@@ -61,6 +101,16 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
61101 options .Description = gitlab .String (v .(string ))
62102 }
63103
104+ avatarHash := ""
105+ if v , ok := d .GetOk ("avatar" ); ok {
106+ avatar , hash , err := resourceGitlabTopicGetAvatar (v .(string ))
107+ if err != nil {
108+ return diag .FromErr (err )
109+ }
110+ options .Avatar = avatar
111+ avatarHash = hash
112+ }
113+
64114 log .Printf ("[DEBUG] create gitlab topic %s" , * options .Name )
65115
66116 topic , _ , err := client .Topics .CreateTopic (options , gitlab .WithContext (ctx ))
@@ -69,7 +119,9 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
69119 }
70120
71121 d .SetId (fmt .Sprintf ("%d" , topic .ID ))
72-
122+ if options .Avatar != nil {
123+ d .Set ("_avatar_hash" , avatarHash )
124+ }
73125 return resourceGitlabTopicRead (ctx , d , meta )
74126}
75127
@@ -95,6 +147,7 @@ func resourceGitlabTopicRead(ctx context.Context, d *schema.ResourceData, meta i
95147 d .SetId (fmt .Sprintf ("%d" , topic .ID ))
96148 d .Set ("name" , topic .Name )
97149 d .Set ("description" , topic .Description )
150+ d .Set ("avatar_url" , topic .AvatarURL )
98151
99152 return nil
100153}
@@ -111,6 +164,26 @@ func resourceGitlabTopicUpdate(ctx context.Context, d *schema.ResourceData, meta
111164 options .Description = gitlab .String (d .Get ("description" ).(string ))
112165 }
113166
167+ if d .HasChanges ("avatar" , "_avatar_hash" ) {
168+ avatarPath := d .Get ("avatar" ).(string )
169+ var avatar * gitlab.TopicAvatar
170+ var avatarHash string
171+ // NOTE: the avatar should be removed
172+ if avatarPath == "" {
173+ avatar = & gitlab.TopicAvatar {}
174+ avatarHash = ""
175+ } else {
176+ changedAvatar , changedAvatarHash , err := resourceGitlabTopicGetAvatar (avatarPath )
177+ if err != nil {
178+ return diag .FromErr (err )
179+ }
180+ avatar = changedAvatar
181+ avatarHash = changedAvatarHash
182+ }
183+ options .Avatar = avatar
184+ d .Set ("_avatar_hash" , avatarHash )
185+ }
186+
114187 log .Printf ("[DEBUG] update gitlab topic %s" , d .Id ())
115188
116189 topicID , err := strconv .Atoi (d .Id ())
@@ -121,7 +194,6 @@ func resourceGitlabTopicUpdate(ctx context.Context, d *schema.ResourceData, meta
121194 if err != nil {
122195 return diag .Errorf ("Failed to update topic %d: %s" , topicID , err )
123196 }
124-
125197 return resourceGitlabTopicRead (ctx , d , meta )
126198}
127199
@@ -166,3 +238,31 @@ func resourceGitlabTopicDelete(ctx context.Context, d *schema.ResourceData, meta
166238
167239 return nil
168240}
241+
242+ func resourceGitlabTopicGetAvatar (avatarPath string ) (* gitlab.TopicAvatar , string , error ) {
243+ avatarFile , err := os .Open (avatarPath )
244+ 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 )
253+ }
254+
255+ avatarImageReader := bytes .NewReader (avatarImageBuf .Bytes ())
256+ return & gitlab.TopicAvatar {
257+ 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
268+ }
0 commit comments