Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion controller/proposal/bare_pod_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (m *BarePodManager) Claim(ctx context.Context, proposalName, step, _ string
Name: podName,
Namespace: m.Namespace,
Labels: map[string]string{
LabelProposal: proposalName,
LabelProposal: truncateK8sName(proposalName),
LabelStep: step,
},
},
Expand Down
27 changes: 27 additions & 0 deletions controller/proposal/bare_pod_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package proposal

import (
"context"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -79,6 +80,32 @@ func TestBarePodManager_Claim_UsesPerProposalSA(t *testing.T) {
}
}

func TestBarePodManager_Claim_TruncatesLongProposalNameInLabel(t *testing.T) {
fc := newBarePodClient().Build()
builder := &PodSpecBuilder{Image: "quay.io/test/sandbox:latest"}
m := NewBarePodManager(fc, builder, "test-ns")
m.SetStep(
&agenticv1alpha1.Agent{Spec: agenticv1alpha1.AgentSpec{Model: "claude-opus-4-6"}},
testLLMProvider(agenticv1alpha1.LLMProviderAnthropic),
nil,
defaultSandboxSA,
)

longName := strings.Repeat("a", 80)
name, err := m.Claim(context.Background(), longName, "analysis", "")
if err != nil {
t.Fatalf("Claim: %v", err)
}

var pod corev1.Pod
if err := fc.Get(context.Background(), types.NamespacedName{Name: name, Namespace: "test-ns"}, &pod); err != nil {
t.Fatalf("pod not created: %v", err)
}
if len(pod.Labels[LabelProposal]) > 63 {
t.Fatalf("proposal label length %d exceeds 63", len(pod.Labels[LabelProposal]))
}
}

