diff --git a/go.mod b/go.mod index f476fb8e6..4bf2c0cf8 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/containernetworking/plugins v1.1.1 github.com/coreos/go-iptables v0.6.0 github.com/coreos/go-semver v0.3.0 - github.com/cosi-project/runtime v0.0.0-20220609185841-c0aa3e101537 + github.com/cosi-project/runtime v0.0.0-20220622192602-9483ac9a4832 github.com/docker/distribution v2.8.1+incompatible github.com/docker/docker v20.10.17+incompatible github.com/docker/go-connections v0.4.0 diff --git a/go.sum b/go.sum index c474ddf52..0354110eb 100644 --- a/go.sum +++ b/go.sum @@ -339,8 +339,8 @@ github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzA github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosi-project/runtime v0.0.0-20220609185841-c0aa3e101537 h1:0ejVbCuG1P97SQS5A4l/gHuQ76wG3aWYATf3EMN+Grs= -github.com/cosi-project/runtime v0.0.0-20220609185841-c0aa3e101537/go.mod h1:4WQcuQDWr7cIT4mUyqf95PkoBwgAgaa2yhMmGwndYCs= +github.com/cosi-project/runtime v0.0.0-20220622192602-9483ac9a4832 h1:ixPX9angaYh/nmaG83TPwine+QXfYqACDvnYhjqB9Ew= +github.com/cosi-project/runtime v0.0.0-20220622192602-9483ac9a4832/go.mod h1:4WQcuQDWr7cIT4mUyqf95PkoBwgAgaa2yhMmGwndYCs= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= diff --git a/go.work.sum b/go.work.sum index e0fd1c7fa..b01170c59 100644 --- a/go.work.sum +++ b/go.work.sum @@ -6,11 +6,13 @@ github.com/containernetworking/cni v1.1.0/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8d github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/talos-systems/siderolink v0.1.1/go.mod h1:1PLRyKRx+MAkz1vWJXIP19p5wChF0TejbIbX/CQMWuw= github.com/vishvananda/netlink v1.2.0-beta h1:CTNzkunO9iTkRaupF540+w47mexyQgNkA/ibnuKc39w= github.com/vishvananda/netlink v1.2.0-beta/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo= go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/hack/release.toml b/hack/release.toml index 4566a821e..a8b625a05 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -42,6 +42,15 @@ See [documentation](https://www.talos.dev/v1.1/reference/configuration/#bridge) * Linux: 5.15.49 """ + [notes.talos-config-kernel-param-variable-substitution] + title = "Variable substitution for URL query parameter in the talos.config kernel parameter" + description="""\ + The kernel parameter talos.config can now substitute system information into placeholders inside its URL query values. This example shows all supported variables: + +```http://example.com/metadata?h=${hostname}&m=${mac}&s=${serial}&u=${uuid}``` +""" + + [make_deps] [make_deps.tools] diff --git a/internal/app/machined/pkg/adapters/hardware/system_information.go b/internal/app/machined/pkg/adapters/hardware/system_information.go new file mode 100644 index 000000000..2f858f214 --- /dev/null +++ b/internal/app/machined/pkg/adapters/hardware/system_information.go @@ -0,0 +1,41 @@ +// 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 hardware + +import ( + "github.com/talos-systems/go-smbios/smbios" + + "github.com/talos-systems/talos/pkg/machinery/resources/hardware" +) + +// SystemInformation adapter provider conversion from smbios.SMBIOS. +// +//nolint:revive,golint +func SystemInformation(p *hardware.SystemInformation) systemInformation { + return systemInformation{ + SystemInformation: p, + } +} + +type systemInformation struct { + *hardware.SystemInformation +} + +// Update current systemInformation info. +func (p systemInformation) Update(systemInformation *smbios.SystemInformation) { + translateSystemInformationInfo := func(in *smbios.SystemInformation) hardware.SystemInformationSpec { + return hardware.SystemInformationSpec{ + Manufacturer: in.Manufacturer, + ProductName: in.ProductName, + Version: in.Version, + SerialNumber: in.SerialNumber, + UUID: in.UUID, + WakeUpType: in.WakeUpType.String(), + SKUNumber: in.SKUNumber, + } + } + + *p.SystemInformation.TypedSpec() = translateSystemInformationInfo(systemInformation) +} diff --git a/internal/app/machined/pkg/controllers/hardware/system.go b/internal/app/machined/pkg/controllers/hardware/system.go index 120f4cd69..54e7f84c1 100644 --- a/internal/app/machined/pkg/controllers/hardware/system.go +++ b/internal/app/machined/pkg/controllers/hardware/system.go @@ -15,6 +15,7 @@ import ( "go.uber.org/zap" hwadapter "github.com/talos-systems/talos/internal/app/machined/pkg/adapters/hardware" + pkgSMBIOS "github.com/talos-systems/talos/internal/pkg/smbios" "github.com/talos-systems/talos/pkg/machinery/resources/hardware" ) @@ -44,6 +45,10 @@ func (ctrl *SystemInfoController) Outputs() []controller.Output { Type: hardware.MemoryModuleType, Kind: controller.OutputExclusive, }, + { + Type: hardware.SystemInformationType, + Kind: controller.OutputExclusive, + }, } } @@ -59,7 +64,7 @@ func (ctrl *SystemInfoController) Run(ctx context.Context, r controller.Runtime, // controller runs only once if ctrl.SMBIOS == nil { - s, err := GetSMBIOSInfo() + s, err := pkgSMBIOS.GetSMBIOSInfo() if err != nil { return err } @@ -67,6 +72,15 @@ func (ctrl *SystemInfoController) Run(ctx context.Context, r controller.Runtime, ctrl.SMBIOS = s } + const systemInfoID = "systeminformation" + if err := r.Modify(ctx, hardware.NewSystemInformation(systemInfoID), func(res resource.Resource) error { + hwadapter.SystemInformation(res.(*hardware.SystemInformation)).Update(&ctrl.SMBIOS.SystemInformation) + + return nil + }); err != nil { + return fmt.Errorf("error updating objects: %w", err) + } + for _, p := range ctrl.SMBIOS.ProcessorInformation { // replaces `CPU 0` with `CPU-0` id := strings.ReplaceAll(p.SocketDesignation, " ", "-") diff --git a/internal/app/machined/pkg/controllers/network/platform_config_test.go b/internal/app/machined/pkg/controllers/network/platform_config_test.go index bab755f41..4c55ee6f5 100644 --- a/internal/app/machined/pkg/controllers/network/platform_config_test.go +++ b/internal/app/machined/pkg/controllers/network/platform_config_test.go @@ -616,7 +616,7 @@ func (mock *platformMock) Name() string { return "mock" } -func (mock *platformMock) Configuration(context.Context) ([]byte, error) { +func (mock *platformMock) Configuration(context.Context, state.State) ([]byte, error) { return nil, nil } diff --git a/internal/app/machined/pkg/controllers/siderolink/manager.go b/internal/app/machined/pkg/controllers/siderolink/manager.go index d8f1d407d..81033c85f 100644 --- a/internal/app/machined/pkg/controllers/siderolink/manager.go +++ b/internal/app/machined/pkg/controllers/siderolink/manager.go @@ -17,7 +17,6 @@ import ( "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/go-pointer" "github.com/talos-systems/go-procfs/procfs" - "github.com/talos-systems/go-smbios/smbios" pb "github.com/talos-systems/siderolink/api/siderolink" "go.uber.org/zap" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -26,6 +25,7 @@ import ( "google.golang.org/grpc/credentials/insecure" "inet.af/netaddr" + "github.com/talos-systems/talos/internal/pkg/smbios" "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" @@ -116,7 +116,7 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo return nil } - s, err := smbios.New() + s, err := smbios.GetSMBIOSInfo() if err != nil { return fmt.Errorf("error reading node UUID: %w", err) } diff --git a/internal/app/machined/pkg/runtime/platform.go b/internal/app/machined/pkg/runtime/platform.go index e159a1471..7c156aac5 100644 --- a/internal/app/machined/pkg/runtime/platform.go +++ b/internal/app/machined/pkg/runtime/platform.go @@ -7,6 +7,7 @@ package runtime import ( "context" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "inet.af/netaddr" @@ -25,7 +26,7 @@ type Platform interface { // // On cloud-like platform it is user-data in metadata service. // For metal platform that is either `talos.config=` URL or mounted ISO image. - Configuration(context.Context) ([]byte, error) + Configuration(context.Context, state.State) ([]byte, error) // KernelArgs returns additional kernel arguments which should be injected for the kernel boot. KernelArgs() procfs.Parameters diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go index b752e5fbc..c115f5169 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/aws/aws.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "inet.af/netaddr" @@ -51,7 +52,7 @@ func (a *AWS) Name() string { } // Configuration implements the runtime.Platform interface. -func (a *AWS) Configuration(ctx context.Context) ([]byte, error) { +func (a *AWS) Configuration(ctx context.Context, r state.State) ([]byte, error) { log.Printf("fetching machine config from AWS") userdata, err := a.metadataClient.GetUserDataWithContext(ctx) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go index 6770e4513..90a170285 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go @@ -18,6 +18,7 @@ import ( "regexp" "strings" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "golang.org/x/sys/unix" "inet.af/netaddr" @@ -159,7 +160,7 @@ func (a *Azure) ParseLoadBalancerIP(lbConfig LoadBalancerMetadata, exIP []netadd } // Configuration implements the platform.Platform interface. -func (a *Azure) Configuration(ctx context.Context) ([]byte, error) { +func (a *Azure) Configuration(ctx context.Context, r state.State) ([]byte, error) { defer func() { if err := linuxAgent(ctx); err != nil { log.Printf("failed to update instance status, err: %s", err.Error()) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go index b97db4bf9..2ee692176 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go @@ -12,6 +12,7 @@ import ( "log" "os" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" @@ -28,7 +29,7 @@ func (c *Container) Name() string { } // Configuration implements the platform.Platform interface. -func (c *Container) Configuration(context.Context) ([]byte, error) { +func (c *Container) Configuration(context.Context, state.State) ([]byte, error) { log.Printf("fetching machine config from: USERDATA environment variable") s := os.Getenv("USERDATA") diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/digitalocean.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/digitalocean.go index 97b0aa15b..33fbf35b8 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/digitalocean.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean/digitalocean.go @@ -9,6 +9,7 @@ import ( stderrors "errors" "log" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "inet.af/netaddr" @@ -36,7 +37,7 @@ func (d *DigitalOcean) Name() string { } // Configuration implements the platform.Platform interface. -func (d *DigitalOcean) Configuration(ctx context.Context) ([]byte, error) { +func (d *DigitalOcean) Configuration(ctx context.Context, r state.State) ([]byte, error) { log.Printf("fetching machine config from: %q", DigitalOceanUserDataEndpoint) return download.Download(ctx, DigitalOceanUserDataEndpoint, diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go index 8b2853a28..da0342dfe 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go @@ -14,6 +14,7 @@ import ( "net/http" "time" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "github.com/talos-systems/go-retry/retry" "inet.af/netaddr" @@ -89,7 +90,7 @@ func (p *EquinixMetal) Name() string { } // Configuration implements the platform.Platform interface. -func (p *EquinixMetal) Configuration(ctx context.Context) ([]byte, error) { +func (p *EquinixMetal) Configuration(ctx context.Context, r state.State) ([]byte, error) { log.Printf("fetching machine config from: %q", EquinixMetalUserDataEndpoint) return download.Download(ctx, EquinixMetalUserDataEndpoint, diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go index 091354e7a..199277aed 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go @@ -9,6 +9,7 @@ import ( "strings" "cloud.google.com/go/compute/metadata" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "inet.af/netaddr" @@ -26,7 +27,7 @@ func (g *GCP) Name() string { } // Configuration implements the platform.Platform interface. -func (g *GCP) Configuration(ctx context.Context) ([]byte, error) { +func (g *GCP) Configuration(ctx context.Context, r state.State) ([]byte, error) { userdata, err := metadata.InstanceAttributeValue("user-data") if err != nil { if _, ok := err.(metadata.NotDefinedError); ok { diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go index 3fb4926c5..407323554 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go @@ -10,6 +10,7 @@ import ( "fmt" "log" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "gopkg.in/yaml.v3" "inet.af/netaddr" @@ -158,7 +159,7 @@ func (h *Hcloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, host, e } // Configuration implements the runtime.Platform interface. -func (h *Hcloud) Configuration(ctx context.Context) ([]byte, error) { +func (h *Hcloud) Configuration(ctx context.Context, r state.State) ([]byte, error) { log.Printf("fetching machine config from: %q", HCloudUserDataEndpoint) return download.Download(ctx, HCloudUserDataEndpoint, diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go index d9db6e351..205b44d29 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go @@ -11,18 +11,24 @@ import ( "log" "net/url" "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/talos-systems/go-blockdevice/blockdevice/filesystem" "github.com/talos-systems/go-blockdevice/blockdevice/probe" "github.com/talos-systems/go-procfs/procfs" - "github.com/talos-systems/go-smbios/smbios" "golang.org/x/sys/unix" "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/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" ) const ( @@ -38,7 +44,7 @@ func (m *Metal) Name() string { } // Configuration implements the platform.Platform interface. -func (m *Metal) Configuration(ctx context.Context) ([]byte, error) { +func (m *Metal) Configuration(ctx context.Context, r state.State) ([]byte, error) { var option *string if option = procfs.ProcCmdline().Get(constants.KernelParamConfig).First(); option == nil { return nil, errors.ErrNoConfigSource @@ -48,13 +54,15 @@ func (m *Metal) Configuration(ctx context.Context) ([]byte, error) { return nil, errors.ErrNoConfigSource } - log.Printf("fetching machine config from: %q", *option) - - downloadURL, err := PopulateURLParameters(*option, getSystemUUID) + downloadURL, err := PopulateURLParameters(ctx, *option, r) if err != nil { + log.Fatalf("failed to populate talos.config fetch URL: %q ; %s", *option, err.Error()) + return nil, err } + log.Printf("fetching machine config from: %q", downloadURL) + switch downloadURL { case constants.MetalConfigISOLabel: return readConfigFromISO() @@ -63,30 +71,129 @@ func (m *Metal) Configuration(ctx context.Context) ([]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. -func PopulateURLParameters(downloadURL string, getSystemUUID func() (string, error)) (string, error) { - u, err := url.Parse(downloadURL) +//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", constants.KernelParamConfig, err) + return "", fmt.Errorf("failed to parse %s: %w", populatedURL, err) } values := u.Query() - for key, qValues := range values { - switch key { - case "uuid": - // don't touch uuid field if it already has some value - if !(len(qValues) == 1 && len(strings.TrimSpace(qValues[0])) > 0) { - uid, err := getSystemUUID() + 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 "", err + return fmt.Errorf("error while substituting UUID: %w", err) } - values.Set("uuid", uid) + values.Set(constants.UUIDKey, uid) + + continue } - default: - log.Printf("unsupported query parameter: %q", key) + + 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() @@ -94,13 +201,88 @@ func PopulateURLParameters(downloadURL string, getSystemUUID func() (string, err return u.String(), nil } -func getSystemUUID() (string, error) { - s, err := smbios.New() +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 "", err + return "", fmt.Errorf("failed to watch %s resources: %w", typ, err) } - return s.SystemInformation.UUID, nil + 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. @@ -108,10 +290,8 @@ func (m *Metal) Mode() runtime.Mode { return runtime.ModeMetal } -func readConfigFromISO() (b []byte, err error) { - var dev *probe.ProbedBlockDevice - - dev, err = probe.GetDevWithFileSystemLabel(constants.MetalConfigISOLabel) +func readConfigFromISO() ([]byte, error) { + dev, err := probe.GetDevWithFileSystemLabel(constants.MetalConfigISOLabel) if err != nil { return nil, fmt.Errorf("failed to find %s iso: %w", constants.MetalConfigISOLabel, err) } @@ -125,14 +305,14 @@ func readConfigFromISO() (b []byte, err error) { } if sb == nil { - return nil, fmt.Errorf("failed to get filesystem type") + return nil, fmt.Errorf("error while substituting filesystem type") } if err = unix.Mount(dev.Device().Name(), mnt, sb.Type(), unix.MS_RDONLY, ""); err != nil { return nil, fmt.Errorf("failed to mount iso: %w", err) } - b, err = ioutil.ReadFile(filepath.Join(mnt, filepath.Base(constants.ConfigPath))) + b, err := ioutil.ReadFile(filepath.Join(mnt, filepath.Base(constants.ConfigPath))) if err != nil { return nil, fmt.Errorf("read config: %s", err.Error()) } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal_test.go index 3c070ca2d..7dd642405 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal_test.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal_test.go @@ -5,17 +5,31 @@ package metal_test import ( + "context" "fmt" + "net" + "net/url" "testing" - "github.com/google/uuid" + "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/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal" + "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 TestPopulateURLParameters(t *testing.T) { - mockUUID := uuid.New().String() + mockUUID := "40dcbd19-3b10-444e-bfff-aaee44a51fda" + + mockMAC := "52:2f:fd:df:fc:c0" + + mockSerialNumber := "0OCZJ19N65" + + mockHostname := "myTestHostname" for _, tt := range []struct { name string @@ -38,6 +52,16 @@ func TestPopulateURLParameters(t *testing.T) { 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", @@ -48,18 +72,86 @@ func TestPopulateURLParameters(t *testing.T) { 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) { - output, err := metal.PopulateURLParameters(tt.url, func() (string, error) { - return mockUUID, nil - }) + ctx := context.Background() + + st := state.WrapCore(namespaced.NewState(inmem.Build)) + + testID := "testID" + sysInfo := hardware.NewSystemInformation(testID) + sysInfo.TypedSpec().UUID = mockUUID + sysInfo.TypedSpec().SerialNumber = mockSerialNumber + assert.NoError(t, st.Create(ctx, sysInfo)) + + hostnameSpec := network.NewHostnameSpec(network.NamespaceName, testID) + hostnameSpec.TypedSpec().Hostname = mockHostname + assert.NoError(t, st.Create(ctx, 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, st.Create(ctx, linkStatusSpec)) + + output, err := metal.PopulateURLParameters(ctx, tt.url, st) if tt.expectedError != "" { assert.EqualError(t, err, tt.expectedError) } else { - assert.Equal(t, output, tt.expectedURL) + u, err := url.Parse(tt.expectedURL) + assert.NoError(t, err) + u.RawQuery = u.Query().Encode() + assert.Equal(t, u.String(), output) } }) } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/metadata.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/metadata.go index b83c40958..45f55c98b 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/metadata.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/metadata.go @@ -16,12 +16,12 @@ import ( "github.com/talos-systems/go-blockdevice/blockdevice/filesystem" "github.com/talos-systems/go-blockdevice/blockdevice/probe" - "github.com/talos-systems/go-smbios/smbios" "golang.org/x/sys/unix" "inet.af/netaddr" "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/pkg/smbios" "github.com/talos-systems/talos/pkg/download" "github.com/talos-systems/talos/pkg/machinery/nethelpers" "github.com/talos-systems/talos/pkg/machinery/resources/network" @@ -179,7 +179,7 @@ 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) { - s, err := smbios.New() + s, err := smbios.GetSMBIOSInfo() if err != nil { return nil, nil, nil, "", err } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/nocloud.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/nocloud.go index 55477b110..df41e29fe 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/nocloud.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/nocloud.go @@ -10,6 +10,7 @@ import ( stderrors "errors" "fmt" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" yaml "gopkg.in/yaml.v3" @@ -59,7 +60,7 @@ func (n *Nocloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, hostna } // Configuration implements the runtime.Platform interface. -func (n *Nocloud) Configuration(ctx context.Context) ([]byte, error) { +func (n *Nocloud) Configuration(ctx context.Context, r state.State) ([]byte, error) { _, _, machineConfigDl, _, err := n.acquireConfig(ctx) //nolint:dogsled if err != nil { return nil, err diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go index 6f3464f8e..43799c955 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go @@ -12,6 +12,7 @@ import ( "fmt" "log" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "inet.af/netaddr" @@ -209,7 +210,7 @@ func (o *Openstack) ParseMetadata(unmarshalledMetadataConfig *MetadataConfig, un } // Configuration implements the runtime.Platform interface. -func (o *Openstack) Configuration(ctx context.Context) (machineConfig []byte, err error) { +func (o *Openstack) Configuration(ctx context.Context, r state.State) (machineConfig []byte, err error) { _, _, machineConfig, err = o.configFromCD() if err != nil { _, _, machineConfig, err = o.configFromNetwork(ctx) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle.go index 18a79a354..f3a55a3d4 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle.go @@ -11,6 +11,7 @@ import ( "fmt" "log" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" @@ -83,7 +84,7 @@ func (o *Oracle) ParseMetadata(interfaceAddresses []NetworkConfig, hostname stri } // Configuration implements the platform.Platform interface. -func (o *Oracle) Configuration(ctx context.Context) ([]byte, error) { +func (o *Oracle) Configuration(ctx context.Context, r state.State) ([]byte, error) { log.Printf("fetching machine config from: %q", OracleUserDataEndpoint) machineConfigDl, err := download.Download(ctx, OracleUserDataEndpoint, diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/scaleway.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/scaleway.go index d0e36477d..a6a47f96d 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/scaleway.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/scaleway.go @@ -10,6 +10,7 @@ import ( "log" "strconv" + "github.com/cosi-project/runtime/pkg/state" "github.com/scaleway/scaleway-sdk-go/api/instance/v1" "github.com/talos-systems/go-procfs/procfs" "inet.af/netaddr" @@ -141,7 +142,7 @@ func (s *Scaleway) ParseMetadata(metadataConfig *instance.Metadata) (*runtime.Pl } // Configuration implements the runtime.Platform interface. -func (s *Scaleway) Configuration(ctx context.Context) ([]byte, error) { +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, diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/upcloud.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/upcloud.go index 396f06225..dc540e50c 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/upcloud.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/upcloud.go @@ -10,6 +10,7 @@ import ( "fmt" "log" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "inet.af/netaddr" @@ -194,7 +195,7 @@ func (u *UpCloud) ParseMetadata(meta *MetaData) (*runtime.PlatformNetworkConfig, } // Configuration implements the runtime.Platform interface. -func (u *UpCloud) Configuration(ctx context.Context) ([]byte, error) { +func (u *UpCloud) Configuration(ctx context.Context, r state.State) ([]byte, error) { log.Printf("fetching machine config from: %q", UpCloudUserDataEndpoint) return download.Download(ctx, UpCloudUserDataEndpoint, diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_amd64.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_amd64.go index 654ebf976..913e0e615 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_amd64.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_amd64.go @@ -15,6 +15,7 @@ import ( "fmt" "log" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "github.com/vmware/govmomi/ovf" "github.com/vmware/vmw-guestinfo/rpcvmx" @@ -111,7 +112,7 @@ func readConfigFromOvf(extraConfig *rpcvmx.Config, key string) ([]byte, error) { // Configuration implements the platform.Platform interface. //nolint:gocyclo -func (v *VMware) Configuration(context.Context) ([]byte, error) { +func (v *VMware) Configuration(context.Context, state.State) ([]byte, error) { var option *string if option = procfs.ProcCmdline().Get(constants.KernelParamConfig).First(); option == nil { return nil, fmt.Errorf("%s not found", constants.KernelParamConfig) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_other.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_other.go index b3f21a1be..3fdcbbe8a 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_other.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_other.go @@ -11,6 +11,7 @@ import ( "context" "fmt" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" @@ -25,7 +26,7 @@ func (v *VMware) Name() string { } // Configuration implements the platform.Platform interface. -func (v *VMware) Configuration(context.Context) ([]byte, error) { +func (v *VMware) Configuration(context.Context, state.State) ([]byte, error) { return nil, fmt.Errorf("arch not supported") } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/vultr.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/vultr.go index b3e598ea1..37881718d 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/vultr.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/vultr.go @@ -12,6 +12,7 @@ import ( "log" "net" + "github.com/cosi-project/runtime/pkg/state" "github.com/talos-systems/go-procfs/procfs" "github.com/vultr/metadata" "inet.af/netaddr" @@ -121,7 +122,7 @@ func (v *Vultr) ParseMetadata(meta *metadata.MetaData, extIP []byte) (*runtime.P } // Configuration implements the runtime.Platform interface. -func (v *Vultr) Configuration(ctx context.Context) ([]byte, error) { +func (v *Vultr) Configuration(ctx context.Context, r state.State) ([]byte, error) { log.Printf("fetching machine config from: %q", VultrUserDataEndpoint) return download.Download(ctx, VultrUserDataEndpoint, diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go index bba1a3717..f7d2af8d8 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go @@ -583,7 +583,7 @@ func SaveConfig(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFu func fetchConfig(ctx context.Context, r runtime.Runtime) (out []byte, err error) { var b []byte - if b, err = r.State().Platform().Configuration(ctx); err != nil { + if b, err = r.State().Platform().Configuration(ctx, r.State().V1Alpha2().Resources()); err != nil { return nil, err } diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go index ca1d1d008..c9f81e63f 100644 --- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go +++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go @@ -91,6 +91,7 @@ func NewState() (*State, error) { &files.EtcFileStatus{}, &hardware.Processor{}, &hardware.MemoryModule{}, + &hardware.SystemInformation{}, &k8s.AdmissionControlConfig{}, &k8s.APIServerConfig{}, &k8s.ConfigStatus{}, diff --git a/internal/pkg/encryption/keys/nodeid.go b/internal/pkg/encryption/keys/nodeid.go index dbb58a0b9..9ebfc1d72 100644 --- a/internal/pkg/encryption/keys/nodeid.go +++ b/internal/pkg/encryption/keys/nodeid.go @@ -7,7 +7,7 @@ package keys import ( "fmt" - "github.com/talos-systems/go-smbios/smbios" + "github.com/talos-systems/talos/internal/pkg/smbios" ) // NodeIDKeyHandler generates the key based on current node information @@ -26,7 +26,7 @@ func (h *NodeIDKeyHandler) GetKey(options ...KeyOption) ([]byte, error) { return nil, err } - s, err := smbios.New() + s, err := smbios.GetSMBIOSInfo() if err != nil { return nil, err } diff --git a/internal/app/machined/pkg/controllers/hardware/smbios.go b/internal/pkg/smbios/smbios.go similarity index 96% rename from internal/app/machined/pkg/controllers/hardware/smbios.go rename to internal/pkg/smbios/smbios.go index 3fc4b8eba..79aee4d43 100644 --- a/internal/app/machined/pkg/controllers/hardware/smbios.go +++ b/internal/pkg/smbios/smbios.go @@ -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/. -package hardware +package smbios import ( "sync" diff --git a/pkg/machinery/constants/constants.go b/pkg/machinery/constants/constants.go index 5d8907b6f..c4e2d78e5 100644 --- a/pkg/machinery/constants/constants.go +++ b/pkg/machinery/constants/constants.go @@ -679,3 +679,11 @@ const ( //nolint:golint SYSLOG_ACTION_READ_ALL = 3 ) + +// names of variable that can be substituted in the talos.config kernel parameter. +const ( + UUIDKey = "uuid" + SerialNumberKey = "serial" + HostnameKey = "hostname" + MacKey = "mac" +) diff --git a/pkg/machinery/resources/hardware/deep_copy.generated.go b/pkg/machinery/resources/hardware/deep_copy.generated.go index 954580101..2efb0e9e1 100644 --- a/pkg/machinery/resources/hardware/deep_copy.generated.go +++ b/pkg/machinery/resources/hardware/deep_copy.generated.go @@ -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 ProcessorSpec -type MemorySpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT. +// Code generated by "deep-copy -type ProcessorSpec -type MemoryModuleSpec -type SystemInformationSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT. package hardware @@ -12,8 +12,14 @@ func (o ProcessorSpec) DeepCopy() ProcessorSpec { return cp } -// DeepCopy generates a deep copy of MemorySpec. +// DeepCopy generates a deep copy of MemoryModuleSpec. func (o MemoryModuleSpec) DeepCopy() MemoryModuleSpec { var cp MemoryModuleSpec = o return cp } + +// DeepCopy generates a deep copy of SystemInformationSpec. +func (o SystemInformationSpec) DeepCopy() SystemInformationSpec { + var cp SystemInformationSpec = o + return cp +} diff --git a/pkg/machinery/resources/hardware/system_information.go b/pkg/machinery/resources/hardware/system_information.go new file mode 100644 index 000000000..42749ff28 --- /dev/null +++ b/pkg/machinery/resources/hardware/system_information.go @@ -0,0 +1,85 @@ +// 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 hardware + +import ( + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/meta" + "github.com/cosi-project/runtime/pkg/resource/typed" +) + +// SystemInformationType is type of SystemInformation resource. +const SystemInformationType = resource.Type("SystemInformations.hardware.talos.dev") + +// SystemInformation resource holds node SystemInformation information. +type SystemInformation = typed.Resource[SystemInformationSpec, SystemInformationRD] + +// SystemInformationSpec represents the system information obtained from smbios. +type SystemInformationSpec struct { + Manufacturer string `yaml:"manufacturer,omitempty"` + ProductName string `yaml:"productName,omitempty"` + Version string `yaml:"version,omitempty"` + SerialNumber string `yaml:"serialnumber,omitempty"` + UUID string `yaml:"uuid,omitempty"` + WakeUpType string `yaml:"wakeUpType,omitempty"` + SKUNumber string `yaml:"skuNumber,omitempty"` +} + +// NewSystemInformation initializes a SystemInformationInfo resource. +func NewSystemInformation(id string) *SystemInformation { + return typed.NewResource[SystemInformationSpec, SystemInformationRD]( + resource.NewMetadata(NamespaceName, SystemInformationType, id, resource.VersionUndefined), + SystemInformationSpec{}, + ) +} + +// SystemInformationRD provides auxiliary methods for SystemInformation. +type SystemInformationRD struct{} + +// ResourceDefinition implements typed.ResourceDefinition interface. +func (c SystemInformationRD) ResourceDefinition(resource.Metadata, SystemInformationSpec) meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: SystemInformationType, + Aliases: []resource.Type{ + "systeminformation", + }, + DefaultNamespace: NamespaceName, + PrintColumns: []meta.PrintColumn{ + { + Name: "Manufacturer", + JSONPath: `{.manufacturer}`, + }, + { + Name: "ProductName", + JSONPath: `{.productName}`, + }, + + { + Name: "Version", + JSONPath: `{.version}`, + }, + + { + Name: "SerialNumber", + JSONPath: `{.serialnumber}`, + }, + + { + Name: "UUID", + JSONPath: `{.uuid}`, + }, + + { + Name: "WakeUpType", + JSONPath: `{.wakeUpType}`, + }, + + { + Name: "SKUNumber", + JSONPath: `{.skuNumber}`, + }, + }, + } +} diff --git a/website/content/v1.2/reference/kernel.md b/website/content/v1.2/reference/kernel.md index 7b4062691..785d3c11c 100644 --- a/website/content/v1.2/reference/kernel.md +++ b/website/content/v1.2/reference/kernel.md @@ -83,6 +83,25 @@ Several of these are enforced by the Kernel Self Protection Project [KSPP](https #### `talos.config` The URL at which the machine configuration data may be found. + + This parameter supports variable substitution inside URL query values for the following case-insensitive placeholders: + + * `${uuid}` the SMBIOS UUID + * `${serial}` the SMBIOS Serial Number + * `${mac}` the MAC address of the first network interface attaining link state `up` + * `${hostname}` the hostname of the machine + + The following example + + `http://example.com/metadata?h=${hostname}&m=${mac}&s=${serial}&u=${uuid}` + + may translate to + + `http://example.com/metadata?h=myTestHostname&m=52%3A2f%3Afd%3Adf%3Afc%3Ac0&s=0OCZJ19N65&u=40dcbd19-3b10-444e-bfff-aaee44a51fda` + + For backwards compatibility we insert the system UUID into the query parameter `uuid` if its value is empty. As in + + `http://example.com/metadata?uuid=` => `http://example.com/metadata?uuid=40dcbd19-3b10-444e-bfff-aaee44a51fda` #### `talos.platform`