1+ package io .kubernetes .client ;
2+
3+ import io .kubernetes .client .Configuration ;
4+ import io .kubernetes .client .models .V1Pod ;
5+ import io .kubernetes .client .util .WebSockets ;
6+ import io .kubernetes .client .util .WebSocketStreamHandler ;
7+
8+ import java .io .InputStream ;
9+ import java .io .IOException ;
10+ import java .io .OutputStream ;
11+ import java .util .ArrayList ;
12+ import java .util .HashMap ;
13+ import java .util .List ;
14+
15+ /**
16+ * Utility class for setting up port-forwarding connections.
17+ * Uses the WebSockets API, not the SPDY API (which the Go client uses)
18+ *
19+ * The protocol is undocumented as far as I can tell, but the PR that added
20+ * it is here:
21+ * https://github.com/kubernetes/kubernetes/pull/33684
22+ *
23+ * And the protocol is:
24+ *
25+ * ws://server/api/v1/namespaces/<namespace>/pods/<pod>/portforward?ports=80&ports=8080
26+ *
27+ * I/O for first port (80) is on Channel 0
28+ * Err for first port (80) is on Channel 1
29+ * I/O for second port (8080) is on Channel 2
30+ * Err for second port (8080) is on Channel 3
31+ * <and so on for remaining ports>
32+ *
33+ * The first two bytes of each output stream is the port that is being forwarded
34+ * in little-endian format.
35+ */
36+ public class PortForward {
37+ private ApiClient apiClient ;
38+
39+ /**
40+ * Simple PortForward API constructor, uses default configuration
41+ */
42+ public PortForward () {
43+ this (Configuration .getDefaultApiClient ());
44+ }
45+
46+ /**
47+ * PortForward API Constructor
48+ * @param apiClient The api client to use.
49+ */
50+ public PortForward (ApiClient apiClient ) {
51+ this .apiClient = apiClient ;
52+ }
53+
54+ /**
55+ * Get the API client for these PortForward operations.
56+ * @return The API client that will be used.
57+ */
58+ public ApiClient getApiClient () {
59+ return apiClient ;
60+ }
61+
62+ /**
63+ * Set the API client for subsequent PortForward operations.
64+ * @param apiClient The new API client to use.
65+ */
66+ public void setApiClient (ApiClient apiClient ) {
67+ this .apiClient = apiClient ;
68+ }
69+
70+ private String makePath (String namespace , String name ) {
71+ return "/api/v1/namespaces/" +
72+ namespace +
73+ "/pods/" +
74+ name +
75+ "/portforward" ;
76+ }
77+
78+ /**
79+ * PortForward to a container
80+ *
81+ * @param pod The pod where the port forward is run.
82+ * @param ports The ports to forward
83+ * @return The result of the Port Forward request.
84+ */
85+ public PortForwardResult forward (V1Pod pod , List <Integer > ports ) throws ApiException , IOException {
86+ return forward (pod .getMetadata ().getNamespace (), pod .getMetadata ().getNamespace (), ports );
87+ }
88+
89+ /**
90+ * PortForward to a container.
91+ *
92+ * @param namespace The namespace of the Pod
93+ * @param name The name of the Pod
94+ * @param ports The ports to forward
95+ * @return The result of the Port Forward request.
96+ */
97+ public PortForwardResult forward (String namespace , String name , List <Integer > ports ) throws ApiException , IOException {
98+ String path = makePath (namespace , name );
99+ WebSocketStreamHandler handler = new WebSocketStreamHandler ();
100+ PortForwardResult result = new PortForwardResult (handler , ports );
101+ List <Pair > queryParams = new ArrayList <>();
102+ for (Integer port : ports ) {
103+ queryParams .add (new Pair ("ports" , port .toString ()));
104+ }
105+ WebSockets .stream (path , "GET" , queryParams , apiClient , handler );
106+
107+ // Wait for streams to start.
108+ result .init ();
109+
110+ return result ;
111+ }
112+
113+ /**
114+ * PortForwardResult contains the result of an Attach call, it includes streams for stdout
115+ * stderr and stdin.
116+ */
117+ public static class PortForwardResult {
118+ private WebSocketStreamHandler handler ;
119+ private HashMap <Integer , Integer > streams ;
120+ private List <Integer > ports ;
121+
122+ /**
123+ * Constructor
124+ * @param handler The web socket handler
125+ * @param ports The list of ports that are being forwarded.
126+ */
127+ public PortForwardResult (WebSocketStreamHandler handler , List <Integer > ports ) throws IOException {
128+ this .handler = handler ;
129+ this .streams = new HashMap <>();
130+ this .ports = ports ;
131+ }
132+
133+ /**
134+ * Initialize the connection. Must be called after the web socket has been opened.
135+ */
136+ public void init () throws IOException {
137+ for (int i = 0 ; i < ports .size (); i ++) {
138+ InputStream is = handler .getInputStream (i );
139+ byte [] data = new byte [2 ];
140+ is .read (data );
141+ int port = data [0 ] + data [1 ] * 256 ;
142+ streams .put (port , i );
143+ }
144+ }
145+
146+ private int findPortIndex (int portNumber ) {
147+ Integer ix = streams .get (portNumber );
148+ if (ix == null ) {
149+ return -1 ;
150+ }
151+ return ix .intValue ();
152+ }
153+
154+ /**
155+ * Get the output stream for the specified port number (e.g. 80)
156+ * @param port The port number to get the stream for.
157+ * @return The OutputStream for the specified port, null if there is no such port.
158+ */
159+ public OutputStream getOutboundStream (int port ) {
160+ int portIndex = findPortIndex (port );
161+ if (portIndex == -1 ) {
162+ return null ;
163+ }
164+ return handler .getOutputStream (portIndex * 2 );
165+ }
166+
167+ /**
168+ * Get the error stream for a port number (e.g. 80)
169+ * @param port The port number to get the stream for.
170+ * @return The error stream, or null if there is no such port.
171+ */
172+ public OutputStream getErrorStream (int port ) {
173+ int portIndex = findPortIndex (port );
174+ if (portIndex == -1 ) {
175+ return null ;
176+ }
177+ return handler .getOutputStream (portIndex * 2 + 1 );
178+ }
179+
180+ /**
181+ * Get the input stream for a port number (e.g. 80)
182+ * @param port The port number to get the stream for.
183+ * @return The input stream, or null if no such port exists.
184+ */
185+ public InputStream getInputStream (int port ) throws IOException {
186+ int portIndex = findPortIndex (port );
187+ if (portIndex == -1 ) {
188+ return null ;
189+ }
190+ return handler .getInputStream (portIndex * 2 );
191+ }
192+ }
193+ }
0 commit comments