feat: platform metadata resource

This resource stores common platform metadata information.
Such as:

* Hostname
* Region
* Zone
* InstanceType (SKU)
* InstanceID
* ProviderID (CCM cloud native magic string)

Signed-off-by: Serge Logvinov <serge.logvinov@sinextra.dev>
Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
Serge Logvinov 2022-09-22 14:27:20 +00:00 committed by Andrey Smirnov
parent 7e50e24c01
commit 8bfa7ac1d6
No known key found for this signature in database
GPG Key ID: 7B26396447AB6DFD
79 changed files with 2954 additions and 916 deletions

View File

@ -45,6 +45,17 @@ message MountStatusSpec {
repeated string options = 4;
}
// PlatformMetadataSpec describes platform metadata properties.
message PlatformMetadataSpec {
string platform = 1;
string hostname = 2;
string region = 3;
string zone = 4;
string instance_type = 5;
string instance_id = 6;
string provider_id = 7;
}
// UnmetCondition is a failure which prevents machine from being ready at the stage.
message UnmetCondition {
string name = 1;

View File

@ -92,6 +92,10 @@ func (ctrl *PlatformConfigController) Outputs() []controller.Output {
Type: network.OperatorSpecType,
Kind: controller.OutputShared,
},
{
Type: runtimeres.PlatformMetadataType,
Kind: controller.OutputExclusive,
},
}
}
@ -173,7 +177,7 @@ func (ctrl *PlatformConfigController) Run(ctx context.Context, r controller.Runt
if stateMounted && networkConfig != nil {
if err := ctrl.storeConfig(filepath.Join(ctrl.StatePath, constants.PlatformNetworkConfigFilename), networkConfig); err != nil {
return fmt.Errorf("error saving config: %w", err)
return fmt.Errorf("error saving platform network config: %w", err)
}
logger.Debug("stored cached platform network config")
@ -198,6 +202,12 @@ func (ctrl *PlatformConfigController) Run(ctx context.Context, r controller.Runt
//nolint:dupl,gocyclo
func (ctrl *PlatformConfigController) apply(ctx context.Context, r controller.Runtime, networkConfig *v1alpha1runtime.PlatformNetworkConfig) error {
metadataLength := 0
if networkConfig.Metadata != nil {
metadataLength = 1
}
// handle all network specs in a loop as all specs can be handled in a similar way
for _, specType := range []struct {
length int
@ -408,6 +418,28 @@ func (ctrl *PlatformConfigController) apply(ctx context.Context, r controller.Ru
status.Scope = nethelpers.ScopeGlobal
return nil
}
},
},
// Platform metadata
{
length: metadataLength,
getter: func(i int) interface{} {
return networkConfig.Metadata
},
idBuilder: func(spec interface{}) resource.ID {
return runtimeres.PlatformMetadataID
},
resourceBuilder: func(id string) resource.Resource {
return runtimeres.NewPlatformMetadataSpec(runtimeres.NamespaceName, id)
},
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
return func(r resource.Resource) error {
metadata := newSpec.(*runtimeres.PlatformMetadataSpec) //nolint:errcheck,forcetypeassert
*r.(*runtimeres.PlatformMetadata).TypedSpec() = *metadata
return nil
}
},

View File

@ -477,6 +477,44 @@ func (suite *PlatformConfigSuite) TestPlatformMockExternalIPs() {
)
}
func (suite *PlatformConfigSuite) TestPlatformMockMetadata() {
suite.Require().NoError(
suite.runtime.RegisterController(
&netctrl.PlatformConfigController{
V1alpha1Platform: &platformMock{
metadata: &runtimeres.PlatformMetadataSpec{
Platform: "mock",
Zone: "mock-zone",
},
},
StatePath: suite.statePath,
PlatformState: suite.state,
},
),
)
suite.startRuntime()
suite.Assert().NoError(
retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
func() error {
return suite.assertResources(
runtimeres.NamespaceName, runtimeres.PlatformMetadataType, []string{
runtimeres.PlatformMetadataID,
}, func(r resource.Resource) error {
spec := r.(*runtimeres.PlatformMetadata).TypedSpec()
suite.Assert().Equal("mock", spec.Platform)
suite.Assert().Equal("mock-zone", spec.Zone)
return nil
},
)
},
),
)
}
const sampleStoredConfig = "addresses: []\nlinks: []\nroutes: []\nhostnames:\n - hostname: talos-e2e-897b4e49-gcp-controlplane-jvcnl\n domainname: \"\"\n layer: default\nresolvers: []\ntimeServers: []\noperators: []\nexternalIPs:\n - 10.3.4.5\n - 2001:470:6d:30e:96f4:4219:5733:b860\n" //nolint:lll
func (suite *PlatformConfigSuite) TestStoreConfig() {
@ -620,6 +658,8 @@ type platformMock struct {
resolvers []netip.Addr
timeServers []string
dhcp4Links []string
metadata *runtimeres.PlatformMetadataSpec
}
func (mock *platformMock) Name() string {
@ -630,6 +670,10 @@ func (mock *platformMock) Configuration(context.Context, state.State) ([]byte, e
return nil, nil
}
func (mock *platformMock) Metadata(context.Context, state.State) (runtimeres.PlatformMetadataSpec, error) {
return runtimeres.PlatformMetadataSpec{Platform: mock.Name()}, nil
}
func (mock *platformMock) Mode() v1alpha1runtime.Mode {
return v1alpha1runtime.ModeCloud
}
@ -741,6 +785,8 @@ func (mock *platformMock) NetworkConfiguration(
)
}
networkConfig.Metadata = mock.metadata
select {
case ch <- networkConfig:
case <-ctx.Done():

View File

@ -12,6 +12,7 @@ import (
"github.com/talos-systems/go-procfs/procfs"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
"github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// Platform defines the requirements for a platform.
@ -55,4 +56,6 @@ type PlatformNetworkConfig struct {
Operators []network.OperatorSpecSpec `yaml:"operators"`
ExternalIPs []netip.Addr `yaml:"externalIPs"`
Metadata *runtime.PlatformMetadataSpec `yaml:"metadata,omitempty"`
}

View File

@ -24,6 +24,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// AWS is the concrete type that implements the runtime.Platform interface.
@ -47,6 +48,44 @@ func NewAWS() (*AWS, error) {
return a, nil
}
// ParseMetadata converts AWS platform metadata into platform network config.
func (a *AWS) ParseMetadata(metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
if metadata.PublicIPv4 != "" {
ip, err := netip.ParseAddr(metadata.PublicIPv4)
if err != nil {
return nil, err
}
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: a.Name(),
Hostname: metadata.Hostname,
Region: metadata.Region,
Zone: metadata.Zone,
InstanceType: metadata.InstanceType,
InstanceID: metadata.InstanceID,
ProviderID: fmt.Sprintf("aws://%s/%s", metadata.Zone, metadata.InstanceID),
}
return networkConfig, nil
}
// Name implements the runtime.Platform interface.
func (a *AWS) Name() string {
return "aws"
@ -87,57 +126,19 @@ func (a *AWS) KernelArgs() procfs.Parameters {
}
// NetworkConfiguration implements the runtime.Platform interface.
//
//nolint:gocyclo
func (a *AWS) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
getMetadataKey := func(key string) (string, error) {
v, err := a.metadataClient.GetMetadataWithContext(ctx, key)
if err != nil {
if awsErr, ok := err.(awserr.RequestFailure); ok {
if awsErr.StatusCode() == http.StatusNotFound {
return "", nil
}
}
log.Printf("fetching aws instance config")
return "", fmt.Errorf("failed to fetch %q from IMDS: %w", key, err)
}
return v, nil
}
networkConfig := &runtime.PlatformNetworkConfig{}
hostname, err := getMetadataKey("hostname")
metadata, err := a.getMetadata(ctx)
if err != nil {
return err
}
if hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err = hostnameSpec.ParseFQDN(hostname); err != nil {
return err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
externalIP, err := getMetadataKey("public-ipv4")
networkConfig, err := a.ParseMetadata(metadata)
if err != nil {
return err
}
if externalIP != "" {
ip, err := netip.ParseAddr(externalIP)
if err != nil {
return err
}
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
select {
case ch <- networkConfig:
case <-ctx.Done():

View File

@ -4,11 +4,36 @@
package aws_test
import "testing"
import (
_ "embed"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/aws"
)
//go:embed testdata/metadata.json
var rawMetadata []byte
//go:embed testdata/expected.yaml
var expectedNetworkConfig string
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation
//
// please remove it once any unit-test is added
// for this package
p := &aws.AWS{}
var metadata aws.MetadataConfig
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
networkConfig, err := p.ParseMetadata(&metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)
require.NoError(t, err)
assert.Equal(t, expectedNetworkConfig, string(marshaled))
}

View File

@ -0,0 +1,77 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package aws
import (
"context"
"fmt"
"net/http"
"github.com/aws/aws-sdk-go/aws/awserr"
)
// MetadataConfig represents a metadata AWS instance.
type MetadataConfig struct {
Hostname string `json:"hostname,omitempty"`
InstanceID string `json:"instance-id,omitempty"`
InstanceType string `json:"instance-type,omitempty"`
PublicIPv4 string `json:"public-ipv4,omitempty"`
PublicIPv6 string `json:"ipv6,omitempty"`
Region string `json:"region,omitempty"`
Zone string `json:"zone,omitempty"`
}
//nolint:gocyclo
func (a *AWS) getMetadata(ctx context.Context) (*MetadataConfig, error) {
getMetadataKey := func(key string) (v string, err error) {
v, err = a.metadataClient.GetMetadataWithContext(ctx, key)
if err != nil {
if awsErr, ok := err.(awserr.RequestFailure); ok {
if awsErr.StatusCode() == http.StatusNotFound {
return "", nil
}
}
return "", fmt.Errorf("failed to fetch %q from IMDS: %w", key, err)
}
return v, nil
}
var (
metadata MetadataConfig
err error
)
if metadata.Hostname, err = getMetadataKey("hostname"); err != nil {
return nil, err
}
if metadata.InstanceType, err = getMetadataKey("instance-type"); err != nil {
return nil, err
}
if metadata.InstanceID, err = getMetadataKey("instance-id"); err != nil {
return nil, err
}
if metadata.PublicIPv4, err = getMetadataKey("public-ipv4"); err != nil {
return nil, err
}
if metadata.PublicIPv6, err = getMetadataKey("ipv6"); err != nil {
return nil, err
}
if metadata.Region, err = getMetadataKey("placement/region"); err != nil {
return nil, err
}
if metadata.Zone, err = getMetadataKey("placement/availability-zone"); err != nil {
return nil, err
}
return &metadata, nil
}

View File

@ -0,0 +1,19 @@
addresses: []
links: []
routes: []
hostnames:
- hostname: talos
domainname: ""
layer: platform
resolvers: []
timeServers: []
operators: []
externalIPs:
- 1.2.3.4
metadata:
platform: aws
hostname: talos
region: us-east-1
zone: us-east-1a
instanceId: i-0a0a0a0a0a0a0a0a0
providerId: aws://us-east-1a/i-0a0a0a0a0a0a0a0a0

View File

@ -0,0 +1,7 @@
{
"hostname": "talos",
"instance-id": "i-0a0a0a0a0a0a0a0a0",
"public-ipv4": "1.2.3.4",
"region": "us-east-1",
"zone": "us-east-1a"
}

View File

@ -27,20 +27,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
const (
// AzureInternalEndpoint is the Azure Internal Channel IP
// https://blogs.msdn.microsoft.com/mast/2015/05/18/what-is-the-ip-address-168-63-129-16/
AzureInternalEndpoint = "http://168.63.129.16"
// AzureHostnameEndpoint is the local endpoint for the hostname.
AzureHostnameEndpoint = "http://169.254.169.254/metadata/instance/compute/osProfile/computerName?api-version=2021-12-13&format=text"
// AzureInterfacesEndpoint is the local endpoint to get external IPs.
AzureInterfacesEndpoint = "http://169.254.169.254/metadata/instance/network/interface?api-version=2021-12-13&format=json"
// AzureLoadbalancerEndpoint is the local endpoint for load balancer config.
AzureLoadbalancerEndpoint = "http://169.254.169.254/metadata/loadbalancer?api-version=2021-05-01&format=json"
mnt = "/mnt"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// NetworkConfig holds network interface meta config.
@ -86,7 +73,7 @@ func (a *Azure) Name() string {
// ParseMetadata parses Azure network metadata into the platform network config.
//
//nolint:gocyclo
func (a *Azure) ParseMetadata(interfaceAddresses []NetworkConfig, host []byte) (*runtime.PlatformNetworkConfig, error) {
func (a *Azure) ParseMetadata(metadata *ComputeMetadata, interfaceAddresses []NetworkConfig, host []byte) (*runtime.PlatformNetworkConfig, error) {
var networkConfig runtime.PlatformNetworkConfig
// hostname
@ -137,6 +124,21 @@ func (a *Azure) ParseMetadata(interfaceAddresses []NetworkConfig, host []byte) (
}
}
zone := metadata.FaultDomain
if metadata.Zone != "" {
zone = fmt.Sprintf("%s-%s", metadata.Location, metadata.Zone)
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: a.Name(),
Hostname: metadata.OSProfile.ComputerName,
Region: strings.ToLower(metadata.Location),
Zone: strings.ToLower(zone),
InstanceType: metadata.VMSize,
InstanceID: metadata.ResourceID,
ProviderID: fmt.Sprintf("azure://%s", metadata.ResourceID),
}
return &networkConfig, nil
}
@ -251,6 +253,13 @@ func (a *Azure) configFromCD() ([]byte, error) {
//
//nolint:gocyclo
func (a *Azure) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
log.Printf("fetching azure instance config from: %q", AzureMetadataEndpoint)
metadata, err := a.getMetadata(ctx)
if err != nil {
return err
}
log.Printf("fetching network config from %q", AzureInterfacesEndpoint)
metadataNetworkConfig, err := download.Download(ctx, AzureInterfacesEndpoint,
@ -265,17 +274,7 @@ func (a *Azure) NetworkConfiguration(ctx context.Context, _ state.State, ch chan
return err
}
log.Printf("fetching hostname from: %q", AzureHostnameEndpoint)
host, err := download.Download(ctx, AzureHostnameEndpoint,
download.WithHeaders(map[string]string{"Metadata": "true"}),
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
return fmt.Errorf("failed to fetch hostname from metadata service: %w", err)
}
networkConfig, err := a.ParseMetadata(interfaceAddresses, host)
networkConfig, err := a.ParseMetadata(metadata, interfaceAddresses, []byte(metadata.OSProfile.ComputerName))
if err != nil {
return fmt.Errorf("failed to parse network metadata: %w", err)
}

View File

@ -16,8 +16,11 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/azure"
)
//go:embed testdata/metadata.json
var rawMetadata []byte
//go:embed testdata/interfaces.json
var rawInterfaces []byte
//go:embed testdata/compute.json
var rawCompute []byte
//go:embed testdata/loadbalancer.json
var rawLoadBalancerMetadata []byte
@ -28,11 +31,15 @@ var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
a := &azure.Azure{}
var m []azure.NetworkConfig
var interfacesMetadata []azure.NetworkConfig
require.NoError(t, json.Unmarshal(rawMetadata, &m))
require.NoError(t, json.Unmarshal(rawInterfaces, &interfacesMetadata))
networkConfig, err := a.ParseMetadata(m, []byte("some.fqdn"))
var computeMetadata azure.ComputeMetadata
require.NoError(t, json.Unmarshal(rawCompute, &computeMetadata))
networkConfig, err := a.ParseMetadata(&computeMetadata, interfacesMetadata, []byte("some.fqdn"))
require.NoError(t, err)
var lb azure.LoadBalancerMetadata

View File

@ -0,0 +1,66 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package azure
import (
"context"
"encoding/json"
stderrors "errors"
"fmt"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
)
const (
// AzureInternalEndpoint is the Azure Internal Channel IP
// https://blogs.msdn.microsoft.com/mast/2015/05/18/what-is-the-ip-address-168-63-129-16/
AzureInternalEndpoint = "http://168.63.129.16"
// AzureMetadataEndpoint is the local endpoint for the metadata.
AzureMetadataEndpoint = "http://169.254.169.254/metadata/instance/compute?api-version=2021-12-13&format=json"
// AzureInterfacesEndpoint is the local endpoint to get external IPs.
AzureInterfacesEndpoint = "http://169.254.169.254/metadata/instance/network/interface?api-version=2021-12-13&format=json"
// AzureLoadbalancerEndpoint is the local endpoint for load balancer config.
AzureLoadbalancerEndpoint = "http://169.254.169.254/metadata/loadbalancer?api-version=2021-05-01&format=json"
mnt = "/mnt"
)
// ComputeMetadata represents metadata compute information.
type ComputeMetadata struct {
Environment string `json:"azEnvironment,omitempty"`
SKU string `json:"sku,omitempty"`
Name string `json:"name,omitempty"`
Zone string `json:"zone,omitempty"`
VMSize string `json:"vmSize,omitempty"`
OSType string `json:"osType,omitempty"`
OSProfile struct {
ComputerName string `json:"computerName,omitempty"`
} `json:"osProfile,omitempty"`
Location string `json:"location,omitempty"`
FaultDomain string `json:"platformFaultDomain,omitempty"`
PlatformSubFaultDomain string `json:"platformSubFaultDomain,omitempty"`
UpdateDomain string `json:"platformUpdateDomain,omitempty"`
ResourceGroup string `json:"resourceGroupName,omitempty"`
ResourceID string `json:"resourceId,omitempty"`
VMScaleSetName string `json:"vmScaleSetName,omitempty"`
SubscriptionID string `json:"subscriptionId,omitempty"`
}
func (a *Azure) getMetadata(ctx context.Context) (*ComputeMetadata, error) {
metadataDl, err := download.Download(ctx, AzureMetadataEndpoint,
download.WithHeaders(map[string]string{"Metadata": "true"}))
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
return nil, fmt.Errorf("error fetching metadata: %w", err)
}
var metadata ComputeMetadata
if err = json.Unmarshal(metadataDl, &metadata); err != nil {
return nil, fmt.Errorf("failed to parse compute metadata: %w", err)
}
return &metadata, nil
}

View File

@ -0,0 +1,14 @@
{
"location": "CentralUS",
"name": "IMDSCanary",
"offer": "RHEL",
"osType": "Linux",
"platformFaultDomain": "0",
"platformUpdateDomain": "0",
"publisher": "RedHat",
"resourceId": "000-000-000-000-000",
"sku": "7.2",
"version": "7.2.20161026",
"vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084",
"vmSize": "Standard_DS2"
}

View File

@ -18,3 +18,10 @@ externalIPs:
- 1.2.3.4
- 2603:1020:10:5::34
- 20.10.5.34
metadata:
platform: azure
region: centralus
zone: "0"
instanceType: Standard_DS2
instanceId: 000-000-000-000-000
providerId: azure://000-000-000-000-000

View File

@ -18,6 +18,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// Container is a platform for installing Talos via an Container image.
@ -64,16 +65,24 @@ func (c *Container) NetworkConfiguration(ctx context.Context, _ state.State, ch
return err
}
hostname = bytes.TrimSpace(hostname)
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(string(bytes.TrimSpace(hostname))); err != nil {
if err := hostnameSpec.ParseFQDN(string(hostname)); err != nil {
return err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: c.Name(),
Hostname: string(hostname),
InstanceType: os.Getenv("TALOSSKU"),
}
select {
case ch <- networkConfig:
case <-ctx.Done():

View File

@ -7,26 +7,21 @@ package digitalocean
import (
"context"
stderrors "errors"
"fmt"
"log"
"net/netip"
"strconv"
"github.com/cosi-project/runtime/pkg/state"
"github.com/talos-systems/go-procfs/procfs"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/utils"
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
const (
// DigitalOceanExternalIPEndpoint displays all external addresses associated with the instance.
DigitalOceanExternalIPEndpoint = "http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address"
// DigitalOceanHostnameEndpoint is the local endpoint for the hostname.
DigitalOceanHostnameEndpoint = "http://169.254.169.254/metadata/v1/hostname"
// DigitalOceanUserDataEndpoint is the local endpoint for the config.
DigitalOceanUserDataEndpoint = "http://169.254.169.254/metadata/v1/user-data"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// DigitalOcean is the concrete type that implements the platform.Platform interface.
@ -37,6 +32,200 @@ func (d *DigitalOcean) Name() string {
return "digital-ocean"
}
// ParseMetadata converts DigitalOcean platform metadata into platform network config.
//
//nolint:gocyclo,cyclop
func (d *DigitalOcean) ParseMetadata(metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
if len(metadata.DNS.Nameservers) > 0 {
var dnsIPs []netip.Addr
for _, dnsIP := range metadata.DNS.Nameservers {
if ip, err := netip.ParseAddr(dnsIP); err == nil {
dnsIPs = append(dnsIPs, ip)
}
}
networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{
DNSServers: dnsIPs,
ConfigLayer: network.ConfigPlatform,
})
}
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
Name: "eth0",
Up: true,
ConfigLayer: network.ConfigPlatform,
})
for _, iface := range metadata.Interfaces["public"] {
if iface.IPv4 != nil {
ifAddr, err := utils.IPPrefixFrom(iface.IPv4.IPAddress, iface.IPv4.Netmask)
if err != nil {
return nil, fmt.Errorf("failed to parse ip address: %w", err)
}
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ifAddr.Addr())
networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: "eth0",
Address: ifAddr,
Scope: nethelpers.ScopeGlobal,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
Family: nethelpers.FamilyInet4,
},
)
if iface.IPv4.Gateway != "" {
gw, err := netip.ParseAddr(iface.IPv4.Gateway)
if err != nil {
return nil, fmt.Errorf("failed to parse gateway ip: %w", err)
}
route := network.RouteSpecSpec{
ConfigLayer: network.ConfigPlatform,
Gateway: gw,
OutLinkName: "eth0",
Table: nethelpers.TableMain,
Protocol: nethelpers.ProtocolStatic,
Type: nethelpers.TypeUnicast,
Family: nethelpers.FamilyInet4,
Priority: 1024,
}
route.Normalize()
networkConfig.Routes = append(networkConfig.Routes, route)
metaServer, _ := netip.ParsePrefix("169.254.169.254/32") //nolint:errcheck
networkConfig.Routes = append(networkConfig.Routes, network.RouteSpecSpec{
ConfigLayer: network.ConfigPlatform,
OutLinkName: "eth0",
Destination: metaServer,
Gateway: gw,
Table: nethelpers.TableMain,
Protocol: nethelpers.ProtocolStatic,
Type: nethelpers.TypeUnicast,
Family: nethelpers.FamilyInet4,
Priority: 512,
})
}
}
if iface.IPv6 != nil {
ifAddr, err := utils.IPPrefixFrom(iface.IPv6.IPAddress, strconv.Itoa(iface.IPv6.CIDR))
if err != nil {
return nil, fmt.Errorf("failed to parse ip address: %w", err)
}
networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: "eth0",
Address: ifAddr,
Scope: nethelpers.ScopeGlobal,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
Family: nethelpers.FamilyInet6,
},
)
if iface.IPv6.Gateway != "" {
gw, err := netip.ParseAddr(iface.IPv6.Gateway)
if err != nil {
return nil, fmt.Errorf("failed to parse gateway ip: %w", err)
}
route := network.RouteSpecSpec{
ConfigLayer: network.ConfigPlatform,
Gateway: gw,
OutLinkName: "eth0",
Table: nethelpers.TableMain,
Protocol: nethelpers.ProtocolStatic,
Type: nethelpers.TypeUnicast,
Family: nethelpers.FamilyInet6,
Priority: 1024,
}
route.Normalize()
networkConfig.Routes = append(networkConfig.Routes, route)
}
}
if iface.AnchorIPv4 != nil {
ifAddr, err := utils.IPPrefixFrom(iface.AnchorIPv4.IPAddress, iface.AnchorIPv4.Netmask)
if err != nil {
return nil, fmt.Errorf("failed to parse ip address: %w", err)
}
networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: "eth0",
Address: ifAddr,
Scope: nethelpers.ScopeGlobal,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
Family: nethelpers.FamilyInet4,
},
)
}
}
for idx, iface := range metadata.Interfaces["private"] {
ifName := fmt.Sprintf("eth%d", idx+1)
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
Name: ifName,
Up: true,
ConfigLayer: network.ConfigPlatform,
})
if iface.IPv4 != nil {
ifAddr, err := utils.IPPrefixFrom(iface.IPv4.IPAddress, iface.IPv4.Netmask)
if err != nil {
return nil, fmt.Errorf("failed to parse ip address: %w", err)
}
networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: ifName,
Address: ifAddr,
Scope: nethelpers.ScopeGlobal,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
Family: nethelpers.FamilyInet4,
},
)
}
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: d.Name(),
Hostname: metadata.Hostname,
Region: metadata.Region,
InstanceID: strconv.Itoa(metadata.DropletID),
ProviderID: fmt.Sprintf("digitalocean://%d", metadata.DropletID),
}
return networkConfig, nil
}
// Configuration implements the platform.Platform interface.
func (d *DigitalOcean) Configuration(ctx context.Context, r state.State) ([]byte, error) {
log.Printf("fetching machine config from: %q", DigitalOceanUserDataEndpoint)
@ -59,43 +248,19 @@ func (d *DigitalOcean) KernelArgs() procfs.Parameters {
}
// NetworkConfiguration implements the runtime.Platform interface.
//
//nolint:gocyclo
func (d *DigitalOcean) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
host, err := download.Download(ctx, DigitalOceanHostnameEndpoint,
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
log.Printf("fetching DigitalOcean instance config from: %q ", DigitalOceanMetadataEndpoint)
metadata, err := d.getMetadata(ctx)
if err != nil {
return err
}
extIP, err := download.Download(ctx, DigitalOceanExternalIPEndpoint,
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
if err != nil && !stderrors.Is(err, errors.ErrNoExternalIPs) {
networkConfig, err := d.ParseMetadata(metadata)
if err != nil {
return err
}
networkConfig := &runtime.PlatformNetworkConfig{}
if len(host) > 0 {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(string(host)); err != nil {
return err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
if len(extIP) > 0 {
if ip, err := netip.ParseAddr(string(extIP)); err == nil {
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
}
select {
case ch <- networkConfig:
case <-ctx.Done():

View File

@ -4,11 +4,36 @@
package digitalocean_test
import "testing"
import (
_ "embed"
"encoding/json"
"testing"
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation
//
// please remove it once any unit-test is added
// for this package
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean"
)
//go:embed testdata/metadata.json
var rawMetadata []byte
//go:embed testdata/expected.yaml
var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
p := &digitalocean.DigitalOcean{}
var metadata digitalocean.MetadataConfig
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
networkConfig, err := p.ParseMetadata(&metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)
require.NoError(t, err)
assert.Equal(t, expectedNetworkConfig, string(marshaled))
}

View File

@ -0,0 +1,81 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package digitalocean
import (
"context"
"encoding/json"
stderrors "errors"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
)
const (
// DigitalOceanExternalIPEndpoint displays all external addresses associated with the instance.
DigitalOceanExternalIPEndpoint = "http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address"
// DigitalOceanMetadataEndpoint is the local endpoint for the platform metadata.
DigitalOceanMetadataEndpoint = "http://169.254.169.254/metadata/v1.json"
// DigitalOceanUserDataEndpoint is the local endpoint for the config.
DigitalOceanUserDataEndpoint = "http://169.254.169.254/metadata/v1/user-data"
)
// MetadataConfig represents a metadata Digital Ocean instance.
type MetadataConfig struct {
Hostname string `json:"hostname,omitempty"`
DropletID int `json:"droplet_id,omitempty"`
Region string `json:"region,omitempty"`
PublicIPv4 string `json:"public-ipv4,omitempty"`
Tags []string `json:"tags,omitempty"`
DNS struct {
Nameservers []string `json:"nameservers,omitempty"`
} `json:"dns,omitempty"`
Interfaces map[string][]struct {
MACAddress string `json:"mac,omitempty"`
Type string `json:"type,omitempty"`
IPv4 *struct {
IPAddress string `json:"ip_address,omitempty"`
Netmask string `json:"netmask,omitempty"`
Gateway string `json:"gateway,omitempty"`
} `json:"ipv4,omitempty"`
IPv6 *struct {
IPAddress string `json:"ip_address,omitempty"`
CIDR int `json:"cidr,omitempty"`
Gateway string `json:"gateway,omitempty"`
} `json:"ipv6,omitempty"`
AnchorIPv4 *struct {
IPAddress string `json:"ip_address,omitempty"`
Netmask string `json:"netmask,omitempty"`
Gateway string `json:"gateway,omitempty"`
} `json:"anchor_ipv4,omitempty"`
} `json:"interfaces,omitempty"`
}
func (d *DigitalOcean) getMetadata(ctx context.Context) (*MetadataConfig, error) {
metaConfigDl, err := download.Download(ctx, DigitalOceanMetadataEndpoint,
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
return nil, err
}
var metadata MetadataConfig
if err = json.Unmarshal(metaConfigDl, &metadata); err != nil {
return nil, err
}
extIP, err := download.Download(ctx, DigitalOceanExternalIPEndpoint,
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
if err != nil && !stderrors.Is(err, errors.ErrNoExternalIPs) {
return nil, err
}
metadata.PublicIPv4 = string(extIP)
return &metadata, nil
}

View File

@ -0,0 +1,96 @@
addresses:
- address: 128.199.52.32/19
linkName: eth0
family: inet4
scope: global
flags: permanent
layer: platform
- address: 2a03:b0c0:2:d0::1478:3001/64
linkName: eth0
family: inet6
scope: global
flags: permanent
layer: platform
- address: 10.18.0.5/16
linkName: eth0
family: inet4
scope: global
flags: permanent
layer: platform
- address: 10.133.0.2/16
linkName: eth1
family: inet4
scope: global
flags: permanent
layer: platform
links:
- name: eth0
logical: false
up: true
mtu: 0
kind: ""
type: netrom
layer: platform
- name: eth1
logical: false
up: true
mtu: 0
kind: ""
type: netrom
layer: platform
routes:
- family: inet4
dst: ""
src: ""
gateway: 128.199.32.1
outLinkName: eth0
table: main
priority: 1024
scope: global
type: unicast
flags: ""
protocol: static
layer: platform
- family: inet4
dst: 169.254.169.254/32
src: ""
gateway: 128.199.32.1
outLinkName: eth0
table: main
priority: 512
scope: global
type: unicast
flags: ""
protocol: static
layer: platform
- family: inet6
dst: ""
src: ""
gateway: 2a03:b0c0:2:d0::1
outLinkName: eth0
table: main
priority: 1024
scope: global
type: unicast
flags: ""
protocol: static
layer: platform
hostnames:
- hostname: debian-s-1vcpu-512mb-10gb-ams3-01
domainname: ""
layer: platform
resolvers:
- dnsServers:
- 67.207.67.2
- 67.207.67.3
layer: platform
timeServers: []
operators: []
externalIPs:
- 128.199.52.32
metadata:
platform: digital-ocean
hostname: debian-s-1vcpu-512mb-10gb-ams3-01
region: ams3
instanceId: "320206672"
providerId: digitalocean://320206672

View File

@ -0,0 +1,70 @@
{
"droplet_id": 320206672,
"hostname": "debian-s-1vcpu-512mb-10gb-ams3-01",
"user_data": "",
"vendor_data": "",
"public_keys": [],
"auth_key": "490eac0a2fc04503267ef85064407f2f",
"region": "ams3",
"interfaces": {
"private": [
{
"ipv4": {
"ip_address": "10.133.0.2",
"netmask": "255.255.0.0",
"gateway": "10.133.0.1"
},
"mac": "2a:3c:79:3d:f3:b7",
"type": "private"
}
],
"public": [
{
"ipv4": {
"ip_address": "128.199.52.32",
"netmask": "255.255.224.0",
"gateway": "128.199.32.1"
},
"ipv6": {
"ip_address": "2A03:B0C0:0002:00D0:0000:0000:1478:3001",
"cidr": 64,
"gateway": "2a03:b0c0:2:d0::1"
},
"anchor_ipv4": {
"ip_address": "10.18.0.5",
"netmask": "255.255.0.0",
"gateway": "10.18.0.1"
},
"mac": "12:2f:49:0c:eb:c0",
"type": "public"
}
]
},
"floating_ip": {
"ipv4": {
"active": false
}
},
"reserved_ip": {
"ipv4": {
"active": false
}
},
"dns": {
"nameservers": [
"67.207.67.2",
"67.207.67.3"
]
},
"tags": [
"label123"
],
"features": {
"dhcp_enabled": false
},
"modify_index": 113261986,
"dotty_status": "running",
"ssh_info": {
"port": 22
}
}

View File

@ -30,6 +30,7 @@ import (
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// Event holds data to pass to the Equinix Metal event URL.
@ -38,13 +39,6 @@ type Event struct {
Message string `json:"msg"`
}
// Metadata holds equinixmetal metadata info.
type Metadata struct {
Hostname string `json:"hostname"`
Network Network `json:"network"`
PrivateSubnets []string `json:"private_subnets"`
}
// Network holds network info from the equinixmetal metadata.
type Network struct {
Bonding Bonding `json:"bonding"`
@ -116,7 +110,7 @@ func (p *EquinixMetal) KernelArgs() procfs.Parameters {
// ParseMetadata converts Equinix Metal metadata into Talos network configuration.
//
//nolint:gocyclo,cyclop
func (p *EquinixMetal) ParseMetadata(ctx context.Context, equinixMetadata *Metadata, st state.State) (*runtime.PlatformNetworkConfig, error) {
func (p *EquinixMetal) ParseMetadata(ctx context.Context, equinixMetadata *MetadataConfig, st state.State) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
// 1. Links
@ -325,6 +319,18 @@ func (p *EquinixMetal) ParseMetadata(ctx context.Context, equinixMetadata *Metad
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
// 5. platform metadata
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: p.Name(),
Hostname: equinixMetadata.Hostname,
Region: equinixMetadata.Metro,
Zone: equinixMetadata.Facility,
InstanceType: equinixMetadata.Plan,
InstanceID: equinixMetadata.ID,
ProviderID: fmt.Sprintf("equinixmetal://%s", equinixMetadata.ID),
}
return networkConfig, nil
}
@ -337,12 +343,12 @@ func (p *EquinixMetal) NetworkConfiguration(ctx context.Context, st state.State,
return err
}
var equinixMetadata Metadata
if err = json.Unmarshal(metadataConfig, &equinixMetadata); err != nil {
var meta MetadataConfig
if err = json.Unmarshal(metadataConfig, &meta); err != nil {
return err
}
networkConfig, err := p.ParseMetadata(ctx, &equinixMetadata, st)
networkConfig, err := p.ParseMetadata(ctx, &meta, st)
if err != nil {
return err
}

View File

@ -31,7 +31,7 @@ var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
p := &equinixmetal.EquinixMetal{}
var m equinixmetal.Metadata
var m equinixmetal.MetadataConfig
require.NoError(t, json.Unmarshal(rawMetadata, &m))

View File

@ -0,0 +1,16 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package equinixmetal
// MetadataConfig holds equinixmetal metadata info.
type MetadataConfig struct {
ID string `json:"id"`
Hostname string `json:"hostname"`
Plan string `json:"plan"`
Metro string `json:"metro"`
Facility string `json:"facility"`
Network Network `json:"network"`
PrivateSubnets []string `json:"private_subnets"`
}

View File

@ -103,3 +103,11 @@ resolvers: []
timeServers: []
operators: []
externalIPs: []
metadata:
platform: equinixMetal
hostname: infra-green-ci
region: ny
zone: ny5
instanceType: c3.medium.x86
instanceId: X
providerId: equinixmetal://X

View File

@ -18,3 +18,6 @@ var ErrNoExternalIPs = errors.New("failed to fetch external addresses from metad
// ErrNoEventURL indicates that the platform does not have an expected events URL in the kernel params.
var ErrNoEventURL = errors.New("no event URL")
// ErrMetadataNotReady indicates that the platform does not have metadata yet.
var ErrMetadataNotReady = errors.New("platform metadata is not ready")

View File

@ -7,6 +7,8 @@ package gcp
import (
"context"
"fmt"
"log"
"net/netip"
"strings"
@ -17,6 +19,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// GCP is the concrete type that implements the platform.Platform interface.
@ -27,6 +30,62 @@ func (g *GCP) Name() string {
return "gcp"
}
// ParseMetadata converts GCP platform metadata into platform network config.
func (g *GCP) ParseMetadata(metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
if metadata.PublicIPv4 != "" {
ip, err := netip.ParseAddr(metadata.PublicIPv4)
if err != nil {
return nil, err
}
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
dns, _ := netip.ParseAddr(gcpResolverServer) //nolint:errcheck
networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{
DNSServers: []netip.Addr{dns},
ConfigLayer: network.ConfigPlatform,
})
networkConfig.TimeServers = append(networkConfig.TimeServers, network.TimeServerSpecSpec{
NTPServers: []string{gcpTimeServer},
ConfigLayer: network.ConfigPlatform,
})
region := metadata.Zone
if idx := strings.LastIndex(region, "-"); idx != -1 {
region = region[:idx]
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: g.Name(),
Hostname: metadata.Hostname,
Region: region,
Zone: metadata.Zone,
InstanceType: metadata.InstanceType,
InstanceID: metadata.InstanceID,
ProviderID: fmt.Sprintf("gce://%s/%s/%s", metadata.ProjectID, metadata.Zone, metadata.Name),
}
return networkConfig, nil
}
// Configuration implements the platform.Platform interface.
func (g *GCP) Configuration(ctx context.Context, r state.State) ([]byte, error) {
userdata, err := metadata.InstanceAttributeValue("user-data")
@ -59,41 +118,18 @@ func (g *GCP) KernelArgs() procfs.Parameters {
// NetworkConfiguration implements the runtime.Platform interface.
func (g *GCP) NetworkConfiguration(ctx context.Context, st state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
networkConfig := &runtime.PlatformNetworkConfig{}
log.Printf("fetching gcp instance config")
hostname, err := metadata.Hostname()
metadata, err := g.getMetadata(ctx)
if err != nil {
return fmt.Errorf("failed to receive GCP metadata: %w", err)
}
networkConfig, err := g.ParseMetadata(metadata)
if err != nil {
return err
}
if hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err = hostnameSpec.ParseFQDN(hostname); err != nil {
return err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
externalIP, err := metadata.ExternalIP()
if err != nil {
if _, ok := err.(metadata.NotDefinedError); !ok {
return err
}
}
if externalIP != "" {
ip, err := netip.ParseAddr(externalIP)
if err != nil {
return err
}
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
select {
case ch <- networkConfig:
case <-ctx.Done():

View File

@ -4,11 +4,36 @@
package gcp_test
import "testing"
import (
_ "embed"
"encoding/json"
"testing"
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation
//
// please remove it once any unit-test is added
// for this package
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp"
)
//go:embed testdata/metadata.json
var rawMetadata []byte
//go:embed testdata/expected.yaml
var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
p := &gcp.GCP{}
var metadata gcp.MetadataConfig
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
networkConfig, err := p.ParseMetadata(&metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)
require.NoError(t, err)
assert.Equal(t, expectedNetworkConfig, string(marshaled))
}

View File

@ -0,0 +1,70 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package gcp
import (
"context"
"strings"
"cloud.google.com/go/compute/metadata"
)
const (
gcpResolverServer = "169.254.169.254"
gcpTimeServer = "metadata.google.internal"
)
// MetadataConfig holds meta info.
type MetadataConfig struct {
ProjectID string `json:"project-id"`
Name string `json:"name,omitempty"`
Hostname string `json:"hostname,omitempty"`
Zone string `json:"zone,omitempty"`
InstanceType string `json:"machine-type"`
InstanceID string `json:"id"`
PublicIPv4 string `json:"external-ip"`
}
func (g *GCP) getMetadata(context.Context) (*MetadataConfig, error) {
var (
meta MetadataConfig
err error
)
if meta.ProjectID, err = metadata.ProjectID(); err != nil {
return nil, err
}
if meta.Name, err = metadata.InstanceName(); err != nil {
return nil, err
}
instanceType, err := metadata.Get("instance/machine-type")
if err != nil {
return nil, err
}
meta.InstanceType = strings.TrimSpace(instanceType[strings.LastIndex(instanceType, "/")+1:])
if meta.InstanceID, err = metadata.InstanceID(); err != nil {
return nil, err
}
if meta.Hostname, err = metadata.Hostname(); err != nil {
return nil, err
}
if meta.Zone, err = metadata.Zone(); err != nil {
return nil, err
}
if meta.PublicIPv4, err = metadata.ExternalIP(); err != nil {
if _, ok := err.(metadata.NotDefinedError); !ok {
return nil, err
}
}
return &meta, nil
}

View File

@ -0,0 +1,25 @@
addresses: []
links: []
routes: []
hostnames:
- hostname: talos
domainname: ""
layer: platform
resolvers:
- dnsServers:
- 169.254.169.254
layer: platform
timeServers:
- timeServers:
- metadata.google.internal
layer: platform
operators: []
externalIPs: []
metadata:
platform: gcp
hostname: talos
region: us-central1
zone: us-central1-a
instanceType: n1-standard-1
instanceId: "0"
providerId: gce://123/us-central1-a/my-server

View File

@ -0,0 +1,8 @@
{
"project-id": "123",
"hostname": "talos",
"id": "0",
"zone": "us-central1-a",
"name": "my-server",
"machine-type": "n1-standard-1"
}

View File

@ -7,7 +7,6 @@ package hcloud
import (
"context"
stderrors "errors"
"fmt"
"log"
"net/netip"
@ -21,40 +20,9 @@ import (
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
const (
// HCloudExternalIPEndpoint is the local hcloud endpoint for the external IP.
HCloudExternalIPEndpoint = "http://169.254.169.254/hetzner/v1/metadata/public-ipv4"
// HCloudNetworkEndpoint is the local hcloud endpoint for the network-config.
HCloudNetworkEndpoint = "http://169.254.169.254/hetzner/v1/metadata/network-config"
// HCloudHostnameEndpoint is the local hcloud endpoint for the hostname.
HCloudHostnameEndpoint = "http://169.254.169.254/hetzner/v1/metadata/hostname"
// HCloudUserDataEndpoint is the local hcloud endpoint for the config.
HCloudUserDataEndpoint = "http://169.254.169.254/hetzner/v1/userdata"
)
// NetworkConfig holds hcloud network-config info.
type NetworkConfig struct {
Version int `yaml:"version"`
Config []struct {
Mac string `yaml:"mac_address"`
Interfaces string `yaml:"name"`
Subnets []struct {
NameServers []string `yaml:"dns_nameservers,omitempty"`
Address string `yaml:"address,omitempty"`
Gateway string `yaml:"gateway,omitempty"`
Ipv4 bool `yaml:"ipv4,omitempty"`
Ipv6 bool `yaml:"ipv6,omitempty"`
Type string `yaml:"type"`
} `yaml:"subnets"`
Type string `yaml:"type"`
} `yaml:"config"`
}
// Hcloud is the concrete type that implements the runtime.Platform interface.
type Hcloud struct{}
@ -66,23 +34,23 @@ func (h *Hcloud) Name() string {
// ParseMetadata converts HCloud metadata to platform network configuration.
//
//nolint:gocyclo
func (h *Hcloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, host, extIP []byte) (*runtime.PlatformNetworkConfig, error) {
func (h *Hcloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if len(host) > 0 {
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(string(host)); err != nil {
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
if len(extIP) > 0 {
if ip, err := netip.ParseAddr(string(extIP)); err == nil {
if metadata.PublicIPv4 != "" {
if ip, err := netip.ParseAddr(metadata.PublicIPv4); err == nil {
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
}
@ -156,6 +124,13 @@ func (h *Hcloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, host, e
}
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: h.Name(),
Hostname: metadata.Hostname,
InstanceID: metadata.InstanceID,
ProviderID: fmt.Sprintf("hcloud://%s", metadata.InstanceID),
}
return networkConfig, nil
}
@ -181,9 +156,12 @@ func (h *Hcloud) KernelArgs() procfs.Parameters {
}
// NetworkConfiguration implements the runtime.Platform interface.
//
//nolint:gocyclo
func (h *Hcloud) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
metadata, err := h.getMetadata(ctx)
if err != nil {
return err
}
log.Printf("fetching hcloud network config from: %q", HCloudNetworkEndpoint)
metadataNetworkConfig, err := download.Download(ctx, HCloudNetworkEndpoint)
@ -201,25 +179,7 @@ func (h *Hcloud) NetworkConfiguration(ctx context.Context, _ state.State, ch cha
return fmt.Errorf("network-config metadata version=%d is not supported", unmarshalledNetworkConfig.Version)
}
log.Printf("fetching hostname from: %q", HCloudHostnameEndpoint)
host, err := download.Download(ctx, HCloudHostnameEndpoint,
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
return err
}
log.Printf("fetching externalIP from: %q", HCloudExternalIPEndpoint)
extIP, err := download.Download(ctx, HCloudExternalIPEndpoint,
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
if err != nil && !stderrors.Is(err, errors.ErrNoExternalIPs) {
return err
}
networkConfig, err := h.ParseMetadata(&unmarshalledNetworkConfig, host, extIP)
networkConfig, err := h.ParseMetadata(&unmarshalledNetworkConfig, metadata)
if err != nil {
return err
}

View File

@ -6,7 +6,6 @@ package hcloud_test
import (
_ "embed"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
@ -25,17 +24,21 @@ var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
h := &hcloud.Hcloud{}
metadata := &hcloud.MetadataConfig{
Hostname: "talos.fqdn",
PublicIPv4: "1.2.3.4",
InstanceID: "0",
}
var m hcloud.NetworkConfig
require.NoError(t, yaml.Unmarshal(rawMetadata, &m))
networkConfig, err := h.ParseMetadata(&m, []byte("some.fqdn"), []byte("1.2.3.4"))
networkConfig, err := h.ParseMetadata(&m, metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)
require.NoError(t, err)
fmt.Print(string(marshaled))
assert.Equal(t, expectedNetworkConfig, string(marshaled))
}

View File

@ -0,0 +1,99 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package hcloud
import (
"context"
stderrors "errors"
"log"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
)
const (
// HCloudExternalIPEndpoint is the local hcloud endpoint for the external IP.
HCloudExternalIPEndpoint = "http://169.254.169.254/hetzner/v1/metadata/public-ipv4"
// HCloudNetworkEndpoint is the local hcloud endpoint for the network-config.
HCloudNetworkEndpoint = "http://169.254.169.254/hetzner/v1/metadata/network-config"
// HCloudHostnameEndpoint is the local hcloud endpoint for the hostname.
HCloudHostnameEndpoint = "http://169.254.169.254/hetzner/v1/metadata/hostname"
// HCloudInstanceIDEndpoint is the local hcloud endpoint for the instance-id.
HCloudInstanceIDEndpoint = "http://169.254.169.254/hetzner/v1/metadata/instance-id"
// HCloudRegionEndpoint is the local hcloud endpoint for the region.
HCloudRegionEndpoint = "http://169.254.169.254/hetzner/v1/metadata/region"
// HCloudZoneEndpoint is the local hcloud endpoint for the zone.
HCloudZoneEndpoint = "http://169.254.169.254/hetzner/v1/metadata/availability-zone"
// HCloudUserDataEndpoint is the local hcloud endpoint for the config.
HCloudUserDataEndpoint = "http://169.254.169.254/hetzner/v1/userdata"
)
// MetadataConfig holds meta info.
type MetadataConfig struct {
Hostname string `yaml:"hostname,omitempty"`
Region string `yaml:"region,omitempty"`
AvailabilityZone string `json:"availability-zone,omitempty"`
InstanceID string `yaml:"instance-id,omitempty"`
PublicIPv4 string `yaml:"public-ipv4,omitempty"`
}
// NetworkConfig holds hcloud network-config info.
type NetworkConfig struct {
Version int `yaml:"version"`
Config []struct {
Mac string `yaml:"mac_address"`
Interfaces string `yaml:"name"`
Subnets []struct {
NameServers []string `yaml:"dns_nameservers,omitempty"`
Address string `yaml:"address,omitempty"`
Gateway string `yaml:"gateway,omitempty"`
Ipv4 bool `yaml:"ipv4,omitempty"`
Ipv6 bool `yaml:"ipv6,omitempty"`
Type string `yaml:"type"`
} `yaml:"subnets"`
Type string `yaml:"type"`
} `yaml:"config"`
}
func (h *Hcloud) getMetadata(ctx context.Context) (*MetadataConfig, error) {
log.Printf("fetching hostname from: %q", HCloudHostnameEndpoint)
host, err := download.Download(ctx, HCloudHostnameEndpoint,
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
return nil, err
}
log.Printf("fetching instance-id from: %q", HCloudInstanceIDEndpoint)
instanceID, err := download.Download(ctx, HCloudInstanceIDEndpoint,
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
return nil, err
}
log.Printf("fetching externalIP from: %q", HCloudExternalIPEndpoint)
extIP, err := download.Download(ctx, HCloudExternalIPEndpoint,
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
if err != nil && !stderrors.Is(err, errors.ErrNoExternalIPs) {
return nil, err
}
return &MetadataConfig{
Hostname: string(host),
InstanceID: string(instanceID),
PublicIPv4: string(extIP),
}, nil
}

View File

@ -26,7 +26,7 @@ routes:
protocol: static
layer: platform
hostnames:
- hostname: some
- hostname: talos
domainname: fqdn
layer: platform
resolvers: []
@ -40,3 +40,8 @@ operators:
layer: platform
externalIPs:
- 1.2.3.4
metadata:
platform: hcloud
hostname: talos.fqdn
instanceId: "0"
providerId: hcloud://0

View File

@ -0,0 +1,59 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package metal
import "regexp"
func keyToVar(key string) string {
return `${` + key + `}`
}
type matcher struct {
Key string
Regexp *regexp.Regexp
}
func newMatcher(key string) *matcher {
return &matcher{
Key: keyToVar(key),
Regexp: regexp.MustCompile(`(?i)` + regexp.QuoteMeta(keyToVar(key))),
}
}
type replacer struct {
original string
Regexp *regexp.Regexp
Matches [][]int
}
func (m *matcher) process(original string) *replacer {
var r replacer
r.Regexp = m.Regexp
r.original = original
r.Matches = m.Regexp.FindAllStringIndex(original, -1)
return &r
}
func (r *replacer) ReplaceMatches(replacement string) string {
var res string
if len(r.Matches) < 1 {
return res
}
res += r.original[:r.Matches[0][0]]
res += replacement
for i := 0; i < len(r.Matches)-1; i++ {
res += r.original[r.Matches[i][1]:r.Matches[i+1][0]]
res += replacement
}
res += r.original[r.Matches[len(r.Matches)-1][1]:]
return res
}

View File

@ -9,15 +9,9 @@ import (
"context"
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/go-blockdevice/blockdevice/filesystem"
"github.com/siderolabs/go-blockdevice/blockdevice/probe"
@ -28,8 +22,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/constants"
hardwareResource "github.com/talos-systems/talos/pkg/machinery/resources/hardware"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
const (
@ -74,221 +67,6 @@ func (m *Metal) Configuration(ctx context.Context, r state.State) ([]byte, error
}
}
func keyToVar(key string) string {
return `${` + key + `}`
}
type matcher struct {
Key string
Regexp *regexp.Regexp
}
func newMatcher(key string) *matcher {
return &matcher{
Key: keyToVar(key),
Regexp: regexp.MustCompile(`(?i)` + regexp.QuoteMeta(keyToVar(key))),
}
}
type replacer struct {
original string
Regexp *regexp.Regexp
Matches [][]int
}
func (m *matcher) process(original string) *replacer {
var r replacer
r.Regexp = m.Regexp
r.original = original
r.Matches = m.Regexp.FindAllStringIndex(original, -1)
return &r
}
func (r *replacer) ReplaceMatches(replacement string) string {
var res string
if len(r.Matches) < 1 {
return res
}
res += r.original[:r.Matches[0][0]]
res += replacement
for i := 0; i < len(r.Matches)-1; i++ {
res += r.original[r.Matches[i][1]:r.Matches[i+1][0]]
res += replacement
}
res += r.original[r.Matches[len(r.Matches)-1][1]:]
return res
}
// PopulateURLParameters fills in empty parameters in the download URL.
//
//nolint:gocyclo
func PopulateURLParameters(ctx context.Context, downloadURL string, r state.State) (string, error) {
populatedURL := downloadURL
genErr := func(varOfKey string, errToWrap error) error {
return fmt.Errorf("error while substituting %s: %w", varOfKey, errToWrap)
}
u, err := url.Parse(populatedURL)
if err != nil {
return "", fmt.Errorf("failed to parse %s: %w", populatedURL, err)
}
values := u.Query()
substitute := func(varToSubstitute string, getFunc func(ctx context.Context, r state.State) (string, error)) error {
m := newMatcher(varToSubstitute)
for qKey, qValues := range values {
if len(qValues) == 0 {
continue
}
qVal := qValues[0]
// backwards compatible behavior for the uuid key
if qKey == constants.UUIDKey && !(len(qValues) == 1 && len(strings.TrimSpace(qVal)) > 0) {
uid, err := getSystemUUID(ctx, r)
if err != nil {
return fmt.Errorf("error while substituting UUID: %w", err)
}
values.Set(constants.UUIDKey, uid)
continue
}
replacer := m.process(qVal)
if len(replacer.Matches) < 1 {
continue
}
val, err := getFunc(ctx, r)
if err != nil {
return genErr(m.Key, err)
}
qVal = replacer.ReplaceMatches(val)
values.Set(qKey, qVal)
}
return nil
}
if err := substitute(constants.UUIDKey, getSystemUUID); err != nil {
return "", err
}
if err := substitute(constants.SerialNumberKey, getSystemSerialNumber); err != nil {
return "", err
}
if err := substitute(constants.MacKey, getMACAddress); err != nil {
return "", err
}
if err := substitute(constants.HostnameKey, getHostname); err != nil {
return "", err
}
u.RawQuery = values.Encode()
return u.String(), nil
}
func getResource[T resource.Resource](ctx context.Context, r state.State, namespace, typ, valName string, isReadyFunc func(T) bool, checkAndGetFunc func(T) string) (string, error) {
metadata := resource.NewMetadata(namespace, typ, "", resource.VersionUndefined)
watchCtx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
events := make(chan safe.WrappedStateEvent[T])
err := safe.StateWatchKind[T](watchCtx, r, metadata, events, state.WithBootstrapContents(true))
if err != nil {
return "", fmt.Errorf("failed to watch %s resources: %w", typ, err)
}
var watchErr error
for {
select {
case <-watchCtx.Done():
err := fmt.Errorf("failed to determine %s of %s: %w", valName, typ, watchCtx.Err())
err = fmt.Errorf("%s; %w", err.Error(), watchErr)
return "", err
case event := <-events:
eventResource, err := event.Resource()
if err != nil {
watchErr = fmt.Errorf("%s; invalid resource in wrapped event: %w", watchErr.Error(), err)
}
if !isReadyFunc(eventResource) {
continue
}
val := checkAndGetFunc(eventResource)
if val == "" {
return "", fmt.Errorf("%s property of resource %s is empty", valName, typ)
}
return val, nil
}
}
}
func getUUIDProperty(r *hardwareResource.SystemInformation) string {
return r.TypedSpec().UUID
}
func getSerialNumberProperty(r *hardwareResource.SystemInformation) string {
return r.TypedSpec().SerialNumber
}
func getSystemUUID(ctx context.Context, r state.State) (string, error) {
return getResource(ctx, r, hardwareResource.NamespaceName, hardwareResource.SystemInformationType, "UUID", func(*hardwareResource.SystemInformation) bool { return true }, getUUIDProperty)
}
func getSystemSerialNumber(ctx context.Context, r state.State) (string, error) {
return getResource(ctx,
r,
hardwareResource.NamespaceName,
hardwareResource.SystemInformationType,
"Serial Number",
func(*hardwareResource.SystemInformation) bool { return true },
getSerialNumberProperty)
}
func getMACAddressProperty(r *network.LinkStatus) string {
return r.TypedSpec().HardwareAddr.String()
}
func checkLinkUp(r *network.LinkStatus) bool {
return r.TypedSpec().LinkState
}
func getMACAddress(ctx context.Context, r state.State) (string, error) {
return getResource(ctx, r, network.NamespaceName, network.LinkStatusType, "MAC Address", checkLinkUp, getMACAddressProperty)
}
func getHostnameProperty(r *network.HostnameSpec) string {
return r.TypedSpec().Hostname
}
func getHostname(ctx context.Context, r state.State) (string, error) {
return getResource(ctx, r, network.NamespaceName, network.HostnameSpecType, "Hostname", func(*network.HostnameSpec) bool { return true }, getHostnameProperty)
}
// Mode implements the platform.Platform interface.
func (m *Metal) Mode() runtime.Mode {
return runtime.ModeMetal
@ -336,6 +114,28 @@ func (m *Metal) KernelArgs() procfs.Parameters {
}
// NetworkConfiguration implements the runtime.Platform interface.
func (m *Metal) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
func (m *Metal) NetworkConfiguration(ctx context.Context, st state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
var metadata runtimeres.PlatformMetadataSpec
metadata.Platform = m.Name()
if option := procfs.ProcCmdline().Get(constants.KernelParamHostname).First(); option != nil {
metadata.Hostname = *option
}
if uuid, err := getSystemUUID(ctx, st); err == nil {
metadata.InstanceID = uuid
}
networkConfig := &runtime.PlatformNetworkConfig{
Metadata: &metadata,
}
select {
case <-ctx.Done():
return ctx.Err()
case ch <- networkConfig:
}
return nil
}

View File

@ -6,243 +6,42 @@ package metal_test
import (
"context"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
"github.com/stretchr/testify/assert"
"github.com/talos-systems/go-procfs/procfs"
"github.com/stretchr/testify/require"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/hardware"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
func createOrUpdate(ctx context.Context, st state.State, r resource.Resource) error {
oldRes, err := st.Get(ctx, r.Metadata())
if err != nil && !state.IsNotFoundError(err) {
return err
}
func TestNetworkConfig(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
t.Cleanup(cancel)
if oldRes == nil {
err = st.Create(ctx, r)
if err != nil {
return err
}
} else {
r.Metadata().SetVersion(oldRes.Metadata().Version())
p := &metal.Metal{}
err = st.Update(ctx, r)
if err != nil {
return err
}
}
ch := make(chan *runtime.PlatformNetworkConfig, 1)
return nil
}
func setup(ctx context.Context, t *testing.T, st state.State, mockUUID, mockSerialNumber, mockHostname, mockMAC string) {
testID := "testID"
sysInfo := hardware.NewSystemInformation(testID)
sysInfo.TypedSpec().UUID = mockUUID
sysInfo.TypedSpec().SerialNumber = mockSerialNumber
assert.NoError(t, createOrUpdate(ctx, st, sysInfo))
hostnameSpec := network.NewHostnameSpec(network.NamespaceName, testID)
hostnameSpec.TypedSpec().Hostname = mockHostname
assert.NoError(t, createOrUpdate(ctx, st, hostnameSpec))
linkStatusSpec := network.NewLinkStatus(network.NamespaceName, testID)
parsedMockMAC, err := net.ParseMAC(mockMAC)
assert.NoError(t, err)
linkStatusSpec.TypedSpec().HardwareAddr = nethelpers.HardwareAddr(parsedMockMAC)
linkStatusSpec.TypedSpec().LinkState = true
assert.NoError(t, createOrUpdate(ctx, st, linkStatusSpec))
}
func TestPopulateURLParameters(t *testing.T) {
mockUUID := "40dcbd19-3b10-444e-bfff-aaee44a51fda"
mockMAC := "52:2f:fd:df:fc:c0"
mockSerialNumber := "0OCZJ19N65"
mockHostname := "myTestHostname"
for _, tt := range []struct {
name string
url string
expectedURL string
expectedError string
}{
{
name: "no uuid",
url: "http://example.com/metadata",
expectedURL: "http://example.com/metadata",
},
{
name: "empty uuid",
url: "http://example.com/metadata?uuid=",
expectedURL: fmt.Sprintf("http://example.com/metadata?uuid=%s", mockUUID),
},
{
name: "uuid present",
url: "http://example.com/metadata?uuid=xyz",
expectedURL: "http://example.com/metadata?uuid=xyz",
},
{
name: "multiple uuids in one query parameter",
url: "http://example.com/metadata?u=this-${uuid}-equals-${uuid}-exactly",
expectedURL: fmt.Sprintf("http://example.com/metadata?u=this-%s-equals-%s-exactly", mockUUID, mockUUID),
},
{
name: "uuid and mac in one query parameter",
url: "http://example.com/metadata?u=this-${uuid}-and-${mac}-together",
expectedURL: fmt.Sprintf("http://example.com/metadata?u=this-%s-and-%s-together", mockUUID, mockMAC),
},
{
name: "other parameters",
url: "http://example.com/metadata?foo=a",
expectedURL: "http://example.com/metadata?foo=a",
},
{
name: "multiple uuids",
url: "http://example.com/metadata?uuid=xyz&uuid=foo",
expectedURL: fmt.Sprintf("http://example.com/metadata?uuid=%s", mockUUID),
},
{
name: "single serial number",
url: "http://example.com/metadata?serial=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?serial=%s", mockSerialNumber),
},
{
name: "single MAC",
url: "http://example.com/metadata?mac=${mac}",
expectedURL: fmt.Sprintf("http://example.com/metadata?mac=%s", mockMAC),
},
{
name: "single hostname",
url: "http://example.com/metadata?host=${hostname}",
expectedURL: fmt.Sprintf("http://example.com/metadata?host=%s", mockHostname),
},
{
name: "serial number, MAC and hostname",
url: "http://example.com/metadata?h=${hostname}&m=${mac}&s=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?h=%s&m=%s&s=%s", mockHostname, mockMAC, mockSerialNumber),
},
{
name: "uuid, serial number, MAC and hostname; case-insensitive",
url: "http://example.com/metadata?h=${HOSTname}&m=${mAC}&s=${SERIAL}&u=${uUid}",
expectedURL: fmt.Sprintf("http://example.com/metadata?h=%s&m=%s&s=%s&u=%s", mockHostname, mockMAC, mockSerialNumber, mockUUID),
},
{
name: "MAC and UUID without variable",
url: "http://example.com/metadata?macaddr=${mac}&uuid=",
expectedURL: fmt.Sprintf("http://example.com/metadata?macaddr=%s&uuid=%s", mockMAC, mockUUID),
},
{
name: "serial number and UUID without variable, order is not preserved",
url: "http://example.com/metadata?uuid=&ser=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?ser=%s&uuid=%s", mockSerialNumber, mockUUID),
},
{
name: "UUID variable",
url: "http://example.com/metadata?uuid=${uuid}",
expectedURL: fmt.Sprintf("http://example.com/metadata?uuid=%s", mockUUID),
},
{
name: "serial number and UUID with variable, order is not preserved",
url: "http://example.com/metadata?uuid=${uuid}&ser=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?ser=%s&uuid=%s", mockSerialNumber, mockUUID),
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
st := state.WrapCore(namespaced.NewState(inmem.Build))
setup(ctx, t, st, mockUUID, mockSerialNumber, mockHostname, mockMAC)
output, err := metal.PopulateURLParameters(ctx, tt.url, st)
if tt.expectedError != "" {
assert.EqualError(t, err, tt.expectedError)
} else {
u, err := url.Parse(tt.expectedURL)
assert.NoError(t, err)
u.RawQuery = u.Query().Encode()
assert.Equal(t, u.String(), output)
}
})
}
}
func TestRepopulateOnRetry(t *testing.T) {
st := state.WrapCore(namespaced.NewState(inmem.Build))
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
uuid := hardware.NewSystemInformation("test")
uuid.TypedSpec().UUID = "0123-4567-89ab-cdef"
require.NoError(t, st.Create(ctx, uuid))
nCalls := 0
err := p.NetworkConfiguration(ctx, st, ch)
require.NoError(t, err)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch nCalls {
case 0:
assert.Equal(t, "h=myTestHostname&m=52%3A2f%3Afd%3Adf%3Afc%3Ac0&s=0OCZJ19N65&u=40dcbd19-3b10-444e-bfff-aaee44a51fda", r.URL.RawQuery)
w.WriteHeader(http.StatusNotFound)
// After the first call we change the resources that should be substituted in the next call.
uuid2 := "9fba530f-767d-40f9-9410-bb1fed5d2134"
mac2 := "aa:aa:bb:bb:cc:cc"
serialNumber2 := "111AAA9N65"
hostname2 := "anotherHostname"
setup(ctx, t, st, uuid2, serialNumber2, hostname2, mac2)
case 1:
// Before the second call Configuration() should have resubstituted all the new parameters in the URL.
assert.Equal(t, "h=anotherHostname&m=aa%3Aaa%3Abb%3Abb%3Acc%3Acc&s=111AAA9N65&u=9fba530f-767d-40f9-9410-bb1fed5d2134", r.URL.RawQuery)
w.WriteHeader(http.StatusOK)
}
nCalls++
}))
defer server.Close()
uuid1 := "40dcbd19-3b10-444e-bfff-aaee44a51fda"
mac1 := "52:2f:fd:df:fc:c0"
serialNumber1 := "0OCZJ19N65"
hostname1 := "myTestHostname"
setup(ctx, t, st, uuid1, serialNumber1, hostname1, mac1)
downloadURL := server.URL + "/metadata?h=${hostname}&m=${mac}&s=${serial}&u=${uuid}"
param := procfs.NewParameter(constants.KernelParamConfig)
param.Append(downloadURL)
procfs.ProcCmdline().Set(constants.KernelParamConfig, param)
defer procfs.ProcCmdline().Set(constants.KernelParamConfig, nil)
go func() {
testObj := metal.Metal{}
_, err := testObj.Configuration(ctx, st)
assert.NoError(t, err)
cancel()
}()
<-ctx.Done()
select {
case <-ctx.Done():
t.Error("timeout")
case cfg := <-ch:
assert.Equal(t, "metal", cfg.Metadata.Platform)
assert.Equal(t, uuid.TypedSpec().UUID, cfg.Metadata.InstanceID)
}
}

View File

@ -0,0 +1,184 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package metal
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/talos-systems/talos/pkg/machinery/constants"
hardwareResource "github.com/talos-systems/talos/pkg/machinery/resources/hardware"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
// PopulateURLParameters fills in empty parameters in the download URL.
//
//nolint:gocyclo
func PopulateURLParameters(ctx context.Context, downloadURL string, r state.State) (string, error) {
populatedURL := downloadURL
genErr := func(varOfKey string, errToWrap error) error {
return fmt.Errorf("error while substituting %s: %w", varOfKey, errToWrap)
}
u, err := url.Parse(populatedURL)
if err != nil {
return "", fmt.Errorf("failed to parse %s: %w", populatedURL, err)
}
values := u.Query()
substitute := func(varToSubstitute string, getFunc func(ctx context.Context, r state.State) (string, error)) error {
m := newMatcher(varToSubstitute)
for qKey, qValues := range values {
if len(qValues) == 0 {
continue
}
qVal := qValues[0]
// backwards compatible behavior for the uuid key
if qKey == constants.UUIDKey && !(len(qValues) == 1 && len(strings.TrimSpace(qVal)) > 0) {
uid, err := getSystemUUID(ctx, r)
if err != nil {
return fmt.Errorf("error while substituting UUID: %w", err)
}
values.Set(constants.UUIDKey, uid)
continue
}
replacer := m.process(qVal)
if len(replacer.Matches) < 1 {
continue
}
val, err := getFunc(ctx, r)
if err != nil {
return genErr(m.Key, err)
}
qVal = replacer.ReplaceMatches(val)
values.Set(qKey, qVal)
}
return nil
}
if err := substitute(constants.UUIDKey, getSystemUUID); err != nil {
return "", err
}
if err := substitute(constants.SerialNumberKey, getSystemSerialNumber); err != nil {
return "", err
}
if err := substitute(constants.MacKey, getMACAddress); err != nil {
return "", err
}
if err := substitute(constants.HostnameKey, getHostname); err != nil {
return "", err
}
u.RawQuery = values.Encode()
return u.String(), nil
}
func getResource[T resource.Resource](ctx context.Context, r state.State, namespace, typ, valName string, isReadyFunc func(T) bool, checkAndGetFunc func(T) string) (string, error) {
metadata := resource.NewMetadata(namespace, typ, "", resource.VersionUndefined)
watchCtx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
events := make(chan safe.WrappedStateEvent[T])
err := safe.StateWatchKind(watchCtx, r, metadata, events, state.WithBootstrapContents(true))
if err != nil {
return "", fmt.Errorf("failed to watch %s resources: %w", typ, err)
}
var watchErr error
for {
select {
case <-watchCtx.Done():
err := fmt.Errorf("failed to determine %s of %s: %w", valName, typ, watchCtx.Err())
err = fmt.Errorf("%s; %w", err.Error(), watchErr)
return "", err
case event := <-events:
eventResource, err := event.Resource()
if err != nil {
watchErr = fmt.Errorf("%s; invalid resource in wrapped event: %w", watchErr.Error(), err)
}
if !isReadyFunc(eventResource) {
continue
}
val := checkAndGetFunc(eventResource)
if val == "" {
return "", fmt.Errorf("%s property of resource %s is empty", valName, typ)
}
return val, nil
}
}
}
func getUUIDProperty(r *hardwareResource.SystemInformation) string {
return r.TypedSpec().UUID
}
func getSerialNumberProperty(r *hardwareResource.SystemInformation) string {
return r.TypedSpec().SerialNumber
}
func getSystemUUID(ctx context.Context, r state.State) (string, error) {
return getResource(ctx, r, hardwareResource.NamespaceName, hardwareResource.SystemInformationType, "UUID", func(*hardwareResource.SystemInformation) bool { return true }, getUUIDProperty)
}
func getSystemSerialNumber(ctx context.Context, r state.State) (string, error) {
return getResource(ctx,
r,
hardwareResource.NamespaceName,
hardwareResource.SystemInformationType,
"Serial Number",
func(*hardwareResource.SystemInformation) bool { return true },
getSerialNumberProperty)
}
func getMACAddressProperty(r *network.LinkStatus) string {
return r.TypedSpec().HardwareAddr.String()
}
func checkLinkUp(r *network.LinkStatus) bool {
return r.TypedSpec().LinkState
}
func getMACAddress(ctx context.Context, r state.State) (string, error) {
return getResource(ctx, r, network.NamespaceName, network.LinkStatusType, "MAC Address", checkLinkUp, getMACAddressProperty)
}
func getHostnameProperty(r *network.HostnameSpec) string {
return r.TypedSpec().Hostname
}
func getHostname(ctx context.Context, r state.State) (string, error) {
return getResource(ctx, r, network.NamespaceName, network.HostnameSpecType, "Hostname", func(*network.HostnameSpec) bool { return true }, getHostnameProperty)
}

View File

@ -0,0 +1,248 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package metal_test
import (
"context"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
"github.com/stretchr/testify/assert"
"github.com/talos-systems/go-procfs/procfs"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/hardware"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
func createOrUpdate(ctx context.Context, st state.State, r resource.Resource) error {
oldRes, err := st.Get(ctx, r.Metadata())
if err != nil && !state.IsNotFoundError(err) {
return err
}
if oldRes == nil {
err = st.Create(ctx, r)
if err != nil {
return err
}
} else {
r.Metadata().SetVersion(oldRes.Metadata().Version())
err = st.Update(ctx, r)
if err != nil {
return err
}
}
return nil
}
func setup(ctx context.Context, t *testing.T, st state.State, mockUUID, mockSerialNumber, mockHostname, mockMAC string) {
testID := "testID"
sysInfo := hardware.NewSystemInformation(testID)
sysInfo.TypedSpec().UUID = mockUUID
sysInfo.TypedSpec().SerialNumber = mockSerialNumber
assert.NoError(t, createOrUpdate(ctx, st, sysInfo))
hostnameSpec := network.NewHostnameSpec(network.NamespaceName, testID)
hostnameSpec.TypedSpec().Hostname = mockHostname
assert.NoError(t, createOrUpdate(ctx, st, hostnameSpec))
linkStatusSpec := network.NewLinkStatus(network.NamespaceName, testID)
parsedMockMAC, err := net.ParseMAC(mockMAC)
assert.NoError(t, err)
linkStatusSpec.TypedSpec().HardwareAddr = nethelpers.HardwareAddr(parsedMockMAC)
linkStatusSpec.TypedSpec().LinkState = true
assert.NoError(t, createOrUpdate(ctx, st, linkStatusSpec))
}
func TestPopulateURLParameters(t *testing.T) {
mockUUID := "40dcbd19-3b10-444e-bfff-aaee44a51fda"
mockMAC := "52:2f:fd:df:fc:c0"
mockSerialNumber := "0OCZJ19N65"
mockHostname := "myTestHostname"
for _, tt := range []struct {
name string
url string
expectedURL string
expectedError string
}{
{
name: "no uuid",
url: "http://example.com/metadata",
expectedURL: "http://example.com/metadata",
},
{
name: "empty uuid",
url: "http://example.com/metadata?uuid=",
expectedURL: fmt.Sprintf("http://example.com/metadata?uuid=%s", mockUUID),
},
{
name: "uuid present",
url: "http://example.com/metadata?uuid=xyz",
expectedURL: "http://example.com/metadata?uuid=xyz",
},
{
name: "multiple uuids in one query parameter",
url: "http://example.com/metadata?u=this-${uuid}-equals-${uuid}-exactly",
expectedURL: fmt.Sprintf("http://example.com/metadata?u=this-%s-equals-%s-exactly", mockUUID, mockUUID),
},
{
name: "uuid and mac in one query parameter",
url: "http://example.com/metadata?u=this-${uuid}-and-${mac}-together",
expectedURL: fmt.Sprintf("http://example.com/metadata?u=this-%s-and-%s-together", mockUUID, mockMAC),
},
{
name: "other parameters",
url: "http://example.com/metadata?foo=a",
expectedURL: "http://example.com/metadata?foo=a",
},
{
name: "multiple uuids",
url: "http://example.com/metadata?uuid=xyz&uuid=foo",
expectedURL: fmt.Sprintf("http://example.com/metadata?uuid=%s", mockUUID),
},
{
name: "single serial number",
url: "http://example.com/metadata?serial=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?serial=%s", mockSerialNumber),
},
{
name: "single MAC",
url: "http://example.com/metadata?mac=${mac}",
expectedURL: fmt.Sprintf("http://example.com/metadata?mac=%s", mockMAC),
},
{
name: "single hostname",
url: "http://example.com/metadata?host=${hostname}",
expectedURL: fmt.Sprintf("http://example.com/metadata?host=%s", mockHostname),
},
{
name: "serial number, MAC and hostname",
url: "http://example.com/metadata?h=${hostname}&m=${mac}&s=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?h=%s&m=%s&s=%s", mockHostname, mockMAC, mockSerialNumber),
},
{
name: "uuid, serial number, MAC and hostname; case-insensitive",
url: "http://example.com/metadata?h=${HOSTname}&m=${mAC}&s=${SERIAL}&u=${uUid}",
expectedURL: fmt.Sprintf("http://example.com/metadata?h=%s&m=%s&s=%s&u=%s", mockHostname, mockMAC, mockSerialNumber, mockUUID),
},
{
name: "MAC and UUID without variable",
url: "http://example.com/metadata?macaddr=${mac}&uuid=",
expectedURL: fmt.Sprintf("http://example.com/metadata?macaddr=%s&uuid=%s", mockMAC, mockUUID),
},
{
name: "serial number and UUID without variable, order is not preserved",
url: "http://example.com/metadata?uuid=&ser=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?ser=%s&uuid=%s", mockSerialNumber, mockUUID),
},
{
name: "UUID variable",
url: "http://example.com/metadata?uuid=${uuid}",
expectedURL: fmt.Sprintf("http://example.com/metadata?uuid=%s", mockUUID),
},
{
name: "serial number and UUID with variable, order is not preserved",
url: "http://example.com/metadata?uuid=${uuid}&ser=${serial}",
expectedURL: fmt.Sprintf("http://example.com/metadata?ser=%s&uuid=%s", mockSerialNumber, mockUUID),
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
st := state.WrapCore(namespaced.NewState(inmem.Build))
setup(ctx, t, st, mockUUID, mockSerialNumber, mockHostname, mockMAC)
output, err := metal.PopulateURLParameters(ctx, tt.url, st)
if tt.expectedError != "" {
assert.EqualError(t, err, tt.expectedError)
} else {
u, err := url.Parse(tt.expectedURL)
assert.NoError(t, err)
u.RawQuery = u.Query().Encode()
assert.Equal(t, u.String(), output)
}
})
}
}
func TestRepopulateOnRetry(t *testing.T) {
st := state.WrapCore(namespaced.NewState(inmem.Build))
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
nCalls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch nCalls {
case 0:
assert.Equal(t, "h=myTestHostname&m=52%3A2f%3Afd%3Adf%3Afc%3Ac0&s=0OCZJ19N65&u=40dcbd19-3b10-444e-bfff-aaee44a51fda", r.URL.RawQuery)
w.WriteHeader(http.StatusNotFound)
// After the first call we change the resources that should be substituted in the next call.
uuid2 := "9fba530f-767d-40f9-9410-bb1fed5d2134"
mac2 := "aa:aa:bb:bb:cc:cc"
serialNumber2 := "111AAA9N65"
hostname2 := "anotherHostname"
setup(ctx, t, st, uuid2, serialNumber2, hostname2, mac2)
case 1:
// Before the second call Configuration() should have resubstituted all the new parameters in the URL.
assert.Equal(t, "h=anotherHostname&m=aa%3Aaa%3Abb%3Abb%3Acc%3Acc&s=111AAA9N65&u=9fba530f-767d-40f9-9410-bb1fed5d2134", r.URL.RawQuery)
w.WriteHeader(http.StatusOK)
}
nCalls++
}))
defer server.Close()
uuid1 := "40dcbd19-3b10-444e-bfff-aaee44a51fda"
mac1 := "52:2f:fd:df:fc:c0"
serialNumber1 := "0OCZJ19N65"
hostname1 := "myTestHostname"
setup(ctx, t, st, uuid1, serialNumber1, hostname1, mac1)
downloadURL := server.URL + "/metadata?h=${hostname}&m=${mac}&s=${serial}&u=${uuid}"
param := procfs.NewParameter(constants.KernelParamConfig)
param.Append(downloadURL)
procfs.ProcCmdline().Set(constants.KernelParamConfig, param)
defer procfs.ProcCmdline().Set(constants.KernelParamConfig, nil)
go func() {
testObj := metal.Metal{}
_, err := testObj.Configuration(ctx, st)
assert.NoError(t, err)
cancel()
}()
<-ctx.Done()
}

View File

@ -19,6 +19,7 @@ import (
"github.com/siderolabs/go-blockdevice/blockdevice/filesystem"
"github.com/siderolabs/go-blockdevice/blockdevice/probe"
"golang.org/x/sys/unix"
yaml "gopkg.in/yaml.v3"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
@ -59,8 +60,8 @@ type NetworkConfig struct {
// Ethernet holds network interface info.
type Ethernet struct {
Match struct {
Name []string `yaml:"name,omitempty"`
HWAddr []string `yaml:"macaddress,omitempty"`
Name string `yaml:"name,omitempty"`
HWAddr string `yaml:"macaddress,omitempty"`
} `yaml:"match,omitempty"`
DHCPv4 bool `yaml:"dhcp4,omitempty"`
DHCPv6 bool `yaml:"dhcp6,omitempty"`
@ -179,14 +180,16 @@ func (n *Nocloud) configFromCD() (metaConfig []byte, networkConfig []byte, machi
}
//nolint:gocyclo
func (n *Nocloud) acquireConfig(ctx context.Context) (metadataConfigDl, metadataNetworkConfigDl, machineConfigDl []byte, hostname string, err error) {
func (n *Nocloud) acquireConfig(ctx context.Context) (metadataConfigDl, metadataNetworkConfigDl, machineConfigDl []byte, metadata *MetadataConfig, err error) {
s, err := smbios.GetSMBIOSInfo()
if err != nil {
return nil, nil, nil, "", err
return nil, nil, nil, nil, err
}
metaBaseURL := ""
networkSource := false
var (
metaBaseURL, hostname string
networkSource bool
)
options := strings.Split(s.SystemInformation.SerialNumber, ";")
for _, option := range options {
@ -220,7 +223,17 @@ func (n *Nocloud) acquireConfig(ctx context.Context) (metadataConfigDl, metadata
metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, err = n.configFromCD()
}
return metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, hostname, err
metadata = &MetadataConfig{}
if metadataConfigDl != nil {
_ = yaml.Unmarshal(metadataConfigDl, metadata) //nolint:errcheck
}
if hostname != "" {
metadata.Hostname = hostname
}
return metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, metadata, err
}
//nolint:gocyclo

View File

@ -17,6 +17,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// Nocloud is the concrete type that implements the runtime.Platform interface.
@ -28,15 +29,15 @@ func (n *Nocloud) Name() string {
}
// ParseMetadata converts nocloud metadata to platform network config.
func (n *Nocloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, hostname string) (*runtime.PlatformNetworkConfig, error) {
func (n *Nocloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if hostname != "" {
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(hostname); err != nil {
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
@ -56,6 +57,12 @@ func (n *Nocloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, hostna
return nil, fmt.Errorf("network-config metadata version=%d is not supported", unmarshalledNetworkConfig.Version)
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: n.Name(),
Hostname: metadata.Hostname,
InstanceID: metadata.InstanceID,
}
return networkConfig, nil
}
@ -89,7 +96,7 @@ func (n *Nocloud) KernelArgs() procfs.Parameters {
//
//nolint:gocyclo
func (n *Nocloud) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
metadataConfigDl, metadataNetworkConfigDl, _, hostname, err := n.acquireConfig(ctx)
metadataConfigDl, metadataNetworkConfigDl, _, metadata, err := n.acquireConfig(ctx)
if stderrors.Is(err, errors.ErrNoConfigSource) {
err = nil
}
@ -98,31 +105,19 @@ func (n *Nocloud) NetworkConfiguration(ctx context.Context, _ state.State, ch ch
return err
}
if metadataConfigDl == nil && metadataNetworkConfigDl == nil && hostname == "" {
if metadataConfigDl == nil && metadataNetworkConfigDl == nil {
// no data, use cached network configuration if available
return nil
}
var (
unmarshalledMetadataConfig MetadataConfig
unmarshalledNetworkConfig NetworkConfig
)
if metadataConfigDl != nil {
_ = yaml.Unmarshal(metadataConfigDl, &unmarshalledMetadataConfig) //nolint:errcheck
}
var unmarshalledNetworkConfig NetworkConfig
if metadataNetworkConfigDl != nil {
if err = yaml.Unmarshal(metadataNetworkConfigDl, &unmarshalledNetworkConfig); err != nil {
return err
}
}
if hostname == "" {
hostname = unmarshalledMetadataConfig.Hostname
}
networkConfig, err := n.ParseMetadata(&unmarshalledNetworkConfig, hostname)
networkConfig, err := n.ParseMetadata(&unmarshalledNetworkConfig, metadata)
if err != nil {
return err
}

View File

@ -32,13 +32,11 @@ func TestParseMetadata(t *testing.T) {
for _, tt := range []struct {
name string
raw []byte
hostname string
expected string
}{
{
name: "V1",
raw: rawMetadataV1,
hostname: "talos",
expected: expectedNetworkConfigV1,
},
{
@ -56,7 +54,12 @@ func TestParseMetadata(t *testing.T) {
require.NoError(t, yaml.Unmarshal(tt.raw, &m))
networkConfig, err := n.ParseMetadata(&m, tt.hostname)
mc := nocloud.MetadataConfig{
Hostname: "talos.fqdn",
InstanceID: "0",
}
networkConfig, err := n.ParseMetadata(&m, &mc)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)

View File

@ -46,7 +46,7 @@ routes:
layer: platform
hostnames:
- hostname: talos
domainname: ""
domainname: fqdn
layer: platform
resolvers:
- dnsServers:
@ -55,3 +55,7 @@ resolvers:
timeServers: []
operators: []
externalIPs: []
metadata:
platform: nocloud
hostname: talos.fqdn
instanceId: "0"

View File

@ -44,7 +44,10 @@ routes:
flags: ""
protocol: static
layer: platform
hostnames: []
hostnames:
- hostname: talos
domainname: fqdn
layer: platform
resolvers:
- dnsServers:
- 8.8.8.8
@ -58,3 +61,7 @@ operators:
routeMetric: 1024
layer: platform
externalIPs: []
metadata:
platform: nocloud
hostname: talos.fqdn
instanceId: "0"

View File

@ -1,6 +1,8 @@
version: 2
ethernets:
eth0:
match:
macaddress: '00:20:6e:1f:f9:a8'
dhcp4: true
addresses:
- 192.168.14.2/24

View File

@ -32,8 +32,8 @@ const (
// OpenstackExternalIPEndpoint is the local Openstack endpoint for the external IP.
OpenstackExternalIPEndpoint = "http://169.254.169.254/latest/meta-data/public-ipv4"
// OpenstackHostnameEndpoint is the local Openstack endpoint for the hostname.
OpenstackHostnameEndpoint = "http://169.254.169.254/latest/meta-data/hostname"
// OpenstackInstanceTypeEndpoint is the local Openstack endpoint for the instance-type.
OpenstackInstanceTypeEndpoint = "http://169.254.169.254/latest/meta-data/instance-type"
// OpenstackMetaDataEndpoint is the local Openstack endpoint for the meta config.
OpenstackMetaDataEndpoint = "http://169.254.169.254/" + configMetadataPath
// OpenstackNetworkDataEndpoint is the local Openstack endpoint for the network config.
@ -71,7 +71,11 @@ type NetworkConfig struct {
// MetadataConfig holds meta info.
type MetadataConfig struct {
Hostname string `json:"hostname,omitempty"`
UUID string `json:"uuid,omitempty"`
Hostname string `json:"hostname,omitempty"`
AvailabilityZone string `json:"availability_zone,omitempty"`
ProjectID string `json:"project_id"`
InstanceType string `json:"instance_type"`
}
func (o *Openstack) configFromNetwork(ctx context.Context) (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
@ -158,20 +162,15 @@ func (o *Openstack) configFromCD() (metaConfig []byte, networkConfig []byte, mac
return metaConfig, networkConfig, machineConfig, nil
}
func (o *Openstack) hostname(ctx context.Context) []byte {
log.Printf("fetching hostname from: %q", OpenstackHostnameEndpoint)
func (o *Openstack) instanceType(ctx context.Context) string {
log.Printf("fetching instance-type from: %q", OpenstackInstanceTypeEndpoint)
hostname, err := download.Download(ctx, OpenstackHostnameEndpoint,
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
sku, err := download.Download(ctx, OpenstackInstanceTypeEndpoint)
if err != nil {
// Platform cannot support this endpoint, or returns timeout.
log.Printf("failed to fetch hostname, ignored: %s", err)
return nil
return ""
}
return hostname
return string(sku)
}
func (o *Openstack) externalIPs(ctx context.Context) (addrs []netip.Addr) {

View File

@ -22,6 +22,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/utils"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// Openstack is the concrete type that implements the runtime.Platform interface.
@ -35,19 +36,15 @@ func (o *Openstack) Name() string {
// ParseMetadata converts OpenStack metadata to platform network configuration.
//
//nolint:gocyclo,cyclop
func (o *Openstack) ParseMetadata(unmarshalledMetadataConfig *MetadataConfig, unmarshalledNetworkConfig *NetworkConfig, hostname string, extIPs []netip.Addr) (*runtime.PlatformNetworkConfig, error) {
func (o *Openstack) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, extIPs []netip.Addr, metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if hostname == "" {
hostname = unmarshalledMetadataConfig.Hostname
}
if hostname != "" {
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(hostname); err != nil {
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
@ -210,6 +207,15 @@ func (o *Openstack) ParseMetadata(unmarshalledMetadataConfig *MetadataConfig, un
}
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: o.Name(),
Hostname: metadata.Hostname,
Zone: metadata.AvailabilityZone,
InstanceID: metadata.UUID,
InstanceType: metadata.InstanceType,
ProviderID: fmt.Sprintf("openstack:///%s", metadata.UUID),
}
return networkConfig, nil
}
@ -246,6 +252,8 @@ func (o *Openstack) KernelArgs() procfs.Parameters {
// NetworkConfiguration implements the runtime.Platform interface.
func (o *Openstack) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
networkSource := false
metadataConfigDl, metadataNetworkConfigDl, _, err := o.configFromCD()
if err != nil {
metadataConfigDl, metadataNetworkConfigDl, _, err = o.configFromNetwork(ctx)
@ -256,21 +264,30 @@ func (o *Openstack) NetworkConfiguration(ctx context.Context, _ state.State, ch
if err != nil {
return err
}
networkSource = true
}
hostname := o.hostname(ctx)
extIPs := o.externalIPs(ctx)
var (
unmarshalledMetadataConfig MetadataConfig
unmarshalledNetworkConfig NetworkConfig
meta MetadataConfig
unmarshalledNetworkConfig NetworkConfig
)
// ignore errors unmarshaling, empty configs work just fine as empty default
_ = json.Unmarshal(metadataConfigDl, &unmarshalledMetadataConfig) //nolint:errcheck
_ = json.Unmarshal(metadataConfigDl, &meta) //nolint:errcheck
_ = json.Unmarshal(metadataNetworkConfigDl, &unmarshalledNetworkConfig) //nolint:errcheck
networkConfig, err := o.ParseMetadata(&unmarshalledMetadataConfig, &unmarshalledNetworkConfig, string(hostname), extIPs)
var extIPs []netip.Addr
if networkSource {
extIPs = o.externalIPs(ctx)
if meta.InstanceType == "" {
meta.InstanceType = o.instanceType(ctx)
}
}
networkConfig, err := o.ParseMetadata(&unmarshalledNetworkConfig, extIPs, &meta)
if err != nil {
return err
}

View File

@ -29,15 +29,15 @@ var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
o := &openstack.Openstack{}
var m openstack.MetadataConfig
var metadata openstack.MetadataConfig
require.NoError(t, json.Unmarshal(rawMetadata, &m))
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
var n openstack.NetworkConfig
require.NoError(t, json.Unmarshal(rawNetwork, &n))
networkConfig, err := o.ParseMetadata(&m, &n, "", []netip.Addr{netip.MustParseAddr("1.2.3.4")})
networkConfig, err := o.ParseMetadata(&n, []netip.Addr{netip.MustParseAddr("1.2.3.4")}, &metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)

View File

@ -133,3 +133,9 @@ operators:
layer: platform
externalIPs:
- 1.2.3.4
metadata:
platform: openstack
hostname: talos
zone: nova
instanceId: 39073b0a-1234-1234-1234-5e76a4bd64b2
providerId: openstack:///39073b0a-1234-1234-1234-5e76a4bd64b2

View File

@ -0,0 +1,51 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package oracle
import (
"context"
"encoding/json"
"fmt"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
)
// Ref: https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm
const (
// OracleMetadataEndpoint is the local metadata endpoint for the hostname.
OracleMetadataEndpoint = "http://169.254.169.254/opc/v2/instance/"
// OracleUserDataEndpoint is the local metadata endpoint inside of Oracle Cloud.
OracleUserDataEndpoint = "http://169.254.169.254/opc/v2/instance/metadata/user_data"
// OracleNetworkEndpoint is the local network metadata endpoint inside of Oracle Cloud.
OracleNetworkEndpoint = "http://169.254.169.254/opc/v2/vnics/"
)
// MetadataConfig represents a metadata Oracle instance.
type MetadataConfig struct {
Hostname string `json:"hostname,omitempty"`
ID string `json:"id,omitempty"`
Region string `json:"region,omitempty"`
AvailabilityDomain string `json:"availabilityDomain,omitempty"`
FaultDomain string `json:"faultDomain,omitempty"`
Shape string `json:"shape,omitempty"`
}
func (o *Oracle) getMetadata(ctx context.Context) (*MetadataConfig, error) {
metaConfigDl, err := download.Download(ctx, OracleMetadataEndpoint, //nolint:errcheck
download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}),
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil {
return nil, fmt.Errorf("error fetching metadata: %w", err)
}
var meta MetadataConfig
if err = json.Unmarshal(metaConfigDl, &meta); err != nil {
return nil, err
}
return &meta, nil
}

View File

@ -11,6 +11,7 @@ import (
"encoding/json"
"fmt"
"log"
"strings"
"github.com/cosi-project/runtime/pkg/state"
"github.com/talos-systems/go-procfs/procfs"
@ -19,16 +20,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
// Ref: https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm
const (
// OracleHostnameEndpoint is the local metadata endpoint for the hostname.
OracleHostnameEndpoint = "http://169.254.169.254/opc/v2/instance/hostname"
// OracleUserDataEndpoint is the local metadata endpoint inside of Oracle Cloud.
OracleUserDataEndpoint = "http://169.254.169.254/opc/v2/instance/metadata/user_data"
// OracleNetworkEndpoint is the local network metadata endpoint inside of Oracle Cloud.
OracleNetworkEndpoint = "http://169.254.169.254/opc/v2/vnics/"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// NetworkConfig holds network interface meta config.
@ -50,15 +42,15 @@ func (o *Oracle) Name() string {
}
// ParseMetadata converts Oracle Cloud metadata into platform network configuration.
func (o *Oracle) ParseMetadata(interfaceAddresses []NetworkConfig, hostname string) (*runtime.PlatformNetworkConfig, error) {
func (o *Oracle) ParseMetadata(interfaceAddresses []NetworkConfig, metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if hostname != "" {
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(hostname); err != nil {
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
@ -81,6 +73,22 @@ func (o *Oracle) ParseMetadata(interfaceAddresses []NetworkConfig, hostname stri
}
}
zone := metadata.AvailabilityDomain
if idx := strings.LastIndex(zone, ":"); idx != -1 {
zone = zone[:idx]
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: o.Name(),
Hostname: metadata.Hostname,
Region: strings.ToLower(metadata.Region),
Zone: strings.ToLower(zone),
InstanceType: metadata.Shape,
InstanceID: metadata.ID,
ProviderID: fmt.Sprintf("oci://%s", metadata.ID),
}
return networkConfig, nil
}
@ -118,28 +126,28 @@ func (o *Oracle) KernelArgs() procfs.Parameters {
// NetworkConfiguration implements the runtime.Platform interface.
func (o *Oracle) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
log.Printf("fetching oracle metadata from: %q", OracleMetadataEndpoint)
metadata, err := o.getMetadata(ctx)
if err != nil {
return err
}
log.Printf("fetching network config from %q", OracleNetworkEndpoint)
metadataNetworkConfig, err := download.Download(ctx, OracleNetworkEndpoint,
metadataNetworkConfigDl, err := download.Download(ctx, OracleNetworkEndpoint,
download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}))
if err != nil {
return fmt.Errorf("failed to fetch network config from metadata service: %w", err)
return fmt.Errorf("failed to fetch network config from: %w", err)
}
var interfaceAddresses []NetworkConfig
if err = json.Unmarshal(metadataNetworkConfig, &interfaceAddresses); err != nil {
if err = json.Unmarshal(metadataNetworkConfigDl, &interfaceAddresses); err != nil {
return err
}
log.Printf("fetching hostname from: %q", OracleHostnameEndpoint)
hostname, _ := download.Download(ctx, OracleHostnameEndpoint, //nolint:errcheck
download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}),
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
networkConfig, err := o.ParseMetadata(interfaceAddresses, string(hostname))
networkConfig, err := o.ParseMetadata(interfaceAddresses, metadata)
if err != nil {
return err
}

View File

@ -19,17 +19,24 @@ import (
//go:embed testdata/metadata.json
var rawMetadata []byte
//go:embed testdata/metadatanetwork.json
var rawMetadataNetwork []byte
//go:embed testdata/expected.yaml
var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
o := &oracle.Oracle{}
p := &oracle.Oracle{}
var metadata oracle.MetadataConfig
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
var m []oracle.NetworkConfig
require.NoError(t, json.Unmarshal(rawMetadata, &m))
require.NoError(t, json.Unmarshal(rawMetadataNetwork, &m))
networkConfig, err := o.ParseMetadata(m, "talos")
networkConfig, err := p.ParseMetadata(m, &metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)

View File

@ -15,3 +15,11 @@ operators:
routeMetric: 1024
layer: platform
externalIPs: []
metadata:
platform: oracle
hostname: talos
region: phx
zone: emir
instanceType: VM.Standard.E3.Flex
instanceId: ocid1.instance.oc1.phx.exampleuniqueID
providerId: oci://ocid1.instance.oc1.phx.exampleuniqueID

View File

@ -1,12 +1,53 @@
[
{
"vnicId": "ocid1.vnic.oc1.eu-amsterdam-1.asdasd",
"privateIp": "172.16.1.11",
"vlanTag": 1,
"macAddr": "02:00:17:00:00:00",
"virtualRouterIp": "172.16.1.1",
"subnetCidrBlock": "172.16.1.0/24",
"ipv6SubnetCidrBlock": "2603:a:b:c::/64",
"ipv6VirtualRouterIp": "fe80::a:b:c:d"
{
"availabilityDomain": "EMIr:PHX-AD-1",
"faultDomain": "FAULT-DOMAIN-3",
"compartmentId": "ocid1.tenancy.oc1..exampleuniqueID",
"displayName": "talos-instance",
"hostname": "talos",
"id": "ocid1.instance.oc1.phx.exampleuniqueID",
"image": "ocid1.image.oc1.phx.exampleuniqueID",
"metadata": {},
"region": "phx",
"canonicalRegionName": "us-phoenix-1",
"ociAdName": "phx-ad-1",
"regionInfo": {
"realmKey": "oc1",
"realmDomainComponent": "oraclecloud.com",
"regionKey": "PHX",
"regionIdentifier": "us-phoenix-1"
},
"shape": "VM.Standard.E3.Flex",
"state": "Running",
"timeCreated": 1600381928581,
"agentConfig": {
"monitoringDisabled": false,
"managementDisabled": false,
"allPluginsDisabled": false,
"pluginsConfig": [
{
"name": "OS Management Service Agent",
"desiredState": "ENABLED"
},
{
"name": "Custom Logs Monitoring",
"desiredState": "ENABLED"
},
{
"name": "Compute Instance Run Command",
"desiredState": "ENABLED"
},
{
"name": "Compute Instance Monitoring",
"desiredState": "ENABLED"
}
]
},
"freeformTags": {
"Department": "Finance"
},
"definedTags": {
"Operations": {
"CostCenter": "42"
}
}
]
}

View File

@ -0,0 +1,12 @@
[
{
"vnicId": "ocid1.vnic.oc1.eu-amsterdam-1.asdasd",
"privateIp": "172.16.1.11",
"vlanTag": 1,
"macAddr": "02:00:17:00:00:00",
"virtualRouterIp": "172.16.1.1",
"subnetCidrBlock": "172.16.1.0/24",
"ipv6SubnetCidrBlock": "2603:a:b:c::/64",
"ipv6VirtualRouterIp": "fe80::a:b:c:d"
}
]

View File

@ -0,0 +1,36 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package scaleway
import (
"context"
"encoding/json"
"fmt"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/talos-systems/talos/pkg/download"
)
const (
// ScalewayMetadataEndpoint is the local Scaleway endpoint.
ScalewayMetadataEndpoint = "http://169.254.42.42/conf?format=json"
// ScalewayUserDataEndpoint is the local Scaleway endpoint for the config.
ScalewayUserDataEndpoint = "http://169.254.42.42/user_data/cloud-init"
)
func (u *Scaleway) getMetadata(ctx context.Context) (*instance.Metadata, error) {
metaConfigDl, err := download.Download(ctx, ScalewayMetadataEndpoint)
if err != nil {
return nil, fmt.Errorf("error fetching metadata: %w", err)
}
var meta instance.Metadata
if err = json.Unmarshal(metaConfigDl, &meta); err != nil {
return nil, err
}
return &meta, nil
}

View File

@ -7,10 +7,11 @@ package scaleway
import (
"context"
"encoding/json"
"fmt"
"log"
"net/netip"
"strconv"
"strings"
"github.com/cosi-project/runtime/pkg/state"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
@ -21,13 +22,7 @@ import (
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
const (
// ScalewayMetadataEndpoint is the local Scaleway endpoint.
ScalewayMetadataEndpoint = "http://169.254.42.42/conf?format=json"
// ScalewayUserDataEndpoint is the local Scaleway endpoint for the config.
ScalewayUserDataEndpoint = "http://169.254.42.42/user_data/cloud-init"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// Scaleway is the concrete type that implements the runtime.Platform interface.
@ -38,24 +33,24 @@ func (s *Scaleway) Name() string {
return "scaleway"
}
// ParseMetadata converts Scaleway met.
func (s *Scaleway) ParseMetadata(metadataConfig *instance.Metadata) (*runtime.PlatformNetworkConfig, error) {
// ParseMetadata converts Scaleway platform metadata into platform network config.
func (s *Scaleway) ParseMetadata(metadata *instance.Metadata) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if metadataConfig.Hostname != "" {
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(metadataConfig.Hostname); err != nil {
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
if metadataConfig.PublicIP.Address != "" {
ip, err := netip.ParseAddr(metadataConfig.PublicIP.Address)
if metadata.PublicIP.Address != "" {
ip, err := netip.ParseAddr(metadata.PublicIP.Address)
if err != nil {
return nil, err
}
@ -94,13 +89,13 @@ func (s *Scaleway) ParseMetadata(metadataConfig *instance.Metadata) (*runtime.Pl
ConfigLayer: network.ConfigPlatform,
})
if metadataConfig.IPv6.Address != "" {
bits, err := strconv.Atoi(metadataConfig.IPv6.Netmask)
if metadata.IPv6.Address != "" {
bits, err := strconv.Atoi(metadata.IPv6.Netmask)
if err != nil {
return nil, err
}
ip, err := netip.ParseAddr(metadataConfig.IPv6.Address)
ip, err := netip.ParseAddr(metadata.IPv6.Address)
if err != nil {
return nil, err
}
@ -118,7 +113,7 @@ func (s *Scaleway) ParseMetadata(metadataConfig *instance.Metadata) (*runtime.Pl
},
)
gw, err := netip.ParseAddr(metadataConfig.IPv6.Gateway)
gw, err := netip.ParseAddr(metadata.IPv6.Gateway)
if err != nil {
return nil, err
}
@ -139,20 +134,34 @@ func (s *Scaleway) ParseMetadata(metadataConfig *instance.Metadata) (*runtime.Pl
networkConfig.Routes = append(networkConfig.Routes, route)
}
zoneParts := strings.Split(metadata.Location.ZoneID, "-")
if len(zoneParts) > 2 {
zoneParts = zoneParts[:2]
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: s.Name(),
Hostname: metadata.Hostname,
Region: strings.Join(zoneParts, "-"),
Zone: metadata.Location.ZoneID,
InstanceType: metadata.CommercialType,
InstanceID: metadata.ID,
ProviderID: fmt.Sprintf("scaleway://instance/%s/%s", metadata.Location.ZoneID, metadata.ID),
}
return networkConfig, nil
}
// Configuration implements the runtime.Platform interface.
//
//nolint:stylecheck
func (s *Scaleway) Configuration(ctx context.Context, r state.State) ([]byte, error) {
log.Printf("fetching machine config from %q", ScalewayUserDataEndpoint)
machineConfigDl, err := download.Download(ctx, ScalewayUserDataEndpoint,
download.WithLowSrcPort())
if err != nil {
return nil, errors.ErrNoConfigSource
}
return machineConfigDl, nil
return download.Download(ctx, ScalewayUserDataEndpoint,
download.WithLowSrcPort(),
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
}
// Mode implements the runtime.Platform interface.
@ -171,16 +180,11 @@ func (s *Scaleway) KernelArgs() procfs.Parameters {
func (s *Scaleway) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
log.Printf("fetching scaleway instance config from: %q", ScalewayMetadataEndpoint)
metadataDl, err := download.Download(ctx, ScalewayMetadataEndpoint)
metadata, err := s.getMetadata(ctx)
if err != nil {
return err
}
metadata := &instance.Metadata{}
if err = json.Unmarshal(metadataDl, metadata); err != nil {
return err
}
networkConfig, err := s.ParseMetadata(metadata)
if err != nil {
return err

View File

@ -26,11 +26,11 @@ var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
p := &scaleway.Scaleway{}
var m instance.Metadata
var metadata instance.Metadata
require.NoError(t, json.Unmarshal(rawMetadata, &m))
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
networkConfig, err := p.ParseMetadata(&m)
networkConfig, err := p.ParseMetadata(&metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)

View File

@ -53,3 +53,9 @@ operators:
layer: platform
externalIPs:
- 11.22.222.222
metadata:
platform: scaleway
hostname: scw-talos
instanceType: DEV1-S
instanceId: 11111111-1111-1111-1111-111111111111
providerId: scaleway://instance//11111111-1111-1111-1111-111111111111

View File

@ -0,0 +1,63 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package upcloud
import (
"context"
"encoding/json"
"fmt"
"github.com/talos-systems/talos/pkg/download"
)
const (
// UpCloudMetadataEndpoint is the local UpCloud endpoint.
UpCloudMetadataEndpoint = "http://169.254.169.254/metadata/v1.json"
// UpCloudUserDataEndpoint is the local UpCloud endpoint for the config.
UpCloudUserDataEndpoint = "http://169.254.169.254/metadata/v1/user_data"
)
// MetadataConfig represents a metadata Upcloud instance.
type MetadataConfig struct {
Hostname string `json:"hostname,omitempty"`
InstanceID string `json:"instance_id,omitempty"`
PublicKeys []string `json:"public_keys,omitempty"`
Zone string `json:"region,omitempty"`
Tags []string `json:"tags,omitempty"`
Network struct {
Interfaces []struct {
Index int `json:"index,omitempty"`
IPAddresses []struct {
Address string `json:"address,omitempty"`
DHCP bool `json:"dhcp,omitempty"`
DNS []string `json:"dns,omitempty"`
Family string `json:"family,omitempty"`
Floating bool `json:"floating,omitempty"`
Gateway string `json:"gateway,omitempty"`
Network string `json:"network,omitempty"`
} `json:"ip_addresses,omitempty"`
MAC string `json:"mac,omitempty"`
NetworkType string `json:"type,omitempty"`
NetworkID string `json:"network_id,omitempty"`
} `json:"interfaces,omitempty"`
DNS []string `json:"dns,omitempty"`
} `json:"network,omitempty"`
}
func (u *UpCloud) getMetadata(ctx context.Context) (*MetadataConfig, error) {
metaConfigDl, err := download.Download(ctx, UpCloudMetadataEndpoint)
if err != nil {
return nil, fmt.Errorf("error fetching metadata: %w", err)
}
var meta MetadataConfig
if err = json.Unmarshal(metaConfigDl, &meta); err != nil {
return nil, err
}
return &meta, nil
}

View File

@ -73,3 +73,8 @@ operators:
layer: platform
externalIPs:
- 185.70.197.2
metadata:
platform: upcloud
hostname: talos
instanceId: 00123456-1111-2222-3333-123456789012
providerId: upcloud://00123456-1111-2222-3333-123456789012

View File

@ -7,7 +7,6 @@ package upcloud
import (
"context"
"encoding/json"
"fmt"
"log"
"net/netip"
@ -20,43 +19,9 @@ import (
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
const (
// UpCloudMetadataEndpoint is the local UpCloud endpoint.
UpCloudMetadataEndpoint = "http://169.254.169.254/metadata/v1.json"
// UpCloudUserDataEndpoint is the local UpCloud endpoint for the config.
UpCloudUserDataEndpoint = "http://169.254.169.254/metadata/v1/user_data"
)
// MetaData represents a metadata Upcloud interface.
type MetaData struct {
Hostname string `json:"hostname,omitempty"`
InstanceID string `json:"instance_id,omitempty"`
PublicKeys []string `json:"public_keys,omitempty"`
Region string `json:"region,omitempty"`
Network struct {
Interfaces []struct {
Index int `json:"index,omitempty"`
IPAddresses []struct {
Address string `json:"address,omitempty"`
DHCP bool `json:"dhcp,omitempty"`
DNS []string `json:"dns,omitempty"`
Family string `json:"family,omitempty"`
Floating bool `json:"floating,omitempty"`
Gateway string `json:"gateway,omitempty"`
Network string `json:"network,omitempty"`
} `json:"ip_addresses,omitempty"`
MAC string `json:"mac,omitempty"`
NetworkType string `json:"type,omitempty"`
NetworkID string `json:"network_id,omitempty"`
} `json:"interfaces,omitempty"`
DNS []string `json:"dns,omitempty"`
} `json:"network,omitempty"`
}
// UpCloud is the concrete type that implements the runtime.Platform interface.
type UpCloud struct{}
@ -68,15 +33,15 @@ func (u *UpCloud) Name() string {
// ParseMetadata converts Upcloud metadata into platform network configuration.
//
//nolint:gocyclo
func (u *UpCloud) ParseMetadata(meta *MetaData) (*runtime.PlatformNetworkConfig, error) {
func (u *UpCloud) ParseMetadata(metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if meta.Hostname != "" {
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(meta.Hostname); err != nil {
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
@ -87,7 +52,7 @@ func (u *UpCloud) ParseMetadata(meta *MetaData) (*runtime.PlatformNetworkConfig,
firstIP := true
for _, addr := range meta.Network.Interfaces {
for _, addr := range metadata.Network.Interfaces {
if addr.Index <= 0 { // protect from negative interface name
continue
}
@ -192,6 +157,14 @@ func (u *UpCloud) ParseMetadata(meta *MetaData) (*runtime.PlatformNetworkConfig,
})
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: u.Name(),
Hostname: metadata.Hostname,
Zone: metadata.Zone,
InstanceID: metadata.InstanceID,
ProviderID: fmt.Sprintf("upcloud://%s", metadata.InstanceID),
}
return networkConfig, nil
}
@ -216,19 +189,14 @@ func (u *UpCloud) KernelArgs() procfs.Parameters {
// NetworkConfiguration implements the runtime.Platform interface.
func (u *UpCloud) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
log.Printf("fetching UpCloud instance config from: %q ", UpCloudMetadataEndpoint)
log.Printf("fetching UpCloud instance config from: %q", UpCloudMetadataEndpoint)
metaConfigDl, err := download.Download(ctx, UpCloudMetadataEndpoint)
metadata, err := u.getMetadata(ctx)
if err != nil {
return fmt.Errorf("failed to fetch network config from metadata service: %w", err)
}
meta := &MetaData{}
if err = json.Unmarshal(metaConfigDl, meta); err != nil {
return err
}
networkConfig, err := u.ParseMetadata(meta)
networkConfig, err := u.ParseMetadata(metadata)
if err != nil {
return err
}

View File

@ -25,11 +25,11 @@ var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
p := &upcloud.UpCloud{}
var m upcloud.MetaData
var metadata upcloud.MetadataConfig
require.NoError(t, json.Unmarshal(rawMetadata, &m))
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
networkConfig, err := p.ParseMetadata(&m)
networkConfig, err := p.ParseMetadata(&metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)

View File

@ -24,6 +24,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
platformerrors "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/machinery/constants"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// VMware is the concrete type that implements the platform.Platform interface.
@ -202,5 +203,15 @@ func (v *VMware) KernelArgs() procfs.Parameters {
// NetworkConfiguration implements the runtime.Platform interface.
func (v *VMware) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
networkConfig := &runtime.PlatformNetworkConfig{
Metadata: &runtimeres.PlatformMetadataSpec{Platform: v.Name()},
}
select {
case <-ctx.Done():
return ctx.Err()
case ch <- networkConfig:
}
return nil
}

View File

@ -0,0 +1,38 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package vultr
import (
"context"
"encoding/json"
"fmt"
"github.com/vultr/metadata"
"github.com/talos-systems/talos/pkg/download"
)
const (
// VultrMetadataEndpoint is the local Vultr endpoint fot the instance metadata.
VultrMetadataEndpoint = "http://169.254.169.254/v1.json"
// VultrExternalIPEndpoint is the local Vultr endpoint for the external IP.
VultrExternalIPEndpoint = "http://169.254.169.254/latest/meta-data/public-ipv4"
// VultrUserDataEndpoint is the local Vultr endpoint for the config.
VultrUserDataEndpoint = "http://169.254.169.254/latest/user-data"
)
func (g *Vultr) getMetadata(ctx context.Context) (*metadata.MetaData, error) {
metaConfigDl, err := download.Download(ctx, VultrMetadataEndpoint)
if err != nil {
return nil, fmt.Errorf("error fetching metadata: %w", err)
}
var meta metadata.MetaData
if err = json.Unmarshal(metaConfigDl, &meta); err != nil {
return nil, err
}
return &meta, nil
}

View File

@ -36,3 +36,9 @@ operators:
layer: platform
externalIPs:
- 1.2.3.4
metadata:
platform: vultr
hostname: talos
region: AMS
instanceId: 91b07056-af72-4551-b15b-d57d34071be9
providerId: vultr://91b07056-af72-4551-b15b-d57d34071be9

View File

@ -58,4 +58,4 @@
"regioncode": "AMS"
},
"user-defined": []
}
}

View File

@ -7,7 +7,6 @@ package vultr
import (
"context"
"encoding/json"
stderrors "errors"
"fmt"
"log"
@ -23,17 +22,7 @@ import (
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/network"
)
const (
// VultrMetadataEndpoint is the local Vultr endpoint fot the instance metadata.
VultrMetadataEndpoint = "http://169.254.169.254/v1.json"
// VultrExternalIPEndpoint is the local Vultr endpoint for the external IP.
VultrExternalIPEndpoint = "http://169.254.169.254/latest/meta-data/public-ipv4"
// VultrHostnameEndpoint is the local Vultr endpoint for the hostname.
VultrHostnameEndpoint = "http://169.254.169.254/latest/meta-data/hostname"
// VultrUserDataEndpoint is the local Vultr endpoint for the config.
VultrUserDataEndpoint = "http://169.254.169.254/latest/user-data"
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
)
// Vultr is the concrete type that implements the runtime.Platform interface.
@ -45,26 +34,28 @@ func (v *Vultr) Name() string {
}
// ParseMetadata converts Vultr platform metadata into platform network config.
func (v *Vultr) ParseMetadata(meta *metadata.MetaData, extIP []byte) (*runtime.PlatformNetworkConfig, error) {
//
//nolint:gocyclo
func (v *Vultr) ParseMetadata(extIP []byte, metadata *metadata.MetaData) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}
if ip, err := netip.ParseAddr(string(extIP)); err == nil {
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
if meta.Hostname != "" {
if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}
if err := hostnameSpec.ParseFQDN(meta.Hostname); err != nil {
if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}
for i, addr := range meta.Interfaces {
for i, addr := range metadata.Interfaces {
iface := fmt.Sprintf("eth%d", i)
link := network.LinkSpecSpec{
@ -119,10 +110,20 @@ func (v *Vultr) ParseMetadata(meta *metadata.MetaData, extIP []byte) (*runtime.P
}
}
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: v.Name(),
Hostname: metadata.Hostname,
Region: metadata.Region.RegionCode,
InstanceID: metadata.InstanceV2ID,
ProviderID: fmt.Sprintf("vultr://%s", metadata.InstanceV2ID),
}
return networkConfig, nil
}
// Configuration implements the runtime.Platform interface.
//
//nolint:stylecheck
func (v *Vultr) Configuration(ctx context.Context, r state.State) ([]byte, error) {
log.Printf("fetching machine config from: %q", VultrUserDataEndpoint)
@ -143,15 +144,10 @@ func (v *Vultr) KernelArgs() procfs.Parameters {
// NetworkConfiguration implements the runtime.Platform interface.
func (v *Vultr) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
log.Printf("fetching Vultr instance config from: %q ", VultrMetadataEndpoint)
log.Printf("fetching Vultr instance metadata from: %q", VultrMetadataEndpoint)
metaConfigDl, err := download.Download(ctx, VultrMetadataEndpoint)
metadata, err := v.getMetadata(ctx)
if err != nil {
return fmt.Errorf("error fetching metadata: %w", err)
}
meta := &metadata.MetaData{}
if err = json.Unmarshal(metaConfigDl, meta); err != nil {
return err
}
@ -162,7 +158,7 @@ func (v *Vultr) NetworkConfiguration(ctx context.Context, _ state.State, ch chan
return err
}
networkConfig, err := v.ParseMetadata(meta, extIP)
networkConfig, err := v.ParseMetadata(extIP, metadata)
if err != nil {
return err
}

View File

@ -26,11 +26,11 @@ var expectedNetworkConfig string
func TestParseMetadata(t *testing.T) {
p := &vultr.Vultr{}
var m metadata.MetaData
var metadata metadata.MetaData
require.NoError(t, json.Unmarshal(rawMetadata, &m))
require.NoError(t, json.Unmarshal(rawMetadata, &metadata))
networkConfig, err := p.ParseMetadata(&m, []byte("1.2.3.4"))
networkConfig, err := p.ParseMetadata([]byte("1.2.3.4"), &metadata)
require.NoError(t, err)
marshaled, err := yaml.Marshal(networkConfig)

View File

@ -156,6 +156,7 @@ func NewState() (*State, error) {
&runtime.KernelParamStatus{},
&runtime.MachineStatus{},
&runtime.MountStatus{},
&runtime.PlatformMetadata{},
&secrets.API{},
&secrets.CertSAN{},
&secrets.Etcd{},

View File

@ -383,6 +383,102 @@ func (x *MountStatusSpec) GetOptions() []string {
return nil
}
// PlatformMetadataSpec describes platform metadata properties.
type PlatformMetadataSpec struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"`
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
Region string `protobuf:"bytes,3,opt,name=region,proto3" json:"region,omitempty"`
Zone string `protobuf:"bytes,4,opt,name=zone,proto3" json:"zone,omitempty"`
InstanceType string `protobuf:"bytes,5,opt,name=instance_type,json=instanceType,proto3" json:"instance_type,omitempty"`
InstanceId string `protobuf:"bytes,6,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"`
ProviderId string `protobuf:"bytes,7,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"`
}
func (x *PlatformMetadataSpec) Reset() {
*x = PlatformMetadataSpec{}
if protoimpl.UnsafeEnabled {
mi := &file_resource_definitions_runtime_runtime_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PlatformMetadataSpec) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PlatformMetadataSpec) ProtoMessage() {}
func (x *PlatformMetadataSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_runtime_runtime_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PlatformMetadataSpec.ProtoReflect.Descriptor instead.
func (*PlatformMetadataSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_runtime_runtime_proto_rawDescGZIP(), []int{6}
}
func (x *PlatformMetadataSpec) GetPlatform() string {
if x != nil {
return x.Platform
}
return ""
}
func (x *PlatformMetadataSpec) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
func (x *PlatformMetadataSpec) GetRegion() string {
if x != nil {
return x.Region
}
return ""
}
func (x *PlatformMetadataSpec) GetZone() string {
if x != nil {
return x.Zone
}
return ""
}
func (x *PlatformMetadataSpec) GetInstanceType() string {
if x != nil {
return x.InstanceType
}
return ""
}
func (x *PlatformMetadataSpec) GetInstanceId() string {
if x != nil {
return x.InstanceId
}
return ""
}
func (x *PlatformMetadataSpec) GetProviderId() string {
if x != nil {
return x.ProviderId
}
return ""
}
// UnmetCondition is a failure which prevents machine from being ready at the stage.
type UnmetCondition struct {
state protoimpl.MessageState
@ -396,7 +492,7 @@ type UnmetCondition struct {
func (x *UnmetCondition) Reset() {
*x = UnmetCondition{}
if protoimpl.UnsafeEnabled {
mi := &file_resource_definitions_runtime_runtime_proto_msgTypes[6]
mi := &file_resource_definitions_runtime_runtime_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -409,7 +505,7 @@ func (x *UnmetCondition) String() string {
func (*UnmetCondition) ProtoMessage() {}
func (x *UnmetCondition) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_runtime_runtime_proto_msgTypes[6]
mi := &file_resource_definitions_runtime_runtime_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -422,7 +518,7 @@ func (x *UnmetCondition) ProtoReflect() protoreflect.Message {
// Deprecated: Use UnmetCondition.ProtoReflect.Descriptor instead.
func (*UnmetCondition) Descriptor() ([]byte, []int) {
return file_resource_definitions_runtime_runtime_proto_rawDescGZIP(), []int{6}
return file_resource_definitions_runtime_runtime_proto_rawDescGZIP(), []int{7}
}
func (x *UnmetCondition) GetName() string {
@ -494,17 +590,31 @@ var file_resource_definitions_runtime_runtime_proto_rawDesc = []byte{
0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54,
0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04,
0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3c, 0x0a,
0x0e, 0x55, 0x6e, 0x6d, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42, 0x4f, 0x5a, 0x4d, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2d,
0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2f, 0x70, 0x6b,
0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe1, 0x01,
0x0a, 0x14, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f,
0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f,
0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16,
0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e,
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64,
0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49,
0x64, 0x22, 0x3c, 0x0a, 0x0e, 0x55, 0x6e, 0x6d, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42,
0x4f, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x61,
0x6c, 0x6f, 0x73, 0x2d, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f,
0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, 0x79, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x64, 0x65, 0x66,
0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -519,7 +629,7 @@ func file_resource_definitions_runtime_runtime_proto_rawDescGZIP() []byte {
return file_resource_definitions_runtime_runtime_proto_rawDescData
}
var file_resource_definitions_runtime_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_resource_definitions_runtime_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_resource_definitions_runtime_runtime_proto_goTypes = []interface{}{
(*KernelModuleSpecSpec)(nil), // 0: talos.resource.definitions.runtime.KernelModuleSpecSpec
(*KernelParamSpecSpec)(nil), // 1: talos.resource.definitions.runtime.KernelParamSpecSpec
@ -527,13 +637,14 @@ var file_resource_definitions_runtime_runtime_proto_goTypes = []interface{}{
(*MachineStatusSpec)(nil), // 3: talos.resource.definitions.runtime.MachineStatusSpec
(*MachineStatusStatus)(nil), // 4: talos.resource.definitions.runtime.MachineStatusStatus
(*MountStatusSpec)(nil), // 5: talos.resource.definitions.runtime.MountStatusSpec
(*UnmetCondition)(nil), // 6: talos.resource.definitions.runtime.UnmetCondition
(enums.RuntimeMachineStage)(0), // 7: talos.resource.definitions.enums.RuntimeMachineStage
(*PlatformMetadataSpec)(nil), // 6: talos.resource.definitions.runtime.PlatformMetadataSpec
(*UnmetCondition)(nil), // 7: talos.resource.definitions.runtime.UnmetCondition
(enums.RuntimeMachineStage)(0), // 8: talos.resource.definitions.enums.RuntimeMachineStage
}
var file_resource_definitions_runtime_runtime_proto_depIdxs = []int32{
7, // 0: talos.resource.definitions.runtime.MachineStatusSpec.stage:type_name -> talos.resource.definitions.enums.RuntimeMachineStage
8, // 0: talos.resource.definitions.runtime.MachineStatusSpec.stage:type_name -> talos.resource.definitions.enums.RuntimeMachineStage
4, // 1: talos.resource.definitions.runtime.MachineStatusSpec.status:type_name -> talos.resource.definitions.runtime.MachineStatusStatus
6, // 2: talos.resource.definitions.runtime.MachineStatusStatus.unmet_conditions:type_name -> talos.resource.definitions.runtime.UnmetCondition
7, // 2: talos.resource.definitions.runtime.MachineStatusStatus.unmet_conditions:type_name -> talos.resource.definitions.runtime.UnmetCondition
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
@ -620,6 +731,18 @@ func file_resource_definitions_runtime_runtime_proto_init() {
}
}
file_resource_definitions_runtime_runtime_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PlatformMetadataSpec); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_resource_definitions_runtime_runtime_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UnmetCondition); i {
case 0:
return &v.state
@ -638,7 +761,7 @@ func file_resource_definitions_runtime_runtime_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_resource_definitions_runtime_runtime_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumMessages: 8,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -343,6 +343,88 @@ func (m *MountStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *PlatformMetadataSpec) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *PlatformMetadataSpec) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *PlatformMetadataSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.ProviderId) > 0 {
i -= len(m.ProviderId)
copy(dAtA[i:], m.ProviderId)
i = encodeVarint(dAtA, i, uint64(len(m.ProviderId)))
i--
dAtA[i] = 0x3a
}
if len(m.InstanceId) > 0 {
i -= len(m.InstanceId)
copy(dAtA[i:], m.InstanceId)
i = encodeVarint(dAtA, i, uint64(len(m.InstanceId)))
i--
dAtA[i] = 0x32
}
if len(m.InstanceType) > 0 {
i -= len(m.InstanceType)
copy(dAtA[i:], m.InstanceType)
i = encodeVarint(dAtA, i, uint64(len(m.InstanceType)))
i--
dAtA[i] = 0x2a
}
if len(m.Zone) > 0 {
i -= len(m.Zone)
copy(dAtA[i:], m.Zone)
i = encodeVarint(dAtA, i, uint64(len(m.Zone)))
i--
dAtA[i] = 0x22
}
if len(m.Region) > 0 {
i -= len(m.Region)
copy(dAtA[i:], m.Region)
i = encodeVarint(dAtA, i, uint64(len(m.Region)))
i--
dAtA[i] = 0x1a
}
if len(m.Hostname) > 0 {
i -= len(m.Hostname)
copy(dAtA[i:], m.Hostname)
i = encodeVarint(dAtA, i, uint64(len(m.Hostname)))
i--
dAtA[i] = 0x12
}
if len(m.Platform) > 0 {
i -= len(m.Platform)
copy(dAtA[i:], m.Platform)
i = encodeVarint(dAtA, i, uint64(len(m.Platform)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *UnmetCondition) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
@ -535,6 +617,46 @@ func (m *MountStatusSpec) SizeVT() (n int) {
return n
}
func (m *PlatformMetadataSpec) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Platform)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
l = len(m.Hostname)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
l = len(m.Region)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
l = len(m.Zone)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
l = len(m.InstanceType)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
l = len(m.InstanceId)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
l = len(m.ProviderId)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
}
return n
}
func (m *UnmetCondition) SizeVT() (n int) {
if m == nil {
return 0
@ -1304,6 +1426,281 @@ func (m *MountStatusSpec) UnmarshalVT(dAtA []byte) error {
}
return nil
}
func (m *PlatformMetadataSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: PlatformMetadataSpec: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: PlatformMetadataSpec: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Platform", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Platform = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Hostname", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Hostname = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Region", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Region = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Zone", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Zone = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field InstanceType", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.InstanceType = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field InstanceId", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.InstanceId = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ProviderId", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ProviderId = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *UnmetCondition) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0

View File

@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Code generated by "deep-copy -type KernelModuleSpecSpec -type KernelParamSpecSpec -type KernelParamStatusSpec -type MachineStatusSpec -type MountStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
// Code generated by "deep-copy -type KernelModuleSpecSpec -type KernelParamSpecSpec -type KernelParamStatusSpec -type MachineStatusSpec -type MountStatusSpec -type PlatformMetadataSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
package runtime
@ -47,3 +47,9 @@ func (o MountStatusSpec) DeepCopy() MountStatusSpec {
}
return cp
}
// DeepCopy generates a deep copy of PlatformMetadataSpec.
func (o PlatformMetadataSpec) DeepCopy() PlatformMetadataSpec {
var cp PlatformMetadataSpec = o
return cp
}

View File

@ -0,0 +1,65 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package runtime
import (
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/cosi-project/runtime/pkg/resource/protobuf"
"github.com/cosi-project/runtime/pkg/resource/typed"
"github.com/talos-systems/talos/pkg/machinery/proto"
)
// PlatformMetadataType is type of Metadata resource.
const PlatformMetadataType = resource.Type("PlatformMetadatas.talos.dev")
// PlatformMetadataID is the ID for Metadata resource platform.
const PlatformMetadataID resource.ID = "platformmetadata"
// PlatformMetadata resource holds.
type PlatformMetadata = typed.Resource[PlatformMetadataSpec, PlatformMetadataRD]
// PlatformMetadataSpec describes platform metadata properties.
//
//gotagsrewrite:gen
type PlatformMetadataSpec struct {
Platform string `yaml:"platform,omitempty" protobuf:"1"`
Hostname string `yaml:"hostname,omitempty" protobuf:"2"`
Region string `yaml:"region,omitempty" protobuf:"3"`
Zone string `yaml:"zone,omitempty" protobuf:"4"`
InstanceType string `yaml:"instanceType,omitempty" protobuf:"5"`
InstanceID string `yaml:"instanceId,omitempty" protobuf:"6"`
ProviderID string `yaml:"providerId,omitempty" protobuf:"7"`
}
// NewPlatformMetadataSpec initializes a MetadataSpec resource.
func NewPlatformMetadataSpec(namespace resource.Namespace, id resource.ID) *PlatformMetadata {
return typed.NewResource[PlatformMetadataSpec, PlatformMetadataRD](
resource.NewMetadata(namespace, PlatformMetadataType, PlatformMetadataID, resource.VersionUndefined),
PlatformMetadataSpec{},
)
}
// PlatformMetadataRD provides auxiliary methods for PlatformMetadata.
type PlatformMetadataRD struct{}
// ResourceDefinition implements typed.ResourceDefinition interface.
func (PlatformMetadataRD) ResourceDefinition(resource.Metadata, PlatformMetadataSpec) meta.ResourceDefinitionSpec {
return meta.ResourceDefinitionSpec{
Type: PlatformMetadataType,
DefaultNamespace: NamespaceName,
PrintColumns: []meta.PrintColumn{},
}
}
func init() {
proto.RegisterDefaultTypes()
err := protobuf.RegisterDynamic[PlatformMetadataSpec](PlatformMetadataType, &PlatformMetadata{})
if err != nil {
panic(err)
}
}

View File

@ -5,4 +5,4 @@
package runtime
//nolint:lll
//go:generate deep-copy -type KernelModuleSpecSpec -type KernelParamSpecSpec -type KernelParamStatusSpec -type MachineStatusSpec -type MountStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
//go:generate deep-copy -type KernelModuleSpecSpec -type KernelParamSpecSpec -type KernelParamStatusSpec -type MachineStatusSpec -type MountStatusSpec -type PlatformMetadataSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .

View File

@ -31,6 +31,7 @@ func TestRegisterResource(t *testing.T) {
&runtime.KernelParamStatus{},
&runtime.MachineStatus{},
&runtime.MountStatus{},
&runtime.PlatformMetadata{},
} {
assert.NoError(t, resourceRegistry.Register(ctx, resource))
}

View File

@ -64,7 +64,10 @@ func (p *provisioner) createNodes(ctx context.Context, clusterReq provision.Clus
//nolint:gocyclo
func (p *provisioner) createNode(ctx context.Context, clusterReq provision.ClusterRequest, nodeReq provision.NodeRequest, options *provision.Options) (provision.NodeInfo, error) {
env := []string{"PLATFORM=container"}
env := []string{
"PLATFORM=container",
fmt.Sprintf("TALOSSKU=%dCPU-%dRAM", nodeReq.NanoCPUs/(1000*1000*1000), nodeReq.Memory/(1024*1024)),
}
if !nodeReq.SkipInjectingConfig {
cfg, err := nodeReq.Config.EncodeString()

View File

@ -173,6 +173,7 @@ description: Talos gRPC API reference.
- [MachineStatusSpec](#talos.resource.definitions.runtime.MachineStatusSpec)
- [MachineStatusStatus](#talos.resource.definitions.runtime.MachineStatusStatus)
- [MountStatusSpec](#talos.resource.definitions.runtime.MountStatusSpec)
- [PlatformMetadataSpec](#talos.resource.definitions.runtime.PlatformMetadataSpec)
- [UnmetCondition](#talos.resource.definitions.runtime.UnmetCondition)
- [resource/definitions/secrets/secrets.proto](#resource/definitions/secrets/secrets.proto)
@ -3133,6 +3134,27 @@ MountStatusSpec describes status of the defined sysctls.
<a name="talos.resource.definitions.runtime.PlatformMetadataSpec"></a>
### PlatformMetadataSpec
PlatformMetadataSpec describes platform metadata properties.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| platform | [string](#string) | | |
| hostname | [string](#string) | | |
| region | [string](#string) | | |
| zone | [string](#string) | | |
| instance_type | [string](#string) | | |
| instance_id | [string](#string) | | |
| provider_id | [string](#string) | | |
<a name="talos.resource.definitions.runtime.UnmetCondition"></a>
### UnmetCondition