Skip to content

Commit 1c28fd9

Browse files
committed
FIX remaining outdated changes in go/core
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
1 parent 2ca2fe9 commit 1c28fd9

5 files changed

Lines changed: 189 additions & 21 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package a2a
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient"
8+
"go.opentelemetry.io/otel/propagation"
9+
"go.opentelemetry.io/otel/trace"
10+
"k8s.io/apimachinery/pkg/types"
11+
)
12+
13+
func TestUpstreamAuthInterceptor_InjectsTraceContext(t *testing.T) {
14+
const rawTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
15+
16+
ctx := propagation.TraceContext{}.Extract(
17+
context.Background(),
18+
propagation.MapCarrier{"traceparent": rawTraceparent},
19+
)
20+
21+
req := &a2aclient.Request{
22+
BaseURL: "http://agent.default:8080",
23+
ServiceParams: a2aclient.ServiceParams{},
24+
}
25+
interceptor := NewUpstreamAuthInterceptor(nil, types.NamespacedName{})
26+
if _, _, err := interceptor.Before(ctx, req); err != nil {
27+
t.Fatalf("unexpected error: %v", err)
28+
}
29+
30+
gotValues := req.ServiceParams.Get("traceparent")
31+
if len(gotValues) == 0 {
32+
t.Fatal("expected traceparent service param on outgoing request, got none")
33+
}
34+
35+
outCtx := propagation.TraceContext{}.Extract(context.Background(), propagation.MapCarrier{"traceparent": gotValues[0]})
36+
wantTraceID := trace.SpanContextFromContext(ctx).TraceID()
37+
gotTraceID := trace.SpanContextFromContext(outCtx).TraceID()
38+
if wantTraceID != gotTraceID {
39+
t.Errorf("trace ID: want %s, got %s", wantTraceID, gotTraceID)
40+
}
41+
}
42+
43+
func TestUpstreamAuthInterceptor_NoTraceContext(t *testing.T) {
44+
req := &a2aclient.Request{
45+
BaseURL: "http://agent.default:8080",
46+
ServiceParams: a2aclient.ServiceParams{},
47+
}
48+
interceptor := NewUpstreamAuthInterceptor(nil, types.NamespacedName{})
49+
if _, _, err := interceptor.Before(context.Background(), req); err != nil {
50+
t.Fatalf("unexpected error: %v", err)
51+
}
52+
53+
if got := req.ServiceParams.Get("traceparent"); len(got) != 0 {
54+
t.Errorf("expected no traceparent service param, got %q", got)
55+
}
56+
}

go/core/internal/a2a/trace.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import (
1616

1717
// a2aTracingMiddleware is an A2A server middleware that creates an invoke_agent
1818
// span for each inbound A2A request, annotated with GenAI semantic convention
19-
// attributes. The span becomes the parent of any outbound proxy calls made by
20-
// traceInjectHandler, giving a clean agent-invocation span hierarchy in Jaeger.
19+
// attributes. Outbound client interceptors inject that span into proxied agent
20+
// calls, giving a clean agent-invocation span hierarchy in Jaeger.
2121
type a2aTracingMiddleware struct {
2222
agentRef types.NamespacedName
2323
provider attribute.KeyValue

go/core/internal/httpserver/handlers/agents.go

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
apierrors "k8s.io/apimachinery/pkg/api/errors"
1919
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020
"k8s.io/apimachinery/pkg/types"
21+
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
2122
"sigs.k8s.io/controller-runtime/pkg/client"
2223
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
2324
)
@@ -32,35 +33,46 @@ func NewAgentsHandler(base *Base) *AgentsHandler {
3233
return &AgentsHandler{Base: base}
3334
}
3435

35-
// HandleListAgents handles GET /api/agents requests using database
36+
// HandleListAgents handles GET /api/agents requests using database.
37+
// Optional query param: namespace=<ns>.
3638
func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) {
3739
log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "list-db")
3840

