mirror of
				https://github.com/siderolabs/talos.git
				synced 2025-10-31 08:21:25 +01:00 
			
		
		
		
	feat: implement machine config documents for event and log streaming
Fixes #7228 Add some changes to make Talos accept partial machine configuration without main v1alpha1 config. With this change, it's possible to connect a machine already running with machine configuration (v1alpha1), the following patch will connect to a local SideroLink endpoint: ```yaml 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" ``` Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
		
							parent
							
								
									e241be85ba
								
							
						
					
					
						commit
						6be5a13d5d
					
				
							
								
								
									
										11
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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= | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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, | ||||
| 	})) | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -12,4 +12,5 @@ type Config interface { | ||||
| 	Machine() MachineConfig | ||||
| 	Cluster() ClusterConfig | ||||
| 	SideroLink() SideroLinkConfig | ||||
| 	Runtime() RuntimeConfig | ||||
| } | ||||
|  | ||||
							
								
								
									
										52
									
								
								pkg/machinery/config/config/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								pkg/machinery/config/config/runtime.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
| @ -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) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @ -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,13 +73,17 @@ 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 { | ||||
| 		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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										12
									
								
								pkg/machinery/config/configloader/testdata/multidoc1.test
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								pkg/machinery/config/configloader/testdata/multidoc1.test
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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" | ||||
| @ -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 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. | ||||
|  | ||||
							
								
								
									
										31
									
								
								pkg/machinery/config/types/runtime/deep_copy.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								pkg/machinery/config/types/runtime/deep_copy.generated.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										84
									
								
								pkg/machinery/config/types/runtime/event_sink.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								pkg/machinery/config/types/runtime/event_sink.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										104
									
								
								pkg/machinery/config/types/runtime/event_sink_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								pkg/machinery/config/types/runtime/event_sink_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										109
									
								
								pkg/machinery/config/types/runtime/kmsg_log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								pkg/machinery/config/types/runtime/kmsg_log.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										135
									
								
								pkg/machinery/config/types/runtime/kmsg_log_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								pkg/machinery/config/types/runtime/kmsg_log_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										8
									
								
								pkg/machinery/config/types/runtime/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								pkg/machinery/config/types/runtime/runtime.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 . | ||||
							
								
								
									
										3
									
								
								pkg/machinery/config/types/runtime/testdata/eventsink.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								pkg/machinery/config/types/runtime/testdata/eventsink.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| apiVersion: v1alpha1 | ||||
| kind: EventSinkConfig | ||||
| endpoint: 10.0.0.1:3333 | ||||
							
								
								
									
										4
									
								
								pkg/machinery/config/types/runtime/testdata/kmsglog.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								pkg/machinery/config/types/runtime/testdata/kmsglog.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| apiVersion: v1alpha1 | ||||
| kind: KmsgLogConfig | ||||
| name: apiSink | ||||
| url: https://kmsglog.api/logs | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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" | ||||
| ) | ||||
|  | ||||
| @ -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{} | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user