diff --git a/.web-docs/components/builder/linode/README.md b/.web-docs/components/builder/linode/README.md index b9fffa9..3728ccc 100644 --- a/.web-docs/components/builder/linode/README.md +++ b/.web-docs/components/builder/linode/README.md @@ -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. @@ -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" @@ -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" } } diff --git a/builder/linode/builder_test.go b/builder/linode/builder_test.go index 592bbc4..9484e46 100644 --- a/builder/linode/builder_test.go +++ b/builder/linode/builder_test.go @@ -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) + } +} diff --git a/builder/linode/config.go b/builder/linode/config.go index 0c0c3ff..5543d44 100644 --- a/builder/linode/config.go +++ b/builder/linode/config.go @@ -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. diff --git a/builder/linode/config.hcl2spec.go b/builder/linode/config.hcl2spec.go index 1291ee9..7dd745c 100644 --- a/builder/linode/config.hcl2spec.go +++ b/builder/linode/config.hcl2spec.go @@ -91,6 +91,7 @@ type FlatConfig struct { Metadata *FlatMetadata `mapstructure:"metadata" required:"false" cty:"metadata" hcl:"metadata"` FirewallID *int `mapstructure:"firewall_id" required:"false" cty:"firewall_id" hcl:"firewall_id"` ImageRegions []string `mapstructure:"image_regions" required:"false" cty:"image_regions" hcl:"image_regions"` + ImageShareGroupIDs []int `mapstructure:"image_share_group_ids" required:"false" cty:"image_share_group_ids" hcl:"image_share_group_ids"` InterfaceGeneration *string `mapstructure:"interface_generation" required:"false" cty:"interface_generation" hcl:"interface_generation"` } @@ -187,6 +188,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "metadata": &hcldec.BlockSpec{TypeName: "metadata", Nested: hcldec.ObjectSpec((*FlatMetadata)(nil).HCL2Spec())}, "firewall_id": &hcldec.AttrSpec{Name: "firewall_id", Type: cty.Number, Required: false}, "image_regions": &hcldec.AttrSpec{Name: "image_regions", Type: cty.List(cty.String), Required: false}, + "image_share_group_ids": &hcldec.AttrSpec{Name: "image_share_group_ids", Type: cty.List(cty.Number), Required: false}, "interface_generation": &hcldec.AttrSpec{Name: "interface_generation", Type: cty.String, Required: false}, } return s diff --git a/builder/linode/step_create_image.go b/builder/linode/step_create_image.go index 3c7f277..659c877 100644 --- a/builder/linode/step_create_image.go +++ b/builder/linode/step_create_image.go @@ -2,6 +2,7 @@ package linode import ( "context" + "fmt" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" @@ -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 { + 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, + }, + }, + }, + ) + 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, diff --git a/docs/builders/linode.mdx b/docs/builders/linode.mdx index 8bc5142..4ff745a 100644 --- a/docs/builders/linode.mdx +++ b/docs/builders/linode.mdx @@ -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" @@ -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" } }