@@ -53,65 +53,95 @@ var curveIDByName = map[string]tls.CurveID{
5353 "secp521r1" : tls .CurveP521 ,
5454}
5555
56- // getMetricsServiceEndpoint returns the namespace and metrics port for the named component service.
57- func getMetricsServiceEndpoint (component string ) (string , int32 , error ) {
56+ // getMetricsService returns the full Service object for the named component's metrics service.
57+ // The namespace is available via svc.Namespace.
58+ func getMetricsService (component string ) (* corev1.Service , error ) {
5859 serviceName := fmt .Sprintf ("%s-service" , component )
5960 serviceNs , err := k8sClient ("get" , "service" , "-A" , "-o" ,
6061 fmt .Sprintf (`jsonpath={.items[?(@.metadata.name=="%s")].metadata.namespace}` , serviceName ))
6162 if err != nil {
62- return "" , 0 , fmt .Errorf ("failed to find namespace for service %s: %w" , serviceName , err )
63+ return nil , fmt .Errorf ("failed to find namespace for service %s: %w" , serviceName , err )
6364 }
6465 serviceNs = strings .TrimSpace (serviceNs )
6566 if serviceNs == "" {
66- return "" , 0 , fmt .Errorf ("service %s not found in any namespace" , serviceName )
67+ return nil , fmt .Errorf ("service %s not found in any namespace" , serviceName )
6768 }
6869
6970 raw , err := k8sClient ("get" , "service" , "-n" , serviceNs , serviceName , "-o" , "json" )
7071 if err != nil {
71- return "" , 0 , fmt .Errorf ("failed to get service %s: %w" , serviceName , err )
72+ return nil , fmt .Errorf ("failed to get service %s: %w" , serviceName , err )
7273 }
7374 var svc corev1.Service
7475 if err := json .Unmarshal ([]byte (raw ), & svc ); err != nil {
75- return "" , 0 , fmt .Errorf ("failed to unmarshal service %s: %w" , serviceName , err )
76+ return nil , fmt .Errorf ("failed to unmarshal service %s: %w" , serviceName , err )
7677 }
78+ return & svc , nil
79+ }
80+
81+ // metricsPort returns the port number of the port named "metrics" on the given service.
82+ func metricsPort (svc * corev1.Service ) (int32 , error ) {
7783 for _ , p := range svc .Spec .Ports {
7884 if p .Name == "metrics" {
79- return serviceNs , p .Port , nil
85+ return p .Port , nil
8086 }
8187 }
82- return "" , 0 , fmt .Errorf ("no port named 'metrics' found on service %s" , serviceName )
88+ return 0 , fmt .Errorf ("no port named 'metrics' found on service %s" , svc . Name )
8389}
8490
85- // withMetricsPortForward starts a kubectl port-forward to the component's metrics service,
86- // waits until a basic TLS connection succeeds (confirming the port-forward is ready),
87- // then calls fn with the local address. The port-forward is torn down when fn returns.
88- func withMetricsPortForward (ctx context.Context , component string , fn func (addr string ) error ) error {
89- ns , metricsPort , err := getMetricsServiceEndpoint (component )
91+ func randomAvailablePort () (int , error ) {
92+ l , err := net .Listen ("tcp" , "127.0.0.1:0" )
9093 if err != nil {
91- return err
94+ return 0 , err
9295 }
96+ defer l .Close ()
97+ return l .Addr ().(* net.TCPAddr ).Port , nil
98+ }
9399
100+ // portForward starts a kubectl port-forward to target (e.g. "service/foo" or "pod/bar")
101+ // in the given namespace, mapping a random local port to remotePort. It returns the
102+ // local address and a cleanup function. The caller is responsible for calling cleanup.
103+ func portForward (ns , target string , remotePort int32 ) (string , func (), error ) {
94104 localPort , err := randomAvailablePort ()
95105 if err != nil {
96- return fmt .Errorf ("failed to find a free local port: %w" , err )
106+ return "" , nil , fmt .Errorf ("failed to find a free local port: %w" , err )
97107 }
98108
99- serviceName := fmt .Sprintf ("%s-service" , component )
100109 pfCmd := exec .Command (k8sCli , "port-forward" , "-n" , ns , //nolint:gosec
101- fmt .Sprintf ("service/%s" , serviceName ),
102- fmt .Sprintf ("%d:%d" , localPort , metricsPort ))
110+ target , fmt .Sprintf ("%d:%d" , localPort , remotePort ))
103111 pfCmd .Env = append (os .Environ (), fmt .Sprintf ("KUBECONFIG=%s" , kubeconfigPath ))
104112 if err := pfCmd .Start (); err != nil {
105- return fmt .Errorf ("failed to start port-forward to %s: %w" , serviceName , err )
113+ return "" , nil , fmt .Errorf ("failed to start port-forward to %s: %w" , target , err )
106114 }
107- defer func () {
115+
116+ cleanup := func () {
108117 if p := pfCmd .Process ; p != nil {
109118 _ = p .Kill ()
110119 _ = pfCmd .Wait ()
111120 }
112- }()
121+ }
113122
114123 addr := fmt .Sprintf ("127.0.0.1:%d" , localPort )
124+ return addr , cleanup , nil
125+ }
126+
127+ // withMetricsPortForward starts a kubectl port-forward to the component's metrics service,
128+ // waits until a basic TLS connection succeeds (confirming the port-forward is ready),
129+ // then calls fn with the local address. The port-forward is torn down when fn returns.
130+ func withMetricsPortForward (ctx context.Context , component string , fn func (addr string ) error ) error {
131+ svc , err := getMetricsService (component )
132+ if err != nil {
133+ return err
134+ }
135+ port , err := metricsPort (svc )
136+ if err != nil {
137+ return err
138+ }
139+
140+ addr , cleanup , err := portForward (svc .Namespace , fmt .Sprintf ("service/%s" , svc .Name ), port )
141+ if err != nil {
142+ return err
143+ }
144+ defer cleanup ()
115145
116146 // Wait until the port-forward is accepting connections. A plain TLS dial (no version
117147 // restrictions) serves as the readiness probe; any successful TLS handshake confirms
0 commit comments