feat(node): declare labels in the node config file instead of --labels - #382
feat(node): declare labels in the node config file instead of --labels#382kaisoz wants to merge 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request transitions the declaration of operator labels from command-line flags to the node configuration file (sam-node.yaml), removing the --labels flag and updating configuration schemas, tests, and documentation. The review feedback identifies multiple potential nil pointer dereference risks when accessing nodeConfig.Labels directly and suggests adding defensive nil checks to prevent panics.
| // inference services announce model IDs, MCP services announce tool names. | ||
| func (n *SamNode) discoverySource() []discovery.Announcement { | ||
| labels := n.config.Labels // validated at startup | ||
| labels := n.nodeConfig.Labels // validated at load |
There was a problem hiding this comment.
Accessing n.nodeConfig.Labels directly without a nil check on n.nodeConfig can cause a nil pointer dereference panic if the node is initialized with a nil configuration (which is common in testing environments or custom setups). Adding a defensive nil check ensures the node remains robust and panic-free.
var labels map[string]string
if n.nodeConfig != nil {
labels = n.nodeConfig.Labels
}There was a problem hiding this comment.
nodeConfig cannot be nil on any node this path reaches. NewSamNode is the only non-test constructor and calls cfg.Default() first, which fills it. It is nil in tests that build SamNode literals, though, so the four accesses now go through a labels() accessor holding a single guard, rather than repeating the check at each call site.
| PublicKey: pubBytes, | ||
| RequestedRole: n.config.RequiredRole, | ||
| Labels: n.config.Labels, // validated at startup | ||
| Labels: n.nodeConfig.Labels, // validated at load |
There was a problem hiding this comment.
Accessing n.nodeConfig.Labels directly without a nil check on n.nodeConfig can cause a nil pointer dereference panic if the node is initialized with a nil configuration. Adding a defensive nil check ensures the node remains robust and panic-free.
| Labels: n.nodeConfig.Labels, // validated at load | |
| Labels: func() map[string]string { if n.nodeConfig != nil { return n.nodeConfig.Labels }; return nil }(), |
| PublicKey: pubBytes, | ||
| RequestedRole: n.config.RequiredRole, | ||
| Labels: n.config.Labels, // validated at startup | ||
| Labels: n.nodeConfig.Labels, // validated at load |
There was a problem hiding this comment.
Accessing n.nodeConfig.Labels directly without a nil check on n.nodeConfig can cause a nil pointer dereference panic if the node is initialized with a nil configuration. Adding a defensive nil check ensures the node remains robust and panic-free.
| Labels: n.nodeConfig.Labels, // validated at load | |
| Labels: func() map[string]string { if n.nodeConfig != nil { return n.nodeConfig.Labels }; return nil }(), |
| return node.revokedPeers != nil && node.revokedPeers.Contains(peerID) | ||
| }, | ||
| localLabels: func() map[string]string { return node.config.Labels }, | ||
| localLabels: func() map[string]string { return node.nodeConfig.Labels }, |
There was a problem hiding this comment.
Accessing node.nodeConfig.Labels directly without a nil check on node.nodeConfig can cause a nil pointer dereference panic if the node is initialized with a nil configuration. Adding a defensive nil check ensures the node remains robust and panic-free.
| localLabels: func() map[string]string { return node.nodeConfig.Labels }, | |
| localLabels: func() map[string]string { if node.nodeConfig != nil { return node.nodeConfig.Labels }; return nil }, |
e08d546 to
3853aa7
Compare
Labels are declared at enrollment, but --labels was registered only on runCmd while joinCmd read the same variable, so `sam-node join` always enrolled with no labels at all. --config is a persistent flag, so moving the declaration into sam-node.yaml makes both enrollment paths carry it. The flag goes with no shim: the config schema is v1alpha1 and every caller lived in this repo. Validation moves to LoadNodeConfig, which names the offending file, and SamNode.LocalPolicy becomes nodeConfig now that the struct carries identity claims alongside policy.
3853aa7 to
fe189d9
Compare
What
Operator-declared node labels move from
sam-node run --labels region=eu,team=platformto a top-levellabels:map in the node config file. The--labelsflag is removed.Why
Labels are declared at enrollment and attested by the control plane. But
--labelswas registered only onrunCmd, whilejoinCmdread the same package-level variable - sosam-node joincould never set it and always enrolled with no labels, silently. That is the path the unauthenticated sidecar tells operators to run.--configis a persistent flag, so moving the declaration intosam-node.yamlmakes both enrollment paths carry it.Breaking change
sam-node run --labels region=eunow fails withunknown flag: --labels. No deprecation shim: the config schema isv1alpha1and every caller lived in this repo.The schema version is not bumped.
LoadNodeConfigusesyaml.UnmarshalStrict, so an older binary already refuses a config carrying an unknownlabels:key - which is what that check is for.Notable details
api.ValidateLabelsruns inLoadNodeConfigand the error names the offending file, matching how a bad service or policy is reported. The check innode.Options.Validateis dropped: nothing covered it, the control plane re-validates enrollment labels fail-closed, and gossiped labels only rank candidates - the gate reads control-plane-signedlabel()facts.,,=, or control characters). YAML would allow them, but the comma-separatedkey=valuewire format did not move: it is still theX-Sam-Required-Labelsheader and thelabel()facts.SamNode.LocalPolicyis renamednodeConfig, since the struct now carries identity claims alongsideServicesand the attenuation rules.Tests
internal/node/config_test.go- labels parsed; malformed key rejected.tests/integration/{a2a,openai_facade}_test.go- labels declared through the config file;writeServicesConfigbecomeswriteNodeConfig(t, dir, labels, services...).tests/e2e/a2a_mesh.bats- labels move intotests/e2e/docker/a2a-echo/sam-node-config.yaml.tests/e2e/auth_flows.bats- new:sam-node joinwith a labels config must come back attested, read from/sam/identityover the Unix socket. This is the regression the change fixes.