@@ -13,6 +13,7 @@ import (
1313 "github.com/coder/coder/codersdk"
1414 "github.com/coder/coder/codersdk/agentsdk"
1515 "github.com/fatih/color"
16+ appsv1 "k8s.io/api/apps/v1"
1617 corev1 "k8s.io/api/core/v1"
1718 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1819 "k8s.io/client-go/informers"
@@ -52,8 +53,9 @@ func newPodEventLogger(ctx context.Context, opts podEventLoggerOptions) (*podEve
5253 errChan : make (chan error , 16 ),
5354 ctx : ctx ,
5455 cancelFunc : cancelFunc ,
55- agentTokenToLogger : map [string ]agentLogger {},
56+ agentTokenToLogger : map [string ]* agentLogger {},
5657 podToAgentTokens : map [string ][]string {},
58+ replicaSetToTokens : map [string ][]string {},
5759 }
5860 return reporter , reporter .init ()
5961}
@@ -67,8 +69,9 @@ type podEventLogger struct {
6769 ctx context.Context
6870 cancelFunc context.CancelFunc
6971 mutex sync.RWMutex
70- agentTokenToLogger map [string ]agentLogger
72+ agentTokenToLogger map [string ]* agentLogger
7173 podToAgentTokens map [string ][]string
74+ replicaSetToTokens map [string ][]string
7275}
7376
7477// init starts the informer factory and registers event handlers.
@@ -91,6 +94,7 @@ func (p *podEventLogger) init() error {
9194 // When a Pod is created, it's added to the map of Pods we're
9295 // interested in. When a Pod is deleted, it's removed from the map.
9396 podInformer := podFactory .Core ().V1 ().Pods ().Informer ()
97+ replicaInformer := podFactory .Apps ().V1 ().ReplicaSets ().Informer ()
9498 eventInformer := eventFactory .Core ().V1 ().Events ().Informer ()
9599
96100 _ , err := podInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
@@ -130,7 +134,7 @@ func (p *podEventLogger) init() error {
130134 }
131135 }
132136 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 ))
134138 }
135139 },
136140 DeleteFunc : func (obj interface {}) {
@@ -153,13 +157,92 @@ func (p *podEventLogger) init() error {
153157 Level : codersdk .LogLevelError ,
154158 })
155159 }
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 ))
157161 },
158162 })
159163 if err != nil {
160164 return fmt .Errorf ("register pod handler: %w" , err )
161165 }
162166
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+
163246 _ , err = eventInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
164247 AddFunc : func (obj interface {}) {
165248 event , ok := obj .(* corev1.Event )
@@ -175,8 +258,14 @@ func (p *podEventLogger) init() error {
175258
176259 p .mutex .Lock ()
177260 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 {
180269 return
181270 }
182271
@@ -210,23 +299,23 @@ func (p *podEventLogger) init() error {
210299// loggerForToken returns a logger for the given pod name and agent token.
211300// If a logger already exists for the token, it's returned. Otherwise a new
212301// 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 ) {
214303 logger , ok := p .agentTokenToLogger [token ]
215304 if ! ok {
216305 client := agentsdk .New (p .coderURL )
217306 client .SetSessionToken (token )
218- client .SDK .Logger = p .logger .Named (podName )
307+ client .SDK .Logger = p .logger .Named (resourceName )
219308 sendLog , closer := client .QueueStartupLogs (p .ctx , p .logDebounce )
220309
221- logger = agentLogger {
310+ logger = & agentLogger {
222311 sendLog : sendLog ,
223312 closer : closer ,
224313 closeTimer : time .AfterFunc (p .logDebounce * 5 , func () {
225314 logger .closed .Store (true )
226315 // We want to have two close cycles for loggers!
227316 err := closer .Close ()
228317 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 ))
230319 }
231320 p .mutex .Lock ()
232321 delete (p .agentTokenToLogger , token )
@@ -239,7 +328,7 @@ func (p *podEventLogger) sendLog(podName, token string, log agentsdk.StartupLog)
239328 // If the logger was already closed, we await the close before
240329 // creating a new logger. This is to ensure all loggers get sent in order!
241330 _ = logger .closer .Close ()
242- p .sendLog (podName , token , log )
331+ p .sendLog (resourceName , token , log )
243332 return
244333 }
245334 // We make this 5x the debounce because it's low-cost to persist a few
0 commit comments