@@ -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"
1617corev1"k8s.io/api/core/v1"
1718v1"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
5253errChan :make (chan error ,16 ),
5354ctx :ctx ,
5455cancelFunc :cancelFunc ,
55- agentTokenToLogger :map [string ]agentLogger {},
56+ agentTokenToLogger :map [string ]* agentLogger {},
5657podToAgentTokens :map [string ][]string {},
58+ replicaSetToTokens :map [string ][]string {},
5759}
5860return reporter ,reporter .init ()
5961}
@@ -67,8 +69,9 @@ type podEventLogger struct {
6769ctx context.Context
6870cancelFunc context.CancelFunc
6971mutex sync.RWMutex
70- agentTokenToLogger map [string ]agentLogger
72+ agentTokenToLogger map [string ]* agentLogger
7173podToAgentTokens 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.
9396podInformer := podFactory .Core ().V1 ().Pods ().Informer ()
97+ replicaInformer := podFactory .Apps ().V1 ().ReplicaSets ().Informer ()
9498eventInformer := eventFactory .Core ().V1 ().Events ().Informer ()
9599
96100_ ,err := podInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
@@ -130,7 +134,7 @@ func (p *podEventLogger) init() error {
130134}
131135}
132136if 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},
136140DeleteFunc :func (obj interface {}) {
@@ -153,13 +157,92 @@ func (p *podEventLogger) init() error {
153157Level :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})
159163if err != nil {
160164return 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 {
164247AddFunc :func (obj interface {}) {
165248event ,ok := obj .(* corev1.Event )
@@ -175,8 +258,14 @@ func (p *podEventLogger) init() error {
175258
176259p .mutex .Lock ()
177260defer 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 {
180269return
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 ) {
214303logger ,ok := p .agentTokenToLogger [token ]
215304if ! ok {
216305client := agentsdk .New (p .coderURL )
217306client .SetSessionToken (token )
218- client .SDK .Logger = p .logger .Named (podName )
307+ client .SDK .Logger = p .logger .Named (resourceName )
219308sendLog ,closer := client .QueueStartupLogs (p .ctx ,p .logDebounce )
220309
221- logger = agentLogger {
310+ logger = & agentLogger {
222311sendLog :sendLog ,
223312closer :closer ,
224313closeTimer :time .AfterFunc (p .logDebounce * 5 ,func () {
225314logger .closed .Store (true )
226315// We want to have two close cycles for loggers!
227316err := closer .Close ()
228317if 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}
231320p .mutex .Lock ()
232321delete (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 )
243332return
244333}
245334// We make this 5x the debounce because it's low-cost to persist a few