Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2e92dd4

Browse files
authored
feat: support CODER_AGENT_TOKEN from Kubernetes secrets (#144)
* feat: support CODER_AGENT_TOKEN from Kubernetes secretsThis change adds support for reading CODER_AGENT_TOKEN from Kubernetessecrets via secretKeyRef, in addition to the existing inline value support.Changes:- Add resolveEnvValue helper function that resolves env var values from either direct values or secretKeyRef references- Update Pod handler to use resolveEnvValue for token resolution- Update ReplicaSet handler to use resolveEnvValue for token resolution- Add comprehensive tests for secretKeyRef functionalityThe implementation is fully backward compatible:- Existing inline env.Value tokens continue to work unchanged- secretKeyRef support is additive, not a breaking change- Optional secrets that don't exist are handled gracefully- Errors fetching required secrets log warnings and skip the podUsers who want to use secretKeyRef will need to ensure their serviceaccount has RBAC permissions to get secrets in the watched namespaces.Fixes#139* chore: add secrets RBAC permission to helm chartRequired to support reading CODER_AGENT_TOKEN from Kubernetes secretsvia secretKeyRef.* test: add integration tests for secretKeyRef supportAdd integration tests that verify CODER_AGENT_TOKEN can be read fromKubernetes secrets via secretKeyRef for both Pods and ReplicaSets.* chore: add *.test to gitignore
1 parent8ea3f21 commit2e92dd4

File tree

5 files changed

+496
-4
lines changed

5 files changed

+496
-4
lines changed

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
coder-logstream-kube
22
coder-logstream-kube-*
3+
*.test
34
build/

‎helm/templates/service.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
-apiGroups:[""]
33
resources:["pods", "events"]
44
verbs:["get", "watch", "list"]
5+
-apiGroups:[""]
6+
resources:["secrets"]
7+
verbs:["get"]
58
-apiGroups:["apps"]
69
resources:["replicasets", "events"]
710
verbs:["get", "watch", "list"]

‎integration_test.go‎

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,209 @@ func TestIntegration_LabelSelector(t *testing.T) {
512512
require.NotContains(t,log,"test-pod-no-label","should not receive logs for unlabeled pod")
513513
}
514514
}
515+
516+
funcTestIntegration_PodWithSecretRef(t*testing.T) {
517+
t.Parallel()
518+
519+
ctx,cancel:=context.WithTimeout(context.Background(),2*time.Minute)
520+
defercancel()
521+
522+
client:=getKubeClient(t)
523+
namespace:=createTestNamespace(t,ctx,client)
524+
525+
// Create a secret containing the agent token
526+
secret:=&corev1.Secret{
527+
ObjectMeta: metav1.ObjectMeta{
528+
Name:"agent-token-secret",
529+
Namespace:namespace,
530+
},
531+
Data:map[string][]byte{
532+
"token": []byte("secret-token-integration"),
533+
},
534+
}
535+
_,err:=client.CoreV1().Secrets(namespace).Create(ctx,secret, metav1.CreateOptions{})
536+
require.NoError(t,err)
537+
538+
// Start fake Coder API server
539+
api:=newFakeAgentAPI(t)
540+
deferapi.server.Close()
541+
542+
agentURL,err:=url.Parse(api.server.URL)
543+
require.NoError(t,err)
544+
545+
// Create the pod event logger
546+
reporter,err:=newPodEventLogger(ctx,podEventLoggerOptions{
547+
client:client,
548+
coderURL:agentURL,
549+
namespaces: []string{namespace},
550+
logger:slogtest.Make(t,nil).Leveled(slog.LevelDebug),
551+
logDebounce:5*time.Second,
552+
})
553+
require.NoError(t,err)
554+
deferreporter.Close()
555+
556+
// Wait for informers to sync
557+
time.Sleep(1*time.Second)
558+
559+
// Create a pod with CODER_AGENT_TOKEN from secretKeyRef
560+
pod:=&corev1.Pod{
561+
ObjectMeta: metav1.ObjectMeta{
562+
Name:"test-pod-secret",
563+
Namespace:namespace,
564+
},
565+
Spec: corev1.PodSpec{
566+
Containers: []corev1.Container{
567+
{
568+
Name:"test-container",
569+
Image:"busybox:latest",
570+
Command: []string{"sleep","3600"},
571+
Env: []corev1.EnvVar{
572+
{
573+
Name:"CODER_AGENT_TOKEN",
574+
ValueFrom:&corev1.EnvVarSource{
575+
SecretKeyRef:&corev1.SecretKeySelector{
576+
LocalObjectReference: corev1.LocalObjectReference{
577+
Name:"agent-token-secret",
578+
},
579+
Key:"token",
580+
},
581+
},
582+
},
583+
},
584+
},
585+
},
586+
NodeSelector:map[string]string{
587+
"non-existent-label":"non-existent-value",
588+
},
589+
},
590+
}
591+
592+
_,err=client.CoreV1().Pods(namespace).Create(ctx,pod, metav1.CreateOptions{})
593+
require.NoError(t,err)
594+
595+
// Wait for log source registration
596+
waitForLogSource(t,ctx,api,30*time.Second)
597+
598+
// Wait for the "Created pod" log
599+
logs,found:=waitForLogContaining(t,ctx,api,30*time.Second,"Created pod")
600+
require.True(t,found,"expected 'Created pod' log, got: %v",logs)
601+
602+
// Delete the pod and verify deletion event
603+
err=client.CoreV1().Pods(namespace).Delete(ctx,pod.Name, metav1.DeleteOptions{})
604+
require.NoError(t,err)
605+
606+
// Wait for the "Deleted pod" log
607+
logs,found=waitForLogContaining(t,ctx,api,30*time.Second,"Deleted pod")
608+
require.True(t,found,"expected 'Deleted pod' log, got: %v",logs)
609+
}
610+
611+
funcTestIntegration_ReplicaSetWithSecretRef(t*testing.T) {
612+
t.Parallel()
613+
614+
ctx,cancel:=context.WithTimeout(context.Background(),2*time.Minute)
615+
defercancel()
616+
617+
client:=getKubeClient(t)
618+
namespace:=createTestNamespace(t,ctx,client)
619+
620+
// Create a secret containing the agent token
621+
secret:=&corev1.Secret{
622+
ObjectMeta: metav1.ObjectMeta{
623+
Name:"agent-token-secret",
624+
Namespace:namespace,
625+
},
626+
Data:map[string][]byte{
627+
"token": []byte("secret-token-rs-integration"),
628+
},
629+
}
630+
_,err:=client.CoreV1().Secrets(namespace).Create(ctx,secret, metav1.CreateOptions{})
631+
require.NoError(t,err)
632+
633+
// Start fake Coder API server
634+
api:=newFakeAgentAPI(t)
635+
deferapi.server.Close()
636+
637+
agentURL,err:=url.Parse(api.server.URL)
638+
require.NoError(t,err)
639+
640+
// Create the pod event logger
641+
reporter,err:=newPodEventLogger(ctx,podEventLoggerOptions{
642+
client:client,
643+
coderURL:agentURL,
644+
namespaces: []string{namespace},
645+
logger:slogtest.Make(t,nil).Leveled(slog.LevelDebug),
646+
logDebounce:5*time.Second,
647+
})
648+
require.NoError(t,err)
649+
deferreporter.Close()
650+
651+
// Wait for informers to sync
652+
time.Sleep(1*time.Second)
653+
654+
// Create a ReplicaSet with CODER_AGENT_TOKEN from secretKeyRef
655+
replicas:=int32(1)
656+
rs:=&appsv1.ReplicaSet{
657+
ObjectMeta: metav1.ObjectMeta{
658+
Name:"test-rs-secret",
659+
Namespace:namespace,
660+
},
661+
Spec: appsv1.ReplicaSetSpec{
662+
Replicas:&replicas,
663+
Selector:&metav1.LabelSelector{
664+
MatchLabels:map[string]string{
665+
"app":"test-rs-secret",
666+
},
667+
},
668+
Template: corev1.PodTemplateSpec{
669+
ObjectMeta: metav1.ObjectMeta{
670+
Labels:map[string]string{
671+
"app":"test-rs-secret",
672+
},
673+
},
674+
Spec: corev1.PodSpec{
675+
Containers: []corev1.Container{
676+
{
677+
Name:"test-container",
678+
Image:"busybox:latest",
679+
Command: []string{"sleep","3600"},
680+
Env: []corev1.EnvVar{
681+
{
682+
Name:"CODER_AGENT_TOKEN",
683+
ValueFrom:&corev1.EnvVarSource{
684+
SecretKeyRef:&corev1.SecretKeySelector{
685+
LocalObjectReference: corev1.LocalObjectReference{
686+
Name:"agent-token-secret",
687+
},
688+
Key:"token",
689+
},
690+
},
691+
},
692+
},
693+
},
694+
},
695+
NodeSelector:map[string]string{
696+
"non-existent-label":"non-existent-value",
697+
},
698+
},
699+
},
700+
},
701+
}
702+
703+
_,err=client.AppsV1().ReplicaSets(namespace).Create(ctx,rs, metav1.CreateOptions{})
704+
require.NoError(t,err)
705+
706+
// Wait for log source registration
707+
waitForLogSource(t,ctx,api,30*time.Second)
708+
709+
// Wait for the "Queued pod from ReplicaSet" log
710+
logs,found:=waitForLogContaining(t,ctx,api,30*time.Second,"Queued pod from ReplicaSet")
711+
require.True(t,found,"expected 'Queued pod from ReplicaSet' log, got: %v",logs)
712+
713+
// Delete the ReplicaSet
714+
err=client.AppsV1().ReplicaSets(namespace).Delete(ctx,rs.Name, metav1.DeleteOptions{})
715+
require.NoError(t,err)
716+
717+
// Wait for the "Deleted ReplicaSet" log
718+
logs,found=waitForLogContaining(t,ctx,api,30*time.Second,"Deleted ReplicaSet")
719+
require.True(t,found,"expected 'Deleted ReplicaSet' log, got: %v",logs)
720+
}

