github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/portforward/pod_forwarder.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 "fmt" 22 "io" 23 "strconv" 24 25 v1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/watch" 27 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 33 schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" 34 ) 35 36 var ( 37 // For testing 38 newPodWatcher = kubernetes.NewPodWatcher 39 topLevelOwnerKey = kubernetes.TopLevelOwnerKey 40 ) 41 42 // WatchingPodForwarder is responsible for selecting pods satisfying a certain condition and port-forwarding the exposed 43 // container ports within those pods. It also tracks and manages the port-forward connections. 44 type WatchingPodForwarder struct { 45 output io.Writer 46 entryManager *EntryManager 47 podWatcher kubernetes.PodWatcher 48 events chan kubernetes.PodEvent 49 kubeContext string 50 51 // portSelector returns a possibly-filtered and possibly-generated set of ports for a pod. 52 containerPorts portSelector 53 } 54 55 // portSelector selects a set of ContainerPorts from a container in a pod. 56 type portSelector func(*v1.Pod, v1.Container) []v1.ContainerPort 57 58 // NewWatchingPodForwarder returns a struct that tracks and port-forwards pods as they are created and modified 59 func NewWatchingPodForwarder(entryManager *EntryManager, kubeContext string, podSelector kubernetes.PodSelector, containerPorts portSelector) *WatchingPodForwarder { 60 return &WatchingPodForwarder{ 61 entryManager: entryManager, 62 podWatcher: newPodWatcher(podSelector), 63 events: make(chan kubernetes.PodEvent), 64 kubeContext: kubeContext, 65 containerPorts: containerPorts, 66 } 67 } 68 69 func (p *WatchingPodForwarder) Start(ctx context.Context, out io.Writer, namespaces []string) error { 70 p.podWatcher.Register(p.events) 71 p.output = out 72 stopWatcher, err := p.podWatcher.Start(ctx, p.kubeContext, namespaces) 73 if err != nil { 74 return err 75 } 76 77 go func() { 78 defer stopWatcher() 79 l := log.Entry(ctx) 80 defer l.Tracef("podForwarder: cease waiting for pod events") 81 l.Tracef("podForwarder: waiting for pod events") 82 for { 83 select { 84 case <-ctx.Done(): 85 l.Tracef("podForwarder: context canceled, ignoring") 86 case evt, ok := <-p.events: 87 if !ok { 88 l.Tracef("podForwarder: channel closed, returning") 89 return 90 } 91 92 // At this point, we know the event's type is "ADDED" or "MODIFIED". 93 // We must take both types into account as it is possible for the pod to have become ready for port-forwarding before we established the watch. 94 pod := evt.Pod 95 if evt.Type != watch.Deleted && pod.Status.Phase == v1.PodRunning && pod.DeletionTimestamp == nil { 96 if err := p.portForwardPod(ctx, pod); err != nil { 97 log.Entry(ctx).Warnf("port forwarding pod failed: %s", err) 98 } 99 } 100 } 101 } 102 }() 103 104 return nil 105 } 106 107 func (p *WatchingPodForwarder) Stop() { 108 p.entryManager.Stop() 109 p.podWatcher.Deregister(p.events) 110 } 111 112 func (p *WatchingPodForwarder) portForwardPod(ctx context.Context, pod *v1.Pod) error { 113 ownerReference := topLevelOwnerKey(ctx, pod, p.kubeContext, pod.Kind) 114 for _, c := range pod.Spec.Containers { 115 for _, port := range p.containerPorts(pod, c) { 116 // get current entry for this container 117 resource := latest.PortForwardResource{ 118 Type: constants.Pod, 119 Name: pod.Name, 120 Namespace: pod.Namespace, 121 Port: schemautil.FromInt(int(port.ContainerPort)), 122 Address: constants.DefaultPortForwardAddress, 123 } 124 125 entry, err := p.podForwardingEntry(pod.ResourceVersion, c.Name, port.Name, ownerReference, resource) 126 if err != nil { 127 return fmt.Errorf("getting pod forwarding entry: %w", err) 128 } 129 if entry.resource.Port.IntVal != entry.localPort { 130 output.Yellow.Fprintf(p.output, "Forwarding container %s/%s to local port %d.\n", pod.Name, c.Name, entry.localPort) 131 } 132 if pe, ok := p.entryManager.forwardedResources.Load(entry.key()); ok { 133 prevEntry := pe.(*portForwardEntry) 134 // Check if this is a new generation of pod 135 if entry.resourceVersion > prevEntry.resourceVersion { 136 p.entryManager.Terminate(prevEntry) 137 } 138 } 139 p.entryManager.forwardPortForwardEntry(ctx, p.output, entry) 140 } 141 } 142 return nil 143 } 144 145 func (p *WatchingPodForwarder) podForwardingEntry(resourceVersion, containerName, portName, ownerReference string, resource latest.PortForwardResource) (*portForwardEntry, error) { 146 rv, err := strconv.Atoi(resourceVersion) 147 if err != nil { 148 return nil, fmt.Errorf("converting resource version to integer: %w", err) 149 } 150 entry := newPortForwardEntry(rv, resource, resource.Name, containerName, portName, ownerReference, 0, true) 151 152 // If we have, return the current entry 153 oe, ok := p.entryManager.forwardedResources.Load(entry.key()) 154 155 if ok { 156 oldEntry := oe.(*portForwardEntry) 157 entry.localPort = oldEntry.localPort 158 return entry, nil 159 } 160 161 // retrieve an open port on the host 162 entry.localPort = retrieveAvailablePort(resource.Address, resource.Port.IntVal, &p.entryManager.forwardedPorts) 163 164 return entry, nil 165 }