Skip to content

Commit 776dab9

Browse files
committed
fix(jobs): support secret regional and uuid, fix diff detection and update
1 parent 6d5814e commit 776dab9

File tree

5 files changed

+1441
-371
lines changed

5 files changed

+1441
-371
lines changed

internal/services/jobs/helpers.go

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package jobs
22

33
import (
4+
"bytes"
5+
"context"
46
"fmt"
57

68
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -93,7 +95,7 @@ func flattenJobDefinitionCron(cron *jobs.CronSchedule) []any {
9395

9496
type JobDefinitionSecret struct {
9597
SecretReferenceID string
96-
SecretID string
98+
SecretID regional.ID
9799
SecretVersion string
98100
File string
99101
Environment string
@@ -119,7 +121,7 @@ func expandJobDefinitionSecret(i any) []JobDefinitionSecret {
119121
}
120122

121123
secret := JobDefinitionSecret{
122-
SecretID: secretMap["secret_id"].(string),
124+
SecretID: regional.ExpandID(secretMap["secret_id"].(string)),
123125
SecretVersion: secretMap["secret_version"].(string),
124126
File: file,
125127
Environment: env,
@@ -134,45 +136,109 @@ func expandJobDefinitionSecret(i any) []JobDefinitionSecret {
134136
return parsedSecrets
135137
}
136138

137-
func CreateJobDefinitionSecret(rawSecretReference any, api *jobs.API, region scw.Region, jobID string) error {
138-
parsedSecretReferences := expandJobDefinitionSecret(rawSecretReference)
139-
secrets := []*jobs.CreateJobDefinitionSecretsRequestSecretConfig{}
139+
func CreateJobDefinitionSecret(ctx context.Context, api *jobs.API, jobSecrets []JobDefinitionSecret, region scw.Region, jobID string) error {
140+
secretConfigs := []*jobs.CreateJobDefinitionSecretsRequestSecretConfig{}
140141

141-
for _, parsedSecretRef := range parsedSecretReferences {
142-
var secretConfig *jobs.CreateJobDefinitionSecretsRequestSecretConfig
142+
for _, parsedSecretRef := range jobSecrets {
143+
secretConfig := &jobs.CreateJobDefinitionSecretsRequestSecretConfig{}
143144

144-
if parsedSecretRef.Environment != "" {
145-
secretConfig = &jobs.CreateJobDefinitionSecretsRequestSecretConfig{
146-
SecretManagerID: parsedSecretRef.SecretID,
147-
SecretManagerVersion: parsedSecretRef.SecretVersion,
148-
EnvVarName: &parsedSecretRef.Environment,
149-
}
150-
}
145+
secretConfigs = append(secretConfigs, secretConfig)
151146

152-
if parsedSecretRef.File != "" {
153-
secretConfig = &jobs.CreateJobDefinitionSecretsRequestSecretConfig{
154-
SecretManagerID: parsedSecretRef.SecretID,
155-
SecretManagerVersion: parsedSecretRef.SecretVersion,
156-
Path: &parsedSecretRef.File,
147+
if parsedSecretRef.SecretID.Region.String() != "" {
148+
if parsedSecretRef.SecretID.Region.String() != region.String() {
149+
return fmt.Errorf("the secret id %s is in the region %s, expected %s", parsedSecretRef.SecretID, parsedSecretRef.SecretID.Region, region)
157150
}
158151
}
159152

160-
if parsedSecretRef.Environment != "" && parsedSecretRef.File != "" {
161-
return fmt.Errorf("the secret id %s must have exactly one mount point: file or environment", parsedSecretRef.SecretID)
153+
secretConfig.SecretManagerID = parsedSecretRef.SecretID.ID
154+
secretConfig.SecretManagerVersion = parsedSecretRef.SecretVersion
155+
156+
if err := validateJobDefinitionSecret(&parsedSecretRef); err != nil {
157+
return err
162158
}
163159

164-
if parsedSecretRef.Environment == "" && parsedSecretRef.File == "" {
165-
return fmt.Errorf("the secret id %s is missing a mount point: file or environment", parsedSecretRef.SecretID)
160+
if parsedSecretRef.Environment != "" {
161+
secretConfig.EnvVarName = &parsedSecretRef.Environment
166162
}
167163

168-
secrets = append(secrets, secretConfig)
164+
if parsedSecretRef.File != "" {
165+
secretConfig.Path = &parsedSecretRef.File
166+
}
169167
}
170168

171169
_, err := api.CreateJobDefinitionSecrets(&jobs.CreateJobDefinitionSecretsRequest{
172170
Region: region,
173171
JobDefinitionID: jobID,
174-
Secrets: secrets,
175-
})
172+
Secrets: secretConfigs,
173+
}, scw.WithContext(ctx))
176174

177175
return err
178176
}
177+
178+
func hashJobDefinitionSecret(secret *JobDefinitionSecret) int {
179+
buf := bytes.NewBufferString(secret.SecretID.String())
180+
buf.WriteString(secret.SecretVersion)
181+
182+
if secret.Environment != "" {
183+
buf.WriteString(secret.Environment)
184+
}
185+
186+
if secret.File != "" {
187+
buf.WriteString(secret.File)
188+
}
189+
190+
return schema.HashString(buf.String())
191+
}
192+
193+
func DiffJobDefinitionSecrets(oldSecretRefs, newSecretRefs []JobDefinitionSecret) (toCreate []JobDefinitionSecret, toDelete []JobDefinitionSecret, err error) {
194+
toCreate = make([]JobDefinitionSecret, 0)
195+
toDelete = make([]JobDefinitionSecret, 0)
196+
197+
// hash the new and old secret sets
198+
oldSecretRefsMap := make(map[int]JobDefinitionSecret, len(oldSecretRefs))
199+
for _, secret := range oldSecretRefs {
200+
oldSecretRefsMap[hashJobDefinitionSecret(&secret)] = secret
201+
}
202+
203+
newSecretRefsMap := make(map[int]JobDefinitionSecret, len(newSecretRefs))
204+
205+
for _, secret := range newSecretRefs {
206+
if err := validateJobDefinitionSecret(&secret); err != nil {
207+
return toCreate, toDelete, err
208+
}
209+
210+
newSecretRefsMap[hashJobDefinitionSecret(&secret)] = secret
211+
}
212+
213+
// filter secrets to delete
214+
for hash, secret := range oldSecretRefsMap {
215+
if _, ok := newSecretRefsMap[hash]; !ok {
216+
toDelete = append(toDelete, secret)
217+
}
218+
}
219+
220+
// filter secrets to create
221+
for hash, secret := range newSecretRefsMap {
222+
if _, ok := oldSecretRefsMap[hash]; !ok {
223+
toCreate = append(toCreate, secret)
224+
}
225+
}
226+
227+
return toCreate, toDelete, nil
228+
}
229+
230+
func validateJobDefinitionSecret(secret *JobDefinitionSecret) error {
231+
if secret == nil {
232+
return nil
233+
}
234+
235+
if secret.Environment != "" && secret.File != "" {
236+
return fmt.Errorf("the secret id %s must have exactly one mount point: file or environment", secret.SecretID)
237+
}
238+
239+
if secret.Environment == "" && secret.File == "" {
240+
return fmt.Errorf("the secret id %s is missing a mount point: file or environment", secret.SecretID)
241+
}
242+
243+
return nil
244+
}

internal/services/jobs/definition.go renamed to internal/services/jobs/jobs.go

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/scaleway/scaleway-sdk-go/scw"
1414
"github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf"
1515
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
16+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
1617
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
1718
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
1819
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
@@ -95,13 +96,24 @@ func ResourceDefinition() *schema.Resource {
9596
Type: schema.TypeSet,
9697
Optional: true,
9798
Description: "A reference to a Secret Manager secret.",
99+
Set: func(v interface{}) int {
100+
secret := v.(map[string]interface{})
101+
if secret["environment"] != "" {
102+
return schema.HashString(locality.ExpandID(secret["secret_id"].(string)) + secret["secret_version"].(string) + secret["environment"].(string))
103+
}
104+
105+
return schema.HashString(locality.ExpandID(secret["secret_id"].(string)) + secret["secret_version"].(string) + secret["file"].(string))
106+
},
98107
Elem: &schema.Resource{
99108
Schema: map[string]*schema.Schema{
100109
"secret_id": {
101-
Type: schema.TypeString,
102-
Description: "The secret UUID.",
103-
Required: true,
104-
ValidateFunc: validation.IsUUID,
110+
Type: schema.TypeString,
111+
Description: "The secret unique identifier, it could be formatted as region/UUID or UUID. In case the region is passed, it must be the same as the job definition.",
112+
Required: true,
113+
DiffSuppressOnRefresh: true,
114+
DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool {
115+
return locality.ExpandID(oldValue) == locality.ExpandID(newValue)
116+
},
105117
},
106118
"secret_reference_id": {
107119
Type: schema.TypeString,
@@ -123,7 +135,7 @@ func ResourceDefinition() *schema.Resource {
123135
Type: schema.TypeString,
124136
Optional: true,
125137
Description: "An environment variable containing the secret value.",
126-
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[A-Z]+(_[A-Z]+)*$`), "environment variable must be composed of uppercase letters separated by an underscore"),
138+
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[A-Z|0-9]+(_[A-Z|0-9]+)*$`), "environment variable must be composed of uppercase letters separated by an underscore"),
127139
},
128140
},
129141
},
@@ -171,7 +183,7 @@ func ResourceJobDefinitionCreate(ctx context.Context, d *schema.ResourceData, m
171183
}
172184

173185
if rawSecretReference, ok := d.GetOk("secret_reference"); ok {
174-
if err := CreateJobDefinitionSecret(rawSecretReference, api, region, definition.ID); err != nil {
186+
if err := CreateJobDefinitionSecret(ctx, api, expandJobDefinitionSecret(rawSecretReference), region, definition.ID); err != nil {
175187
return diag.FromErr(err)
176188
}
177189
}
@@ -304,21 +316,29 @@ func ResourceJobDefinitionUpdate(ctx context.Context, d *schema.ResourceData, m
304316
}
305317

306318
if d.HasChange("secret_reference") {
307-
oldRawSecretRefs, _ := d.GetChange("secret_reference")
319+
oldRawSecretRefs, newRawSecretRefs := d.GetChange("secret_reference")
320+
308321
oldSecretRefs := expandJobDefinitionSecret(oldRawSecretRefs)
322+
newSecretRefs := expandJobDefinitionSecret(newRawSecretRefs)
309323

310-
for _, oldSecretRef := range oldSecretRefs {
311-
if err := api.DeleteJobDefinitionSecret(&jobs.DeleteJobDefinitionSecretRequest{
324+
toCreate, toDelete, err := DiffJobDefinitionSecrets(oldSecretRefs, newSecretRefs)
325+
if err != nil {
326+
return diag.FromErr(err)
327+
}
328+
329+
for _, secret := range toDelete {
330+
deleteReq := &jobs.DeleteJobDefinitionSecretRequest{
312331
Region: region,
313332
JobDefinitionID: id,
314-
SecretID: oldSecretRef.SecretReferenceID,
315-
}); err != nil {
333+
SecretID: secret.SecretReferenceID,
334+
}
335+
if err := api.DeleteJobDefinitionSecret(deleteReq, scw.WithContext(ctx)); err != nil {
316336
return diag.FromErr(err)
317337
}
318338
}
319339

320-
if rawSecretReference, ok := d.GetOk("secret_reference"); ok {
321-
if err := CreateJobDefinitionSecret(rawSecretReference, api, region, id); err != nil {
340+
if len(toCreate) > 0 {
341+
if err := CreateJobDefinitionSecret(ctx, api, toCreate, region, id); err != nil {
322342
return diag.FromErr(err)
323343
}
324344
}

0 commit comments

Comments
 (0)