‎logger.go‎

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/google/uuid"
1414
appsv1"k8s.io/api/apps/v1"
1515
corev1"k8s.io/api/core/v1"
16+
k8serrors"k8s.io/apimachinery/pkg/api/errors"
1617
v1"k8s.io/apimachinery/pkg/apis/meta/v1"
1718
"k8s.io/client-go/informers"
1819
"k8s.io/client-go/kubernetes"
@@ -117,6 +118,39 @@ type podEventLogger struct {
117118
lq*logQueuer
118119
}
119120

121+
// resolveEnvValue resolves the value of an environment variable, supporting both
122+
// direct values and secretKeyRef references. Returns empty string if the value
123+
// cannot be resolved (e.g., optional secret not found).
124+
func (p*podEventLogger)resolveEnvValue(ctx context.Context,namespacestring,env corev1.EnvVar) (string,error) {
125+
// Direct value takes precedence (existing behavior)
126+
ifenv.Value!="" {
127+
returnenv.Value,nil
128+
}
129+
130+
// Check for secretKeyRef
131+
ifenv.ValueFrom!=nil&&env.ValueFrom.SecretKeyRef!=nil {
132+
ref:=env.ValueFrom.SecretKeyRef
133+
secret,err:=p.client.CoreV1().Secrets(namespace).Get(ctx,ref.Name, v1.GetOptions{})
134+
iferr!=nil {
135+
// Handle optional secrets gracefully - only ignore NotFound errors
136+
ifref.Optional!=nil&&*ref.Optional&&k8serrors.IsNotFound(err) {
137+
return"",nil
138+
}
139+
return"",fmt.Errorf("get secret %s: %w",ref.Name,err)
140+
}
141+
value,ok:=secret.Data[ref.Key]
142+
if!ok {
143+
ifref.Optional!=nil&&*ref.Optional {
144+
return"",nil
145+
}
146+
return"",fmt.Errorf("secret %s has no key %s",ref.Name,ref.Key)
147+
}
148+
returnstring(value),nil
149+
}
150+
151+
return"",nil
152+
}
153+
120154
// initNamespace starts the informer factory and registers event handlers for a given namespace.
121155
// If provided namespace is empty, it will start the informer factory and register event handlers for all namespaces.
122156
func (p*podEventLogger)initNamespace(namespacestring)error {
@@ -157,15 +191,28 @@ func (p *podEventLogger) initNamespace(namespace string) error {
157191
ifenv.Name!="CODER_AGENT_TOKEN" {
158192
continue
159193
}
194+
195+
token,err:=p.resolveEnvValue(p.ctx,pod.Namespace,env)
196+
iferr!=nil {
197+
p.logger.Warn(p.ctx,"failed to resolve CODER_AGENT_TOKEN",
198+
slog.F("pod",pod.Name),
199+
slog.F("namespace",pod.Namespace),
200+
slog.Error(err))
201+
continue
202+
}
203+
iftoken=="" {
204+
continue
205+
}
206+
160207
registered=true
161-
p.tc.setPodToken(pod.Name,env.Value)
208+
p.tc.setPodToken(pod.Name,token)
162209

163210
// We don't want to add logs to workspaces that are already started!
164211
if!pod.CreationTimestamp.After(startTime) {
165212
continue
166213
}
167214

168-
p.sendLog(pod.Name,env.Value, agentsdk.Log{
215+
p.sendLog(pod.Name,token, agentsdk.Log{
169216
CreatedAt:time.Now(),
170217
Output:fmt.Sprintf("🐳 %s: %s",newColor(color.Bold).Sprint("Created pod"),pod.Name),
171218
Level:codersdk.LogLevelInfo,
@@ -218,10 +265,23 @@ func (p *podEventLogger) initNamespace(namespace string) error {
218265
ifenv.Name!="CODER_AGENT_TOKEN" {
219266
continue
220267
}
268+
269+
token,err:=p.resolveEnvValue(p.ctx,replicaSet.Namespace,env)
270+
iferr!=nil {
271+
p.logger.Warn(p.ctx,"failed to resolve CODER_AGENT_TOKEN",
272+
slog.F("replicaset",replicaSet.Name),
273+
slog.F("namespace",replicaSet.Namespace),
274+
slog.Error(err))
275+
continue
276+
}
277+
iftoken=="" {
278+
continue
279+
}
280+
221281
registered=true
222-
p.tc.setReplicaSetToken(replicaSet.Name,env.Value)
282+
p.tc.setReplicaSetToken(replicaSet.Name,token)
223283

224-
p.sendLog(replicaSet.Name,env.Value, agentsdk.Log{
284+
p.sendLog(replicaSet.Name,token, agentsdk.Log{
225285
CreatedAt:time.Now(),
226286
Output:fmt.Sprintf("🐳 %s: %s",newColor(color.Bold).Sprint("Queued pod from ReplicaSet"),replicaSet.Name),
227287
Level:codersdk.LogLevelInfo,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp