feat: add dynamicLabels to session pod config#113
Conversation
Add DynamicLabelRule struct and dynamicLabels field on PodConfig, enabling per-session pod labels derived from runtime .Job data. Supports direct field mapping (labelKey) and regex pattern extraction (match + labelPrefix) for use cases like Entra group cost tracking.
|
Claude finished @ian-flores's task —— View job Review:
|
Changes: - Add `ValidateDynamicLabelRules()` function that validates regex compilation (ReDoS prevention via Go's RE2 engine) and enforces mutual exclusivity between `labelKey` and `match`/`labelPrefix` - Call validation from `GenerateSessionConfigTemplate()` to reject invalid rules before template rendering - Fix template to handle scalar fields in the `match` branch using `kindIs "slice"` type check instead of unconditional `join` - Add `trunc 63` to label key suffix in template to enforce Kubernetes label value length limits - Remove lossy `replace "-" "_"` transformation — hyphens are valid in label keys - Add kubebuilder `MaxLength`/`MinLength` validation markers to `DynamicLabelRule` fields - Add validation unit tests covering mutual exclusivity, missing fields, invalid regex, and generation-time rejection
| {{- $str := (kindIs "slice" $val) | ternary ($val | join " ") ($val | toString) }} | ||
| {{- $matches := regexFindAll $rule.match $str -1 }} | ||
| {{- range $match := $matches }} | ||
| {{ trimPrefix ($rule.trimPrefix | default "") $match | lower | replace " " "_" | trunc 63 | printf "%s%s" $rule.labelPrefix }}: {{ $rule.labelValue | default "true" | quote }} |
There was a problem hiding this comment.
Design choice: The lower | replace " " "_" | replace "-" "_" normalization is intentionally hardcoded rather than configurable. This covers the common case (Kubernetes label compliance) and avoids complexity. If customers need different sanitization, we can add a transform field later.
| // to a label (using labelKey) or extracts multiple labels via regex (using match). | ||
| // +kubebuilder:object:generate=true | ||
| type DynamicLabelRule struct { | ||
| // Field is the name of a top-level .Job field to read (e.g., "user", "args"). |
There was a problem hiding this comment.
Trust model: field accepts any top-level .Job key, which means CRD authors can surface any launcher job field as a pod label. This is acceptable because CRD write access is already a privileged operation (cluster admin or namespace admin). Documented this explicitly in the field comment.
| LabelValue string `json:"labelValue,omitempty"` | ||
| } | ||
|
|
||
| // ValidateDynamicLabelRules validates a slice of DynamicLabelRule, checking for |
There was a problem hiding this comment.
Validation strategy: Mutual exclusivity (labelKey vs match) and regex compilation are validated programmatically in ValidateDynamicLabelRules(), called at template generation time. Kubebuilder markers handle length constraints at admission. This catches errors before they reach the Go template engine at session launch.
| {{- if $rule.labelKey }} | ||
| {{ $rule.labelKey }}: {{ $val | toString | quote }} | ||
| {{- else if $rule.match }} | ||
| {{- $str := (kindIs "slice" $val) | ternary ($val | join " ") ($val | toString) }} |
There was a problem hiding this comment.
Scalar vs array handling: Uses kindIs "slice" with a ternary to handle both array fields (like args) and scalar fields (like user). Scalars get toString, arrays get join " ". This prevents the runtime template error that would occur if join was called on a string value.
Review findings addressedAll 7 items from the review have been addressed across commits
Not addressed (future scope):
|
- Merge main and regenerate Helm chart CRDs - Remove accidentally committed .claude/tsc-cache files
Summary
DynamicLabelRulestruct anddynamicLabelsfield onPodConfigCRD, enabling per-session pod labels derived from runtime.JobdatalabelKey) and regex pattern extraction (match+labelPrefix)job.tplto render dynamic labels at session launch timeCloses PTDC-199
Context
A customer needs to track Workbench session costs per Entra ID group using OpenCost. Group names are extracted from session launch arguments and added as pod labels. The customer had a working Go template solution, but the operator reconciles the template ConfigMap, overwriting manual edits. This adds a first-class CRD field.
Usage example
Produces:
posit.entra.group.research_team: "true",posit.entra.group.data_science: "true"Test plan
just mgeneratesucceeds (deepcopy + CRD manifests)just testpassesdynamicLabels, launch session, verify pod labels