1
1
package provider
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
6
+ "crypto/sha256"
5
7
"fmt"
8
+ "io"
6
9
"log"
10
+ "os"
7
11
"strconv"
8
12
9
13
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -32,21 +36,57 @@ var _ = registerResource("gitlab_topic", func() *schema.Resource {
32
36
33
37
Schema : map [string ]* schema.Schema {
34
38
"name" : {
35
- Description : "The topic's name" ,
39
+ Description : "The topic's name. " ,
36
40
Type : schema .TypeString ,
37
41
Required : true ,
38
42
},
39
43
"soft_destroy" : {
40
- Description : "Empty the topics fields instead of deleting it" ,
44
+ Description : "Empty the topics fields instead of deleting it. " ,
41
45
Type : schema .TypeBool ,
42
46
Optional : true ,
43
47
Deprecated : "GitLab 14.9 introduced the proper deletion of topics. This field is no longer needed." ,
44
48
},
45
49
"description" : {
46
- Description : "A text describing the topic" ,
50
+ Description : "A text describing the topic. " ,
47
51
Type : schema .TypeString ,
48
52
Optional : true ,
49
53
},
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
50
90
},
51
91
}
52
92
})
@@ -61,6 +101,16 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
61
101
options .Description = gitlab .String (v .(string ))
62
102
}
63
103
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
+
64
114
log .Printf ("[DEBUG] create gitlab topic %s" , * options .Name )
65
115
66
116
topic , _ , err := client .Topics .CreateTopic (options , gitlab .WithContext (ctx ))
@@ -69,7 +119,9 @@ func resourceGitlabTopicCreate(ctx context.Context, d *schema.ResourceData, meta
69
119
}
70
120
71
121
d .SetId (fmt .Sprintf ("%d" , topic .ID ))
72
-
122
+ if options .Avatar != nil {
123
+ d .Set ("_avatar_hash" , avatarHash )
124
+ }
73
125
return resourceGitlabTopicRead (ctx , d , meta )
74
126
}
75
127
@@ -95,6 +147,7 @@ func resourceGitlabTopicRead(ctx context.Context, d *schema.ResourceData, meta i
95
147
d .SetId (fmt .Sprintf ("%d" , topic .ID ))
96
148
d .Set ("name" , topic .Name )
97
149
d .Set ("description" , topic .Description )
150
+ d .Set ("avatar_url" , topic .AvatarURL )
98
151
99
152
return nil
100
153
}
@@ -111,6 +164,26 @@ func resourceGitlabTopicUpdate(ctx context.Context, d *schema.ResourceData, meta
111
164
options .Description = gitlab .String (d .Get ("description" ).(string ))
112
165
}
113
166
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
+
114
187
log .Printf ("[DEBUG] update gitlab topic %s" , d .Id ())
115
188
116
189
topicID , err := strconv .Atoi (d .Id ())
@@ -121,7 +194,6 @@ func resourceGitlabTopicUpdate(ctx context.Context, d *schema.ResourceData, meta
121
194
if err != nil {
122
195
return diag .Errorf ("Failed to update topic %d: %s" , topicID , err )
123
196
}
124
-
125
197
return resourceGitlabTopicRead (ctx , d , meta )
126
198
}
127
199
@@ -166,3 +238,31 @@ func resourceGitlabTopicDelete(ctx context.Context, d *schema.ResourceData, meta
166
238
167
239
return nil
168
240
}
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