39-
if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil {
40-
w.RespondWithError(err)
41+
namespace := r.URL.Query().Get("namespace")
42+
if namespace == "" {
43+
h.handleListAgents(w, r, log)
4144
return
4245
}
4346

44-
agentList := &v1alpha2.AgentList{}
45-
if err := h.KubeClient.List(r.Context(), agentList); err != nil {
46-
w.RespondWithError(errors.NewInternalServerError("Failed to list Agents from Kubernetes", err))
47+
if strings.TrimSpace(namespace) != namespace {
48+
w.RespondWithError(errors.NewBadRequestError(
49+
fmt.Sprintf("invalid namespace %q: must not contain leading or trailing whitespace", namespace),
50+
nil,
51+
))
4752
return
4853
}
4954

50-
agentsWithID := make([]api.AgentResponse, 0)
51-
h.appendAgentResponses(r.Context(), log, agentObjects(agentList.Items), &agentsWithID)
55+
if errs := utilvalidation.IsDNS1123Label(namespace); len(errs) > 0 {
56+
w.RespondWithError(errors.NewBadRequestError(
57+
fmt.Sprintf("invalid namespace %q: %s", namespace, strings.Join(errs, "; ")),
58+
nil,
59+
))
60+
return
61+
}
5262

53-
harnessList := &v1alpha2.AgentHarnessList{}
54-
if err := h.KubeClient.List(r.Context(), harnessList); err != nil {
55-
w.RespondWithError(errors.NewInternalServerError("Failed to list AgentHarness resources from Kubernetes", err))
63+
h.handleListAgents(w, r, log.WithValues("namespace", namespace), client.InNamespace(namespace))
64+
}
65+
66+
func (h *AgentsHandler) handleListAgents(w ErrorResponseWriter, r *http.Request, log logr.Logger, opts ...client.ListOption) {
67+
if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil {
68+
w.RespondWithError(err)
5669
return
5770
}
58-
for i := range harnessList.Items {
59-
sb := &harnessList.Items[i]
60-
if sb.Spec.Backend != v1alpha2.AgentHarnessBackendOpenClaw && sb.Spec.Backend != v1alpha2.AgentHarnessBackendNemoClaw {
61-
continue
62-
}
63-
agentsWithID = append(agentsWithID, h.openshellAgentHarnessAgentResponse(r.Context(), log, sb))
71+
72+
agentsWithID, err := h.listAgentResponses(r.Context(), log, opts...)
73+
if err != nil {
74+
w.RespondWithError(err)
75+
return
6476
}
6577

6678
log.Info("Successfully listed agents", "count", len(agentsWithID))

go/core/internal/httpserver/handlers/agents_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,92 @@ func TestHandleListAgents(t *testing.T) {
500500
}
501501
require.True(t, found)
502502
})
503+
504+
t.Run("filters Agent and AgentHarness rows by namespace query parameter", func(t *testing.T) {
505+
modelConfig := createTestModelConfig()
506+
agentDefault := createTestAgent("agent-in-default", modelConfig)
507+
agentOther := &v1alpha2.Agent{
508+
ObjectMeta: metav1.ObjectMeta{Name: "agent-in-other", Namespace: "other"},
509+
Spec: v1alpha2.AgentSpec{
510+
Type: v1alpha2.AgentType_Declarative,
511+
Declarative: &v1alpha2.DeclarativeAgentSpec{
512+
ModelConfig: modelConfig.Name,
513+
},
514+
},
515+
}
516+
harnessDefault := &v1alpha2.AgentHarness{
517+
ObjectMeta: metav1.ObjectMeta{Name: "harness-default", Namespace: "default"},
518+
Spec: v1alpha2.AgentHarnessSpec{
519+
Backend: v1alpha2.AgentHarnessBackendOpenClaw,
520+
ModelConfigRef: "test-model-config",
521+
},
522+
}
523+
harnessOther := &v1alpha2.AgentHarness{
524+
ObjectMeta: metav1.ObjectMeta{Name: "harness-other", Namespace: "other"},
525+
Spec: v1alpha2.AgentHarnessSpec{
526+
Backend: v1alpha2.AgentHarnessBackendOpenClaw,
527+
ModelConfigRef: "test-model-config",
528+
},
529+
}
530+
unsupportedHarnessDefault := &v1alpha2.AgentHarness{
531+
ObjectMeta: metav1.ObjectMeta{Name: "unsupported-harness", Namespace: "default"},
532+
Spec: v1alpha2.AgentHarnessSpec{
533+
Backend: v1alpha2.AgentHarnessBackendType("unsupported"),
534+
ModelConfigRef: "test-model-config",
535+
},
536+
}
537+
handler, _ := setupTestHandler(t, agentDefault, agentOther, harnessDefault, harnessOther, unsupportedHarnessDefault, modelConfig)
538+
539+
req := httptest.NewRequest("GET", "/api/agents?namespace=default", nil)
540+
req = setUser(req, "test-user")
541+
w := httptest.NewRecorder()
542+
543+
handler.HandleListAgents(&testErrorResponseWriter{w}, req)
544+
545+
require.Equal(t, http.StatusOK, w.Code)
546+
var response api.StandardResponse[[]api.AgentResponse]
547+
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response))
548+
require.Len(t, response.Data, 2)
549+
550+
byName := make(map[string]api.AgentResponse, len(response.Data))
551+
for _, row := range response.Data {
552+
byName[row.Agent.Metadata.Name] = row
553+
require.Equal(t, "default", row.Agent.Metadata.Namespace)
554+
}
555+
require.Contains(t, byName, "agent-in-default")
556+
require.Contains(t, byName, "harness-default")
557+
require.NotContains(t, byName, "agent-in-other")
558+
require.NotContains(t, byName, "harness-other")
559+
require.NotContains(t, byName, "unsupported-harness")
560+
})
561+
562+
// Kubernetes namespace names must be DNS-1123 labels. Rejecting invalid input
563+
// before calling the Kubernetes client keeps the list path consistent with
564+
// other resource handlers and avoids surprising cross-namespace behavior.
565+
t.Run("returns 400 for invalid namespace query value", func(t *testing.T) {
566+
handler, _ := setupTestHandler(t)
567+
568+
req := httptest.NewRequest("GET", "/api/agents?namespace=INVALID_NS!", nil)
569+
req = setUser(req, "test-user")
570+
w := httptest.NewRecorder()
571+
572+
handler.HandleListAgents(&testErrorResponseWriter{w}, req)
573+
574+
require.Equal(t, http.StatusBadRequest, w.Code)
575+
})
576+
577+
t.Run("returns 400 for namespace query value with leading or trailing whitespace", func(t *testing.T) {
578+
handler, _ := setupTestHandler(t)
579+
580+
req := httptest.NewRequest("GET", "/api/agents?namespace=%20default", nil)
581+
req = setUser(req, "test-user")
582+
w := httptest.NewRecorder()
583+
584+
handler.HandleListAgents(&testErrorResponseWriter{w}, req)
585+
586+
require.Equal(t, http.StatusBadRequest, w.Code)
587+
require.Contains(t, w.Body.String(), "must not contain leading or trailing whitespace")
588+
})
503589
}
504590

