feat: add system information for transformer

Add SystemInformation resource values to transformer templater.

Signed-off-by: Serge Logvinov <serge.logvinov@sinextra.dev>
This commit is contained in:
Serge Logvinov
2025-02-13 23:02:22 +02:00
parent 67f83c6533
commit 5a31bb2874
8 changed files with 133 additions and 12 deletions

View File

@@ -114,6 +114,10 @@ transformations:
platformMetadata:
Region: "{{ .Region }}-on-metal"
Zone: "us-west-1f"
# SKUNumber is a system information variable "t2.micro"
InstanceType: "{{ .SKUNumber }}"
# UUID is a system information variable "e8e8c388-5812-4db0-87e2-ad1fee51a1c1"
ProviderID: "someproviderID:///{{ .UUID }}"
# Features flags for nodes that match the transformation
features:
@@ -182,7 +186,32 @@ type PlatformMetadataSpec struct {
You can use the following command to get the platform metadata:
```bash
talosctl get PlatformMetadatas.talos.dev -oyaml
talosctl get PlatformMetadatas -oyaml
```
### System information variables
Additionally you can use the system information variables in the transformations rules.
Go struct for system information,
original code: [system_information.go](https://github.com/siderolabs/talos/blob/main/pkg/machinery/resources/hardware/system_information.go)
```go
type SystemInformationSpec struct {
Manufacturer string `yaml:"manufacturer,omitempty" protobuf:"1"`
ProductName string `yaml:"productName,omitempty" protobuf:"2"`
Version string `yaml:"version,omitempty" protobuf:"3"`
SerialNumber string `yaml:"serialnumber,omitempty" protobuf:"4"`
UUID string `yaml:"uuid,omitempty" protobuf:"5"`
WakeUpType string `yaml:"wakeUpType,omitempty" protobuf:"6"`
SKUNumber string `yaml:"skuNumber,omitempty" protobuf:"7"`
}
```
You can use the following command to get the system information:
```bash
talosctl get SystemInformation -oyaml
```
### Transformations functions
@@ -277,3 +306,11 @@ You can use the following functions in the Go template:
```yaml
{{ b64dec "aGVsbG8=" }} -> hello
```
#### String slice functions
* `getValue` - the function to get the value from the map by key.
```yaml
{{ getValue "ds=nocloud;i=1234" "i" }} -> 1234
```

View File

@@ -16,3 +16,14 @@ transformations:
node-role.kubernetes.io/web: ""
taints:
node.cloudprovider.kubernetes.io/storage-type: "NoSchedule"
- name: nocloud
nodeSelector:
- matchExpressions:
- key: platform
operator: In
values:
- nocloud
platformMetadata:
InstanceType: "{{ .SKUNumber }}"
ProviderID: proxmox://region-1/{{ getValue .SerialNumber "i" }}

View File

@@ -357,7 +357,7 @@ func TestSyncNodeLabels(t *testing.T) {
},
} {
t.Run(tt.name, func(t *testing.T) {
nodeSpec, err := transformer.TransformNode(client.config.Transformations, tt.meta)
nodeSpec, err := transformer.TransformNode(client.config.Transformations, tt.meta, nil)
assert.NoError(t, err)
labels := setTalosNodeLabels(client, tt.meta)

View File

@@ -11,6 +11,7 @@ import (
"github.com/siderolabs/talos-cloud-controller-manager/pkg/transformer"
"github.com/siderolabs/talos-cloud-controller-manager/pkg/utils/net"
"github.com/siderolabs/talos-cloud-controller-manager/pkg/utils/platform"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
v1 "k8s.io/api/core/v1"
@@ -138,9 +139,20 @@ func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud
}
}
mct := metrics.NewMetricContext("metadata")
var sysInfo *hardware.SystemInformationSpec
nodeSpec, err := transformer.TransformNode(i.c.config.Transformations, meta)
if len(i.c.config.Transformations) > 0 {
msys := metrics.NewMetricContext(hardware.SystemInformationID)
sysInfo, err = i.c.talos.GetNodeSystemInfo(ctx, nodeIP)
if msys.ObserveRequest(err) != nil {
return nil, fmt.Errorf("error getting system info from the node %s: %w", node.Name, err)
}
}
mct := metrics.NewMetricContext("transformer")
nodeSpec, err := transformer.TransformNode(i.c.config.Transformations, meta, sysInfo)
if mct.ObserveTransformer(err) != nil {
return nil, fmt.Errorf("error transforming node: %w", err)
}

View File

@@ -32,6 +32,7 @@ import (
talos "github.com/siderolabs/talos/pkg/machinery/client"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
"github.com/siderolabs/talos/pkg/machinery/resources/k8s"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
@@ -133,6 +134,8 @@ func (c *Client) GetNodeIfaces(ctx context.Context, nodeIP string) ([]network.Ad
}
// GetNodeMetadata returns the metadata of the node.
//
//nolint:dupl
func (c *Client) GetNodeMetadata(ctx context.Context, nodeIP string) (*runtime.PlatformMetadataSpec, error) {
nodeCtx := talos.WithNode(ctx, nodeIP)
@@ -162,6 +165,38 @@ func (c *Client) GetNodeMetadata(ctx context.Context, nodeIP string) (*runtime.P
return &meta, nil
}
// GetNodeSystemInfo returns the system information of the node.
//
//nolint:dupl
func (c *Client) GetNodeSystemInfo(ctx context.Context, nodeIP string) (*hardware.SystemInformationSpec, error) {
nodeCtx := talos.WithNode(ctx, nodeIP)
var resources resource.Resource
err := retry.Constant(10*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
var getErr error
resources, getErr = c.talos.COSI.Get(nodeCtx, resource.NewMetadata(hardware.NamespaceName, hardware.SystemInformationType, hardware.SystemInformationID, resource.VersionUndefined))
if getErr != nil {
err := c.refreshTalosClient(ctx) //nolint:errcheck
if err != nil {
return retry.ExpectedError(err)
}
return getErr
}
return nil
})
if err != nil {
return nil, fmt.Errorf("error get resources: %w", err)
}
meta := resources.Spec().(*hardware.SystemInformationSpec).DeepCopy() //nolint:errcheck
return &meta, nil
}
// GetClusterName returns cluster name.
func (c *Client) GetClusterName() string {
return c.talos.GetClusterName()

View File

@@ -26,6 +26,9 @@ var genericMap = map[string]interface{}{
// Encoding functions:
"b64enc": base64encode,
"b64dec": base64decode,
// String slice functions:
"getValue": getValue,
}
// GenericFuncMap returns a copy of the basic function map as a map[string]interface{}.
@@ -83,3 +86,15 @@ func base64decode(v string) (string, error) {
return string(data), nil
}
func getValue(source string, key string) string {
parts := strings.Split(source, ";")
for _, part := range parts {
kv := strings.Split(part, "=")
if kv[0] == key {
return kv[1]
}
}
return ""
}

View File

@@ -10,6 +10,7 @@ import (
"strings"
"github.com/siderolabs/talos-cloud-controller-manager/pkg/nodeselector"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
v1 "k8s.io/api/core/v1"
@@ -36,6 +37,11 @@ type NodeSpec struct {
Features NodeFeaturesFlagSpec
}
type nodeTransformationValues struct {
runtime.PlatformMetadataSpec
hardware.SystemInformationSpec
}
// NodeFeaturesFlagSpec represents the node features flags.
type NodeFeaturesFlagSpec struct {
// PublicIPDiscovery try to find public IP on the node
@@ -47,7 +53,7 @@ var prohibitedPlatformMetadataKeys = []string{"hostname", "platform"}
// TransformNode transforms the node metadata based on the node transformation rules.
//
//nolint:gocyclo,cyclop
func TransformNode(terms []NodeTerm, platformMetadata *runtime.PlatformMetadataSpec) (*NodeSpec, error) {
func TransformNode(terms []NodeTerm, platformMetadata *runtime.PlatformMetadataSpec, sysinfo *hardware.SystemInformationSpec) (*NodeSpec, error) {
node := &NodeSpec{
Annotations: make(map[string]string),
Labels: make(map[string]string),
@@ -58,7 +64,12 @@ func TransformNode(terms []NodeTerm, platformMetadata *runtime.PlatformMetadataS
return node, nil
}
metadata := metadataFromStruct(platformMetadata)
values := nodeTransformationValues{PlatformMetadataSpec: *platformMetadata}
if sysinfo != nil {
values.SystemInformationSpec = *sysinfo
}
metadata := mapFromStruct(platformMetadata)
for _, term := range terms {
match, err := nodeselector.Match(term.NodeSelector, metadata)
@@ -69,7 +80,7 @@ func TransformNode(terms []NodeTerm, platformMetadata *runtime.PlatformMetadataS
if match {
if term.Annotations != nil {
for k, v := range term.Annotations {
t, err := executeTemplate(v, platformMetadata)
t, err := executeTemplate(v, values)
if err != nil {
return nil, fmt.Errorf("failed to transformer annotation %q: %w", k, err)
}
@@ -84,7 +95,7 @@ func TransformNode(terms []NodeTerm, platformMetadata *runtime.PlatformMetadataS
if term.Labels != nil {
for k, v := range term.Labels {
t, err := executeTemplate(v, platformMetadata)
t, err := executeTemplate(v, values)
if err != nil {
return nil, fmt.Errorf("failed to transformer label %q: %w", k, err)
}
@@ -103,7 +114,7 @@ func TransformNode(terms []NodeTerm, platformMetadata *runtime.PlatformMetadataS
if term.Taints != nil {
for k, v := range term.Taints {
t, err := executeTemplate(v, platformMetadata)
t, err := executeTemplate(v, values)
if err != nil {
return nil, fmt.Errorf("failed to transformer label %q: %w", k, err)
}
@@ -129,7 +140,7 @@ func TransformNode(terms []NodeTerm, platformMetadata *runtime.PlatformMetadataS
continue
}
t, err := executeTemplate(v, platformMetadata)
t, err := executeTemplate(v, values)
if err != nil {
return nil, fmt.Errorf("failed to transformer platform metadata %q: %w", k, err)
}
@@ -170,7 +181,7 @@ func executeTemplate(tmpl string, data interface{}) (string, error) {
return buf.String(), nil
}
func metadataFromStruct(in *runtime.PlatformMetadataSpec) map[string]string {
func mapFromStruct(in interface{}) map[string]string {
if in == nil {
return nil
}

View File

@@ -317,7 +317,7 @@ func TestMatch(t *testing.T) {
},
} {
t.Run(tt.name, func(t *testing.T) {
node, err := transformer.TransformNode(tt.terms, &tt.metadata)
node, err := transformer.TransformNode(tt.terms, &tt.metadata, nil)
if tt.expectedError != nil {
assert.NotNil(t, err)