Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .web-docs/components/builder/linode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ can also be supplied to override the typical auto-generated key:

- `image_regions` ([]string) - The regions where the outcome image will be replicated to.

- `image_share_group_ids` ([]int) - Image Share Group IDs to add the newly created private image to
immediately after image creation.

- `interface_generation` (string) - Specifies the interface type for the Linode. The value can be either
`legacy_config` or `linode`. The default value is determined by the
`interfaces_for_new_linodes` setting in the account settings.
Expand Down Expand Up @@ -426,6 +429,7 @@ source "linode" "example" {
image = "linode/debian11"
image_description = "My Private Image"
image_label = "private-image-${local.timestamp}"
image_share_group_ids = [12345]
instance_label = "temporary-linode-${local.timestamp}"
instance_type = "g6-nanode-1"
linode_token = "YOUR API TOKEN"
Expand Down Expand Up @@ -453,6 +457,7 @@ build {
"instance_label": "temporary-linode-{{timestamp}}",
"image_label": "private-image-{{timestamp}}",
"image_description": "My Private Image",
"image_share_group_ids": [12345],
"ssh_username": "root"
}
}
Expand Down
31 changes: 31 additions & 0 deletions builder/linode/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,34 @@ func TestBuilderPrepare_MetadataTagsFirewallID(t *testing.T) {
t.Errorf("got %v, expected %v", b.config.Tags, expectedTags)
}
}

func TestBuilderPrepare_ImageShareGroupIDs(t *testing.T) {
var b Builder
config := testConfig()

delete(config, "image_share_group_ids")
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(b.config.ImageShareGroupIDs) != 0 {
t.Errorf("expected nil or empty, got %v", b.config.ImageShareGroupIDs)
}

expected := []int{101, 202, 303}
config["image_share_group_ids"] = expected
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if !reflect.DeepEqual(b.config.ImageShareGroupIDs, expected) {
t.Errorf("got %v, expected %v", b.config.ImageShareGroupIDs, expected)
}
}
4 changes: 4 additions & 0 deletions builder/linode/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ type Config struct {
// The regions where the outcome image will be replicated to.
ImageRegions []string `mapstructure:"image_regions" required:"false"`

// Image Share Group IDs to add the newly created private image to
// immediately after image creation.
ImageShareGroupIDs []int `mapstructure:"image_share_group_ids" required:"false"`

// Specifies the interface type for the Linode. The value can be either
// `legacy_config` or `linode`. The default value is determined by the
// `interfaces_for_new_linodes` setting in the account settings.
Expand Down
2 changes: 2 additions & 0 deletions builder/linode/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions builder/linode/step_create_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package linode

import (
"context"
"fmt"

"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
Expand Down Expand Up @@ -45,6 +46,39 @@ func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul
return handleError("Failed to wait for image creation", err)
}

// Add the image to Image Share Groups, if configured
if len(c.ImageShareGroupIDs) > 0 {
for _, shareGroupID := range c.ImageShareGroupIDs {
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Images are being added to share groups sequentially. If multiple share groups are specified, this could be slow. Consider whether parallel addition or batch operations are supported by the API to improve performance.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API only supports adding images to one Share Group at a time so I think adding them sequentially is fine. Again, happy to hear others' thoughts.

ui.Say(fmt.Sprintf(
"Adding image %s to image share group %d...",
image.ID,
shareGroupID,
))

_, err := s.client.ImageShareGroupAddImages(
ctx,
shareGroupID,
linodego.ImageShareGroupAddImagesOptions{
Images: []linodego.ImageShareGroupImage{
{
ID: image.ID,
},
},
},
)
Comment on lines +58 to +68
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value from ImageShareGroupAddImages is being discarded. If the API returns important information (such as confirmation details or partial success status), it should be logged or handled appropriately.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns essentially the same exact information that is already contained in the image, so I think it is fine to discard the return value. Happy to hear others' thoughts on this.

if err != nil {
return handleError(
fmt.Sprintf(
"Failed to add image %s to image share group %d",
image.ID,
shareGroupID,
),
err,
)
}
}
}

if len(c.ImageRegions) > 0 {
image, err = s.client.ReplicateImage(ctx, image.ID, linodego.ImageReplicateOptions{
Regions: c.ImageRegions,
Expand Down
2 changes: 2 additions & 0 deletions docs/builders/linode.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ source "linode" "example" {
image = "linode/debian11"
image_description = "My Private Image"
image_label = "private-image-${local.timestamp}"
image_share_group_ids = [12345]
instance_label = "temporary-linode-${local.timestamp}"
instance_type = "g6-nanode-1"
linode_token = "YOUR API TOKEN"
Expand Down Expand Up @@ -213,6 +214,7 @@ build {
"instance_label": "temporary-linode-{{timestamp}}",
"image_label": "private-image-{{timestamp}}",
"image_description": "My Private Image",
"image_share_group_ids": [12345],
"ssh_username": "root"
}
}
Expand Down