@@ -3,6 +3,7 @@ package vercel
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "time"
6
7
7
8
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
8
9
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
@@ -161,7 +162,7 @@ type ProjectEnvironmentVariables struct {
161
162
Variables types.Set `tfsdk:"variables"`
162
163
}
163
164
164
- func (p * ProjectEnvironmentVariables ) environment (ctx context.Context ) ([] EnvironmentItem , diag.Diagnostics ) {
165
+ func (p * ProjectEnvironmentVariables ) environment (ctx context.Context ) (EnvironmentItems , diag.Diagnostics ) {
165
166
if p .Variables .IsNull () {
166
167
return nil , nil
167
168
}
@@ -239,44 +240,41 @@ func (r *projectEnvironmentVariablesResource) ModifyPlan(ctx context.Context, re
239
240
}
240
241
}
241
242
242
- func (e * ProjectEnvironmentVariables ) toCreateEnvironmentVariablesRequest (ctx context.Context ) (r client.CreateEnvironmentVariablesRequest , diags diag.Diagnostics ) {
243
- envs , diags := e .environment (ctx )
244
- if diags .HasError () {
245
- return r , diags
246
- }
243
+ type EnvironmentItems []EnvironmentItem
247
244
245
+ func (e * EnvironmentItems ) toCreateEnvironmentVariablesRequest (ctx context.Context , projectID types.String , teamID types.String ) (r client.CreateEnvironmentVariablesRequest , diags diag.Diagnostics ) {
248
246
variables := []client.EnvironmentVariableRequest {}
249
- for _ , e := range envs {
247
+ for _ , env := range * e {
250
248
var target []string
251
- diags = e .Target .ElementsAs (ctx , & target , true )
249
+ diags = env .Target .ElementsAs (ctx , & target , true )
252
250
if diags .HasError () {
253
251
return r , diags
254
252
}
255
253
var customEnvironmentIDs []string
256
- diags = e .CustomEnvironmentIDs .ElementsAs (ctx , & customEnvironmentIDs , true )
254
+ diags = env .CustomEnvironmentIDs .ElementsAs (ctx , & customEnvironmentIDs , true )
257
255
if diags .HasError () {
258
256
return r , diags
259
257
}
260
258
var envVariableType string
261
- if e .Sensitive .ValueBool () {
259
+ if env .Sensitive .ValueBool () {
262
260
envVariableType = "sensitive"
263
261
} else {
264
262
envVariableType = "encrypted"
265
263
}
266
264
variables = append (variables , client.EnvironmentVariableRequest {
267
- Key : e .Key .ValueString (),
268
- Value : e .Value .ValueString (),
265
+ Key : env .Key .ValueString (),
266
+ Value : env .Value .ValueString (),
269
267
Target : target ,
270
268
CustomEnvironmentIDs : customEnvironmentIDs ,
271
269
Type : envVariableType ,
272
- GitBranch : e .GitBranch .ValueStringPointer (),
273
- Comment : e .Comment .ValueString (),
270
+ GitBranch : env .GitBranch .ValueStringPointer (),
271
+ Comment : env .Comment .ValueString (),
274
272
})
275
273
}
276
274
277
275
return client.CreateEnvironmentVariablesRequest {
278
- ProjectID : e . ProjectID .ValueString (),
279
- TeamID : e . TeamID .ValueString (),
276
+ ProjectID : projectID .ValueString (),
277
+ TeamID : teamID .ValueString (),
280
278
EnvironmentVariables : variables ,
281
279
}, nil
282
280
}
@@ -323,7 +321,7 @@ func convertResponseToProjectEnvironmentVariables(
323
321
if e .Type == "sensitive" {
324
322
value = types .StringNull ()
325
323
}
326
- if ! e .Decrypted || e .Type == "sensitive" {
324
+ if e . Decrypted != nil && ! * e .Decrypted || e .Type == "sensitive" {
327
325
for _ , p := range environment {
328
326
var target []string
329
327
diags := p .Target .ElementsAs (ctx , & target , true )
@@ -393,7 +391,13 @@ func (r *projectEnvironmentVariablesResource) Create(ctx context.Context, req re
393
391
return
394
392
}
395
393
396
- request , diags := plan .toCreateEnvironmentVariablesRequest (ctx )
394
+ envs , diags := plan .environment (ctx )
395
+ if diags .HasError () {
396
+ resp .Diagnostics .Append (diags ... )
397
+ return
398
+ }
399
+
400
+ request , diags := envs .toCreateEnvironmentVariablesRequest (ctx , plan .ProjectID , plan .TeamID )
397
401
if diags .HasError () {
398
402
resp .Diagnostics .Append (diags ... )
399
403
return
@@ -442,7 +446,7 @@ func (r *projectEnvironmentVariablesResource) Read(ctx context.Context, req reso
442
446
}
443
447
existingIDs := map [string ]struct {}{}
444
448
for _ , e := range existing {
445
- if e .ID .ValueString () = = "" {
449
+ if e .ID .ValueString () ! = "" {
446
450
existingIDs [e .ID .ValueString ()] = struct {}{}
447
451
}
448
452
}
@@ -451,7 +455,7 @@ func (r *projectEnvironmentVariablesResource) Read(ctx context.Context, req reso
451
455
return
452
456
}
453
457
454
- envs , err := r .client .ListEnvironmentVariables (ctx , state .TeamID .ValueString (), state .ProjectID .ValueString ())
458
+ envs , err := r .client .GetEnvironmentVariables (ctx , state .ProjectID .ValueString (), state .TeamID .ValueString ())
455
459
if client .NotFound (err ) {
456
460
resp .State .RemoveResource (ctx )
457
461
return
@@ -467,9 +471,34 @@ func (r *projectEnvironmentVariablesResource) Read(ctx context.Context, req reso
467
471
var toUse []client.EnvironmentVariable
468
472
for _ , e := range envs {
469
473
if _ , ok := existingIDs [e .ID ]; ok {
474
+ // This ID exists in the env vars we have already. So use it.
470
475
toUse = append (toUse , e )
471
476
}
472
477
}
478
+ for _ , e := range envs {
479
+ if _ , ok := existingIDs [e .ID ]; ! ok {
480
+ // The env var exists at the moment, but not in TF state (the ID isn't present).
481
+ // Check if it has the same `key`, `target` and `custom_environment_ids` as an existing env var.
482
+ // This detects drift for stuff like: deleting an env var and then creating it again (the ID changes).
483
+ for _ , ee := range existing {
484
+ var target []string
485
+ diags := ee .Target .ElementsAs (ctx , & target , true )
486
+ if diags .HasError () {
487
+ resp .Diagnostics .Append (diags ... )
488
+ return
489
+ }
490
+ var customEnvironmentIDs []string
491
+ diags = ee .CustomEnvironmentIDs .ElementsAs (ctx , & customEnvironmentIDs , true )
492
+ if diags .HasError () {
493
+ resp .Diagnostics .Append (diags ... )
494
+ return
495
+ }
496
+ if ee .Key .ValueString () == e .Key && isSameStringSet (target , e .Target ) && isSameStringSet (customEnvironmentIDs , e .CustomEnvironmentIDs ) {
497
+ toUse = append (toUse , e )
498
+ }
499
+ }
500
+ }
501
+ }
473
502
474
503
result , diags := convertResponseToProjectEnvironmentVariables (ctx , toUse , state , nil )
475
504
if diags .HasError () {
@@ -516,7 +545,7 @@ func (r *projectEnvironmentVariablesResource) Update(ctx context.Context, req re
516
545
return
517
546
}
518
547
plannedEnvsByID := map [string ]EnvironmentItem {}
519
- toAdd := [] EnvironmentItem {}
548
+ var toAdd EnvironmentItems
520
549
for _ , e := range planEnvs {
521
550
if e .ID .ValueString () != "" {
522
551
plannedEnvsByID [e .ID .ValueString ()] = e
@@ -525,8 +554,8 @@ func (r *projectEnvironmentVariablesResource) Update(ctx context.Context, req re
525
554
}
526
555
}
527
556
528
- var toRemove [] EnvironmentItem
529
- var unchanged [] EnvironmentItem
557
+ var toRemove EnvironmentItems
558
+ var unchanged EnvironmentItems
530
559
for _ , e := range stateEnvs {
531
560
plannedEnv , ok := plannedEnvsByID [e .ID .ValueString ()]
532
561
if ! ok {
@@ -541,11 +570,105 @@ func (r *projectEnvironmentVariablesResource) Update(ctx context.Context, req re
541
570
unchanged = append (unchanged , e )
542
571
}
543
572
544
- tflog .Info (ctx , "Removing environment variables" , map [string ]any {"to_remove" : toRemove })
545
- tflog .Info (ctx , "Adding environment variables" , map [string ]any {"to_add" : toAdd })
573
+ envsFromAPI , err := r .client .GetEnvironmentVariables (ctx , state .ProjectID .ValueString (), state .TeamID .ValueString ())
574
+ if client .NotFound (err ) {
575
+ resp .State .RemoveResource (ctx )
576
+ return
577
+ }
578
+ if err != nil {
579
+ resp .Diagnostics .AddError (
580
+ "Error reading project environment variables as part of environment variable update" ,
581
+ "Could not read environment variables as part of updating, unexpected error: " + err .Error (),
582
+ )
583
+ return
584
+ }
585
+ skipAdding := map [int ]bool {}
586
+ for _ , e := range envsFromAPI {
587
+ // The env var exists at the moment, but not in TF state (the ID isn't present).
588
+ // Check if it has the same `key`, `target` and `custom_environment_ids` and value as any env var we are adding.
589
+ // This detects drift for stuff like: deleting an env var and then creating it again (the ID changes, but
590
+ // nothing else).
591
+ if _ , ok := plannedEnvsByID [e .ID ]; ! ok { // env isn't in the planned envs
592
+ for i , ee := range toAdd { // look for a matching env var in the toAdd list
593
+ var target []string
594
+ diags := ee .Target .ElementsAs (ctx , & target , true )
595
+ if diags .HasError () {
596
+ resp .Diagnostics .Append (diags ... )
597
+ return
598
+ }
599
+ var customEnvironmentIDs []string
600
+ diags = ee .CustomEnvironmentIDs .ElementsAs (ctx , & customEnvironmentIDs , true )
601
+ if diags .HasError () {
602
+ resp .Diagnostics .Append (diags ... )
603
+ return
604
+ }
605
+ if ee .Key .ValueString () == e .Key && isSameStringSet (target , e .Target ) && isSameStringSet (customEnvironmentIDs , e .CustomEnvironmentIDs ) {
606
+ if e .Decrypted != nil && ! * e .Decrypted {
607
+ continue // We don't know if it's value is encrypted.
608
+ }
609
+ if e .Type == "sensitive" {
610
+ continue // We don't know if it's the same env var if sensitive
611
+ }
612
+ if e .Value != ee .Value .ValueString () {
613
+ continue // Value mismatches, so we need to update it.
614
+ }
615
+
616
+ var targetValue types.Set
617
+ if len (e .Target ) > 0 {
618
+ target := make ([]attr.Value , 0 , len (e .Target ))
619
+ for _ , t := range e .Target {
620
+ target = append (target , types .StringValue (t ))
621
+ }
622
+ targetValue = types .SetValueMust (types .StringType , target )
623
+ } else {
624
+ targetValue = types .SetNull (types .StringType )
625
+ }
626
+
627
+ var customEnvIDsValue types.Set
628
+ if len (e .CustomEnvironmentIDs ) > 0 {
629
+ customEnvIDs := make ([]attr.Value , 0 , len (e .CustomEnvironmentIDs ))
630
+ for _ , c := range e .CustomEnvironmentIDs {
631
+ customEnvIDs = append (customEnvIDs , types .StringValue (c ))
632
+ }
633
+ customEnvIDsValue = types .SetValueMust (types .StringType , customEnvIDs )
634
+ } else {
635
+ customEnvIDsValue = types .SetNull (types .StringType )
636
+ }
637
+ unchanged = append (unchanged , EnvironmentItem {
638
+ Key : types .StringValue (e .Key ),
639
+ Value : types .StringValue (e .Value ),
640
+ Target : targetValue ,
641
+ CustomEnvironmentIDs : customEnvIDsValue ,
642
+ GitBranch : types .StringPointerValue (e .GitBranch ),
643
+ ID : types .StringValue (e .ID ),
644
+ Sensitive : types .BoolValue (e .Type == "sensitive" ),
645
+ Comment : types .StringValue (e .Comment ),
646
+ })
647
+ skipAdding [i ] = true
648
+ }
649
+ }
650
+ }
651
+ }
652
+ var filteredToAdd EnvironmentItems
653
+ for i , e := range toAdd {
654
+ if _ , ok := skipAdding [i ]; ok {
655
+ continue
656
+ }
657
+ filteredToAdd = append (filteredToAdd , e )
658
+ }
659
+ toAdd = filteredToAdd
660
+
661
+ tflog .Info (ctx , "Updating environment variables" , map [string ]any {
662
+ "to_remove" : len (toRemove ),
663
+ "to_add" : len (toAdd ),
664
+ "unchanged" : len (unchanged ),
665
+ })
546
666
547
667
for _ , v := range toRemove {
548
668
err := r .client .DeleteEnvironmentVariable (ctx , state .ProjectID .ValueString (), state .TeamID .ValueString (), v .ID .ValueString ())
669
+ if client .NotFound (err ) {
670
+ continue
671
+ }
549
672
if err != nil {
550
673
resp .Diagnostics .AddError (
551
674
"Error updating Project Environment Variables" ,
@@ -566,16 +689,18 @@ func (r *projectEnvironmentVariablesResource) Update(ctx context.Context, req re
566
689
}
567
690
568
691
var response []client.EnvironmentVariable
569
- var err error
570
692
if len (toAdd ) > 0 {
571
- request , diags := plan .toCreateEnvironmentVariablesRequest (ctx )
693
+ if len (toRemove ) > 0 {
694
+ // Sleep a bit to ensure the environment variables are fully propagated before we try to create them
695
+ // This is disgusting, but what you gonna do?
696
+ time .Sleep (time .Second * 5 )
697
+ }
698
+ request , diags := toAdd .toCreateEnvironmentVariablesRequest (ctx , plan .ProjectID , plan .TeamID )
699
+
572
700
if diags .HasError () {
573
701
resp .Diagnostics .Append (diags ... )
574
702
return
575
703
}
576
- tflog .Info (ctx , "create request" , map [string ]any {
577
- "request" : request ,
578
- })
579
704
response , err = r .client .CreateEnvironmentVariables (ctx , request )
580
705
if err != nil {
581
706
resp .Diagnostics .AddError (
@@ -586,10 +711,6 @@ func (r *projectEnvironmentVariablesResource) Update(ctx context.Context, req re
586
711
}
587
712
}
588
713
589
- tflog .Info (ctx , "project env var response" , map [string ]any {
590
- "response" : response ,
591
- })
592
-
593
714
result , diags := convertResponseToProjectEnvironmentVariables (ctx , response , plan , unchanged )
594
715
if diags .HasError () {
595
716
resp .Diagnostics .Append (diags ... )
0 commit comments