mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
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:
parent
7e50e24c01
commit
8bfa7ac1d6
@ -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;
|
||||
|
||||
@ -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
|
||||
}
|
||||
},
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
19
internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected.yaml
vendored
Normal file
19
internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/expected.yaml
vendored
Normal 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
|
||||
7
internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata.json
vendored
Normal file
7
internal/app/machined/pkg/runtime/v1alpha1/platform/aws/testdata/metadata.json
vendored
Normal 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"
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
14
internal/app/machined/pkg/runtime/v1alpha1/platform/azure/testdata/compute.json
vendored
Normal file
14
internal/app/machined/pkg/runtime/v1alpha1/platform/azure/testdata/compute.json
vendored
Normal 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"
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
96
internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/testdata/expected.yaml
vendored
Normal file
96
internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/testdata/expected.yaml
vendored
Normal 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
|
||||
70
internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/testdata/metadata.json
vendored
Normal file
70
internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/testdata/metadata.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
25
internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/testdata/expected.yaml
vendored
Normal file
25
internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/testdata/expected.yaml
vendored
Normal 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
|
||||
8
internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/testdata/metadata.json
vendored
Normal file
8
internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/testdata/metadata.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"project-id": "123",
|
||||
"hostname": "talos",
|
||||
"id": "0",
|
||||
"zone": "us-central1-a",
|
||||
"name": "my-server",
|
||||
"machine-type": "n1-standard-1"
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
184
internal/app/machined/pkg/runtime/v1alpha1/platform/metal/url.go
Normal file
184
internal/app/machined/pkg/runtime/v1alpha1/platform/metal/url.go
Normal 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)
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
match:
|
||||
macaddress: '00:20:6e:1f:f9:a8'
|
||||
dhcp4: true
|
||||
addresses:
|
||||
- 192.168.14.2/24
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
12
internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/testdata/metadatanetwork.json
vendored
Normal file
12
internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/testdata/metadatanetwork.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -58,4 +58,4 @@
|
||||
"regioncode": "AMS"
|
||||
},
|
||||
"user-defined": []
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -156,6 +156,7 @@ func NewState() (*State, error) {
|
||||
&runtime.KernelParamStatus{},
|
||||
&runtime.MachineStatus{},
|
||||
&runtime.MountStatus{},
|
||||
&runtime.PlatformMetadata{},
|
||||
&secrets.API{},
|
||||
&secrets.CertSAN{},
|
||||
&secrets.Etcd{},
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
65
pkg/machinery/resources/runtime/platform_metadata.go
Normal file
65
pkg/machinery/resources/runtime/platform_metadata.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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 .
|
||||
|
||||
@ -31,6 +31,7 @@ func TestRegisterResource(t *testing.T) {
|
||||
&runtime.KernelParamStatus{},
|
||||
&runtime.MachineStatus{},
|
||||
&runtime.MountStatus{},
|
||||
&runtime.PlatformMetadata{},
|
||||
} {
|
||||
assert.NoError(t, resourceRegistry.Register(ctx, resource))
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user