@@ -13,6 +13,7 @@ import (
13
13
"github.com/coder/coder/codersdk"
14
14
"github.com/coder/coder/codersdk/agentsdk"
15
15
"github.com/fatih/color"
16
+ appsv1 "k8s.io/api/apps/v1"
16
17
corev1 "k8s.io/api/core/v1"
17
18
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18
19
"k8s.io/client-go/informers"
@@ -52,8 +53,9 @@ func newPodEventLogger(ctx context.Context, opts podEventLoggerOptions) (*podEve
52
53
errChan : make (chan error , 16 ),
53
54
ctx : ctx ,
54
55
cancelFunc : cancelFunc ,
55
- agentTokenToLogger : map [string ]agentLogger {},
56
+ agentTokenToLogger : map [string ]* agentLogger {},
56
57
podToAgentTokens : map [string ][]string {},
58
+ replicaSetToTokens : map [string ][]string {},
57
59
}
58
60
return reporter , reporter .init ()
59
61
}
@@ -67,8 +69,9 @@ type podEventLogger struct {
67
69
ctx context.Context
68
70
cancelFunc context.CancelFunc
69
71
mutex sync.RWMutex
70
- agentTokenToLogger map [string ]agentLogger
72
+ agentTokenToLogger map [string ]* agentLogger
71
73
podToAgentTokens map [string ][]string
74
+ replicaSetToTokens map [string ][]string
72
75
}
73
76
74
77
// init starts the informer factory and registers event handlers.
@@ -91,6 +94,7 @@ func (p *podEventLogger) init() error {
91
94
// When a Pod is created, it's added to the map of Pods we're
92
95
// interested in. When a Pod is deleted, it's removed from the map.
93
96
podInformer := podFactory .Core ().V1 ().Pods ().Informer ()
97
+ replicaInformer := podFactory .Apps ().V1 ().ReplicaSets ().Informer ()
94
98
eventInformer := eventFactory .Core ().V1 ().Events ().Informer ()
95
99
96
100
_ , err := podInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
@@ -130,7 +134,7 @@ func (p *podEventLogger) init() error {
130
134
}
131
135
}
132
136
if registered {
133
- p .logger .Info (p .ctx , "registered agent pod" , slog .F ("pod " , pod .Name ))
137
+ p .logger .Info (p .ctx , "registered agent pod" , slog .F ("name " , pod .Name ), slog . F ( "namespace" , pod . Namespace ))
134
138
}
135
139
},
136
140
DeleteFunc : func (obj interface {}) {
@@ -153,13 +157,92 @@ func (p *podEventLogger) init() error {
153
157
Level : codersdk .LogLevelError ,
154
158
})
155
159
}
156
- p .logger .Info (p .ctx , "unregistered agent pod" , slog .F ("pod " , pod .Name ))
160
+ p .logger .Info (p .ctx , "unregistered agent pod" , slog .F ("name " , pod .Name ))
157
161
},
158
162
})
159
163
if err != nil {
160
164
return fmt .Errorf ("register pod handler: %w" , err )
161
165
}
162
166
167
+ _ , err = replicaInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
168
+ AddFunc : func (obj interface {}) {
169
+ replica , ok := obj .(* appsv1.ReplicaSet )
170
+ if ! ok {
171
+ p .errChan <- fmt .Errorf ("unexpected replica object type: %T" , obj )
172
+ return
173
+ }
174
+
175
+ // We don't want to add logs to workspaces that are already started!
176
+ if ! replica .CreationTimestamp .After (startTime ) {
177
+ return
178
+ }
179
+
180
+ p .mutex .Lock ()
181
+ defer p .mutex .Unlock ()
182
+
183
+ var registered bool
184
+ for _ , container := range replica .Spec .Template .Spec .Containers {
185
+ for _ , env := range container .Env {
186
+ if env .Name != "CODER_AGENT_TOKEN" {
187
+ continue
188
+ }
189
+ registered = true
190
+ tokens , ok := p .replicaSetToTokens [replica .Name ]
191
+ if ! ok {
192
+ tokens = make ([]string , 0 )
193
+ }
194
+ tokens = append (tokens , env .Value )
195
+ p .replicaSetToTokens [replica .Name ] = tokens
196
+
197
+ p .sendLog (replica .Name , env .Value , agentsdk.StartupLog {
198
+ CreatedAt : time .Now (),
199
+ Output : fmt .Sprintf ("🐳 %s: %s" , newColor (color .Bold ).Sprint ("Queued pod from ReplicaSet" ), replica .Name ),
200
+ Level : codersdk .LogLevelInfo ,
201
+ })
202
+ }
203
+ }
204
+ if registered {
205
+ p .logger .Info (p .ctx , "registered agent pod from ReplicaSet" , slog .F ("name" , replica .Name ))
206
+ }
207
+ },
208
+ DeleteFunc : func (obj interface {}) {
209
+ replicaSet , ok := obj .(* appsv1.ReplicaSet )
210
+ if ! ok {
211
+ p .errChan <- fmt .Errorf ("unexpected replica set delete object type: %T" , obj )
212
+ return
213
+ }
214
+ p .mutex .Lock ()
215
+ defer p .mutex .Unlock ()
216
+ _ , ok = p .replicaSetToTokens [replicaSet .Name ]
217
+ if ! ok {
218
+ return
219
+ }
220
+ delete (p .replicaSetToTokens , replicaSet .Name )
221
+ for _ , pod := range replicaSet .Spec .Template .Spec .Containers {
222
+ name := pod .Name
223
+ if name == "" {
224
+ name = replicaSet .Spec .Template .Name
225
+ }
226
+ tokens , ok := p .podToAgentTokens [name ]
227
+ if ! ok {
228
+ continue
229
+ }
230
+ delete (p .podToAgentTokens , name )
231
+ for _ , token := range tokens {
232
+ p .sendLog (pod .Name , token , agentsdk.StartupLog {
233
+ CreatedAt : time .Now (),
234
+ Output : fmt .Sprintf ("🗑️ %s: %s" , newColor (color .Bold ).Sprint ("Deleted ReplicaSet" ), replicaSet .Name ),
235
+ Level : codersdk .LogLevelError ,
236
+ })
237
+ }
238
+ }
239
+ p .logger .Info (p .ctx , "unregistered ReplicaSet" , slog .F ("name" , replicaSet .Name ))
240
+ },
241
+ })
242
+ if err != nil {
243
+ return fmt .Errorf ("register replicaset handler: %w" , err )
244
+ }
245
+
163
246
_ , err = eventInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
164
247
AddFunc : func (obj interface {}) {
165
248
event , ok := obj .(* corev1.Event )
@@ -175,8 +258,14 @@ func (p *podEventLogger) init() error {
175
258
176
259
p .mutex .Lock ()
177
260
defer p .mutex .Unlock ()
178
- tokens , ok := p .podToAgentTokens [event .InvolvedObject .Name ]
179
- if ! ok {
261
+ var tokens []string
262
+ switch event .InvolvedObject .Kind {
263
+ case "Pod" :
264
+ tokens , ok = p .podToAgentTokens [event .InvolvedObject .Name ]
265
+ case "ReplicaSet" :
266
+ tokens , ok = p .replicaSetToTokens [event .InvolvedObject .Name ]
267
+ }
268
+ if tokens == nil || ! ok {
180
269
return
181
270
}
182
271
@@ -210,23 +299,23 @@ func (p *podEventLogger) init() error {
210
299
// loggerForToken returns a logger for the given pod name and agent token.
211
300
// If a logger already exists for the token, it's returned. Otherwise a new
212
301
// logger is created and returned.
213
- func (p * podEventLogger ) sendLog (podName , token string , log agentsdk.StartupLog ) {
302
+ func (p * podEventLogger ) sendLog (resourceName , token string , log agentsdk.StartupLog ) {
214
303
logger , ok := p .agentTokenToLogger [token ]
215
304
if ! ok {
216
305
client := agentsdk .New (p .coderURL )
217
306
client .SetSessionToken (token )
218
- client .SDK .Logger = p .logger .Named (podName )
307
+ client .SDK .Logger = p .logger .Named (resourceName )
219
308
sendLog , closer := client .QueueStartupLogs (p .ctx , p .logDebounce )
220
309
221
- logger = agentLogger {
310
+ logger = & agentLogger {
222
311
sendLog : sendLog ,
223
312
closer : closer ,
224
313
closeTimer : time .AfterFunc (p .logDebounce * 5 , func () {
225
314
logger .closed .Store (true )
226
315
// We want to have two close cycles for loggers!
227
316
err := closer .Close ()
228
317
if err != nil {
229
- p .logger .Error (p .ctx , "close agent logger" , slog .Error (err ), slog .F ("pod" , podName ))
318
+ p .logger .Error (p .ctx , "close agent logger" , slog .Error (err ), slog .F ("pod" , resourceName ))
230
319
}
231
320
p .mutex .Lock ()
232
321
delete (p .agentTokenToLogger , token )
@@ -239,7 +328,7 @@ func (p *podEventLogger) sendLog(podName, token string, log agentsdk.StartupLog)
239
328
// If the logger was already closed, we await the close before
240
329
// creating a new logger. This is to ensure all loggers get sent in order!
241
330
_ = logger .closer .Close ()
242
- p .sendLog (podName , token , log )
331
+ p .sendLog (resourceName , token , log )
243
332
return
244
333
}
245
334
// We make this 5x the debounce because it's low-cost to persist a few
0 commit comments