From 1371596d750b431776cdcb4fd68916d4e9b710bb Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Fri, 1 May 2026 19:49:59 +0400 Subject: [PATCH] fix: provide proper AWS platform metadata 1. Explicitly enable DHCPv4 on v4 instances. 2. Run DHCP6 if IPv6 is connected. 3. Support v6-only environments. 4. Add DNS for v6. Signed-off-by: Andrey Smirnov --- .../pkg/runtime/v1alpha1/platform/aws/aws.go | 253 ++++++++++++++++-- .../runtime/v1alpha1/platform/aws/aws_test.go | 54 +++- .../runtime/v1alpha1/platform/aws/metadata.go | 111 +++++++- .../platform/aws/testdata/expected-v6.yaml | 49 ++++ .../aws/testdata/expected-v6only.yaml | 40 +++ .../platform/aws/testdata/expected.yaml | 20 +- .../platform/aws/testdata/metadata-v6.json | 17 ++ .../aws/testdata/metadata-v6only.json | 14 + .../platform/aws/testdata/metadata.json | 13 +- 9 files changed, 528 insertions(+), 43 deletions(-) create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6.yaml create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6only.yaml create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6.json create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6only.json diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go index 7f413344e..c9796f671 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go @@ -12,7 +12,9 @@ import ( "log" "net/netip" "strings" + "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/cosi-project/runtime/pkg/state" @@ -20,7 +22,7 @@ import ( "github.com/siderolabs/go-retry/retry" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" - "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors" + platformerrors "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/netutils" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/imager/quirks" @@ -28,26 +30,122 @@ import ( runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime" ) +// awsInterfaceName is the name of the (single) network interface configured by the AWS platform. +const awsInterfaceName = "eth0" + +// awsIPv6DNSServer is the link-local IPv6 address of the Amazon-provided DNS resolver inside a VPC. +// +// See https://docs.aws.amazon.com/vpc/latest/userguide/AmazonDNS-concepts.html +// +// AWS distributes IPv6 addresses via DHCPv6 (stateful) and the IPv6 default route via Router Advertisements +// (the kernel installs it automatically when accept_ra is enabled). DHCPv6 in EC2 does not advertise +// resolvers, so we configure the well-known DNS address explicitly to make IPv6-only instances usable. +const awsIPv6DNSServer = "fd00:ec2::253" + // AWS is the concrete type that implements the runtime.Platform interface. type AWS struct { - metadataClient *imds.Client + cfg aws.Config } -// NewAWS initializes AWS platform building the IMDS client. +// NewAWS initializes AWS platform. +// +// The IMDS client is built lazily on first use because the IMDS endpoint +// (IPv4 vs IPv6) can only be determined once the network is reachable — +// AWS supports IPv4-only, IPv6-only, and dual-stack instances and the SDK +// will not auto-fall back between the two endpoints. func NewAWS() (*AWS, error) { - a := &AWS{} - cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { return nil, fmt.Errorf("error initializing AWS default config: %w", err) } - a.metadataClient = imds.NewFromConfig(cfg) + return &AWS{cfg: cfg}, nil +} - return a, nil +// buildIMDSClient picks an IMDS endpoint that responds (IPv4 or IPv6) and +// returns a client bound to it. It races a probe against both endpoints and +// returns the first to succeed; if both fail it retries with backoff because +// the network stack may not be fully up on the very first attempt. +func (a *AWS) buildIMDSClient(ctx context.Context) (*imds.Client, error) { + var resolved *imds.Client + + err := retry.Constant( + 30*time.Second, + retry.WithUnits(2*time.Second), + retry.WithErrorLogging(true), + ).RetryWithContext(ctx, func(ctx context.Context) error { + client, probeErr := a.probeIMDS(ctx) + if probeErr != nil { + return retry.ExpectedError(probeErr) + } + + resolved = client + + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to reach IMDS on IPv4 or IPv6: %w", err) + } + + return resolved, nil +} + +// probeIMDS races a metadata request against the IPv4 and IPv6 IMDS endpoints +// and returns a client bound to whichever one responds first. +func (a *AWS) probeIMDS(ctx context.Context) (*imds.Client, error) { + probeCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + type result struct { + client *imds.Client + mode string + err error + } + + candidates := []struct { + mode imds.EndpointModeState + name string + }{ + {imds.EndpointModeStateIPv4, "IPv4"}, + {imds.EndpointModeStateIPv6, "IPv6"}, + } + + ch := make(chan result, len(candidates)) + + for _, c := range candidates { + client := imds.NewFromConfig(a.cfg, func(o *imds.Options) { + o.EndpointMode = c.mode + }) + + go func(client *imds.Client, name string) { + _, err := client.GetMetadata(probeCtx, &imds.GetMetadataInput{Path: "instance-id"}) + ch <- result{client: client, mode: name, err: err} + }(client, c.name) + } + + var lastErr error + + for range candidates { + select { + case r := <-ch: + if r.err == nil { + log.Printf("AWS IMDS reachable via %s endpoint", r.mode) + + return r.client, nil + } + + lastErr = r.err + case <-ctx.Done(): + return nil, ctx.Err() + } + } + + return nil, lastErr } // ParseMetadata converts AWS platform metadata into platform network config. +// +//nolint:gocyclo func (a *AWS) ParseMetadata(metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) { networkConfig := &runtime.PlatformNetworkConfig{ TimeServers: []network.TimeServerSpecSpec{ @@ -77,18 +175,64 @@ func (a *AWS) ParseMetadata(metadata *MetadataConfig) (*runtime.PlatformNetworkC networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec) } - var publicIPs []string + // Configure the primary interface based on which address families are present. + // + // AWS supports IPv4-only, IPv6-only, and dual-stack instances. We detect IPv6-only + // by the absence of any IPv4 address (neither private nor public) and skip DHCPv4 + // in that case so the network comes up cleanly without a doomed DHCPv4 client. + // + // The IPv6 default gateway is delivered via Router Advertisements (the kernel adds + // it when accept_ra is on) — DHCPv6 only hands out addresses — so we don't add any + // static route for IPv6 here. + if iface := metadata.PrimaryInterface; iface != nil { + networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{ + Name: awsInterfaceName, + Up: true, + ConfigLayer: network.ConfigPlatform, + }) + + hasIPv4 := len(iface.LocalIPv4s) > 0 || metadata.PublicIPv4 != "" + hasIPv6 := len(iface.IPv6s) > 0 + + // Default to IPv4 if the metadata is ambiguous (e.g. neither list populated). + if !hasIPv4 && !hasIPv6 { + hasIPv4 = true + } + + if hasIPv4 { + networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{ + Operator: network.OperatorDHCP4, + LinkName: awsInterfaceName, + RequireUp: true, + DHCP4: network.DHCP4OperatorSpec{ + RouteMetric: network.DefaultRouteMetric, + }, + ConfigLayer: network.ConfigPlatform, + }) + } + + if hasIPv6 { + networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{ + Operator: network.OperatorDHCP6, + LinkName: awsInterfaceName, + RequireUp: true, + DHCP6: network.DHCP6OperatorSpec{ + RouteMetric: network.DefaultRouteMetric, + }, + ConfigLayer: network.ConfigPlatform, + }) + + dns, _ := netip.ParseAddr(awsIPv6DNSServer) //nolint:errcheck + + networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{ + DNSServers: []netip.Addr{dns}, + ConfigLayer: network.ConfigPlatform, + }) + } + } if metadata.PublicIPv4 != "" { - publicIPs = append(publicIPs, metadata.PublicIPv4) - } - - if metadata.PublicIPv6 != "" { - publicIPs = append(publicIPs, metadata.PublicIPv6) - } - - for _, ipStr := range publicIPs { - if ip, err := netip.ParseAddr(ipStr); err == nil { + if ip, err := netip.ParseAddr(metadata.PublicIPv4); err == nil { networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip) } } @@ -123,23 +267,30 @@ func (a *AWS) Configuration(ctx context.Context, r state.State) ([]byte, error) log.Printf("fetching machine config from AWS") - userdata, err := netutils.RetryFetch(ctx, a.fetchConfiguration) + client, err := a.buildIMDSClient(ctx) + if err != nil { + return nil, err + } + + userdata, err := netutils.RetryFetch(ctx, func(ctx context.Context) (string, error) { + return fetchConfiguration(ctx, client) + }) if err != nil { return nil, err } if strings.TrimSpace(userdata) == "" { - return nil, errors.ErrNoConfigSource + return nil, platformerrors.ErrNoConfigSource } return []byte(userdata), nil } -func (a *AWS) fetchConfiguration(ctx context.Context) (string, error) { - resp, err := a.metadataClient.GetUserData(ctx, &imds.GetUserDataInput{}) +func fetchConfiguration(ctx context.Context, client *imds.Client) (string, error) { + resp, err := client.GetUserData(ctx, &imds.GetUserDataInput{}) if err != nil { if isNotFoundError(err) { - return "", errors.ErrNoConfigSource + return "", platformerrors.ErrNoConfigSource } return "", retry.ExpectedErrorf("failed to fetch EC2 userdata: %w", err) @@ -167,9 +318,29 @@ func (a *AWS) KernelArgs(string, quirks.Quirks) procfs.Parameters { // NetworkConfiguration implements the runtime.Platform interface. func (a *AWS) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error { + // Emit a bootstrap config before fetching IMDS. In IPv6-only deployments + // (subnets with IPv4 disabled) the IMDS endpoint at [fd00:ec2::254] is only + // reachable from a non-link-local IPv6 address — which we get from DHCPv6 — + // but the DHCPv6 operator only starts once the platform publishes a config + // asking for it. Without this step the platform deadlocks: IMDS is + // unreachable, NetworkConfiguration never returns, and DHCPv6 never runs. + // The bootstrap brings up eth0 and starts both DHCPv4 and DHCPv6 so either + // family can come up; the post-IMDS config below replaces it with the + // family that actually applies. + select { + case ch <- bootstrapNetworkConfig(): + case <-ctx.Done(): + return ctx.Err() + } + log.Printf("fetching aws instance config") - metadata, err := a.getMetadata(ctx) + client, err := a.buildIMDSClient(ctx) + if err != nil { + return err + } + + metadata, err := a.getMetadata(ctx, client) if err != nil { return err } @@ -187,3 +358,39 @@ func (a *AWS) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- return nil } + +// bootstrapNetworkConfig returns the platform network config emitted before +// IMDS metadata is available. It brings up the primary interface and enables +// both DHCPv4 and DHCPv6 so the instance can reach IMDS regardless of which +// address family the VPC exposes. +func bootstrapNetworkConfig() *runtime.PlatformNetworkConfig { + return &runtime.PlatformNetworkConfig{ + Links: []network.LinkSpecSpec{ + { + Name: awsInterfaceName, + Up: true, + ConfigLayer: network.ConfigPlatform, + }, + }, + Operators: []network.OperatorSpecSpec{ + { + Operator: network.OperatorDHCP4, + LinkName: awsInterfaceName, + RequireUp: true, + DHCP4: network.DHCP4OperatorSpec{ + RouteMetric: network.DefaultRouteMetric, + }, + ConfigLayer: network.ConfigPlatform, + }, + { + Operator: network.OperatorDHCP6, + LinkName: awsInterfaceName, + RequireUp: true, + DHCP6: network.DHCP6OperatorSpec{ + RouteMetric: network.DefaultRouteMetric, + }, + ConfigLayer: network.ConfigPlatform, + }, + }, + } +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws_test.go index 0b6c16f90..64462d353 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws_test.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws_test.go @@ -19,21 +19,57 @@ import ( //go:embed testdata/metadata.json var rawMetadata []byte +//go:embed testdata/metadata-v6.json +var rawMetadataV6 []byte + +//go:embed testdata/metadata-v6only.json +var rawMetadataV6Only []byte + //go:embed testdata/expected.yaml var expectedNetworkConfig string -func TestEmpty(t *testing.T) { - p := &aws.AWS{} +//go:embed testdata/expected-v6.yaml +var expectedNetworkConfigV6 string - var metadata aws.MetadataConfig +//go:embed testdata/expected-v6only.yaml +var expectedNetworkConfigV6Only string - require.NoError(t, json.Unmarshal(rawMetadata, &metadata)) +func TestParseMetadata(t *testing.T) { + for _, tt := range []struct { + name string + raw []byte + expected string + }{ + { + name: "IPv4 only", + raw: rawMetadata, + expected: expectedNetworkConfig, + }, + { + name: "dual stack", + raw: rawMetadataV6, + expected: expectedNetworkConfigV6, + }, + { + name: "IPv6 only", + raw: rawMetadataV6Only, + expected: expectedNetworkConfigV6Only, + }, + } { + t.Run(tt.name, func(t *testing.T) { + p := &aws.AWS{} - networkConfig, err := p.ParseMetadata(&metadata) - require.NoError(t, err) + var metadata aws.MetadataConfig - marshaled, err := yaml.Marshal(networkConfig) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(tt.raw, &metadata)) - assert.Equal(t, expectedNetworkConfig, string(marshaled)) + networkConfig, err := p.ParseMetadata(&metadata) + require.NoError(t, err) + + marshaled, err := yaml.Marshal(networkConfig) + require.NoError(t, err) + + assert.Equal(t, tt.expected, string(marshaled)) + }) + } } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/metadata.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/metadata.go index 1641aa0eb..afffca842 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/metadata.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/metadata.go @@ -23,19 +23,31 @@ type MetadataConfig struct { InstanceType string `json:"instance-type,omitempty"` InstanceLifeCycle string `json:"instance-life-cycle,omitempty"` PublicIPv4 string `json:"public-ipv4,omitempty"` - PublicIPv6 string `json:"ipv6,omitempty"` InternalDNS string `json:"local-hostname,omitempty"` ExternalDNS string `json:"public-hostname,omitempty"` Region string `json:"region,omitempty"` Zone string `json:"zone,omitempty"` Tags map[string]string `json:"tags,omitempty"` + + // PrimaryInterface holds metadata for the primary network interface. + // + // Talos only supports a single NIC on AWS, so secondary interfaces are ignored. + PrimaryInterface *InterfaceConfig `json:"primary-interface,omitempty"` +} + +// InterfaceConfig holds the IMDS metadata for a single network interface. +type InterfaceConfig struct { + MAC string `json:"mac,omitempty"` + DeviceNumber string `json:"device-number,omitempty"` + LocalIPv4s []string `json:"local-ipv4s,omitempty"` + IPv6s []string `json:"ipv6s,omitempty"` } //nolint:gocyclo -func (a *AWS) getMetadata(ctx context.Context) (*MetadataConfig, error) { +func (a *AWS) getMetadata(ctx context.Context, client *imds.Client) (*MetadataConfig, error) { // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html getMetadataKey := func(key string) (string, error) { - resp, err := a.metadataClient.GetMetadata(ctx, &imds.GetMetadataInput{ + resp, err := client.GetMetadata(ctx, &imds.GetMetadataInput{ Path: key, }) if err != nil { @@ -77,10 +89,6 @@ func (a *AWS) getMetadata(ctx context.Context) (*MetadataConfig, error) { return nil, err } - if metadata.PublicIPv6, err = getMetadataKey("ipv6"); err != nil { - return nil, err - } - if metadata.InternalDNS, err = getMetadataKey("local-hostname"); err != nil { return nil, err } @@ -107,9 +115,98 @@ func (a *AWS) getMetadata(ctx context.Context) (*MetadataConfig, error) { } } + if metadata.PrimaryInterface, err = a.getPrimaryInterface(getMetadataKey); err != nil { + return nil, err + } + return &metadata, nil } +// getPrimaryInterface returns metadata for the primary network interface. +// +// IMDS lists every NIC under network/interfaces/macs/, but Talos only supports +// the primary NIC. Pick the entry with device-number=0 — AWS guarantees this is +// the primary — and fall back to the first listed MAC if the field is missing. +// +//nolint:gocyclo +func (a *AWS) getPrimaryInterface(getMetadataKey func(string) (string, error)) (*InterfaceConfig, error) { + macsList, err := getMetadataKey("network/interfaces/macs/") + if err != nil { + return nil, err + } + + var macs []string + + for line := range strings.Lines(macsList) { + mac := strings.TrimSuffix(strings.TrimSpace(line), "/") + if mac == "" { + continue + } + + macs = append(macs, mac) + } + + if len(macs) == 0 { + return nil, nil + } + + primaryMAC := macs[0] + + for _, mac := range macs { + deviceNumber, err := getMetadataKey(fmt.Sprintf("network/interfaces/macs/%s/device-number", mac)) + if err != nil { + return nil, err + } + + if strings.TrimSpace(deviceNumber) == "0" { + primaryMAC = mac + + break + } + } + + iface := &InterfaceConfig{ + MAC: primaryMAC, + } + + if iface.DeviceNumber, err = getMetadataKey(fmt.Sprintf("network/interfaces/macs/%s/device-number", primaryMAC)); err != nil { + return nil, err + } + + iface.DeviceNumber = strings.TrimSpace(iface.DeviceNumber) + + if iface.LocalIPv4s, err = fetchAddressList(getMetadataKey, fmt.Sprintf("network/interfaces/macs/%s/local-ipv4s", primaryMAC)); err != nil { + return nil, err + } + + if iface.IPv6s, err = fetchAddressList(getMetadataKey, fmt.Sprintf("network/interfaces/macs/%s/ipv6s", primaryMAC)); err != nil { + return nil, err + } + + return iface, nil +} + +// fetchAddressList reads a newline-separated list of addresses from IMDS. +func fetchAddressList(getMetadataKey func(string) (string, error), path string) ([]string, error) { + raw, err := getMetadataKey(path) + if err != nil { + return nil, err + } + + var addrs []string + + for line := range strings.Lines(raw) { + addr := strings.TrimSpace(line) + if addr == "" { + continue + } + + addrs = append(addrs, addr) + } + + return addrs, nil +} + func isNotFoundError(err error) bool { var awsErr *smithyhttp.ResponseError if errors.As(err, &awsErr) { diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6.yaml b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6.yaml new file mode 100644 index 000000000..615b736c2 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6.yaml @@ -0,0 +1,49 @@ +addresses: [] +links: + - name: eth0 + logical: false + up: true + mtu: 0 + kind: "" + type: netrom + layer: platform +routes: [] +hostnames: + - hostname: talos + domainname: "" + layer: platform +resolvers: + - dnsServers: + - fd00:ec2::253 + layer: platform +timeServers: + - timeServers: + - 169.254.169.123 + - fd00:ec2::123 + layer: platform +operators: + - operator: dhcp4 + linkName: eth0 + requireUp: true + dhcp4: + routeMetric: 1024 + layer: platform + - operator: dhcp6 + linkName: eth0 + requireUp: true + dhcp6: + routeMetric: 1024 + layer: platform +externalIPs: + - 1.2.3.4 +metadata: + platform: aws + hostname: talos + region: us-east-1 + zone: us-east-1a + instanceType: t3.micro + instanceId: i-0a0a0a0a0a0a0a0a0 + providerId: aws:///us-east-1a/i-0a0a0a0a0a0a0a0a0 + spot: true + internalDNS: ip-10-0-0-10.ec2.internal + externalDNS: ec2-1-2-3-4.compute-1.amazonaws.com diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6only.yaml b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6only.yaml new file mode 100644 index 000000000..26473ffba --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected-v6only.yaml @@ -0,0 +1,40 @@ +addresses: [] +links: + - name: eth0 + logical: false + up: true + mtu: 0 + kind: "" + type: netrom + layer: platform +routes: [] +hostnames: + - hostname: talos + domainname: "" + layer: platform +resolvers: + - dnsServers: + - fd00:ec2::253 + layer: platform +timeServers: + - timeServers: + - 169.254.169.123 + - fd00:ec2::123 + layer: platform +operators: + - operator: dhcp6 + linkName: eth0 + requireUp: true + dhcp6: + routeMetric: 1024 + layer: platform +externalIPs: [] +metadata: + platform: aws + hostname: talos + region: us-east-1 + zone: us-east-1a + instanceType: t3.micro + instanceId: i-0a0a0a0a0a0a0a0a0 + providerId: aws:///us-east-1a/i-0a0a0a0a0a0a0a0a0 + internalDNS: ip-10-0-0-10.ec2.internal diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected.yaml b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected.yaml index 956547df2..da1f8cdd3 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected.yaml +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected.yaml @@ -1,5 +1,12 @@ addresses: [] -links: [] +links: + - name: eth0 + logical: false + up: true + mtu: 0 + kind: "" + type: netrom + layer: platform routes: [] hostnames: - hostname: talos @@ -11,7 +18,13 @@ timeServers: - 169.254.169.123 - fd00:ec2::123 layer: platform -operators: [] +operators: + - operator: dhcp4 + linkName: eth0 + requireUp: true + dhcp4: + routeMetric: 1024 + layer: platform externalIPs: - 1.2.3.4 metadata: @@ -19,7 +32,10 @@ metadata: hostname: talos region: us-east-1 zone: us-east-1a + instanceType: t3.micro instanceId: i-0a0a0a0a0a0a0a0a0 providerId: aws:///us-east-1a/i-0a0a0a0a0a0a0a0a0 + internalDNS: ip-10-0-0-10.ec2.internal + externalDNS: ec2-1-2-3-4.compute-1.amazonaws.com tags: cluster: mycluster diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6.json b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6.json new file mode 100644 index 000000000..53ea1c157 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6.json @@ -0,0 +1,17 @@ +{ + "hostname": "talos", + "instance-id": "i-0a0a0a0a0a0a0a0a0", + "instance-type": "t3.micro", + "instance-life-cycle": "spot", + "public-ipv4": "1.2.3.4", + "local-hostname": "ip-10-0-0-10.ec2.internal", + "public-hostname": "ec2-1-2-3-4.compute-1.amazonaws.com", + "region": "us-east-1", + "zone": "us-east-1a", + "primary-interface": { + "mac": "06:17:04:d7:f0:6b", + "device-number": "0", + "local-ipv4s": ["10.0.0.10"], + "ipv6s": ["2001:db8::1"] + } +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6only.json b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6only.json new file mode 100644 index 000000000..f234c2141 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata-v6only.json @@ -0,0 +1,14 @@ +{ + "hostname": "talos", + "instance-id": "i-0a0a0a0a0a0a0a0a0", + "instance-type": "t3.micro", + "instance-life-cycle": "on-demand", + "local-hostname": "ip-10-0-0-10.ec2.internal", + "region": "us-east-1", + "zone": "us-east-1a", + "primary-interface": { + "mac": "06:17:04:d7:f0:6b", + "device-number": "0", + "ipv6s": ["2001:db8::1"] + } +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata.json b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata.json index 476200264..3df7df901 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata.json +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata.json @@ -1,8 +1,17 @@ { "hostname": "talos", - "instance-id": "i-0a0a0a0a0a0a0a0a0", + "instance-id": "i-0a0a0a0a0a0a0a0a0", + "instance-type": "t3.micro", + "instance-life-cycle": "on-demand", "public-ipv4": "1.2.3.4", + "local-hostname": "ip-10-0-0-10.ec2.internal", + "public-hostname": "ec2-1-2-3-4.compute-1.amazonaws.com", "region": "us-east-1", "zone": "us-east-1a", - "tags": {"cluster": "mycluster"} + "tags": {"cluster": "mycluster"}, + "primary-interface": { + "mac": "06:17:04:d7:f0:6b", + "device-number": "0", + "local-ipv4s": ["10.0.0.10"] + } }