505591
func TestHandleListSandboxAgents(t *testing.T) {

go/core/test/e2e/invoke_api_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,15 @@ func TestE2EInvokeCrewAIAgent(t *testing.T) {
10751075
}
10761076

10771077
func TestE2EInvokeSTSIntegration(t *testing.T) {
1078+
runE2EInvokeSTSIntegration(t, "python", nil)
1079+
}
1080+
1081+
func TestE2EGoInvokeSTSIntegration(t *testing.T) {
1082+
goRuntime := v1alpha2.DeclarativeRuntime_Go
1083+
runE2EInvokeSTSIntegration(t, "go", &goRuntime)
1084+
}
1085+
1086+
func runE2EInvokeSTSIntegration(t *testing.T, runtimeName string, runtimeOverride *v1alpha2.DeclarativeRuntime) {
10781087
// Setup mock STS server
10791088
agentName := "test-sts"
10801089
agentServiceAccount := fmt.Sprintf("system:serviceaccount:kagent:%s", agentName)
@@ -1110,8 +1119,9 @@ func TestE2EInvokeSTSIntegration(t *testing.T) {
11101119

11111120
modelCfg := setupModelConfig(t, cli, baseURL)
11121121
agent := setupAgentWithOptions(t, cli, modelCfg.Name, tools, AgentOptions{
1113-
Name: "test-sts-agent",
1122+
Name: "test-sts-agent-" + runtimeName,
11141123
SystemMessage: "You are an agent that adds numbers using the add tool available to you through the everything-mcp-server.",
1124+
Runtime: runtimeOverride,
11151125
Env: []corev1.EnvVar{
11161126
{
11171127
Name: "STS_WELL_KNOWN_URI",
@@ -1139,7 +1149,7 @@ func TestE2EInvokeSTSIntegration(t *testing.T) {
11391149
a2aURL := a2aUrl(agent.Namespace, agent.Name)
11401150
a2aClient := newA2AClient(t, a2aURL, httpClient, nil)
11411151

1142-
t.Run("sync_invocation", func(t *testing.T) {
1152+
t.Run(runtimeName+"/sts_exchange_sync_invocation", func(t *testing.T) {
11431153
runSyncTest(t, a2aClient, "add 3 and 5", "8", nil)
11441154

11451155
// verify our mock STS server received the token exchange request
@@ -1150,6 +1160,10 @@ func TestE2EInvokeSTSIntegration(t *testing.T) {
11501160
// which contains the may act claim
11511161
stsRequest := stsRequests[0]
11521162
require.Equal(t, subjectToken, stsRequest.SubjectToken)
1163+
require.Equal(t, "urn:ietf:params:oauth:grant-type:token-exchange", stsRequest.GrantType)
1164+
require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", stsRequest.SubjectTokenType)
1165+
require.NotEmpty(t, stsRequest.ActorToken)
1166+
require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", stsRequest.ActorTokenType)
11531167
})
11541168
}
11551169

0 commit comments

Comments
 (0)