diff --git a/.web-docs/components/builder/linode/README.md b/.web-docs/components/builder/linode/README.md index e588e301..ffcbbe41 100644 --- a/.web-docs/components/builder/linode/README.md +++ b/.web-docs/components/builder/linode/README.md @@ -275,6 +275,8 @@ This section outlines the fields configurable for a newer Linode interface objec - `ipv4` (\*VPCInterfaceIPv4) - Interfaces can be configured with IPv4 addresses or ranges. +- `ipv6` (\*VPCInterfaceIPv6) - IPv6 configuration for this VPC interface. + @@ -330,6 +332,43 @@ This section outlines the fields configurable for a newer Linode interface objec +##### VPC Linode Interface IPv6 configuration object (VPCInterfaceIPv6) + +###### Optional + + + +- `slaac` ([]VPCInterfaceIPv6SLAAC) - IPv6 SLAAC settings for this VPC interface. + +- `ranges` ([]VPCInterfaceIPv6Range) - IPv6 ranges for this VPC interface. + +- `is_public` (\*bool) - Whether the IPv6 addresses are publicly routable. + + + + +##### VPC Linode Interface IPv6 SLAAC configuration object (VPCInterfaceIPv6SLAAC) + +###### Required + + + +- `range` (string) - The IPv6 SLAAC range for this VPC interface. + + + + +##### VPC Linode Interface IPv6 Range configuration object (VPCInterfaceIPv6Range) + +###### Required + + + +- `range` (string) - The IPv6 range for this VPC interface. + + + + ##### VLAN Linode Interface configuration object (VLANInterface) ###### Required diff --git a/builder/linode/builder_test.go b/builder/linode/builder_test.go index 901fa7c9..b88dfdd3 100644 --- a/builder/linode/builder_test.go +++ b/builder/linode/builder_test.go @@ -569,6 +569,71 @@ func TestBuilderPrepare_LinodeNetworkInterfaces(t *testing.T) { t.Fatalf("unexpected error: %v", err) } + config["linode_interface"] = []map[string]any{ + { + "firewall_id": 123, + "default_route": map[string]any{ + "ipv4": true, + "ipv6": true, + }, + "public": map[string]any{ + "ipv4": map[string]any{ + "address": []map[string]any{ + { + "address": "auto", + "primary": true, + }, + }, + }, + "ipv6": map[string]any{ + "ranges": []map[string]any{ + { + "range": "/64", + }, + }, + }, + }, + }, + { + "firewall_id": 123, + "default_route": map[string]any{ + "ipv4": false, + "ipv6": false, + }, + "vpc": map[string]any{ + "subnet_id": 12345, + "ipv4": map[string]any{ + "addresses": []map[string]any{ + {"address": "auto", "primary": false, "nat_1_1_address": "auto"}, + }, + }, + "ipv6": map[string]any{ + "slaac": []map[string]any{ + { + "range": "2600:3c03:e000:123::/64", + }, + }, + "ranges": []map[string]any{ + { + "range": "2600:3c03:e000:123:1::/64", + }, + }, + "is_public": true, + }, + }, + }, + { + "default_route": map[string]any{ + "ipv4": false, + "ipv6": false, + }, + "vlan": map[string]any{ + "vlan_label": "vlan-1", + "ipam_address": "10.0.0.1/24", + }, + }, + } + expectedLinodeInterfaces := []LinodeInterface{ { FirewallID: linodego.Pointer(123), @@ -611,6 +676,19 @@ func TestBuilderPrepare_LinodeNetworkInterfaces(t *testing.T) { }, }, }, + IPv6: &VPCInterfaceIPv6{ + SLAAC: []VPCInterfaceIPv6SLAAC{ + { + Range: "2600:3c03:e000:123::/64", + }, + }, + Ranges: []VPCInterfaceIPv6Range{ + { + Range: "2600:3c03:e000:123:1::/64", + }, + }, + IsPublic: linodego.Pointer(true), + }, }, }, { @@ -625,8 +703,6 @@ func TestBuilderPrepare_LinodeNetworkInterfaces(t *testing.T) { }, } - // Test set - config["linode_interface"] = expectedLinodeInterfaces b = Builder{} _, warnings, err = b.Prepare(config) if len(warnings) > 0 { diff --git a/builder/linode/linode_interfaces.go b/builder/linode/linode_interfaces.go index 9281aee1..ef4945b7 100644 --- a/builder/linode/linode_interfaces.go +++ b/builder/linode/linode_interfaces.go @@ -1,5 +1,5 @@ //go:generate packer-sdc struct-markdown -//go:generate packer-sdc mapstructure-to-hcl2 -type LinodeInterface,InterfaceDefaultRoute,PublicInterface,PublicInterfaceIPv4,PublicInterfaceIPv6,PublicInterfaceIPv4Address,PublicInterfaceIPv6Range,VPCInterface,VPCInterfaceIPv4,VPCInterfaceIPv4Address,VPCInterfaceIPv4Range,VLANInterface +//go:generate packer-sdc mapstructure-to-hcl2 -type LinodeInterface,InterfaceDefaultRoute,PublicInterface,PublicInterfaceIPv4,PublicInterfaceIPv6,PublicInterfaceIPv4Address,PublicInterfaceIPv6Range,VPCInterface,VPCInterfaceIPv4,VPCInterfaceIPv4Address,VPCInterfaceIPv4Range,VPCInterfaceIPv6,VPCInterfaceIPv6SLAAC,VPCInterfaceIPv6Range,VLANInterface package linode type LinodeInterface struct { @@ -79,7 +79,11 @@ type VPCInterface struct { // Interfaces can be configured with IPv4 addresses or ranges. IPv4 *VPCInterfaceIPv4 `mapstructure:"ipv4" required:"false"` + + // IPv6 configuration for this VPC interface. + IPv6 *VPCInterfaceIPv6 `mapstructure:"ipv6" required:"false"` } + type VPCInterfaceIPv4 struct { // IPv4 address settings for this VPC interface. Addresses []VPCInterfaceIPv4Address `mapstructure:"addresses" required:"false"` @@ -110,6 +114,27 @@ type VPCInterfaceIPv4Range struct { Range string `mapstructure:"range" required:"true"` } +type VPCInterfaceIPv6 struct { + // IPv6 SLAAC settings for this VPC interface. + SLAAC []VPCInterfaceIPv6SLAAC `mapstructure:"slaac" required:"false"` + + // IPv6 ranges for this VPC interface. + Ranges []VPCInterfaceIPv6Range `mapstructure:"ranges" required:"false"` + + // Whether the IPv6 addresses are publicly routable. + IsPublic *bool `mapstructure:"is_public" required:"false"` +} + +type VPCInterfaceIPv6SLAAC struct { + // The IPv6 SLAAC range for this VPC interface. + Range string `mapstructure:"range" required:"true"` +} + +type VPCInterfaceIPv6Range struct { + // The IPv6 range for this VPC interface. + Range string `mapstructure:"range" required:"true"` +} + type VLANInterface struct { // The VLAN's unique label. VLAN interfaces on the same Linode must have a unique `vlan_label`. VLANLabel string `mapstructure:"vlan_label" required:"true"` diff --git a/builder/linode/linode_interfaces.hcl2spec.go b/builder/linode/linode_interfaces.hcl2spec.go index 24ac5f79..b940dd24 100644 --- a/builder/linode/linode_interfaces.hcl2spec.go +++ b/builder/linode/linode_interfaces.hcl2spec.go @@ -212,6 +212,7 @@ func (*FlatVLANInterface) HCL2Spec() map[string]hcldec.Spec { type FlatVPCInterface struct { SubnetID *int `mapstructure:"subnet_id" required:"true" cty:"subnet_id" hcl:"subnet_id"` IPv4 *FlatVPCInterfaceIPv4 `mapstructure:"ipv4" required:"false" cty:"ipv4" hcl:"ipv4"` + IPv6 *FlatVPCInterfaceIPv6 `mapstructure:"ipv6" required:"false" cty:"ipv6" hcl:"ipv6"` } // FlatMapstructure returns a new FlatVPCInterface. @@ -228,6 +229,7 @@ func (*FlatVPCInterface) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.Number, Required: false}, "ipv4": &hcldec.BlockSpec{TypeName: "ipv4", Nested: hcldec.ObjectSpec((*FlatVPCInterfaceIPv4)(nil).HCL2Spec())}, + "ipv6": &hcldec.BlockSpec{TypeName: "ipv6", Nested: hcldec.ObjectSpec((*FlatVPCInterfaceIPv6)(nil).HCL2Spec())}, } return s } @@ -306,3 +308,76 @@ func (*FlatVPCInterfaceIPv4Range) HCL2Spec() map[string]hcldec.Spec { } return s } + +// FlatVPCInterfaceIPv6 is an auto-generated flat version of VPCInterfaceIPv6. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatVPCInterfaceIPv6 struct { + SLAAC []FlatVPCInterfaceIPv6SLAAC `mapstructure:"slaac" required:"false" cty:"slaac" hcl:"slaac"` + Ranges []FlatVPCInterfaceIPv6Range `mapstructure:"ranges" required:"false" cty:"ranges" hcl:"ranges"` + IsPublic *bool `mapstructure:"is_public" required:"false" cty:"is_public" hcl:"is_public"` +} + +// FlatMapstructure returns a new FlatVPCInterfaceIPv6. +// FlatVPCInterfaceIPv6 is an auto-generated flat version of VPCInterfaceIPv6. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*VPCInterfaceIPv6) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatVPCInterfaceIPv6) +} + +// HCL2Spec returns the hcl spec of a VPCInterfaceIPv6. +// This spec is used by HCL to read the fields of VPCInterfaceIPv6. +// The decoded values from this spec will then be applied to a FlatVPCInterfaceIPv6. +func (*FlatVPCInterfaceIPv6) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "slaac": &hcldec.BlockListSpec{TypeName: "slaac", Nested: hcldec.ObjectSpec((*FlatVPCInterfaceIPv6SLAAC)(nil).HCL2Spec())}, + "ranges": &hcldec.BlockListSpec{TypeName: "ranges", Nested: hcldec.ObjectSpec((*FlatVPCInterfaceIPv6Range)(nil).HCL2Spec())}, + "is_public": &hcldec.AttrSpec{Name: "is_public", Type: cty.Bool, Required: false}, + } + return s +} + +// FlatVPCInterfaceIPv6Range is an auto-generated flat version of VPCInterfaceIPv6Range. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatVPCInterfaceIPv6Range struct { + Range *string `mapstructure:"range" required:"true" cty:"range" hcl:"range"` +} + +// FlatMapstructure returns a new FlatVPCInterfaceIPv6Range. +// FlatVPCInterfaceIPv6Range is an auto-generated flat version of VPCInterfaceIPv6Range. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*VPCInterfaceIPv6Range) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatVPCInterfaceIPv6Range) +} + +// HCL2Spec returns the hcl spec of a VPCInterfaceIPv6Range. +// This spec is used by HCL to read the fields of VPCInterfaceIPv6Range. +// The decoded values from this spec will then be applied to a FlatVPCInterfaceIPv6Range. +func (*FlatVPCInterfaceIPv6Range) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "range": &hcldec.AttrSpec{Name: "range", Type: cty.String, Required: false}, + } + return s +} + +// FlatVPCInterfaceIPv6SLAAC is an auto-generated flat version of VPCInterfaceIPv6SLAAC. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatVPCInterfaceIPv6SLAAC struct { + Range *string `mapstructure:"range" required:"true" cty:"range" hcl:"range"` +} + +// FlatMapstructure returns a new FlatVPCInterfaceIPv6SLAAC. +// FlatVPCInterfaceIPv6SLAAC is an auto-generated flat version of VPCInterfaceIPv6SLAAC. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*VPCInterfaceIPv6SLAAC) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatVPCInterfaceIPv6SLAAC) +} + +// HCL2Spec returns the hcl spec of a VPCInterfaceIPv6SLAAC. +// This spec is used by HCL to read the fields of VPCInterfaceIPv6SLAAC. +// The decoded values from this spec will then be applied to a FlatVPCInterfaceIPv6SLAAC. +func (*FlatVPCInterfaceIPv6SLAAC) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "range": &hcldec.AttrSpec{Name: "range", Type: cty.String, Required: false}, + } + return s +} diff --git a/builder/linode/step_create_linode.go b/builder/linode/step_create_linode.go index c9a43fee..888c58db 100644 --- a/builder/linode/step_create_linode.go +++ b/builder/linode/step_create_linode.go @@ -95,6 +95,25 @@ func flattenVPCInterface(vpc *VPCInterface) *linodego.VPCInterfaceCreateOptions Ranges: linodego.Pointer(ranges), } } + if vpc.IPv6 != nil { + slaac := make([]linodego.VPCInterfaceIPv6SLAACCreateOptions, len(vpc.IPv6.SLAAC)) + ranges := make([]linodego.VPCInterfaceIPv6RangeCreateOptions, len(vpc.IPv6.Ranges)) + for i, s := range vpc.IPv6.SLAAC { + slaac[i] = linodego.VPCInterfaceIPv6SLAACCreateOptions{ + Range: s.Range, + } + } + for i, r := range vpc.IPv6.Ranges { + ranges[i] = linodego.VPCInterfaceIPv6RangeCreateOptions{ + Range: r.Range, + } + } + result.IPv6 = &linodego.VPCInterfaceIPv6CreateOptions{ + SLAAC: linodego.Pointer(slaac), + Ranges: linodego.Pointer(ranges), + IsPublic: vpc.IPv6.IsPublic, + } + } return result } diff --git a/builder/linode/step_create_linode_test.go b/builder/linode/step_create_linode_test.go index 3d25bfea..f95a958a 100644 --- a/builder/linode/step_create_linode_test.go +++ b/builder/linode/step_create_linode_test.go @@ -55,6 +55,44 @@ func TestFlattenVPCInterface_IPv4AddressFields(t *testing.T) { } } +func TestFlattenVPCInterface_IPv6Fields(t *testing.T) { + vpc := &VPCInterface{ + SubnetID: 12345, + IPv6: &VPCInterfaceIPv6{ + SLAAC: []VPCInterfaceIPv6SLAAC{ + {Range: "2600:3c03:e000:123::/64"}, + }, + Ranges: []VPCInterfaceIPv6Range{ + {Range: "2600:3c03:e000:123:1::/64"}, + }, + IsPublic: linodego.Pointer(true), + }, + } + + got := flattenVPCInterface(vpc) + if got == nil { + t.Fatal("flattenVPCInterface() returned nil") + } + if got.IPv6 == nil { + t.Fatal("flattenVPCInterface().IPv6 returned nil") + } + if got.IPv6.SLAAC == nil || len(*got.IPv6.SLAAC) != 1 { + t.Fatalf("slaac = %v, want one slaac range", got.IPv6.SLAAC) + } + if (*got.IPv6.SLAAC)[0].Range != "2600:3c03:e000:123::/64" { + t.Fatalf("slaac range = %q, want 2600:3c03:e000:123::/64", (*got.IPv6.SLAAC)[0].Range) + } + if got.IPv6.Ranges == nil || len(*got.IPv6.Ranges) != 1 { + t.Fatalf("ranges = %v, want one ipv6 range", got.IPv6.Ranges) + } + if (*got.IPv6.Ranges)[0].Range != "2600:3c03:e000:123:1::/64" { + t.Fatalf("range = %q, want 2600:3c03:e000:123:1::/64", (*got.IPv6.Ranges)[0].Range) + } + if got.IPv6.IsPublic == nil || !*got.IPv6.IsPublic { + t.Fatalf("is_public = %v, want true", got.IPv6.IsPublic) + } +} + func TestFlattenConfigInterface_AllFields(t *testing.T) { vpcIP := &InterfaceIPv4{VPC: "10.0.0.2", NAT1To1: linodego.Pointer("198.51.100.2")} iface := Interface{ diff --git a/docs/builders/linode.mdx b/docs/builders/linode.mdx index b9824f40..f9789f22 100644 --- a/docs/builders/linode.mdx +++ b/docs/builders/linode.mdx @@ -127,6 +127,24 @@ This section outlines the fields configurable for a newer Linode interface objec @include 'builder/linode/VPCInterfaceIPv4Range-required.mdx' +##### VPC Linode Interface IPv6 configuration object (VPCInterfaceIPv6) + +###### Optional + +@include 'builder/linode/VPCInterfaceIPv6-not-required.mdx' + +##### VPC Linode Interface IPv6 SLAAC configuration object (VPCInterfaceIPv6SLAAC) + +###### Required + +@include 'builder/linode/VPCInterfaceIPv6SLAAC-required.mdx' + +##### VPC Linode Interface IPv6 Range configuration object (VPCInterfaceIPv6Range) + +###### Required + +@include 'builder/linode/VPCInterfaceIPv6Range-required.mdx' + ##### VLAN Linode Interface configuration object (VLANInterface) ###### Required