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 }