Skip to content

Commit ed63603

Browse files
authored
Local node labels (#984)
* register: Send local labels Implement sending labels from the elemental-register binary to the elemental-operator. The labels can be specified as flags to the binary '--label env=staging --label region=eu-north' or in a local file (defaults to '/etc/elemental/labels.yaml' but can be changed using --local-labels-file flag.) Signed-off-by: Fredrik Lönnegren <fredrik.lonnegren@suse.com> * Add label prefix field to MachineRegistration The prefix can be used to set a custom prefix to the labels generated for MachineInventories. If a '-' is specified, no prefix is added to the Labels and Annotations for the MachineInventory. * Updates for e2e tests This commit tries to bump dependencies and fix configuration for the e2e tests. Following actions were taken: * Remove namespace from rbac role-binding roleRef (not a valid field) * Update kubernetes version to v1.34.3 used by kind * Update tested rancher version to v2.13.3 * Update cert-manager version to v1.20.2 * Update nginx-controller version to v1.15.1 * Update system-upgrade-controller version to v0.19.2 * Update logic in tests checking for rancher readiness * Remove CATTLE_BOOTSTRAP_PASSWORD environment (setting duplicate env) Signed-off-by: Fredrik Lönnegren <fredrik.lonnegren@suse.com> * Set labels using the templater This change makes it possible to use the value templates (eg ${Runtime/Hostname} as a value for the local labels sent to the registration API. Signed-off-by: Fredrik Lönnegren <fredrik.lonnegren@suse.com>
1 parent df7f425 commit ed63603

26 files changed

Lines changed: 269 additions & 1247 deletions

File tree

.obs/chartfile/elemental-operator-crds-helm/templates/crds.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,10 @@ spec:
897897
emulated-tpm-seed:
898898
format: int64
899899
type: integer
900+
labels:
901+
additionalProperties:
902+
type: string
903+
type: object
900904
no-smbios:
901905
type: boolean
902906
no-toolkit:
@@ -992,6 +996,11 @@ spec:
992996
type: object
993997
type: object
994998
type: object
999+
labelPrefix:
1000+
description: |-
1001+
LabelPrefix is prepended to client-sent label and annotation keys (with a "/" separator).
1002+
Defaults to "elemental.cattle.io". Set to "-" to disable prefixing.
1003+
type: string
9951004
machineInventoryAnnotations:
9961005
additionalProperties:
9971006
type: string

.obs/chartfile/elemental-operator-helm/templates/rbac.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,6 @@ roleRef:
330330
apiGroup: rbac.authorization.k8s.io
331331
kind: Role
332332
name: '{{ .Release.Name }}'
333-
namespace: fleet-default
334333
subjects:
335334
- kind: ServiceAccount
336335
name: '{{ .Release.Name }}'

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ endif
2020
export ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
2121
CHART?=$(shell find $(ROOT_DIR) -type f -name "elemental-operator-$(CHART_VERSION).tgz" -print)
2222
CHART_CRDS?=$(shell find $(ROOT_DIR) -type f -name "elemental-operator-crds-$(CHART_VERSION).tgz" -print)
23-
KUBE_VERSION?="v1.27.10"
23+
KUBE_VERSION?="v1.34.3"
2424
CLUSTER_NAME?="operator-e2e"
2525
COMMITDATE?=$(shell git log -n1 --format="%as")
2626
GO_TPM_TAG?=$(shell grep google/go-tpm-tools go.mod | awk '{print $$2}')

api/v1beta1/machineregistration_types.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,26 @@ type MachineRegistrationSpec struct {
4545
// MachineInventoryAnnotations annotations to be added to the created MachineInventory object.
4646
// +optional
4747
MachineInventoryAnnotations map[string]string `json:"machineInventoryAnnotations,omitempty"`
48+
// LabelPrefix is prepended to client-sent label and annotation keys (with a "/" separator).
49+
// Defaults to "elemental.cattle.io". Set to "-" to disable prefixing.
50+
// +optional
51+
LabelPrefix string `json:"labelPrefix,omitempty"`
4852
// Config the cloud config that will be used to provision the node.
4953
// +optional
5054
Config Config `json:"config,omitempty"`
5155
}
5256

57+
func (s *MachineRegistrationSpec) GetLabelPrefix() string {
58+
switch s.LabelPrefix {
59+
case "":
60+
return "elemental.cattle.io/"
61+
case "-":
62+
return ""
63+
default:
64+
return s.LabelPrefix + "/"
65+
}
66+
}
67+
5368
type MachineRegistrationStatus struct {
5469
// Conditions describe the state of the machine registration object.
5570
// +optional

api/v1beta1/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ type Registration struct {
109109
Auth string `json:"auth,omitempty" yaml:"auth,omitempty" mapstructure:"auth"`
110110
// +optional
111111
NoToolkit bool `json:"no-toolkit,omitempty" yaml:"no-toolkit,omitempty" mapstructure:"no-toolkit"`
112+
// +optional
113+
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty" mapstructure:"labels"`
112114
}
113115

114116
type SystemAgent struct {

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/register/main.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const (
4141
defaultLiveStatePath = "/tmp/registration/state.yaml"
4242
defaultConfigPath = "/oem/registration/config.yaml"
4343
defaultLiveConfigPath = "/run/initramfs/live/livecd-cloud-config.yaml"
44+
defaultLocalLabelsFile = "/etc/elemental/labels.yaml"
4445
registrationUpdateSuppressTimer = 24 * time.Hour
4546
)
4647

@@ -53,6 +54,8 @@ var (
5354
disableBootEntry bool
5455
configPath string
5556
statePath string
57+
localLabelsFile string
58+
labels map[string]string
5659
)
5760

5861
var (
@@ -100,6 +103,17 @@ func newCommand(fs vfs.FS, client register.Client, stateHandler register.StateHa
100103
if err := initConfig(fs); err != nil {
101104
return fmt.Errorf("initializing configuration: %w", err)
102105
}
106+
// Merge labels from local file into config
107+
if err := mergeLocalLabelsFile(fs); err != nil {
108+
return fmt.Errorf("loading local labels file: %w", err)
109+
}
110+
// Merge CLI labels into config (overrides file labels)
111+
for k, v := range labels {
112+
if cfg.Elemental.Registration.Labels == nil {
113+
cfg.Elemental.Registration.Labels = map[string]string{}
114+
}
115+
cfg.Elemental.Registration.Labels[k] = v
116+
}
103117
// Load Registration State
104118
if err := stateHandler.Init(statePath); err != nil {
105119
return fmt.Errorf("initializing state handler on path '%s': %w", statePath, err)
@@ -189,6 +203,8 @@ func newCommand(fs vfs.FS, client register.Client, stateHandler register.StateHa
189203
cmd.Flags().BoolVar(&installation, "install", false, "Install a new machine")
190204
cmd.Flags().BoolVar(&cfg.Elemental.Registration.NoToolkit, "no-toolkit", false, "No OS management via elemental-toolkit, only Install agent config files to local filesystem (for pre-installed hosts)")
191205
cmd.Flags().BoolVar(&disableBootEntry, "disable-boot-entry", false, "Don't create an EFI entry for the system during install/reset")
206+
cmd.Flags().StringToStringVar(&labels, "label", nil, "Client-side labels to add to the MachineInventory (key=value pairs)")
207+
cmd.Flags().StringVar(&localLabelsFile, "local-labels-file", defaultLocalLabelsFile, "Path to a YAML file containing labels to add to the MachineInventory")
192208
return cmd
193209
}
194210

@@ -239,6 +255,29 @@ func initConfig(fs vfs.FS) error {
239255
return nil
240256
}
241257

258+
func mergeLocalLabelsFile(fs vfs.FS) error {
259+
if _, err := fs.Stat(localLabelsFile); err != nil {
260+
log.Debugf("Local labels file '%s' not found, skipping", localLabelsFile)
261+
return nil
262+
}
263+
data, err := fs.ReadFile(localLabelsFile)
264+
if err != nil {
265+
return fmt.Errorf("reading local labels file '%s': %w", localLabelsFile, err)
266+
}
267+
fileLabels := map[string]string{}
268+
if err := yaml.Unmarshal(data, &fileLabels); err != nil {
269+
return fmt.Errorf("parsing local labels file '%s': %w", localLabelsFile, err)
270+
}
271+
log.Infof("Loaded %d labels from '%s'", len(fileLabels), localLabelsFile)
272+
for k, v := range fileLabels {
273+
if cfg.Elemental.Registration.Labels == nil {
274+
cfg.Elemental.Registration.Labels = map[string]string{}
275+
}
276+
cfg.Elemental.Registration.Labels[k] = v
277+
}
278+
return nil
279+
}
280+
242281
func getRegistrationCA(fs vfs.FS, config elementalv1.Config) ([]byte, error) {
243282
registration := config.Elemental.Registration
244283

cmd/register/main_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,76 @@ var _ = Describe("elemental-register --reset", Label("registration", "cli", "res
363363
})
364364
})
365365

366+
var _ = Describe("elemental-register --local-labels-file", Label("registration", "cli", "labels"), func() {
367+
var fs vfs.FS
368+
var err error
369+
var fsCleanup func()
370+
var cmd *cobra.Command
371+
var mockCtrl *gomock.Controller
372+
var client *rmocks.MockClient
373+
var installer *imocks.MockInstaller
374+
var stateHandler *rmocks.MockStateHandler
375+
BeforeEach(func() {
376+
fs, fsCleanup, err = vfst.NewTestFS(map[string]interface{}{})
377+
Expect(err).ToNot(HaveOccurred())
378+
mockCtrl = gomock.NewController(GinkgoT())
379+
client = rmocks.NewMockClient(mockCtrl)
380+
installer = imocks.NewMockInstaller(mockCtrl)
381+
stateHandler = rmocks.NewMockStateHandler(mockCtrl)
382+
cmd = newCommand(fs, client, stateHandler, installer)
383+
DeferCleanup(fsCleanup)
384+
})
385+
When("using existing default config", func() {
386+
BeforeEach(func() {
387+
marshalIntoFile(fs, baseConfigFixture, defaultConfigPath)
388+
stateHandler.EXPECT().Init(defaultStatePath).Return(nil)
389+
stateHandler.EXPECT().Load().Return(stateFixture, nil)
390+
stateHandler.EXPECT().Save(stateFixture).Return(nil)
391+
})
392+
It("should load labels from the default file path", func() {
393+
fileLabels := map[string]string{"env": "production", "region": "us-east"}
394+
marshalIntoFile(fs, fileLabels, defaultLocalLabelsFile)
395+
cmd.SetArgs([]string{})
396+
wantReg := baseConfigFixture.Elemental.Registration.DeepCopy()
397+
wantReg.Labels = map[string]string{"env": "production", "region": "us-east"}
398+
client.EXPECT().
399+
Register(*wantReg, []byte(wantReg.CACert), &stateFixture).
400+
Return(marshalToBytes(baseConfigFixture), nil)
401+
Expect(cmd.Execute()).ToNot(HaveOccurred())
402+
})
403+
It("should skip missing labels file without error", func() {
404+
cmd.SetArgs([]string{})
405+
client.EXPECT().
406+
Register(baseConfigFixture.Elemental.Registration, []byte(baseConfigFixture.Elemental.Registration.CACert), &stateFixture).
407+
Return(marshalToBytes(baseConfigFixture), nil)
408+
Expect(cmd.Execute()).ToNot(HaveOccurred())
409+
})
410+
It("should let CLI labels override file labels", func() {
411+
fileLabels := map[string]string{"env": "from-file", "region": "us-east"}
412+
marshalIntoFile(fs, fileLabels, defaultLocalLabelsFile)
413+
cmd.SetArgs([]string{"--label", "env=from-cli"})
414+
wantReg := baseConfigFixture.Elemental.Registration.DeepCopy()
415+
wantReg.Labels = map[string]string{"env": "from-cli", "region": "us-east"}
416+
client.EXPECT().
417+
Register(*wantReg, []byte(wantReg.CACert), &stateFixture).
418+
Return(marshalToBytes(baseConfigFixture), nil)
419+
Expect(cmd.Execute()).ToNot(HaveOccurred())
420+
})
421+
It("should load labels from a custom file path", func() {
422+
customPath := "/custom/path/labels.yaml"
423+
fileLabels := map[string]string{"custom": "label"}
424+
marshalIntoFile(fs, fileLabels, customPath)
425+
cmd.SetArgs([]string{"--local-labels-file", customPath})
426+
wantReg := baseConfigFixture.Elemental.Registration.DeepCopy()
427+
wantReg.Labels = map[string]string{"custom": "label"}
428+
client.EXPECT().
429+
Register(*wantReg, []byte(wantReg.CACert), &stateFixture).
430+
Return(marshalToBytes(baseConfigFixture), nil)
431+
Expect(cmd.Execute()).ToNot(HaveOccurred())
432+
})
433+
})
434+
})
435+
366436
func marshalIntoFile(fs vfs.FS, input any, filePath string) {
367437
bytes := marshalToBytes(input)
368438
Expect(vfs.MkdirAll(fs, path.Dir(filePath), os.ModePerm)).ToNot(HaveOccurred())

config/crd/bases/elemental.cattle.io_machineregistrations.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ spec:
130130
emulated-tpm-seed:
131131
format: int64
132132
type: integer
133+
labels:
134+
additionalProperties:
135+
type: string
136+
type: object
133137
no-smbios:
134138
type: boolean
135139
no-toolkit:
@@ -225,6 +229,11 @@ spec:
225229
type: object
226230
type: object
227231
type: object
232+
labelPrefix:
233+
description: |-
234+
LabelPrefix is prepended to client-sent label and annotation keys (with a "/" separator).
235+
Defaults to "elemental.cattle.io". Set to "-" to disable prefixing.
236+
type: string
228237
machineInventoryAnnotations:
229238
additionalProperties:
230239
type: string

config/rbac/bases/role_binding.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ roleRef:
77
apiGroup: rbac.authorization.k8s.io
88
kind: Role
99
name: manager-role
10-
namespace: fleet-default
1110
subjects:
1211
- kind: ServiceAccount
1312
name: manager-role

0 commit comments

Comments
 (0)