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 2
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
+ * @returns 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
+ */
84
+ public PortForwardResult forward (V1Pod pod , List <Integer > ports ) throws ApiException , IOException {
85
+ return forward (pod .getMetadata ().getNamespace (), pod .getMetadata ().getNamespace (), ports );
86
+ }
87
+
88
+ /**
89
+ * PortForward to a container.
90
+ *
91
+ * @param namespace The namespace of the Pod
92
+ * @param name The name of the Pod
93
+ * @param ports The ports to forward
94
+ */
95
+ public PortForwardResult forward (String namespace , String name , List <Integer > ports ) throws ApiException , IOException {
96
+ String path = makePath (namespace , name );
97
+ WebSocketStreamHandler handler = new WebSocketStreamHandler ();
98
+ PortForwardResult result = new PortForwardResult (handler , ports );
99
+ List <Pair > queryParams = new ArrayList <>();
100
+ queryParams .add (new Pair ("ports" , "80" ));
101
+ WebSockets .stream (path , "GET" , queryParams , apiClient , handler );
102
+
103
+ // Wait for streams to start.
104
+ result .init ();
105
+
106
+ return result ;
107
+ }
108
+
109
+ /**
110
+ * PortForwardResult contains the result of an Attach call, it includes streams for stdout
111
+ * stderr and stdin.
112
+ */
113
+ public static class PortForwardResult {
114
+ private WebSocketStreamHandler handler ;
115
+ private HashMap <Integer , Integer > streams ;
116
+ private List <Integer > ports ;
117
+
118
+ public PortForwardResult (WebSocketStreamHandler handler , List <Integer > ports ) throws IOException {
119
+ this .handler = handler ;
120
+ this .streams = new HashMap <>();
121
+ this .ports = ports ;
122
+ }
123
+
124
+ public void init () throws IOException {
125
+ for (int i = 0 ; i < ports .size (); i ++) {
126
+ InputStream is = handler .getInputStream (i );
127
+ byte [] data = new byte [2 ];
128
+ is .read (data );
129
+ int port = data [0 ] + data [1 ] * 256 ;
130
+ streams .put (port , i );
131
+ }
132
+ }
133
+
134
+ private int findPortIndex (int portNumber ) {
135
+ Integer ix = streams .get (portNumber );
136
+ if (ix == null ) {
137
+ return -1 ;
138
+ }
139
+ return ix .intValue ();
140
+ }
141
+
142
+ /**
143
+ * Get the output stream for the specified port number (e.g. 80)
144
+ * @param port The port number to get the stream for.
145
+ */
146
+ public OutputStream getOutboundStream (int port ) {
147
+ int portIndex = findPortIndex (port );
148
+ if (portIndex == -1 ) {
149
+ throw new IllegalArgumentException ("No such port!" );
150
+ }
151
+ return handler .getOutputStream (portIndex * 2 );
152
+ }
153
+
154
+ /**
155
+ * Get the error stream for a port number (e.g. 80)
156
+ * @param port The port number to get the stream for.
157
+ * @return The error stream
158
+ */
159
+ public OutputStream getErrorStream (int port ) {
160
+ int portIndex = findPortIndex (port );
161
+ if (portIndex == -1 ) {
162
+ throw new IllegalArgumentException ("No such port!" );
163
+ }
164
+ return handler .getOutputStream (portIndex * 2 + 1 );
165
+ }
166
+
167
+ /**
168
+ * Get the input stream for a port number (e.g. 80)
169
+ * @param port The port number to get the stream for.
170
+ * @return The error stream
171
+ */
172
+ public InputStream getInputStream (int port ) throws IOException {
173
+ int portIndex = findPortIndex (port );
174
+ if (portIndex == -1 ) {
175
+ throw new IllegalArgumentException ("No such port!" );
176
+ }
177
+ return handler .getInputStream (portIndex * 2 );
178
+ }
179
+ }
180
+ }
0 commit comments