@@ -3,6 +3,7 @@ package provider
3
3
import (
4
4
"bufio"
5
5
"context"
6
+ "encoding/json"
6
7
"fmt"
7
8
"io"
8
9
@@ -339,7 +340,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
339
340
Computed : true ,
340
341
},
341
342
"name" : schema.StringAttribute {
342
- MarkdownDescription : "The name of the template version. Automatically generated if not provided." ,
343
+ MarkdownDescription : "The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents are updated. " ,
343
344
Optional : true ,
344
345
Computed : true ,
345
346
},
@@ -495,6 +496,11 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
495
496
data .ID = UUIDValue (templateResp .ID )
496
497
data .DisplayName = types .StringValue (templateResp .DisplayName )
497
498
499
+ resp .Diagnostics .Append (data .Versions .writePrivateState (ctx , resp .Private )... )
500
+ if resp .Diagnostics .HasError () {
501
+ return
502
+ }
503
+
498
504
// Save data into Terraform sutate
499
505
resp .Diagnostics .Append (resp .State .Set (ctx , & data )... )
500
506
}
@@ -562,11 +568,11 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
562
568
}
563
569
564
570
func (r * TemplateResource ) Update (ctx context.Context , req resource.UpdateRequest , resp * resource.UpdateResponse ) {
565
- var planState TemplateResourceModel
571
+ var newState TemplateResourceModel
566
572
var curState TemplateResourceModel
567
573
568
574
// Read Terraform plan data into the model
569
- resp .Diagnostics .Append (req .Plan .Get (ctx , & planState )... )
575
+ resp .Diagnostics .Append (req .Plan .Get (ctx , & newState )... )
570
576
571
577
if resp .Diagnostics .HasError () {
572
578
return
@@ -578,25 +584,25 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
578
584
return
579
585
}
580
586
581
- if planState .OrganizationID .IsUnknown () {
582
- planState .OrganizationID = UUIDValue (r .data .DefaultOrganizationID )
587
+ if newState .OrganizationID .IsUnknown () {
588
+ newState .OrganizationID = UUIDValue (r .data .DefaultOrganizationID )
583
589
}
584
590
585
- if planState .DisplayName .IsUnknown () {
586
- planState .DisplayName = planState .Name
591
+ if newState .DisplayName .IsUnknown () {
592
+ newState .DisplayName = newState .Name
587
593
}
588
594
589
- orgID := planState .OrganizationID .ValueUUID ()
595
+ orgID := newState .OrganizationID .ValueUUID ()
590
596
591
- templateID := planState .ID .ValueUUID ()
597
+ templateID := newState .ID .ValueUUID ()
592
598
593
599
client := r .data .Client
594
600
595
- templateMetadataChanged := ! planState .EqualTemplateMetadata (curState )
601
+ templateMetadataChanged := ! newState .EqualTemplateMetadata (curState )
596
602
// This is required, as the API will reject no-diff updates.
597
603
if templateMetadataChanged {
598
604
tflog .Trace (ctx , "change in template metadata detected, updating." )
599
- updateReq := planState .toUpdateRequest (ctx , resp )
605
+ updateReq := newState .toUpdateRequest (ctx , resp )
600
606
if resp .Diagnostics .HasError () {
601
607
return
602
608
}
@@ -611,9 +617,9 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
611
617
612
618
// Since the everyone group always gets deleted by `DisableEveryoneGroupAccess`, we need to run this even if there
613
619
// were no ACL changes but the template metadata was updated.
614
- if ! planState .ACL .IsNull () && (! curState .ACL .Equal (planState .ACL ) || templateMetadataChanged ) {
620
+ if ! newState .ACL .IsNull () && (! curState .ACL .Equal (newState .ACL ) || templateMetadataChanged ) {
615
621
var acl ACL
616
- resp .Diagnostics .Append (planState .ACL .As (ctx , & acl , basetypes.ObjectAsOptions {})... )
622
+ resp .Diagnostics .Append (newState .ACL .As (ctx , & acl , basetypes.ObjectAsOptions {})... )
617
623
if resp .Diagnostics .HasError () {
618
624
return
619
625
}
@@ -625,51 +631,62 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
625
631
tflog .Trace (ctx , "successfully updated template ACL" )
626
632
}
627
633
628
- for idx , plannedVersion := range planState .Versions {
629
- var curVersionID uuid.UUID
630
- // All versions in the state are guaranteed to have known IDs
631
- foundVersion := curState .Versions .ByID (plannedVersion .ID )
632
- // If the version is new, or if the directory hash has changed, create a new version
633
- if foundVersion == nil || foundVersion .DirectoryHash != plannedVersion .DirectoryHash {
634
+ // Populate version IDs, based off previously created template versions stored in private state.
635
+ resp .Diagnostics .Append (readPrivateState (ctx , newState .Versions , req .Private )... )
636
+ if resp .Diagnostics .HasError () {
637
+ return
638
+ }
639
+ for idx := range newState .Versions {
640
+ if newState .Versions [idx ].ID .IsUnknown () {
634
641
tflog .Trace (ctx , "discovered a new or modified template version" )
635
- versionResp , err := newVersion (ctx , client , newVersionRequest {
636
- Version : & plannedVersion ,
642
+ uploadResp , err := newVersion (ctx , client , newVersionRequest {
643
+ Version : & newState . Versions [ idx ] ,
637
644
OrganizationID : orgID ,
638
645
TemplateID : & templateID ,
639
646
})
640
647
if err != nil {
641
648
resp .Diagnostics .AddError ("Client Error" , err .Error ())
642
649
return
643
650
}
644
- curVersionID = versionResp .ID
651
+ versionResp , err := client .TemplateVersion (ctx , uploadResp .ID )
652
+ if err != nil {
653
+ resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to get template version: %s" , err ))
654
+ return
655
+ }
656
+ newState .Versions [idx ].ID = UUIDValue (versionResp .ID )
645
657
} else {
646
- // Or if it's an existing version, get the ID
647
- curVersionID = plannedVersion .ID .ValueUUID ()
648
- }
649
- versionResp , err := client .TemplateVersion (ctx , curVersionID )
650
- if err != nil {
651
- resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to get template version: %s" , err ))
652
- return
658
+ _ , err := client .UpdateTemplateVersion (ctx , newState .Versions [idx ].ID .ValueUUID (), codersdk.PatchTemplateVersionRequest {
659
+ Name : newState .Versions [idx ].Name .ValueString (),
660
+ Message : newState .Versions [idx ].Message .ValueStringPointer (),
661
+ })
662
+ if err != nil {
663
+ resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to update template version metadata: %s" , err ))
664
+ return
665
+ }
653
666
}
654
- if plannedVersion .Active .ValueBool () {
667
+ if newState . Versions [ idx ] .Active .ValueBool () {
655
668
tflog .Trace (ctx , "marking template version as active" , map [string ]any {
656
- "version_id" : versionResp . ID ,
657
- "template_id" : templateID ,
669
+ "version_id" : newState . Versions [ idx ]. ID . ValueString () ,
670
+ "template_id" : templateID . String () ,
658
671
})
659
672
err := client .UpdateActiveTemplateVersion (ctx , templateID , codersdk.UpdateActiveTemplateVersion {
660
- ID : versionResp . ID ,
673
+ ID : newState . Versions [ idx ]. ID . ValueUUID () ,
661
674
})
662
675
if err != nil {
663
676
resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to update active template version: %s" , err ))
664
677
return
665
678
}
666
679
tflog .Trace (ctx , "marked template version as active" )
667
680
}
668
- planState .Versions [idx ].ID = UUIDValue (versionResp .ID )
681
+ }
682
+
683
+ resp .Diagnostics .Append (newState .Versions .writePrivateState (ctx , resp .Private )... )
684
+ if resp .Diagnostics .HasError () {
685
+ return
669
686
}
670
687
671
688
// Save updated data into Terraform state
672
- resp .Diagnostics .Append (resp .State .Set (ctx , & planState )... )
689
+ resp .Diagnostics .Append (resp .State .Set (ctx , & newState )... )
673
690
}
674
691
675
692
func (r * TemplateResource ) Delete (ctx context.Context , req resource.DeleteRequest , resp * resource.DeleteResponse ) {
@@ -1053,3 +1070,58 @@ func (r *TemplateResourceModel) toCreateRequest(ctx context.Context, resp *resou
1053
1070
DisableEveryoneGroupAccess : ! r .ACL .IsNull (),
1054
1071
}
1055
1072
}
1073
+
1074
+ type PreviousTemplateVersion struct {
1075
+ ID uuid.UUID `json:"id"`
1076
+ Name string `json:"name"`
1077
+ }
1078
+
1079
+ type privateState interface {
1080
+ GetKey (ctx context.Context , key string ) ([]byte , diag.Diagnostics )
1081
+ SetKey (ctx context.Context , key string , value []byte ) diag.Diagnostics
1082
+ }
1083
+
1084
+ func (v Versions ) writePrivateState (ctx context.Context , ps privateState ) (diags diag.Diagnostics ) {
1085
+ for _ , version := range v {
1086
+ prevBytes , err := json .Marshal (PreviousTemplateVersion {ID : version .ID .ValueUUID (), Name : version .Name .ValueString ()})
1087
+ if err != nil {
1088
+ diags .AddError ("Client Error" , fmt .Sprintf ("Failed to marshal name to json bytes: %s" , err ))
1089
+ return diags
1090
+ }
1091
+ diag := ps .SetKey (ctx , version .DirectoryHash .ValueString (), prevBytes )
1092
+ if diag .HasError () {
1093
+ return diag
1094
+ }
1095
+ }
1096
+ return diags
1097
+ }
1098
+
1099
+ func readPrivateState (ctx context.Context , v Versions , ps privateState ) (diags diag.Diagnostics ) {
1100
+ for idx , version := range v {
1101
+ jsonBytes , diag := ps .GetKey (ctx , version .DirectoryHash .ValueString ())
1102
+ if diag .HasError () {
1103
+ return diag
1104
+ }
1105
+ // If not in state, create it
1106
+ if jsonBytes == nil {
1107
+ continue
1108
+ }
1109
+ var prev PreviousTemplateVersion
1110
+ err := json .Unmarshal (jsonBytes , & prev )
1111
+ if err != nil {
1112
+ diags .AddError ("Client Error" , fmt .Sprintf ("Failed to unmarshal name from json bytes: %s" , err ))
1113
+ return diags
1114
+ }
1115
+ // If in the state, but with a different name, create it
1116
+ if prev .Name != version .Name .ValueString () {
1117
+ continue
1118
+ }
1119
+ // If in the state, but with no name, create it
1120
+ if prev .Name == "" {
1121
+ continue
1122
+ }
1123
+ // Otherwise, use the ID from last time
1124
+ v [idx ].ID = UUIDValue (prev .ID )
1125
+ }
1126
+ return
1127
+ }
0 commit comments