Skip to content

Commit c6aa483

Browse files
committed
image: Implement incus image alias nested block
Signed-off-by: Ruihua Wen <[email protected]>
1 parent 1811a53 commit c6aa483

File tree

5 files changed

+200
-437
lines changed

5 files changed

+200
-437
lines changed

docs/resources/image_alias.md

Lines changed: 0 additions & 33 deletions
This file was deleted.

internal/image/resource_image.go

Lines changed: 200 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type ImageModel struct {
4242
SourceImage types.Object `tfsdk:"source_image"`
4343
SourceInstance types.Object `tfsdk:"source_instance"`
4444
Aliases types.Set `tfsdk:"aliases"`
45+
Alias types.Set `tfsdk:"alias"`
4546
Project types.String `tfsdk:"project"`
4647
Remote types.String `tfsdk:"remote"`
4748

@@ -70,6 +71,11 @@ type SourceInstanceModel struct {
7071
Snapshot types.String `tfsdk:"snapshot"`
7172
}
7273

74+
type ImageAliasModel struct {
75+
Name types.String `tfsdk:"name"`
76+
Description types.String `tfsdk:"description"`
77+
}
78+
7379
// ImageResource represent Incus cached image resource.
7480
type ImageResource struct {
7581
provider *provider_config.IncusProviderConfig
@@ -226,6 +232,36 @@ func (r ImageResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
226232
ElementType: types.StringType,
227233
},
228234
},
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+
},
229265
}
230266
}
231267

@@ -338,14 +374,28 @@ func (r ImageResource) Update(ctx context.Context, req resource.UpdateRequest, r
338374
diags = req.State.GetAttribute(ctx, path.Root("aliases"), &newAliases)
339375
resp.Diagnostics.Append(diags...)
340376

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+
341385
if resp.Diagnostics.HasError() {
342386
return
343387
}
344388

345389
removed, added := utils.DiffSlices(oldAliases, newAliases)
346390

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+
347397
// Delete removed aliases.
348-
for _, alias := range removed {
398+
for _, alias := range allRemoved {
349399
err := server.DeleteImageAlias(alias)
350400
if err != nil {
351401
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
366416
}
367417
}
368418

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+
369445
// Update Terraform state.
370446
diags = r.SyncState(ctx, &resp.State, server, plan)
371447
resp.Diagnostics.Append(diags...)
@@ -422,8 +498,6 @@ func (r ImageResource) SyncState(ctx context.Context, tfState *tfsdk.State, serv
422498
return respDiags
423499
}
424500

425-
originalStateAliases := m.Aliases
426-
427501
if !m.SourceImage.IsNull() {
428502
var sourceImageModel SourceImageModel
429503
respDiags = m.SourceImage.As(ctx, &sourceImageModel, basetypes.ObjectAsOptions{})
@@ -453,22 +527,33 @@ func (r ImageResource) SyncState(ctx context.Context, tfState *tfsdk.State, serv
453527
copiedAliases, diags := ToAliasList(ctx, m.CopiedAliases)
454528
respDiags.Append(diags...)
455529

530+
configAliasBlocks, diags := ToAliasList(ctx, m.Alias)
531+
respDiags.Append(diags...)
532+
456533
// Copy aliases from image state that are present in user defined
457534
// config or are not copied.
458535
var aliases []string
536+
var aliasBlocks []api.ImageAlias
459537
for _, a := range image.Aliases {
460538
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+
461543
aliases = append(aliases, a.Name)
462544
}
463545
}
464546

465547
aliasSet, diags := ToAliasSetType(ctx, aliases)
466548
respDiags.Append(diags...)
467549

550+
aliasBlocksSet, diags := ToAliasBlocksSetType(ctx, aliasBlocks)
551+
respDiags.Append(diags...)
552+
468553
m.Fingerprint = types.StringValue(image.Fingerprint)
469554
m.CreatedAt = types.Int64Value(image.CreatedAt.Unix())
470555
m.Aliases = aliasSet
471-
m.Aliases = originalStateAliases
556+
m.Alias = aliasBlocksSet
472557

473558
if respDiags.HasError() {
474559
return respDiags
@@ -571,7 +656,16 @@ func (r ImageResource) createImageFromSourceFile(ctx context.Context, resp *reso
571656
return
572657
}
573658

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))
575669
for _, alias := range aliases {
576670
// Ensure image alias does not already exist.
577671
aliasTarget, _, _ := server.GetImageAlias(alias)
@@ -586,6 +680,26 @@ func (r ImageResource) createImageFromSourceFile(ctx context.Context, resp *reso
586680

587681
imageAliases = append(imageAliases, ia)
588682
}
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+
589703
image.Aliases = imageAliases
590704

591705
op, err := server.CreateImage(image, createArgs)
@@ -693,6 +807,15 @@ func (r ImageResource) createImageFromSourceImage(ctx context.Context, resp *res
693807
return
694808
}
695809

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+
696819
imageAliases := make([]api.ImageAlias, 0, len(aliases))
697820
for _, alias := range aliases {
698821
// Ensure image alias does not already exist.
@@ -709,6 +832,25 @@ func (r ImageResource) createImageFromSourceImage(ctx context.Context, resp *res
709832
imageAliases = append(imageAliases, ia)
710833
}
711834

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+
712854
// Get data about remote image (also checks if image exists).
713855
imageInfo, _, err := imageServer.GetImage(image)
714856
if err != nil {
@@ -807,6 +949,15 @@ func (r ImageResource) createImageFromSourceInstance(ctx context.Context, resp *
807949
return
808950
}
809951

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+
810961
imageAliases := make([]api.ImageAlias, 0, len(aliases))
811962
for _, alias := range aliases {
812963
// Ensure image alias does not already exist.
@@ -823,6 +974,25 @@ func (r ImageResource) createImageFromSourceInstance(ctx context.Context, resp *
823974
imageAliases = append(imageAliases, ia)
824975
}
825976

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+
826996
var source *api.ImagesPostSource
827997
if !sourceInstanceModel.Snapshot.IsNull() {
828998
snapsnotName := sourceInstanceModel.Snapshot.ValueString()
@@ -888,6 +1058,31 @@ func ToAliasSetType(ctx context.Context, aliases []string) (types.Set, diag.Diag
8881058
return types.SetValueFrom(ctx, types.StringType, aliases)
8891059
}
8901060

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+
8911086
// createImageResourceID creates new image ID by concatenating remote and
8921087
// image fingerprint using colon.
8931088
func createImageResourceID(remote string, fingerprint string) string {

0 commit comments

Comments
 (0)