Skip to content

Commit cd7e87a

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

File tree

5 files changed

+1351
-379
lines changed

5 files changed

+1351
-379
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: 36 additions & 15 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,
@@ -110,8 +122,9 @@ func ResourceDefinition() *schema.Resource {
110122
},
111123
"secret_version": {
112124
Type: schema.TypeString,
113-
Description: "The secret version, default to Latest.",
114-
Required: true,
125+
Description: "The secret version.",
126+
Default: "latest",
127+
Optional: true,
115128
},
116129
"file": {
117130
Type: schema.TypeString,
@@ -123,7 +136,7 @@ func ResourceDefinition() *schema.Resource {
123136
Type: schema.TypeString,
124137
Optional: true,
125138
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"),
139+
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"),
127140
},
128141
},
129142
},
@@ -171,7 +184,7 @@ func ResourceJobDefinitionCreate(ctx context.Context, d *schema.ResourceData, m
171184
}
172185

173186
if rawSecretReference, ok := d.GetOk("secret_reference"); ok {
174-
if err := CreateJobDefinitionSecret(rawSecretReference, api, region, definition.ID); err != nil {
187+
if err := CreateJobDefinitionSecret(ctx, api, expandJobDefinitionSecret(rawSecretReference), region, definition.ID); err != nil {
175188
return diag.FromErr(err)
176189
}
177190
}
@@ -304,21 +317,29 @@ func ResourceJobDefinitionUpdate(ctx context.Context, d *schema.ResourceData, m
304317
}
305318

306319
if d.HasChange("secret_reference") {
307-
oldRawSecretRefs, _ := d.GetChange("secret_reference")
320+
oldRawSecretRefs, newRawSecretRefs := d.GetChange("secret_reference")
321+
308322
oldSecretRefs := expandJobDefinitionSecret(oldRawSecretRefs)
323+
newSecretRefs := expandJobDefinitionSecret(newRawSecretRefs)
309324

310-
for _, oldSecretRef := range oldSecretRefs {
311-
if err := api.DeleteJobDefinitionSecret(&jobs.DeleteJobDefinitionSecretRequest{
325+
toCreate, toDelete, err := DiffJobDefinitionSecrets(oldSecretRefs, newSecretRefs)
326+
if err != nil {
327+
return diag.FromErr(err)
328+
}
329+
330+
for _, secret := range toDelete {
331+
deleteReq := &jobs.DeleteJobDefinitionSecretRequest{
312332
Region: region,
313333
JobDefinitionID: id,
314-
SecretID: oldSecretRef.SecretReferenceID,
315-
}); err != nil {
334+
SecretID: secret.SecretReferenceID,
335+
}
336+
if err := api.DeleteJobDefinitionSecret(deleteReq, scw.WithContext(ctx)); err != nil {
316337
return diag.FromErr(err)
317338
}
318339
}
319340

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

0 commit comments

Comments
 (0)