@@ -42,6 +42,7 @@ type ImageModel struct {
42
42
SourceImage types.Object `tfsdk:"source_image"`
43
43
SourceInstance types.Object `tfsdk:"source_instance"`
44
44
Aliases types.Set `tfsdk:"aliases"`
45
+ Alias types.Set `tfsdk:"alias"`
45
46
Project types.String `tfsdk:"project"`
46
47
Remote types.String `tfsdk:"remote"`
47
48
@@ -70,6 +71,11 @@ type SourceInstanceModel struct {
70
71
Snapshot types.String `tfsdk:"snapshot"`
71
72
}
72
73
74
+ type ImageAliasModel struct {
75
+ Name types.String `tfsdk:"name"`
76
+ Description types.String `tfsdk:"description"`
77
+ }
78
+
73
79
// ImageResource represent Incus cached image resource.
74
80
type ImageResource struct {
75
81
provider * provider_config.IncusProviderConfig
@@ -226,6 +232,36 @@ func (r ImageResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
226
232
ElementType : types .StringType ,
227
233
},
228
234
},
235
+
236
+ Blocks : map [string ]schema.Block {
237
+ "alias" : schema.SetNestedBlock {
238
+ Description : "Image alias" ,
239
+ NestedObject : schema.NestedBlockObject {
240
+ Attributes : map [string ]schema.Attribute {
241
+ "name" : schema.StringAttribute {
242
+ Required : true ,
243
+ Description : "Image alias name" ,
244
+ Validators : []validator.String {
245
+ stringvalidator .LengthAtLeast (1 ),
246
+ },
247
+ },
248
+ "description" : schema.StringAttribute {
249
+ Optional : true ,
250
+ Description : "Image alias description" ,
251
+ PlanModifiers : []planmodifier.String {
252
+ stringplanmodifier .RequiresReplace (),
253
+ },
254
+ Validators : []validator.String {
255
+ stringvalidator .LengthAtLeast (1 ),
256
+ },
257
+ },
258
+ },
259
+ PlanModifiers : []planmodifier.Object {
260
+ objectplanmodifier .RequiresReplace (),
261
+ },
262
+ },
263
+ },
264
+ },
229
265
}
230
266
}
231
267
@@ -338,14 +374,28 @@ func (r ImageResource) Update(ctx context.Context, req resource.UpdateRequest, r
338
374
diags = req .State .GetAttribute (ctx , path .Root ("aliases" ), & newAliases )
339
375
resp .Diagnostics .Append (diags ... )
340
376
377
+ // Extract old and new nested alias blocks
378
+ oldAliasBlocks , diags := ToAliasList (ctx , plan .Alias )
379
+ resp .Diagnostics .Append (diags ... )
380
+
381
+ newAliasBlocks := make ([]string , 0 , len (plan .Alias .Elements ()))
382
+ diags = req .State .GetAttribute (ctx , path .Root ("alias" ), & newAliasBlocks )
383
+ resp .Diagnostics .Append (diags ... )
384
+
341
385
if resp .Diagnostics .HasError () {
342
386
return
343
387
}
344
388
345
389
removed , added := utils .DiffSlices (oldAliases , newAliases )
346
390
391
+ // Calculate differences for nested alias blocks
392
+ removedAliasBlocks , addedAliasBlocks := utils .DiffSlices (oldAliasBlocks , newAliasBlocks )
393
+
394
+ // Combine all removals
395
+ allRemoved := append (removed , removedAliasBlocks ... )
396
+
347
397
// Delete removed aliases.
348
- for _ , alias := range removed {
398
+ for _ , alias := range allRemoved {
349
399
err := server .DeleteImageAlias (alias )
350
400
if err != nil {
351
401
resp .Diagnostics .AddError (fmt .Sprintf ("Failed to delete alias %q for cached image with fingerprint %q" , alias , imageFingerprint ), err .Error ())
@@ -366,6 +416,32 @@ func (r ImageResource) Update(ctx context.Context, req resource.UpdateRequest, r
366
416
}
367
417
}
368
418
419
+ // Add new nested alias blocks (with descriptions)
420
+ var planAliasBlocks []ImageAliasModel
421
+ if ! plan .Alias .IsNull () {
422
+ diags = plan .Alias .ElementsAs (ctx , & planAliasBlocks , false )
423
+ resp .Diagnostics .Append (diags ... )
424
+ if resp .Diagnostics .HasError () {
425
+ return
426
+ }
427
+ }
428
+
429
+ // Create new alias blocks
430
+ for _ , aliasBlock := range planAliasBlocks {
431
+ if utils .ValueInSlice (aliasBlock .Name .ValueString (), addedAliasBlocks ) {
432
+ req := api.ImageAliasesPost {}
433
+ req .Name = aliasBlock .Name .ValueString ()
434
+ req .Description = aliasBlock .Description .ValueString ()
435
+ req .Target = imageFingerprint
436
+
437
+ err := server .CreateImageAlias (req )
438
+ if err != nil {
439
+ resp .Diagnostics .AddError (fmt .Sprintf ("Failed to create alias %q for cached image with fingerprint %q" , aliasBlock .Name .ValueString (), imageFingerprint ), err .Error ())
440
+ return
441
+ }
442
+ }
443
+ }
444
+
369
445
// Update Terraform state.
370
446
diags = r .SyncState (ctx , & resp .State , server , plan )
371
447
resp .Diagnostics .Append (diags ... )
@@ -422,8 +498,6 @@ func (r ImageResource) SyncState(ctx context.Context, tfState *tfsdk.State, serv
422
498
return respDiags
423
499
}
424
500
425
- originalStateAliases := m .Aliases
426
-
427
501
if ! m .SourceImage .IsNull () {
428
502
var sourceImageModel SourceImageModel
429
503
respDiags = m .SourceImage .As (ctx , & sourceImageModel , basetypes.ObjectAsOptions {})
@@ -453,22 +527,33 @@ func (r ImageResource) SyncState(ctx context.Context, tfState *tfsdk.State, serv
453
527
copiedAliases , diags := ToAliasList (ctx , m .CopiedAliases )
454
528
respDiags .Append (diags ... )
455
529
530
+ configAliasBlocks , diags := ToAliasList (ctx , m .Alias )
531
+ respDiags .Append (diags ... )
532
+
456
533
// Copy aliases from image state that are present in user defined
457
534
// config or are not copied.
458
535
var aliases []string
536
+ var aliasBlocks []api.ImageAlias
459
537
for _ , a := range image .Aliases {
460
538
if utils .ValueInSlice (a .Name , configAliases ) || ! utils .ValueInSlice (a .Name , copiedAliases ) {
539
+ if utils .ValueInSlice (a .Name , configAliasBlocks ) {
540
+ aliasBlocks = append (aliasBlocks , a )
541
+ }
542
+
461
543
aliases = append (aliases , a .Name )
462
544
}
463
545
}
464
546
465
547
aliasSet , diags := ToAliasSetType (ctx , aliases )
466
548
respDiags .Append (diags ... )
467
549
550
+ aliasBlocksSet , diags := ToAliasBlocksSetType (ctx , aliasBlocks )
551
+ respDiags .Append (diags ... )
552
+
468
553
m .Fingerprint = types .StringValue (image .Fingerprint )
469
554
m .CreatedAt = types .Int64Value (image .CreatedAt .Unix ())
470
555
m .Aliases = aliasSet
471
- m .Aliases = originalStateAliases
556
+ m .Alias = aliasBlocksSet
472
557
473
558
if respDiags .HasError () {
474
559
return respDiags
@@ -571,7 +656,16 @@ func (r ImageResource) createImageFromSourceFile(ctx context.Context, resp *reso
571
656
return
572
657
}
573
658
574
- imageAliases := make ([]api.ImageAlias , 0 , len (aliases ))
659
+ var imageAliasBlocks []ImageAliasModel
660
+ if ! plan .Alias .IsNull () {
661
+ diags = plan .Alias .ElementsAs (ctx , & imageAliasBlocks , false )
662
+ if diags .HasError () {
663
+ resp .Diagnostics .Append (diags ... )
664
+ return
665
+ }
666
+ }
667
+
668
+ imageAliases := make ([]api.ImageAlias , 0 , len (aliases )+ len (imageAliasBlocks ))
575
669
for _ , alias := range aliases {
576
670
// Ensure image alias does not already exist.
577
671
aliasTarget , _ , _ := server .GetImageAlias (alias )
@@ -586,6 +680,26 @@ func (r ImageResource) createImageFromSourceFile(ctx context.Context, resp *reso
586
680
587
681
imageAliases = append (imageAliases , ia )
588
682
}
683
+
684
+ for _ , imageAliasBlock := range imageAliasBlocks {
685
+ // Ensure image alias does not already exist.
686
+ name := imageAliasBlock .Name .ValueString ()
687
+ description := imageAliasBlock .Description .ValueString ()
688
+
689
+ aliasTarget , _ , _ := server .GetImageAlias (name )
690
+ if aliasTarget != nil {
691
+ resp .Diagnostics .AddError (fmt .Sprintf ("Image alias %q already exists" , name ), "" )
692
+ return
693
+ }
694
+
695
+ ia := api.ImageAlias {
696
+ Name : name ,
697
+ Description : description ,
698
+ }
699
+
700
+ imageAliases = append (imageAliases , ia )
701
+ }
702
+
589
703
image .Aliases = imageAliases
590
704
591
705
op , err := server .CreateImage (image , createArgs )
@@ -693,6 +807,15 @@ func (r ImageResource) createImageFromSourceImage(ctx context.Context, resp *res
693
807
return
694
808
}
695
809
810
+ var imageAliasBlocks []ImageAliasModel
811
+ if ! plan .Alias .IsNull () {
812
+ diags = plan .Alias .ElementsAs (ctx , & imageAliasBlocks , false )
813
+ if diags .HasError () {
814
+ resp .Diagnostics .Append (diags ... )
815
+ return
816
+ }
817
+ }
818
+
696
819
imageAliases := make ([]api.ImageAlias , 0 , len (aliases ))
697
820
for _ , alias := range aliases {
698
821
// Ensure image alias does not already exist.
@@ -709,6 +832,25 @@ func (r ImageResource) createImageFromSourceImage(ctx context.Context, resp *res
709
832
imageAliases = append (imageAliases , ia )
710
833
}
711
834
835
+ for _ , imageAliasBlock := range imageAliasBlocks {
836
+ // Ensure image alias does not already exist.
837
+ name := imageAliasBlock .Name .ValueString ()
838
+ description := imageAliasBlock .Description .ValueString ()
839
+
840
+ aliasTarget , _ , _ := server .GetImageAlias (name )
841
+ if aliasTarget != nil {
842
+ resp .Diagnostics .AddError (fmt .Sprintf ("Image alias %q already exists" , name ), "" )
843
+ return
844
+ }
845
+
846
+ ia := api.ImageAlias {
847
+ Name : name ,
848
+ Description : description ,
849
+ }
850
+
851
+ imageAliases = append (imageAliases , ia )
852
+ }
853
+
712
854
// Get data about remote image (also checks if image exists).
713
855
imageInfo , _ , err := imageServer .GetImage (image )
714
856
if err != nil {
@@ -807,6 +949,15 @@ func (r ImageResource) createImageFromSourceInstance(ctx context.Context, resp *
807
949
return
808
950
}
809
951
952
+ var imageAliasBlocks []ImageAliasModel
953
+ if ! plan .Alias .IsNull () {
954
+ diags = plan .Alias .ElementsAs (ctx , & imageAliasBlocks , false )
955
+ if diags .HasError () {
956
+ resp .Diagnostics .Append (diags ... )
957
+ return
958
+ }
959
+ }
960
+
810
961
imageAliases := make ([]api.ImageAlias , 0 , len (aliases ))
811
962
for _ , alias := range aliases {
812
963
// Ensure image alias does not already exist.
@@ -823,6 +974,25 @@ func (r ImageResource) createImageFromSourceInstance(ctx context.Context, resp *
823
974
imageAliases = append (imageAliases , ia )
824
975
}
825
976
977
+ for _ , imageAliasBlock := range imageAliasBlocks {
978
+ // Ensure image alias does not already exist.
979
+ name := imageAliasBlock .Name .ValueString ()
980
+ description := imageAliasBlock .Description .ValueString ()
981
+
982
+ aliasTarget , _ , _ := server .GetImageAlias (name )
983
+ if aliasTarget != nil {
984
+ resp .Diagnostics .AddError (fmt .Sprintf ("Image alias %q already exists" , name ), "" )
985
+ return
986
+ }
987
+
988
+ ia := api.ImageAlias {
989
+ Name : name ,
990
+ Description : description ,
991
+ }
992
+
993
+ imageAliases = append (imageAliases , ia )
994
+ }
995
+
826
996
var source * api.ImagesPostSource
827
997
if ! sourceInstanceModel .Snapshot .IsNull () {
828
998
snapsnotName := sourceInstanceModel .Snapshot .ValueString ()
@@ -888,6 +1058,31 @@ func ToAliasSetType(ctx context.Context, aliases []string) (types.Set, diag.Diag
888
1058
return types .SetValueFrom (ctx , types .StringType , aliases )
889
1059
}
890
1060
1061
+ // ToAliasBlocksSetType converts slice of strings into aliases of type types.Set.
1062
+ func ToAliasBlocksSetType (ctx context.Context , aliases []api.ImageAlias ) (types.Set , diag.Diagnostics ) {
1063
+ aliasType := map [string ]attr.Type {
1064
+ "name" : types .StringType ,
1065
+ "description" : types .StringType ,
1066
+ }
1067
+
1068
+ aliasList := make ([]ImageAliasModel , 0 , len (aliases ))
1069
+ for _ , a := range aliases {
1070
+ alias := ImageAliasModel {
1071
+ Name : types .StringValue (a .Name ),
1072
+ }
1073
+
1074
+ if a .Description != "" {
1075
+ alias .Description = types .StringValue (a .Description )
1076
+ } else {
1077
+ alias .Description = types .StringNull ()
1078
+ }
1079
+
1080
+ aliasList = append (aliasList , alias )
1081
+ }
1082
+
1083
+ return types .SetValueFrom (ctx , types.ObjectType {AttrTypes : aliasType }, aliasList )
1084
+ }
1085
+
891
1086
// createImageResourceID creates new image ID by concatenating remote and
892
1087
// image fingerprint using colon.
893
1088
func createImageResourceID (remote string , fingerprint string ) string {
0 commit comments