github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/portforward/forwarder_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 portforward
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"io"
    23  
    24  	"golang.org/x/sync/singleflight"
    25  	v1 "k8s.io/api/core/v1"
    26  
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug/types"
    30  	eventV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/v2"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubectl"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    35  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    36  )
    37  
    38  type Config interface {
    39  	kubectl.Config
    40  
    41  	Mode() config.RunMode
    42  	PortForwardResources() []*latest.PortForwardResource
    43  	PortForwardOptions() config.PortForwardOptions
    44  }
    45  
    46  // Forwarder is an interface that can modify and manage port-forward processes
    47  type Forwarder interface {
    48  	// Start initiates the forwarder's operation. It should not return until any ports have been allocated.
    49  	Start(ctx context.Context, out io.Writer, namespaces []string) error
    50  	Stop()
    51  }
    52  
    53  // ForwarderManager manages all forwarders
    54  type ForwarderManager struct {
    55  	forwarders   []Forwarder
    56  	entryManager *EntryManager
    57  	label        string
    58  
    59  	singleRun  singleflight.Group
    60  	namespaces *[]string
    61  }
    62  
    63  // NewForwarderManager returns a new port manager which handles starting and stopping port forwarding
    64  func NewForwarderManager(cli *kubectl.CLI, podSelector kubernetes.PodSelector, label string, runMode config.RunMode, namespaces *[]string,
    65  	options config.PortForwardOptions, userDefined []*latest.PortForwardResource) *ForwarderManager {
    66  	if !options.Enabled() {
    67  		return nil
    68  	}
    69  
    70  	entryManager := NewEntryManager(NewKubectlForwarder(cli))
    71  
    72  	// The order matters to ensure user-defined port-forwards with local-ports are processed first.
    73  	var forwarders []Forwarder
    74  	if options.ForwardUser(runMode) {
    75  		forwarders = append(forwarders, NewUserDefinedForwarder(entryManager, cli.KubeContext, userDefined))
    76  	}
    77  	if options.ForwardServices(runMode) {
    78  		forwarders = append(forwarders, NewServicesForwarder(entryManager, cli.KubeContext, label))
    79  	}
    80  	if options.ForwardPods(runMode) {
    81  		forwarders = append(forwarders, NewWatchingPodForwarder(entryManager, cli.KubeContext, podSelector, allPorts))
    82  	} else if options.ForwardDebug(runMode) {
    83  		forwarders = append(forwarders, NewWatchingPodForwarder(entryManager, cli.KubeContext, podSelector, debugPorts))
    84  	}
    85  
    86  	return &ForwarderManager{
    87  		forwarders:   forwarders,
    88  		entryManager: entryManager,
    89  		label:        label,
    90  		singleRun:    singleflight.Group{},
    91  		namespaces:   namespaces,
    92  	}
    93  }
    94  
    95  func allPorts(pod *v1.Pod, c v1.Container) []v1.ContainerPort {
    96  	return c.Ports
    97  }
    98  
    99  func debugPorts(pod *v1.Pod, c v1.Container) []v1.ContainerPort {
   100  	var ports []v1.ContainerPort
   101  
   102  	annot, found := pod.ObjectMeta.Annotations[types.DebugConfig]
   103  	if !found {
   104  		return nil
   105  	}
   106  	var configurations map[string]types.ContainerDebugConfiguration
   107  	if err := json.Unmarshal([]byte(annot), &configurations); err != nil {
   108  		log.Entry(context.TODO()).Warnf("could not decode debug annotation on pod/%s (%q): %v", pod.Name, annot, err)
   109  		return nil
   110  	}
   111  	dc, found := configurations[c.Name]
   112  	if !found {
   113  		log.Entry(context.TODO()).Debugf("no debug configuration found on pod/%s/%s", pod.Name, c.Name)
   114  		return nil
   115  	}
   116  	for _, port := range c.Ports {
   117  		for _, exposed := range dc.Ports {
   118  			if uint32(port.ContainerPort) == exposed {
   119  				log.Entry(context.TODO()).Debugf("selecting debug port for pod/%s/%s: %v", pod.Name, c.Name, port)
   120  				ports = append(ports, port)
   121  			}
   122  		}
   123  	}
   124  	return ports
   125  }
   126  
   127  // Start begins all forwarders managed by the ForwarderManager
   128  func (p *ForwarderManager) Start(ctx context.Context, out io.Writer) error {
   129  	// Port forwarding is not enabled.
   130  	if p == nil {
   131  		return nil
   132  	}
   133  
   134  	_, err, _ := p.singleRun.Do(p.label, func() (interface{}, error) {
   135  		return struct{}{}, p.start(ctx, out)
   136  	})
   137  	return err
   138  }
   139  
   140  // Start begins all forwarders managed by the ForwarderManager
   141  func (p *ForwarderManager) start(ctx context.Context, out io.Writer) error {
   142  	eventV2.TaskInProgress(constants.PortForward, "Port forward URLs")
   143  	ctx, endTrace := instrumentation.StartTrace(ctx, "Start")
   144  	defer endTrace()
   145  
   146  	p.entryManager.Start(out)
   147  	for _, f := range p.forwarders {
   148  		if err := f.Start(ctx, out, *p.namespaces); err != nil {
   149  			eventV2.TaskFailed(constants.PortForward, err)
   150  			endTrace(instrumentation.TraceEndError(err))
   151  			return err
   152  		}
   153  	}
   154  
   155  	eventV2.TaskSucceeded(constants.PortForward)
   156  	return nil
   157  }
   158  
   159  func (p *ForwarderManager) Stop() {
   160  	// Port forwarding is not enabled.
   161  	if p == nil {
   162  		return
   163  	}
   164  	p.singleRun.Do(p.label, func() (interface{}, error) {
   165  		p.stop()
   166  		return struct{}{}, nil
   167  	})
   168  }
   169  
   170  // Stop cleans up and terminates all forwarders managed by the ForwarderManager
   171  func (p *ForwarderManager) stop() {
   172  	for _, f := range p.forwarders {
   173  		f.Stop()
   174  	}
   175  }
   176  
   177  func (p *ForwarderManager) Name() string {
   178  	return "PortForwarding"
   179  }
   180  
   181  func (p *ForwarderManager) AddPodForwarder(cli *kubectl.CLI, podSelector kubernetes.PodSelector, runMode config.RunMode, options config.PortForwardOptions) {
   182  	if options.ForwardPods(runMode) {
   183  		p.forwarders = append(p.forwarders, NewWatchingPodForwarder(p.entryManager, cli.KubeContext, podSelector, allPorts))
   184  	} else if options.ForwardDebug(runMode) {
   185  		p.forwarders = append(p.forwarders, NewWatchingPodForwarder(p.entryManager, cli.KubeContext, podSelector, debugPorts))
   186  	}
   187  }