func TestBarePodManager_Claim_AlreadyExists(t *testing.T) {
existing := &corev1.Pod{}
existing.Name = "ls-analysis-my-proposal"
Expand Down
4 changes: 2 additions & 2 deletions controller/proposal/rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func rbacTargetNamespaces(proposal *agenticv1alpha1.Proposal, rbacResult *agenti

func truncateK8sName(name string) string {
if len(name) > 63 {
name = strings.TrimRight(name[:63], "-")
name = strings.TrimRight(name[:63], "-._")
}
return name
}
Expand All @@ -228,7 +228,7 @@ func clusterRoleName(proposalName string) string {

func rbacLabels(proposalName, component string) map[string]string {
return map[string]string{
LabelProposal: proposalName,
LabelProposal: truncateK8sName(proposalName),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really about the truncateK8sName helper a few lines up (around line 216) rather than this line exactly, but it's the same concern this PR is fixing so I'll flag it here.

The helper does strings.TrimRight(name[:63], "-"), which only strips a trailing -. Since a Proposal is namespaced, its metadata.name is a DNS-1123 subdomain, so it can also contain ., and a few of the derived names mix in _. A label value has to start and end with an alphanumeric, so if the 63 byte cut happens to land right after a . or _, we'd still produce an invalid label value and hit the exact failure this PR is trying to prevent.

Could we widen the cutset to cover all of them?

name = strings.TrimRight(name[:63], "-._")

Because TrimRight takes a cutset, this cleans up any trailing run of those characters, which is the full set that's legal in the middle of a name but not at the boundary.

It might also be worth adding a truncation test case that ends on one of these, something like strings.Repeat("a", 62) + ".bb", since the current Repeat("a", 80) inputs never exercise a trailing non alphanumeric, so the new behavior would otherwise ship without coverage.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

LabelComponent: component,
}
}
Expand Down
14 changes: 14 additions & 0 deletions controller/proposal/rbac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ func TestTruncateK8sName(t *testing.T) {
{"exactly_63", strings.Repeat("a", 63), strings.Repeat("a", 63)},
{"over_63", strings.Repeat("a", 70), strings.Repeat("a", 63)},
{"trailing_dash_trimmed", strings.Repeat("a", 60) + "---" + strings.Repeat("b", 5), strings.Repeat("a", 60)},
{"trailing_dot_trimmed", strings.Repeat("a", 60) + "..." + strings.Repeat("b", 5), strings.Repeat("a", 60)},
{"trailing_underscore_trimmed", strings.Repeat("a", 60) + "___" + strings.Repeat("b", 5), strings.Repeat("a", 60)},
{"trailing_mixed_trimmed", strings.Repeat("a", 58) + "-._.-" + strings.Repeat("b", 5), strings.Repeat("a", 58)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -778,3 +781,14 @@ func TestRBACLabels(t *testing.T) {
t.Fatalf("expected 2 labels, got %d", len(labels))
}
}

func TestRBACLabels_TruncatesLongProposalName(t *testing.T) {
longName := strings.Repeat("a", 80)
labels := rbacLabels(longName, "execution-rbac")
if len(labels[LabelProposal]) > 63 {
t.Fatalf("proposal label length %d exceeds 63", len(labels[LabelProposal]))
}
if labels[LabelProposal] != strings.Repeat("a", 63) {
t.Errorf("proposal label = %q, want %q", labels[LabelProposal], strings.Repeat("a", 63))
}
}
2 changes: 1 addition & 1 deletion controller/proposal/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func proposalOwnerRef(proposal *agenticv1alpha1.Proposal) metav1.OwnerReference

func resultLabels(proposalName, step string) map[string]string {
return map[string]string{
LabelProposal: proposalName,
LabelProposal: truncateK8sName(proposalName),
LabelStep: step,
}
}
Expand Down
15 changes: 15 additions & 0 deletions controller/proposal/results_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package proposal

import (
"context"
"strings"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -11,6 +12,20 @@ import (
agenticv1alpha1 "github.com/openshift/lightspeed-agentic-operator/api/v1alpha1"
)

func TestResultLabels_TruncatesLongProposalName(t *testing.T) {
longName := strings.Repeat("a", 80)
labels := resultLabels(longName, "analysis")
if len(labels[LabelProposal]) > 63 {
t.Fatalf("proposal label length %d exceeds 63", len(labels[LabelProposal]))
}
if labels[LabelProposal] != strings.Repeat("a", 63) {
t.Errorf("proposal label = %q, want %q", labels[LabelProposal], strings.Repeat("a", 63))
}
if labels[LabelStep] != "analysis" {
t.Errorf("step label = %q, want analysis", labels[LabelStep])
}
}

func TestCreateIdempotent_StatusFieldsWritten(t *testing.T) {
scheme := testScheme()
fc := fake.NewClientBuilder().WithScheme(scheme).
Expand Down
2 changes: 1 addition & 1 deletion controller/proposal/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (m *SandboxManager) buildClaim(claimName, proposalName, step, templateName
"name": claimName,
"namespace": m.Namespace,
"labels": map[string]any{
LabelProposal: proposalName,
LabelProposal: truncateK8sName(proposalName),
LabelStep: step,
},
},
Expand Down
14 changes: 14 additions & 0 deletions controller/proposal/sandbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ func TestBuildClaim_Labels(t *testing.T) {
}
}

func TestBuildClaim_TruncatesLongProposalName(t *testing.T) {
longName := strings.Repeat("a", 80)
m := NewSandboxManager(nil, "ns", "")
claim := m.buildClaim("c", longName, "execution", "tpl")

labels := claim.GetLabels()
if len(labels[LabelProposal]) > 63 {
t.Fatalf("proposal label length %d exceeds 63", len(labels[LabelProposal]))
}
if labels[LabelProposal] != strings.Repeat("a", 63) {
t.Errorf("proposal label = %q, want %q", labels[LabelProposal], strings.Repeat("a", 63))
}
}

func TestBuildClaim_TemplateRef(t *testing.T) {
m := NewSandboxManager(nil, "ns", "")
claim := m.buildClaim("c", "p", "analysis", "my-template")
Expand Down