github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/agentmap/generator.go (about) 1 package agentmap 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "strings" 8 9 "go.opentelemetry.io/otel" 10 core "k8s.io/api/core/v1" 11 "k8s.io/apimachinery/pkg/util/intstr" 12 "k8s.io/apimachinery/pkg/util/validation" 13 14 "github.com/datawire/k8sapi/pkg/k8sapi" 15 "github.com/telepresenceio/telepresence/v2/pkg/agentconfig" 16 "github.com/telepresenceio/telepresence/v2/pkg/tracing" 17 ) 18 19 const ( 20 ServicePortAnnotation = agentconfig.DomainPrefix + "inject-service-port" 21 ServiceNameAnnotation = agentconfig.DomainPrefix + "inject-service-name" 22 ManagerAppName = "traffic-manager" 23 ) 24 25 type GeneratorConfig interface { 26 // Generate generates a configuration for the given workload. If replaceContainers is given it will be used to configure 27 // container replacement EXCEPT if existingConfig is not nil, in which replaceContainers will be 28 // ignored and the value from existingConfig used. 0 can be conventionally passed in as replaceContainers in this case. 29 Generate( 30 ctx context.Context, 31 wl k8sapi.Workload, 32 existingConfig agentconfig.SidecarExt, 33 ) (sc agentconfig.SidecarExt, err error) 34 } 35 36 var GeneratorConfigFunc func(qualifiedAgentImage string) (GeneratorConfig, error) //nolint:gochecknoglobals // extension point 37 38 type BasicGeneratorConfig struct { 39 ManagerPort uint16 40 AgentPort uint16 41 APIPort uint16 42 TracingPort uint16 43 QualifiedAgentImage string 44 ManagerNamespace string 45 LogLevel string 46 InitResources *core.ResourceRequirements 47 Resources *core.ResourceRequirements 48 PullPolicy string 49 PullSecrets []core.LocalObjectReference 50 AppProtocolStrategy k8sapi.AppProtocolStrategy 51 SecurityContext *core.SecurityContext 52 } 53 54 func (cfg *BasicGeneratorConfig) Generate( 55 ctx context.Context, 56 wl k8sapi.Workload, 57 existingConfig agentconfig.SidecarExt, 58 ) (sc agentconfig.SidecarExt, err error) { 59 ctx, span := otel.GetTracerProvider().Tracer("").Start(ctx, "agentmap.Generate") 60 defer tracing.EndAndRecord(span, err) 61 62 pod := wl.GetPodTemplate() 63 pod.Namespace = wl.GetNamespace() 64 cns := pod.Spec.Containers 65 for i := range cns { 66 cn := &cns[i] 67 if cn.Name == agentconfig.ContainerName { 68 continue 69 } 70 ports := cn.Ports 71 for pi := range ports { 72 if ports[pi].ContainerPort == int32(cfg.AgentPort) { 73 return nil, fmt.Errorf( 74 "the %s.%s pod container %s is exposing the same port (%d) as the %s sidecar", 75 pod.Name, pod.Namespace, cn.Name, cfg.AgentPort, agentconfig.ContainerName) 76 } 77 } 78 } 79 80 svcs, err := findServicesForPod(ctx, pod, pod.Annotations[ServiceNameAnnotation]) 81 if err != nil { 82 return nil, err 83 } 84 85 var ccs []*agentconfig.Container 86 pns := make(map[int32]uint16) 87 portNumber := func(cnPort int32) uint16 { 88 if p, ok := pns[cnPort]; ok { 89 // Port already mapped. Reuse that mapping 90 return p 91 } 92 p := cfg.AgentPort + uint16(len(pns)) 93 pns[cnPort] = p 94 return p 95 } 96 97 for _, svc := range svcs { 98 svcImpl, _ := k8sapi.ServiceImpl(svc) 99 if ccs, err = appendAgentContainerConfigs(ctx, svcImpl, pod, portNumber, ccs, existingConfig, cfg.AppProtocolStrategy); err != nil { 100 return nil, err 101 } 102 } 103 if len(ccs) == 0 { 104 return nil, fmt.Errorf("found no service with a port that matches a container in pod %s.%s", pod.Name, pod.Namespace) 105 } 106 107 ag := &agentconfig.Sidecar{ 108 AgentImage: cfg.QualifiedAgentImage, 109 AgentName: wl.GetName(), 110 LogLevel: cfg.LogLevel, 111 Namespace: wl.GetNamespace(), 112 WorkloadName: wl.GetName(), 113 WorkloadKind: wl.GetKind(), 114 ManagerHost: ManagerAppName + "." + cfg.ManagerNamespace, 115 ManagerPort: cfg.ManagerPort, 116 APIPort: cfg.APIPort, 117 TracingPort: cfg.TracingPort, 118 Containers: ccs, 119 InitResources: cfg.InitResources, 120 Resources: cfg.Resources, 121 PullPolicy: cfg.PullPolicy, 122 PullSecrets: cfg.PullSecrets, 123 SecurityContext: cfg.SecurityContext, 124 } 125 ag.RecordInSpan(span) 126 return ag, nil 127 } 128 129 func appendAgentContainerConfigs( 130 ctx context.Context, 131 svc *core.Service, 132 pod *core.PodTemplateSpec, 133 portNumber func(int32) uint16, 134 ccs []*agentconfig.Container, 135 existingConfig agentconfig.SidecarExt, 136 aps k8sapi.AppProtocolStrategy, 137 ) ([]*agentconfig.Container, error) { 138 portNameOrNumber := pod.Annotations[ServicePortAnnotation] 139 ports, err := filterServicePorts(svc, portNameOrNumber) 140 if err != nil { 141 return nil, err 142 } 143 ignoredVolumeMounts := agentconfig.GetIgnoredVolumeMounts(pod.ObjectMeta.Annotations) 144 nextSvcPort: 145 for _, port := range ports { 146 cn, i := findContainerMatchingPort(&port, pod.Spec.Containers) 147 if cn == nil || cn.Name == agentconfig.ContainerName { 148 continue 149 } 150 var appPort core.ContainerPort 151 if i < 0 { 152 // Can only happen if the service port is numeric, so it's safe to use TargetPort.IntVal here 153 appPort = core.ContainerPort{ 154 Protocol: port.Protocol, 155 ContainerPort: port.TargetPort.IntVal, 156 } 157 } else { 158 appPort = cn.Ports[i] 159 } 160 161 ic := &agentconfig.Intercept{ 162 ServiceName: svc.Name, 163 ServiceUID: svc.UID, 164 ServicePortName: port.Name, 165 ServicePort: uint16(port.Port), 166 TargetPortNumeric: port.TargetPort.Type == intstr.Int, 167 Protocol: port.Protocol, 168 AppProtocol: k8sapi.GetAppProto(ctx, aps, &port), 169 AgentPort: portNumber(appPort.ContainerPort), 170 ContainerPortName: appPort.Name, 171 ContainerPort: uint16(appPort.ContainerPort), 172 } 173 174 // Validate that we're not being asked to clobber an existing configuration 175 var replaceContainer agentconfig.ReplacePolicy 176 if existingConfig != nil { 177 for _, cc := range existingConfig.AgentConfig().Containers { 178 if cc.Name == cn.Name { 179 replaceContainer = cc.Replace 180 break 181 } 182 } 183 } 184 185 // The container might already have intercepts declared 186 for _, cc := range ccs { 187 if cc.Name == cn.Name { 188 cc.Intercepts = append(cc.Intercepts, ic) 189 continue nextSvcPort 190 } 191 } 192 var mounts []string 193 if l := len(cn.VolumeMounts); l > 0 { 194 mounts = make([]string, 0, l) 195 for _, vm := range cn.VolumeMounts { 196 if !ignoredVolumeMounts.IsVolumeIgnored(vm.Name, vm.MountPath) { 197 mounts = append(mounts, vm.MountPath) 198 } 199 } 200 } 201 ccs = append(ccs, &agentconfig.Container{ 202 Name: cn.Name, 203 EnvPrefix: CapsBase26(uint64(len(ccs))) + "_", 204 MountPoint: agentconfig.MountPrefixApp + "/" + cn.Name, 205 Mounts: mounts, 206 Intercepts: []*agentconfig.Intercept{ic}, 207 Replace: replaceContainer, 208 }) 209 } 210 return ccs, nil 211 } 212 213 // filterServicePorts iterates through a list of ports in a service and 214 // only returns the ports that match the given nameOrNumber. All ports will 215 // be returned if nameOrNumber is equal to the empty string. 216 func filterServicePorts(svc *core.Service, nameOrNumber string) ([]core.ServicePort, error) { 217 ports := svc.Spec.Ports 218 if nameOrNumber == "" { 219 return ports, nil 220 } 221 svcPorts := make([]core.ServicePort, 0) 222 if number, err := strconv.Atoi(nameOrNumber); err != nil { 223 errs := validation.IsValidPortName(nameOrNumber) 224 if len(errs) > 0 { 225 return nil, fmt.Errorf(strings.Join(errs, "\n")) 226 } 227 for _, port := range ports { 228 if port.Name == nameOrNumber { 229 svcPorts = append(svcPorts, port) 230 } 231 } 232 } else { 233 for _, port := range ports { 234 pn := int32(0) 235 if port.TargetPort.Type == intstr.Int { 236 pn = port.TargetPort.IntVal 237 } 238 if pn == 0 { 239 pn = port.Port 240 } 241 if pn == int32(number) { 242 svcPorts = append(svcPorts, port) 243 } 244 } 245 } 246 return svcPorts, nil 247 }