github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/debugging/container_manager.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package debugging
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/watch"
    25  
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug/types"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
    28  	eventV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/v2"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    31  )
    32  
    33  var (
    34  	// For testing
    35  	notifyDebuggingContainerStarted    = event.DebuggingContainerStarted
    36  	notifyDebuggingContainerTerminated = event.DebuggingContainerTerminated
    37  	debuggingContainerStartedV2        = eventV2.DebuggingContainerStarted
    38  	debuggingContainerTerminatedV2     = eventV2.DebuggingContainerTerminated
    39  )
    40  
    41  type ContainerManager struct {
    42  	podWatcher  kubernetes.PodWatcher
    43  	active      map[string]string // set of containers that have been notified
    44  	events      chan kubernetes.PodEvent
    45  	stopWatcher func()
    46  	namespaces  *[]string
    47  	kubeContext string
    48  }
    49  
    50  func NewContainerManager(podSelector kubernetes.PodSelector, namespaces *[]string, kubeContext string) *ContainerManager {
    51  	// Create the channel here as Stop() may be called before Start() when a build fails, thus
    52  	// avoiding the possibility of closing a nil channel. Channels are cheap.
    53  	return &ContainerManager{
    54  		podWatcher:  kubernetes.NewPodWatcher(podSelector),
    55  		active:      map[string]string{},
    56  		events:      make(chan kubernetes.PodEvent),
    57  		stopWatcher: func() {},
    58  		namespaces:  namespaces,
    59  		kubeContext: kubeContext,
    60  	}
    61  }
    62  
    63  func (d *ContainerManager) Start(ctx context.Context) error {
    64  	if d == nil {
    65  		// debug mode probably not enabled
    66  		return nil
    67  	}
    68  
    69  	d.podWatcher.Register(d.events)
    70  	stopWatcher, err := d.podWatcher.Start(ctx, d.kubeContext, *d.namespaces)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	go func() {
    76  		defer stopWatcher()
    77  		l := log.Entry(ctx)
    78  		defer l.Tracef("containerManager: cease waiting for pod events")
    79  		l.Tracef("containerManager: waiting for pod events")
    80  		for {
    81  			select {
    82  			case <-ctx.Done():
    83  				l.Tracef("containerManager: context canceled, ignoring")
    84  			case evt, ok := <-d.events:
    85  				if !ok {
    86  					l.Tracef("containerManager: channel closed, returning")
    87  					return
    88  				}
    89  
    90  				d.checkPod(evt.Type, evt.Pod)
    91  			}
    92  		}
    93  	}()
    94  
    95  	return nil
    96  }
    97  
    98  func (d *ContainerManager) Stop() {
    99  	// if nil then debug mode probably not enabled
   100  	if d == nil {
   101  		return
   102  	}
   103  	d.podWatcher.Deregister(d.events)
   104  }
   105  
   106  func (d *ContainerManager) Name() string {
   107  	return "Debug Manager"
   108  }
   109  
   110  func (d *ContainerManager) checkPod(evtType watch.EventType, pod *v1.Pod) {
   111  	debugConfigString, found := pod.Annotations[types.DebugConfig]
   112  	if !found {
   113  		return
   114  	}
   115  	var configurations map[string]types.ContainerDebugConfiguration
   116  	if err := json.Unmarshal([]byte(debugConfigString), &configurations); err != nil {
   117  		log.Entry(context.TODO()).Warnf("Unable to parse debug-config for pod %s/%s: '%s'", pod.Namespace, pod.Name, debugConfigString)
   118  		return
   119  	}
   120  	for _, c := range pod.Status.ContainerStatuses {
   121  		// only examine debuggable containers
   122  		if config, found := configurations[c.Name]; found {
   123  			key := pod.Namespace + "/" + pod.Name + "/" + c.Name
   124  			// only notify of first appearance or disappearance
   125  			_, seen := d.active[key]
   126  			switch {
   127  			case evtType != watch.Deleted && c.State.Running != nil && !seen:
   128  				d.active[key] = key
   129  				notifyDebuggingContainerStarted(
   130  					pod.Name,
   131  					c.Name,
   132  					pod.Namespace,
   133  					config.Artifact,
   134  					config.Runtime,
   135  					config.WorkingDir,
   136  					config.Ports)
   137  				debuggingContainerStartedV2(pod.Name, c.Name, pod.Namespace, config.Artifact, config.Runtime, config.WorkingDir, config.Ports)
   138  
   139  			case (evtType == watch.Deleted || c.State.Terminated != nil) && seen:
   140  				delete(d.active, key)
   141  				notifyDebuggingContainerTerminated(pod.Name, c.Name, pod.Namespace,
   142  					config.Artifact,
   143  					config.Runtime,
   144  					config.WorkingDir,
   145  					config.Ports)
   146  				debuggingContainerTerminatedV2(pod.Name, c.Name, pod.Namespace, config.Artifact, config.Runtime, config.WorkingDir, config.Ports)
   147  			}
   148  		}
   149  	}
   150  }