diff --git a/go.sum b/go.sum index 35b88b6ff..9e3b1df87 100644 --- a/go.sum +++ b/go.sum @@ -235,7 +235,6 @@ github.com/Microsoft/hcsshim v0.9.9/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfy github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= @@ -538,7 +537,6 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= @@ -577,7 +575,6 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -795,7 +792,6 @@ github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhE github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -810,7 +806,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= -github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -842,7 +837,6 @@ github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2C github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jsimonetti/rtnetlink v1.3.3 h1:ycpm3z8XlAzmaacVRjdUT3x6MM1o3YBXsXc7DXSRNCE= github.com/jsimonetti/rtnetlink v1.3.3/go.mod h1:mW4xSP3wkiqWxHMlfG/gOufp3XnhAxu7EhfABmrWSh8= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -854,7 +848,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw= github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -933,7 +926,6 @@ github.com/mdlayher/netx v0.0.0-20230430222610-7e21880baee8/go.mod h1:qhZhwMDNWw github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= -github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= @@ -982,7 +974,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nberlee/go-netstat v0.1.2 h1:wgPV1YOUo+kDFypqiKgfxMtnSs1Wb42c7ahI4qyEUJc= github.com/nberlee/go-netstat v0.1.2/go.mod h1:GvDCRLsUKMRN1wULkt7tpnDmjSIE6YGf5zeVq+mBO64= @@ -1210,9 +1201,7 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= diff --git a/internal/app/machined/pkg/controllers/runtime/events_sink_config.go b/internal/app/machined/pkg/controllers/runtime/events_sink_config.go index f11a37dde..2ae2d6def 100644 --- a/internal/app/machined/pkg/controllers/runtime/events_sink_config.go +++ b/internal/app/machined/pkg/controllers/runtime/events_sink_config.go @@ -11,10 +11,12 @@ import ( "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/go-pointer" "github.com/siderolabs/go-procfs/procfs" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/runtime" ) @@ -30,7 +32,14 @@ func (ctrl *EventsSinkConfigController) Name() string { // Inputs implements controller.Controller interface. func (ctrl *EventsSinkConfigController) Inputs() []controller.Input { - return nil + return []controller.Input{ + { + Namespace: config.NamespaceName, + Type: config.MachineConfigType, + ID: pointer.To(config.V1Alpha1ID), + Kind: controller.InputWeak, + }, + } } // Outputs implements controller.Controller interface. @@ -62,6 +71,15 @@ func (ctrl *EventsSinkConfigController) Run(ctx context.Context, r controller.Ru } } + cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.V1Alpha1ID) + if err != nil && !state.IsNotFoundError(err) { + return fmt.Errorf("error getting machine config: %w", err) + } + + if cfg != nil && cfg.Config().Runtime().EventsEndpoint() != nil { + endpoint = *cfg.Config().Runtime().EventsEndpoint() + } + if endpoint == "" { if err := r.Destroy(ctx, runtime.NewEventSinkConfig().Metadata()); err != nil && !state.IsNotFoundError(err) { return fmt.Errorf("error destroying event sink config: %w", err) diff --git a/internal/app/machined/pkg/controllers/runtime/events_sink_config_test.go b/internal/app/machined/pkg/controllers/runtime/events_sink_config_test.go index c96d3cfa6..abcde8a17 100644 --- a/internal/app/machined/pkg/controllers/runtime/events_sink_config_test.go +++ b/internal/app/machined/pkg/controllers/runtime/events_sink_config_test.go @@ -15,7 +15,10 @@ import ( "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest" runtimectrls "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/runtime" + "github.com/siderolabs/talos/pkg/machinery/config/container" + runtimecfg "github.com/siderolabs/talos/pkg/machinery/config/types/runtime" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/runtime" ) @@ -33,10 +36,36 @@ func (suite *EventsSinkConfigSuite) TestEventSinkConfigNone() { rtestutils.AssertNoResource[*runtime.EventSinkConfig](suite.Ctx(), suite.T(), suite.State(), runtime.EventSinkConfigID) } +func (suite *EventsSinkConfigSuite) TestEventSinkConfigMachineConfig() { + suite.Require().NoError(suite.Runtime().RegisterController(&runtimectrls.EventsSinkConfigController{})) + + eventSinkConfig := &runtimecfg.EventSinkV1Alpha1{ + Endpoint: "10.0.0.2:4444", + } + + cfg, err := container.New(eventSinkConfig) + suite.Require().NoError(err) + + suite.Require().NoError(suite.State().Create(suite.Ctx(), config.NewMachineConfig(cfg))) + + rtestutils.AssertResources[*runtime.EventSinkConfig](suite.Ctx(), suite.T(), suite.State(), []resource.ID{runtime.EventSinkConfigID}, + func(cfg *runtime.EventSinkConfig, asrt *assert.Assertions) { + asrt.Equal( + "10.0.0.2:4444", + cfg.TypedSpec().Endpoint, + ) + }) +} + func (suite *EventsSinkConfigSuite) TestEventSinkConfigCmdline() { cmdline := procfs.NewCmdline("") cmdline.Append(constants.KernelParamEventsSink, "10.0.0.1:3333") + cfg, err := container.New() + suite.Require().NoError(err) + + suite.Require().NoError(suite.State().Create(suite.Ctx(), config.NewMachineConfig(cfg))) + suite.Require().NoError(suite.Runtime().RegisterController(&runtimectrls.EventsSinkConfigController{ Cmdline: cmdline, })) diff --git a/internal/app/machined/pkg/controllers/runtime/kmsg_log_config.go b/internal/app/machined/pkg/controllers/runtime/kmsg_log_config.go index f67ec58c8..cc99d30ef 100644 --- a/internal/app/machined/pkg/controllers/runtime/kmsg_log_config.go +++ b/internal/app/machined/pkg/controllers/runtime/kmsg_log_config.go @@ -12,10 +12,13 @@ import ( "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/gen/slices" + "github.com/siderolabs/go-pointer" "github.com/siderolabs/go-procfs/procfs" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/runtime" ) @@ -31,7 +34,14 @@ func (ctrl *KmsgLogConfigController) Name() string { // Inputs implements controller.Controller interface. func (ctrl *KmsgLogConfigController) Inputs() []controller.Input { - return nil + return []controller.Input{ + { + Namespace: config.NamespaceName, + Type: config.MachineConfigType, + ID: pointer.To(config.V1Alpha1ID), + Kind: controller.InputWeak, + }, + } } // Outputs implements controller.Controller interface. @@ -68,6 +78,21 @@ func (ctrl *KmsgLogConfigController) Run(ctx context.Context, r controller.Runti } } + cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.V1Alpha1ID) + if err != nil && !state.IsNotFoundError(err) { + return fmt.Errorf("error getting machine config: %w", err) + } + + if cfg != nil { + // remove duplicate URLs in case same destination is specified in both machine config and kernel args + destinations = append(destinations, slices.Filter(cfg.Config().Runtime().KmsgLogURLs(), + func(u *url.URL) bool { + return !slices.Contains(destinations, func(v *url.URL) bool { + return v.String() == u.String() + }) + })...) + } + if len(destinations) == 0 { if err := r.Destroy(ctx, runtime.NewKmsgLogConfig().Metadata()); err != nil && !state.IsNotFoundError(err) { return fmt.Errorf("error destroying kmsg log config: %w", err) diff --git a/internal/app/machined/pkg/controllers/runtime/kmsg_log_config_test.go b/internal/app/machined/pkg/controllers/runtime/kmsg_log_config_test.go index b78cd9571..79316ebe8 100644 --- a/internal/app/machined/pkg/controllers/runtime/kmsg_log_config_test.go +++ b/internal/app/machined/pkg/controllers/runtime/kmsg_log_config_test.go @@ -17,7 +17,11 @@ import ( "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest" runtimectrls "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/runtime" + "github.com/siderolabs/talos/pkg/machinery/config/container" + "github.com/siderolabs/talos/pkg/machinery/config/types/meta" + runtimecfg "github.com/siderolabs/talos/pkg/machinery/config/types/runtime" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/runtime" ) @@ -35,6 +39,45 @@ func (suite *KmsgLogConfigSuite) TestKmsgLogConfigNone() { rtestutils.AssertNoResource[*runtime.KmsgLogConfig](suite.Ctx(), suite.T(), suite.State(), runtime.KmsgLogConfigID) } +func (suite *KmsgLogConfigSuite) TestKmsgLogConfigMachineConfig() { + cmdline := procfs.NewCmdline("") + cmdline.Append(constants.KernelParamLoggingKernel, "https://10.0.0.1:3333/logs") + + suite.Require().NoError(suite.Runtime().RegisterController(&runtimectrls.KmsgLogConfigController{ + Cmdline: cmdline, + })) + + kmsgLogConfig1 := &runtimecfg.KmsgLogV1Alpha1{ + MetaName: "1", + KmsgLogURL: meta.URL{ + URL: must(url.Parse("https://10.0.0.2:4444/logs")), + }, + } + + kmsgLogConfig2 := &runtimecfg.KmsgLogV1Alpha1{ + MetaName: "2", + KmsgLogURL: meta.URL{ + URL: must(url.Parse("https://10.0.0.1:3333/logs")), + }, + } + + cfg, err := container.New(kmsgLogConfig1, kmsgLogConfig2) + suite.Require().NoError(err) + + suite.Require().NoError(suite.State().Create(suite.Ctx(), config.NewMachineConfig(cfg))) + + rtestutils.AssertResources[*runtime.KmsgLogConfig](suite.Ctx(), suite.T(), suite.State(), []resource.ID{runtime.KmsgLogConfigID}, + func(cfg *runtime.KmsgLogConfig, asrt *assert.Assertions) { + asrt.Equal( + []string{ + "https://10.0.0.1:3333/logs", + "https://10.0.0.2:4444/logs", + }, + slices.Map(cfg.TypedSpec().Destinations, func(u *url.URL) string { return u.String() }), + ) + }) +} + func (suite *KmsgLogConfigSuite) TestKmsgLogConfigCmdline() { cmdline := procfs.NewCmdline("") cmdline.Append(constants.KernelParamLoggingKernel, "https://10.0.0.1:3333/logs") @@ -51,3 +94,11 @@ func (suite *KmsgLogConfigSuite) TestKmsgLogConfigCmdline() { ) }) } + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + + return t +} diff --git a/pkg/machinery/config/config/config.go b/pkg/machinery/config/config/config.go index 84ea7e6b9..d2ce753a9 100644 --- a/pkg/machinery/config/config/config.go +++ b/pkg/machinery/config/config/config.go @@ -12,4 +12,5 @@ type Config interface { Machine() MachineConfig Cluster() ClusterConfig SideroLink() SideroLinkConfig + Runtime() RuntimeConfig } diff --git a/pkg/machinery/config/config/runtime.go b/pkg/machinery/config/config/runtime.go new file mode 100644 index 000000000..35ca35901 --- /dev/null +++ b/pkg/machinery/config/config/runtime.go @@ -0,0 +1,52 @@ +// 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 config + +import "net/url" + +// RuntimeConfig defines the interface to access Talos runtime configuration. +type RuntimeConfig interface { + EventsEndpoint() *string + KmsgLogURLs() []*url.URL +} + +// WrapRuntimeConfigList wraps a list of RuntimeConfig into a single RuntimeConfig aggregating the results. +func WrapRuntimeConfigList(configs ...RuntimeConfig) RuntimeConfig { + return runtimeConfigWrapper(configs) +} + +type runtimeConfigWrapper []RuntimeConfig + +func (w runtimeConfigWrapper) EventsEndpoint() *string { + return findFirstValue(w, func(c RuntimeConfig) *string { + return c.EventsEndpoint() + }) +} + +func (w runtimeConfigWrapper) KmsgLogURLs() []*url.URL { + return aggregateValues(w, func(c RuntimeConfig) []*url.URL { + return c.KmsgLogURLs() + }) +} + +func findFirstValue[T any, R any](documents []T, getter func(T) *R) *R { + for _, document := range documents { + if value := getter(document); value != nil { + return value + } + } + + return nil +} + +func aggregateValues[T any, R any](documents []T, getter func(T) []R) []R { + var result []R + + for _, document := range documents { + result = append(result, getter(document)...) + } + + return result +} diff --git a/pkg/machinery/config/configloader/configloader_fuzz_test.go b/pkg/machinery/config/configloader/configloader_fuzz_test.go index 832fa2c4d..daf4a898b 100644 --- a/pkg/machinery/config/configloader/configloader_fuzz_test.go +++ b/pkg/machinery/config/configloader/configloader_fuzz_test.go @@ -31,6 +31,6 @@ func FuzzConfigLoader(f *testing.F) { f.Fuzz(func(t *testing.T, b []byte) { t.Parallel() - testConfigLoaderBytes(t, b) + testConfigLoaderBytes(t, b, false) }) } diff --git a/pkg/machinery/config/configloader/configloader_test.go b/pkg/machinery/config/configloader/configloader_test.go index 404d1a258..4746591c0 100644 --- a/pkg/machinery/config/configloader/configloader_test.go +++ b/pkg/machinery/config/configloader/configloader_test.go @@ -16,6 +16,8 @@ import ( ) // callMethods calls obj's "getter" methods recursively and fails on panic. +// +//nolint:gocyclo func callMethods(t testing.TB, obj reflect.Value, chain ...string) { t.Helper() @@ -44,6 +46,8 @@ func callMethods(t testing.TB, obj reflect.Value, chain ...string) { fallthrough case "MarshalYAML": fallthrough + case "Doc": + fallthrough case "Endpoint": // t.Logf("Skipping %v", nextChain) continue @@ -69,12 +73,16 @@ func callMethods(t testing.TB, obj reflect.Value, chain ...string) { } } -func testConfigLoaderBytes(t testing.TB, b []byte) { +func testConfigLoaderBytes(t testing.TB, b []byte, failOnError bool) { t.Helper() p, err := configloader.NewFromBytes(b) if err != nil { - t.Skipf("Failed to load, skipping: %s.", err) + if failOnError { + t.Fatalf("Failed to load: %s.", err) + } else { + t.Skipf("Failed to load, skipping: %s.", err) + } } callMethods(t, reflect.ValueOf(p)) @@ -95,7 +103,7 @@ func TestConfigLoader(t *testing.T) { b, err := os.ReadFile(file) require.NoError(t, err) - testConfigLoaderBytes(t, b) + testConfigLoaderBytes(t, b, true) }) } } diff --git a/pkg/machinery/config/configloader/testdata/multidoc1.test b/pkg/machinery/config/configloader/testdata/multidoc1.test new file mode 100644 index 000000000..4dc787760 --- /dev/null +++ b/pkg/machinery/config/configloader/testdata/multidoc1.test @@ -0,0 +1,12 @@ +apiVersion: v1alpha1 +kind: SideroLinkConfig +apiUrl: grpc://172.20.0.1:4000/?jointoken=foo +--- +apiVersion: v1alpha1 +kind: KmsgLogConfig +name: apiSink +url: tcp://[fdae:41e4:649b:9303::1]:4001/ +--- +apiVersion: v1alpha1 +kind: EventSinkConfig +endpoint: "[fdae:41e4:649b:9303::1]:8080" diff --git a/pkg/machinery/config/container/container.go b/pkg/machinery/config/container/container.go index 33b2cc558..c527316d4 100644 --- a/pkg/machinery/config/container/container.go +++ b/pkg/machinery/config/container/container.go @@ -112,7 +112,7 @@ func (container *Container) Debug() bool { // Persist implements config.Config interface. func (container *Container) Persist() bool { if container.v1alpha1Config == nil { - return false + return true } return container.v1alpha1Config.Persist() @@ -136,15 +136,31 @@ func (container *Container) Cluster() config.ClusterConfig { return container.v1alpha1Config.Cluster() } -// SideroLink implements config.Config interface. -func (container *Container) SideroLink() config.SideroLinkConfig { - for _, doc := range container.documents { - if c, ok := doc.(config.SideroLinkConfig); ok { - return c +func findMatchingDocs[T any](documents []config.Document) []T { + var result []T + + for _, doc := range documents { + if c, ok := doc.(T); ok { + result = append(result, c) } } - return nil + return result +} + +// SideroLink implements config.Config interface. +func (container *Container) SideroLink() config.SideroLinkConfig { + matching := findMatchingDocs[config.SideroLinkConfig](container.documents) + if len(matching) == 0 { + return nil + } + + return matching[0] +} + +// Runtime implements config.Config interface. +func (container *Container) Runtime() config.RuntimeConfig { + return config.WrapRuntimeConfigList(findMatchingDocs[config.RuntimeConfig](container.documents)...) } // Bytes returns source YAML representation (if available) or does default encoding. diff --git a/pkg/machinery/config/types/runtime/deep_copy.generated.go b/pkg/machinery/config/types/runtime/deep_copy.generated.go new file mode 100644 index 000000000..2602ecb28 --- /dev/null +++ b/pkg/machinery/config/types/runtime/deep_copy.generated.go @@ -0,0 +1,31 @@ +// 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/. + +// Code generated by "deep-copy -type EventSinkV1Alpha1 -type KmsgLogV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT. + +package runtime + +import ( + "net/url" +) + +// DeepCopy generates a deep copy of *EventSinkV1Alpha1. +func (o *EventSinkV1Alpha1) DeepCopy() *EventSinkV1Alpha1 { + var cp EventSinkV1Alpha1 = *o + return &cp +} + +// DeepCopy generates a deep copy of *KmsgLogV1Alpha1. +func (o *KmsgLogV1Alpha1) DeepCopy() *KmsgLogV1Alpha1 { + var cp KmsgLogV1Alpha1 = *o + if o.KmsgLogURL.URL != nil { + cp.KmsgLogURL.URL = new(url.URL) + *cp.KmsgLogURL.URL = *o.KmsgLogURL.URL + if o.KmsgLogURL.URL.User != nil { + cp.KmsgLogURL.URL.User = new(url.Userinfo) + *cp.KmsgLogURL.URL.User = *o.KmsgLogURL.URL.User + } + } + return &cp +} diff --git a/pkg/machinery/config/types/runtime/event_sink.go b/pkg/machinery/config/types/runtime/event_sink.go new file mode 100644 index 000000000..e92083182 --- /dev/null +++ b/pkg/machinery/config/types/runtime/event_sink.go @@ -0,0 +1,84 @@ +// 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 ( + "fmt" + "net" + "net/url" + + "github.com/siderolabs/go-pointer" + + "github.com/siderolabs/talos/pkg/machinery/config/config" + "github.com/siderolabs/talos/pkg/machinery/config/internal/registry" + "github.com/siderolabs/talos/pkg/machinery/config/types/meta" + "github.com/siderolabs/talos/pkg/machinery/config/validation" +) + +// EventSinkKind is a event sink config document kind. +const EventSinkKind = "EventSinkConfig" + +func init() { + registry.Register(EventSinkKind, func(version string) config.Document { + switch version { + case "v1alpha1": + return &EventSinkV1Alpha1{} + default: + return nil + } + }) +} + +// Check interfaces. +var ( + _ config.RuntimeConfig = &EventSinkV1Alpha1{} + _ config.Validator = &EventSinkV1Alpha1{} +) + +// EventSinkV1Alpha1 is a event sink config document. +type EventSinkV1Alpha1 struct { + meta.Meta `yaml:",inline"` + Endpoint string `yaml:"endpoint"` +} + +// NewEventSinkV1Alpha1 creates a new eventsink config document. +func NewEventSinkV1Alpha1() *EventSinkV1Alpha1 { + return &EventSinkV1Alpha1{ + Meta: meta.Meta{ + MetaKind: EventSinkKind, + MetaAPIVersion: "v1alpha1", + }, + } +} + +// Clone implements config.Document interface. +func (s *EventSinkV1Alpha1) Clone() config.Document { + return s.DeepCopy() +} + +// Runtime implements config.Config interface. +func (s *EventSinkV1Alpha1) Runtime() config.RuntimeConfig { + return s +} + +// EventsEndpoint implements config.RuntimeConfig interface. +func (s *EventSinkV1Alpha1) EventsEndpoint() *string { + return pointer.To(s.Endpoint) +} + +// KmsgLogURLs implements config.RuntimeConfig interface. +func (s *EventSinkV1Alpha1) KmsgLogURLs() []*url.URL { + return nil +} + +// Validate implements config.Validator interface. +func (s *EventSinkV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) { + _, _, err := net.SplitHostPort(s.Endpoint) + if err != nil { + return nil, fmt.Errorf("event sink endpoint: %w", err) + } + + return nil, nil +} diff --git a/pkg/machinery/config/types/runtime/event_sink_test.go b/pkg/machinery/config/types/runtime/event_sink_test.go new file mode 100644 index 000000000..3cb34a449 --- /dev/null +++ b/pkg/machinery/config/types/runtime/event_sink_test.go @@ -0,0 +1,104 @@ +// 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_test + +import ( + _ "embed" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/siderolabs/talos/pkg/machinery/config/encoder" + "github.com/siderolabs/talos/pkg/machinery/config/types/runtime" +) + +//go:embed testdata/eventsink.yaml +var expectedEventSinkDocument []byte + +func TestEventSinkMarshalStability(t *testing.T) { + cfg := runtime.NewEventSinkV1Alpha1() + cfg.Endpoint = "10.0.0.1:3333" + + marshaled, err := encoder.NewEncoder(cfg).Encode() + require.NoError(t, err) + + t.Log(string(marshaled)) + + assert.Equal(t, expectedEventSinkDocument, marshaled) +} + +func TestEventSinkValidate(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + name string + cfg func() *runtime.EventSinkV1Alpha1 + + expectedError string + expectedWarnings []string + }{ + { + name: "empty", + cfg: runtime.NewEventSinkV1Alpha1, + + expectedError: "event sink endpoint: missing port in address", + }, + { + name: "just IP", + cfg: func() *runtime.EventSinkV1Alpha1 { + cfg := runtime.NewEventSinkV1Alpha1() + cfg.Endpoint = "10.0.0.1" + + return cfg + }, + + expectedError: "event sink endpoint: address 10.0.0.1: missing port in address", + }, + { + name: "valid", + cfg: func() *runtime.EventSinkV1Alpha1 { + cfg := runtime.NewEventSinkV1Alpha1() + cfg.Endpoint = "[ff:80::01]:334" + + return cfg + }, + }, + } { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + warnings, err := test.cfg().Validate(validationMode{}) + + assert.Equal(t, test.expectedWarnings, warnings) + + if test.expectedError != "" { + assert.EqualError(t, err, test.expectedError) + } else { + assert.NoError(t, err) + } + }) + } +} + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + + return t +} + +type validationMode struct{} + +func (validationMode) String() string { + return "" +} + +func (validationMode) RequiresInstall() bool { + return false +} diff --git a/pkg/machinery/config/types/runtime/kmsg_log.go b/pkg/machinery/config/types/runtime/kmsg_log.go new file mode 100644 index 000000000..000bcb2a4 --- /dev/null +++ b/pkg/machinery/config/types/runtime/kmsg_log.go @@ -0,0 +1,109 @@ +// 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 ( + "fmt" + "net/url" + + "github.com/siderolabs/talos/pkg/machinery/config/config" + "github.com/siderolabs/talos/pkg/machinery/config/internal/registry" + "github.com/siderolabs/talos/pkg/machinery/config/types/meta" + "github.com/siderolabs/talos/pkg/machinery/config/validation" +) + +// KmsgLogKind is a kmsg log config document kind. +const KmsgLogKind = "KmsgLogConfig" + +func init() { + registry.Register(KmsgLogKind, func(version string) config.Document { + switch version { + case "v1alpha1": + return &KmsgLogV1Alpha1{} + default: + return nil + } + }) +} + +// Check interfaces. +var ( + _ config.RuntimeConfig = &KmsgLogV1Alpha1{} + _ config.NamedDocument = &KmsgLogV1Alpha1{} + _ config.Validator = &KmsgLogV1Alpha1{} +) + +// KmsgLogV1Alpha1 is a event sink config document. +type KmsgLogV1Alpha1 struct { + meta.Meta `yaml:",inline"` + MetaName string `yaml:"name"` + KmsgLogURL meta.URL `yaml:"url"` +} + +// NewKmsgLogV1Alpha1 creates a new eventsink config document. +func NewKmsgLogV1Alpha1() *KmsgLogV1Alpha1 { + return &KmsgLogV1Alpha1{ + Meta: meta.Meta{ + MetaKind: KmsgLogKind, + MetaAPIVersion: "v1alpha1", + }, + } +} + +// Name implements config.NamedDocument interface. +func (s *KmsgLogV1Alpha1) Name() string { + return s.MetaName +} + +// Clone implements config.Document interface. +func (s *KmsgLogV1Alpha1) Clone() config.Document { + return s.DeepCopy() +} + +// Runtime implements config.Config interface. +func (s *KmsgLogV1Alpha1) Runtime() config.RuntimeConfig { + return s +} + +// EventsEndpoint implements config.RuntimeConfig interface. +func (s *KmsgLogV1Alpha1) EventsEndpoint() *string { + return nil +} + +// KmsgLogURLs implements config.RuntimeConfig interface. +func (s *KmsgLogV1Alpha1) KmsgLogURLs() []*url.URL { + return []*url.URL{s.KmsgLogURL.URL} +} + +// Validate implements config.Validator interface. +func (s *KmsgLogV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) { + if s.MetaName == "" { + return nil, fmt.Errorf("name is required") + } + + if s.KmsgLogURL.URL == nil { + return nil, fmt.Errorf("url is required") + } + + switch s.KmsgLogURL.URL.Scheme { + case "tcp": + case "udp": + default: + return nil, fmt.Errorf("url scheme must be tcp:// or udp://") + } + + switch s.KmsgLogURL.URL.Path { + case "/": + case "": + default: + return nil, fmt.Errorf("url path must be empty") + } + + if s.KmsgLogURL.URL.Port() == "" { + return nil, fmt.Errorf("url port is required") + } + + return nil, nil +} diff --git a/pkg/machinery/config/types/runtime/kmsg_log_test.go b/pkg/machinery/config/types/runtime/kmsg_log_test.go new file mode 100644 index 000000000..3902f3401 --- /dev/null +++ b/pkg/machinery/config/types/runtime/kmsg_log_test.go @@ -0,0 +1,135 @@ +// 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_test + +import ( + _ "embed" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/siderolabs/talos/pkg/machinery/config/encoder" + "github.com/siderolabs/talos/pkg/machinery/config/types/runtime" +) + +//go:embed testdata/kmsglog.yaml +var expectedKmsgLogDocument []byte + +func TestKmsgLogMarshalStability(t *testing.T) { + cfg := runtime.NewKmsgLogV1Alpha1() + cfg.MetaName = "apiSink" + cfg.KmsgLogURL.URL = must(url.Parse("https://kmsglog.api/logs")) + + marshaled, err := encoder.NewEncoder(cfg).Encode() + require.NoError(t, err) + + t.Log(string(marshaled)) + + assert.Equal(t, expectedKmsgLogDocument, marshaled) +} + +func TestKmsgLogValidate(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + name string + cfg func() *runtime.KmsgLogV1Alpha1 + + expectedError string + expectedWarnings []string + }{ + { + name: "empty", + cfg: runtime.NewKmsgLogV1Alpha1, + + expectedError: "name is required", + }, + { + name: "no URL", + cfg: func() *runtime.KmsgLogV1Alpha1 { + cfg := runtime.NewKmsgLogV1Alpha1() + cfg.MetaName = "name1" + + return cfg + }, + + expectedError: "url is required", + }, + { + name: "wrong scheme", + cfg: func() *runtime.KmsgLogV1Alpha1 { + cfg := runtime.NewKmsgLogV1Alpha1() + cfg.MetaName = "name2" + cfg.KmsgLogURL.URL = must(url.Parse("https://some.destination/path")) + + return cfg + }, + + expectedError: "url scheme must be tcp:// or udp://", + }, + { + name: "extra path", + cfg: func() *runtime.KmsgLogV1Alpha1 { + cfg := runtime.NewKmsgLogV1Alpha1() + cfg.MetaName = "name5" + cfg.KmsgLogURL.URL = must(url.Parse("tcp://some.destination:34/path")) + + return cfg + }, + + expectedError: "url path must be empty", + }, + { + name: "no port", + cfg: func() *runtime.KmsgLogV1Alpha1 { + cfg := runtime.NewKmsgLogV1Alpha1() + cfg.MetaName = "name6" + cfg.KmsgLogURL.URL = must(url.Parse("tcp://some.destination/")) + + return cfg + }, + + expectedError: "url port is required", + }, + { + name: "valid TCP", + cfg: func() *runtime.KmsgLogV1Alpha1 { + cfg := runtime.NewKmsgLogV1Alpha1() + cfg.MetaName = "name3" + cfg.KmsgLogURL.URL = must(url.Parse("tcp://10.2.3.4:5000/")) + + return cfg + }, + }, + { + name: "valid UDP", + cfg: func() *runtime.KmsgLogV1Alpha1 { + cfg := runtime.NewKmsgLogV1Alpha1() + cfg.MetaName = "name4" + cfg.KmsgLogURL.URL = must(url.Parse("udp://10.2.3.4:5000/")) + + return cfg + }, + }, + } { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + warnings, err := test.cfg().Validate(validationMode{}) + + assert.Equal(t, test.expectedWarnings, warnings) + + if test.expectedError != "" { + assert.EqualError(t, err, test.expectedError) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/machinery/config/types/runtime/runtime.go b/pkg/machinery/config/types/runtime/runtime.go new file mode 100644 index 000000000..6d225b831 --- /dev/null +++ b/pkg/machinery/config/types/runtime/runtime.go @@ -0,0 +1,8 @@ +// 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 provides Talos runtime config documents. +package runtime + +//go:generate deep-copy -type EventSinkV1Alpha1 -type KmsgLogV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go . diff --git a/pkg/machinery/config/types/runtime/testdata/eventsink.yaml b/pkg/machinery/config/types/runtime/testdata/eventsink.yaml new file mode 100644 index 000000000..6f3e5eb8e --- /dev/null +++ b/pkg/machinery/config/types/runtime/testdata/eventsink.yaml @@ -0,0 +1,3 @@ +apiVersion: v1alpha1 +kind: EventSinkConfig +endpoint: 10.0.0.1:3333 diff --git a/pkg/machinery/config/types/runtime/testdata/kmsglog.yaml b/pkg/machinery/config/types/runtime/testdata/kmsglog.yaml new file mode 100644 index 000000000..185a6f7a5 --- /dev/null +++ b/pkg/machinery/config/types/runtime/testdata/kmsglog.yaml @@ -0,0 +1,4 @@ +apiVersion: v1alpha1 +kind: KmsgLogConfig +name: apiSink +url: https://kmsglog.api/logs diff --git a/pkg/machinery/config/types/siderolink/siderolink.go b/pkg/machinery/config/types/siderolink/siderolink.go index 6dead0305..29da74872 100644 --- a/pkg/machinery/config/types/siderolink/siderolink.go +++ b/pkg/machinery/config/types/siderolink/siderolink.go @@ -6,11 +6,13 @@ package siderolink import ( + "fmt" "net/url" "github.com/siderolabs/talos/pkg/machinery/config/config" "github.com/siderolabs/talos/pkg/machinery/config/internal/registry" "github.com/siderolabs/talos/pkg/machinery/config/types/meta" + "github.com/siderolabs/talos/pkg/machinery/config/validation" ) //go:generate deep-copy -type ConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go . @@ -30,7 +32,11 @@ func init() { } // Check interfaces. -var _ config.SecretDocument = &ConfigV1Alpha1{} +var ( + _ config.SecretDocument = &ConfigV1Alpha1{} + _ config.SideroLinkConfig = &ConfigV1Alpha1{} + _ config.Validator = &ConfigV1Alpha1{} +) // ConfigV1Alpha1 is a siderolink config document. type ConfigV1Alpha1 struct { @@ -78,3 +84,26 @@ func (s *ConfigV1Alpha1) APIUrl() *url.URL { return s.APIUrlConfig.URL } + +// Validate implements config.Validator interface. +func (s *ConfigV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) { + if s.APIUrlConfig.URL == nil { + return nil, fmt.Errorf("apiUrl is required") + } + + switch s.APIUrlConfig.URL.Scheme { + case "https": + case "grpc": + default: + return nil, fmt.Errorf("apiUrl scheme must be https:// or grpc://") + } + + switch s.APIUrlConfig.URL.Path { + case "/": + case "": + default: + return nil, fmt.Errorf("apiUrl path must be empty") + } + + return nil, nil +} diff --git a/pkg/machinery/config/types/siderolink/siderolink_test.go b/pkg/machinery/config/types/siderolink/siderolink_test.go index fd1e6f506..830ccb21a 100644 --- a/pkg/machinery/config/types/siderolink/siderolink_test.go +++ b/pkg/machinery/config/types/siderolink/siderolink_test.go @@ -17,6 +17,8 @@ import ( ) func TestRedact(t *testing.T) { + t.Parallel() + cfg := siderolink.NewConfigV1Alpha1() cfg.APIUrlConfig.URL = must(url.Parse("https://siderolink.api/join?jointoken=secret&user=alice")) @@ -31,6 +33,8 @@ func TestRedact(t *testing.T) { var expectedDocument []byte func TestMarshalStability(t *testing.T) { + t.Parallel() + cfg := siderolink.NewConfigV1Alpha1() cfg.APIUrlConfig.URL = must(url.Parse("https://siderolink.api/join?jointoken=secret&user=alice")) @@ -40,6 +44,72 @@ func TestMarshalStability(t *testing.T) { assert.Equal(t, expectedDocument, marshaled) } +func TestValidate(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + name string + cfg func() *siderolink.ConfigV1Alpha1 + + expectedError string + expectedWarnings []string + }{ + { + name: "empty", + cfg: siderolink.NewConfigV1Alpha1, + + expectedError: "apiUrl is required", + }, + { + name: "wrong scheme", + cfg: func() *siderolink.ConfigV1Alpha1 { + cfg := siderolink.NewConfigV1Alpha1() + cfg.APIUrlConfig.URL = must(url.Parse("http://siderolink.api/")) + + return cfg + }, + + expectedError: "apiUrl scheme must be https:// or grpc://", + }, + { + name: "extra path", + cfg: func() *siderolink.ConfigV1Alpha1 { + cfg := siderolink.NewConfigV1Alpha1() + cfg.APIUrlConfig.URL = must(url.Parse("grpc://siderolink.api/path?jointoken=foo")) + + return cfg + }, + + expectedError: "apiUrl path must be empty", + }, + { + name: "valid", + cfg: func() *siderolink.ConfigV1Alpha1 { + cfg := siderolink.NewConfigV1Alpha1() + cfg.APIUrlConfig.URL = must(url.Parse("https://siderolink.api:434/?jointoken=foo")) + + return cfg + }, + }, + } { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + warnings, err := test.cfg().Validate(validationMode{}) + + assert.Equal(t, test.expectedWarnings, warnings) + + if test.expectedError != "" { + assert.EqualError(t, err, test.expectedError) + } else { + assert.NoError(t, err) + } + }) + } +} + func must[T any](t T, err error) T { if err != nil { panic(err) @@ -47,3 +117,13 @@ func must[T any](t T, err error) T { return t } + +type validationMode struct{} + +func (validationMode) String() string { + return "" +} + +func (validationMode) RequiresInstall() bool { + return false +} diff --git a/pkg/machinery/config/types/types.go b/pkg/machinery/config/types/types.go index a5fa0eb9c..fdbe8415f 100644 --- a/pkg/machinery/config/types/types.go +++ b/pkg/machinery/config/types/types.go @@ -6,6 +6,7 @@ package types import ( - _ "github.com/siderolabs/talos/pkg/machinery/config/types/siderolink" //nolint:revive + _ "github.com/siderolabs/talos/pkg/machinery/config/types/runtime" //nolint:revive + _ "github.com/siderolabs/talos/pkg/machinery/config/types/siderolink" _ "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1" ) diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go index 16c0c419c..cb7c5b94e 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go @@ -52,17 +52,25 @@ func (c *Config) APIVersion() string { // Debug implements the config.Provider interface. func (c *Config) Debug() bool { + if c == nil { + return false + } + return pointer.SafeDeref(c.ConfigDebug) } // Persist implements the config.Provider interface. func (c *Config) Persist() bool { + if c == nil { + return true + } + return pointer.SafeDeref(c.ConfigPersist) } // Machine implements the config.Provider interface. func (c *Config) Machine() config.MachineConfig { - if c.MachineConfig == nil { + if c == nil || c.MachineConfig == nil { return &MachineConfig{} } @@ -91,7 +99,7 @@ func (m *MachineConfig) NodeLabels() config.NodeLabels { // Cluster implements the config.Provider interface. func (c *Config) Cluster() config.ClusterConfig { - if c.ClusterConfig == nil { + if c == nil || c.ClusterConfig == nil { return &ClusterConfig{} }