2018-05-01 00:16:06 +02:00
using k8s.Models ;
2017-12-11 05:21:09 +01:00
using Microsoft.AspNetCore.WebUtilities ;
using Microsoft.Rest ;
2018-05-01 00:16:06 +02:00
using Microsoft.Rest.Serialization ;
2017-12-11 05:21:09 +01:00
using System ;
using System.Collections.Generic ;
2018-03-20 16:03:28 +11:00
using System.Linq ;
2018-05-01 00:16:06 +02:00
using System.Net ;
2017-12-11 05:21:09 +01:00
using System.Net.Http ;
using System.Net.WebSockets ;
2019-01-24 12:06:34 -08:00
#if NET452
using System.Net.Security ;
#endif
2018-03-20 16:03:28 +11:00
using System.Security.Cryptography.X509Certificates ;
2017-12-11 05:21:09 +01:00
using System.Threading ;
using System.Threading.Tasks ;
namespace k8s
{
public partial class Kubernetes
{
/// <summary>
/// Gets a function which returns a <see cref="WebSocketBuilder"/> which <see cref="Kubernetes"/> will use to
/// create a new <see cref="WebSocket"/> connection to the Kubernetes cluster.
/// </summary>
public Func < WebSocketBuilder > CreateWebSocketBuilder { get ; set ; } = ( ) = > new WebSocketBuilder ( ) ;
/// <inheritdoc/>
2018-05-03 07:02:05 +02:00
public Task < WebSocket > WebSocketNamespacedPodExecAsync ( string name , string @namespace = "default" , string command = null , string container = null , bool stderr = true , bool stdin = true , bool stdout = true , bool tty = true , string webSocketSubProtol = null , Dictionary < string , List < string > > customHeaders = null , CancellationToken cancellationToken = default ( CancellationToken ) )
2018-03-31 07:56:52 +02:00
{
2018-05-03 07:02:05 +02:00
return WebSocketNamespacedPodExecAsync ( name , @namespace , new string [ ] { command } , container , stderr , stdin , stdout , tty , webSocketSubProtol , customHeaders , cancellationToken ) ;
2018-03-31 07:56:52 +02:00
}
/// <inheritdoc/>
2019-04-01 23:18:34 +02:00
public virtual async Task < IStreamDemuxer > MuxedStreamNamespacedPodExecAsync ( string name , string @namespace = "default" , IEnumerable < string > command = null , string container = null , bool stderr = true , bool stdin = true , bool stdout = true , bool tty = true , string webSocketSubProtol = WebSocketProtocol . V4BinaryWebsocketProtocol , Dictionary < string , List < string > > customHeaders = null , CancellationToken cancellationToken = default ( CancellationToken ) )
{
WebSocket webSocket = await this . WebSocketNamespacedPodExecAsync ( name : name , @namespace : @namespace , command : command , container : container , tty : tty , cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
StreamDemuxer muxer = new StreamDemuxer ( webSocket ) ;
return muxer ;
}
/// <inheritdoc/>
public virtual Task < WebSocket > WebSocketNamespacedPodExecAsync ( string name , string @namespace = "default" , IEnumerable < string > command = null , string container = null , bool stderr = true , bool stdin = true , bool stdout = true , bool tty = true , string webSocketSubProtol = WebSocketProtocol . V4BinaryWebsocketProtocol , Dictionary < string , List < string > > customHeaders = null , CancellationToken cancellationToken = default ( CancellationToken ) )
2017-12-11 05:21:09 +01:00
{
if ( name = = null )
{
throw new ArgumentNullException ( nameof ( name ) ) ;
}
if ( @namespace = = null )
{
throw new ArgumentNullException ( nameof ( @namespace ) ) ;
}
if ( command = = null )
{
throw new ArgumentNullException ( nameof ( command ) ) ;
}
2018-03-31 07:56:52 +02:00
if ( ! command . Any ( ) )
{
throw new ArgumentOutOfRangeException ( nameof ( command ) ) ;
}
2019-04-01 23:18:34 +02:00
if ( command = = null )
{
throw new ArgumentNullException ( nameof ( command ) ) ;
}
var commandArray = command . ToArray ( ) ;
foreach ( var c in commandArray )
{
if ( c . Length > 0 & & c [ 0 ] = = 0xfeff )
{
throw new InvalidOperationException ( $"Detected an attempt to execute a command which starts with a Unicode byte order mark (BOM). This is probably incorrect. The command was {c}" ) ;
}
}
2017-12-11 05:21:09 +01:00
// Tracing
bool _shouldTrace = ServiceClientTracing . IsEnabled ;
string _invocationId = null ;
if ( _shouldTrace )
{
_invocationId = ServiceClientTracing . NextInvocationId . ToString ( ) ;
Dictionary < string , object > tracingParameters = new Dictionary < string , object > ( ) ;
tracingParameters . Add ( "command" , command ) ;
tracingParameters . Add ( "container" , container ) ;
tracingParameters . Add ( "name" , name ) ;
tracingParameters . Add ( "namespace" , @namespace ) ;
tracingParameters . Add ( "stderr" , stderr ) ;
tracingParameters . Add ( "stdin" , stdin ) ;
tracingParameters . Add ( "stdout" , stdout ) ;
tracingParameters . Add ( "tty" , tty ) ;
2018-05-03 07:02:05 +02:00
tracingParameters . Add ( "webSocketSubProtol" , webSocketSubProtol ) ;
2017-12-11 05:21:09 +01:00
tracingParameters . Add ( "cancellationToken" , cancellationToken ) ;
ServiceClientTracing . Enter ( _invocationId , this , nameof ( WebSocketNamespacedPodExecAsync ) , tracingParameters ) ;
}
// Construct URL
var uriBuilder = new UriBuilder ( BaseUri ) ;
uriBuilder . Scheme = BaseUri . Scheme = = "https" ? "wss" : "ws" ;
if ( ! uriBuilder . Path . EndsWith ( "/" ) )
{
uriBuilder . Path + = "/" ;
}
uriBuilder . Path + = $"api/v1/namespaces/{@namespace}/pods/{name}/exec" ;
2018-03-31 07:56:52 +02:00
var query = string . Empty ;
2017-12-11 05:21:09 +01:00
2018-03-31 07:56:52 +02:00
foreach ( var c in command )
2017-12-11 05:21:09 +01:00
{
2018-03-31 07:56:52 +02:00
query = QueryHelpers . AddQueryString ( query , "command" , c ) ;
}
if ( container ! = null )
{
query = QueryHelpers . AddQueryString ( query , "container" , Uri . EscapeDataString ( container ) ) ;
}
query = QueryHelpers . AddQueryString ( query , new Dictionary < string , string >
{
{ "stderr" , stderr ? "1" : "0" } ,
{ "stdin" , stdin ? "1" : "0" } ,
{ "stdout" , stdout ? "1" : "0" } ,
{ "tty" , tty ? "1" : "0" }
2018-01-26 14:03:45 +08:00
} ) . TrimStart ( '?' ) ;
2017-12-11 05:21:09 +01:00
2018-03-31 07:56:52 +02:00
uriBuilder . Query = query ;
2018-05-03 07:02:05 +02:00
return this . StreamConnectAsync ( uriBuilder . Uri , _invocationId , webSocketSubProtol , customHeaders , cancellationToken ) ;
2017-12-11 05:21:09 +01:00
}
/// <inheritdoc/>
2018-05-03 07:02:05 +02:00
public Task < WebSocket > WebSocketNamespacedPodPortForwardAsync ( string name , string @namespace , IEnumerable < int > ports , string webSocketSubProtocol = null , Dictionary < string , List < string > > customHeaders = null , CancellationToken cancellationToken = default ( CancellationToken ) )
2017-12-11 05:21:09 +01:00
{
if ( name = = null )
{
throw new ArgumentNullException ( nameof ( name ) ) ;
}
if ( @namespace = = null )
{
throw new ArgumentNullException ( nameof ( @namespace ) ) ;
}
if ( ports = = null )
{
throw new ArgumentNullException ( nameof ( ports ) ) ;
}
// Tracing
bool _shouldTrace = ServiceClientTracing . IsEnabled ;
string _invocationId = null ;
if ( _shouldTrace )
{
_invocationId = ServiceClientTracing . NextInvocationId . ToString ( ) ;
Dictionary < string , object > tracingParameters = new Dictionary < string , object > ( ) ;
tracingParameters . Add ( "name" , name ) ;
tracingParameters . Add ( "@namespace" , @namespace ) ;
tracingParameters . Add ( "ports" , ports ) ;
2018-05-03 07:02:05 +02:00
tracingParameters . Add ( "webSocketSubProtocol" , webSocketSubProtocol ) ;
2017-12-11 05:21:09 +01:00
tracingParameters . Add ( "cancellationToken" , cancellationToken ) ;
ServiceClientTracing . Enter ( _invocationId , this , nameof ( WebSocketNamespacedPodPortForwardAsync ) , tracingParameters ) ;
}
// Construct URL
var uriBuilder = new UriBuilder ( this . BaseUri ) ;
uriBuilder . Scheme = this . BaseUri . Scheme = = "https" ? "wss" : "ws" ;
if ( ! uriBuilder . Path . EndsWith ( "/" ) )
{
uriBuilder . Path + = "/" ;
}
uriBuilder . Path + = $"api/v1/namespaces/{@namespace}/pods/{name}/portforward" ;
2018-01-26 14:03:45 +08:00
var q = "" ;
2017-12-11 05:21:09 +01:00
foreach ( var port in ports )
{
2018-01-26 14:03:45 +08:00
q = QueryHelpers . AddQueryString ( q , "ports" , $"{port}" ) ;
2017-12-11 05:21:09 +01:00
}
2018-01-26 14:03:45 +08:00
uriBuilder . Query = q . TrimStart ( '?' ) ;
2017-12-11 05:21:09 +01:00
2018-05-03 07:02:05 +02:00
return StreamConnectAsync ( uriBuilder . Uri , _invocationId , webSocketSubProtocol , customHeaders , cancellationToken ) ;
2017-12-11 05:21:09 +01:00
}
/// <inheritdoc/>
2018-05-03 07:02:05 +02:00
public Task < WebSocket > WebSocketNamespacedPodAttachAsync ( string name , string @namespace , string container = default ( string ) , bool stderr = true , bool stdin = false , bool stdout = true , bool tty = false , string webSocketSubProtol = null , Dictionary < string , List < string > > customHeaders = null , CancellationToken cancellationToken = default ( CancellationToken ) )
2017-12-11 05:21:09 +01:00
{
if ( name = = null )
{
throw new ArgumentNullException ( nameof ( name ) ) ;
}
if ( @namespace = = null )
{
throw new ArgumentNullException ( nameof ( @namespace ) ) ;
}
// Tracing
bool _shouldTrace = ServiceClientTracing . IsEnabled ;
string _invocationId = null ;
if ( _shouldTrace )
{
_invocationId = ServiceClientTracing . NextInvocationId . ToString ( ) ;
Dictionary < string , object > tracingParameters = new Dictionary < string , object > ( ) ;
tracingParameters . Add ( "container" , container ) ;
tracingParameters . Add ( "name" , name ) ;
tracingParameters . Add ( "namespace" , @namespace ) ;
tracingParameters . Add ( "stderr" , stderr ) ;
tracingParameters . Add ( "stdin" , stdin ) ;
tracingParameters . Add ( "stdout" , stdout ) ;
tracingParameters . Add ( "tty" , tty ) ;
2018-05-03 07:02:05 +02:00
tracingParameters . Add ( "webSocketSubProtol" , webSocketSubProtol ) ;
2017-12-11 05:21:09 +01:00
tracingParameters . Add ( "cancellationToken" , cancellationToken ) ;
ServiceClientTracing . Enter ( _invocationId , this , nameof ( WebSocketNamespacedPodAttachAsync ) , tracingParameters ) ;
}
// Construct URL
var uriBuilder = new UriBuilder ( this . BaseUri ) ;
uriBuilder . Scheme = this . BaseUri . Scheme = = "https" ? "wss" : "ws" ;
if ( ! uriBuilder . Path . EndsWith ( "/" ) )
{
uriBuilder . Path + = "/" ;
}
2018-03-23 22:07:51 -07:00
uriBuilder . Path + = $"api/v1/namespaces/{@namespace}/pods/{name}/attach" ;
2017-12-11 05:21:09 +01:00
uriBuilder . Query = QueryHelpers . AddQueryString ( string . Empty , new Dictionary < string , string >
{
{ "container" , container } ,
{ "stderr" , stderr ? "1" : "0" } ,
{ "stdin" , stdin ? "1" : "0" } ,
{ "stdout" , stdout ? "1" : "0" } ,
{ "tty" , tty ? "1" : "0" }
2018-01-26 14:03:45 +08:00
} ) . TrimStart ( '?' ) ;
2017-12-11 05:21:09 +01:00
2018-05-03 07:02:05 +02:00
return StreamConnectAsync ( uriBuilder . Uri , _invocationId , webSocketSubProtol , customHeaders , cancellationToken ) ;
2017-12-11 05:21:09 +01:00
}
2018-05-03 07:02:05 +02:00
protected async Task < WebSocket > StreamConnectAsync ( Uri uri , string invocationId = null , string webSocketSubProtocol = null , Dictionary < string , List < string > > customHeaders = null , CancellationToken cancellationToken = default ( CancellationToken ) )
2017-12-11 05:21:09 +01:00
{
bool _shouldTrace = ServiceClientTracing . IsEnabled ;
// Create WebSocket transport objects
WebSocketBuilder webSocketBuilder = this . CreateWebSocketBuilder ( ) ;
// Set Headers
if ( customHeaders ! = null )
{
foreach ( var _header in customHeaders )
{
webSocketBuilder . SetRequestHeader ( _header . Key , string . Join ( " " , _header . Value ) ) ;
}
}
2018-03-20 16:03:28 +11:00
// Set Credentials
2018-01-26 14:03:45 +08:00
#if NET452
2018-03-20 16:03:28 +11:00
foreach ( var cert in ( ( WebRequestHandler ) this . HttpClientHandler ) . ClientCertificates . OfType < X509Certificate2 > ( ) )
2018-01-26 14:03:45 +08:00
#else
2018-03-20 16:03:28 +11:00
foreach ( var cert in this . HttpClientHandler . ClientCertificates . OfType < X509Certificate2 > ( ) )
2018-01-26 14:03:45 +08:00
#endif
2017-12-11 05:21:09 +01:00
{
webSocketBuilder . AddClientCertificate ( cert ) ;
}
2018-03-31 07:23:45 +02:00
if ( this . Credentials ! = null )
2017-12-11 05:21:09 +01:00
{
2018-03-31 07:23:45 +02:00
// Copy the default (credential-related) request headers from the HttpClient to the WebSocket
HttpRequestMessage message = new HttpRequestMessage ( ) ;
await this . Credentials . ProcessHttpRequestAsync ( message , cancellationToken ) ;
foreach ( var _header in message . Headers )
{
webSocketBuilder . SetRequestHeader ( _header . Key , string . Join ( " " , _header . Value ) ) ;
}
2017-12-11 05:21:09 +01:00
}
2019-01-24 12:06:34 -08:00
#if NET452
2019-03-11 06:39:28 -07:00
if ( this . CaCerts ! = null )
2019-01-24 12:06:34 -08:00
{
webSocketBuilder . SetServerCertificateValidationCallback ( this . ServerCertificateValidationCallback ) ;
}
#endif
2018-03-20 16:03:28 +11:00
#if NETCOREAPP2_1
2019-03-11 06:39:28 -07:00
if ( this . CaCerts ! = null )
2018-03-23 21:47:40 -07:00
{
2019-03-11 06:39:28 -07:00
webSocketBuilder . ExpectServerCertificate ( this . CaCerts ) ;
2018-03-23 21:47:40 -07:00
}
2018-05-03 07:02:05 +02:00
2018-03-23 21:47:40 -07:00
if ( this . SkipTlsVerify )
{
2018-03-20 16:03:28 +11:00
webSocketBuilder . SkipServerCertificateValidation ( ) ;
2018-03-23 21:47:40 -07:00
}
2018-05-03 07:02:05 +02:00
if ( webSocketSubProtocol ! = null )
{
2018-06-08 09:47:39 +10:00
webSocketBuilder . Options . AddSubProtocol ( webSocketSubProtocol ) ;
2018-05-03 07:02:05 +02:00
}
2018-03-20 16:03:28 +11:00
#endif // NETCOREAPP2_1
2017-12-11 05:21:09 +01:00
// Send Request
cancellationToken . ThrowIfCancellationRequested ( ) ;
WebSocket webSocket = null ;
try
{
webSocket = await webSocketBuilder . BuildAndConnectAsync ( uri , CancellationToken . None ) . ConfigureAwait ( false ) ;
}
2018-05-01 00:16:06 +02:00
catch ( WebSocketException wse ) when ( wse . WebSocketErrorCode = = WebSocketError . HeaderError | | ( wse . InnerException is WebSocketException & & ( ( WebSocketException ) wse . InnerException ) . WebSocketErrorCode = = WebSocketError . HeaderError ) )
{
// This usually indicates the server sent an error message, like 400 Bad Request. Unfortunately, the WebSocket client
// class doesn't give us a lot of information about what went wrong. So, retry the connection.
var uriBuilder = new UriBuilder ( uri ) ;
uriBuilder . Scheme = uri . Scheme = = "wss" ? "https" : "http" ;
var response = await this . HttpClient . GetAsync ( uriBuilder . Uri , cancellationToken ) . ConfigureAwait ( false ) ;
if ( response . StatusCode = = HttpStatusCode . SwitchingProtocols )
{
// This should never happen - the server just allowed us to switch to WebSockets but the previous call didn't work.
// Rethrow the original exception
response . Dispose ( ) ;
throw ;
}
else
{
var content = await response . Content . ReadAsStringAsync ( ) . ConfigureAwait ( false ) ;
// Try to parse the content as a V1Status object
var genericObject = SafeJsonConvert . DeserializeObject < KubernetesObject > ( content ) ;
V1Status status = null ;
if ( genericObject . ApiVersion = = "v1" & & genericObject . Kind = = "Status" )
{
status = SafeJsonConvert . DeserializeObject < V1Status > ( content ) ;
}
var ex = new HttpOperationException ( $"The operation returned an invalid status code: {response.StatusCode}" , wse )
{
Response = new HttpResponseMessageWrapper ( response , content ) ,
Body = status ! = null ? ( object ) status : content ,
} ;
response . Dispose ( ) ;
throw ex ;
}
}
2017-12-11 05:21:09 +01:00
catch ( Exception ex )
{
if ( _shouldTrace )
{
ServiceClientTracing . Error ( invocationId , ex ) ;
}
throw ;
}
finally
{
if ( _shouldTrace )
{
ServiceClientTracing . Exit ( invocationId , null ) ;
}
2019-01-24 12:06:34 -08:00
#if NET452
2019-03-11 06:39:28 -07:00
if ( this . CaCerts ! = null )
2019-01-24 12:06:34 -08:00
{
webSocketBuilder . CleanupServerCertificateValidationCallback ( this . ServerCertificateValidationCallback ) ;
}
#endif
2017-12-11 05:21:09 +01:00
}
return webSocket ;
}
2019-01-24 12:06:34 -08:00
#if NET452
internal bool ServerCertificateValidationCallback ( object sender , X509Certificate certificate , X509Chain chain , SslPolicyErrors sslPolicyErrors )
{
2019-03-11 06:39:28 -07:00
return Kubernetes . CertificateValidationCallBack ( sender , this . CaCerts , certificate , chain , sslPolicyErrors ) ;
2019-01-24 12:06:34 -08:00
}
#endif
2017-12-11 05:21:09 +01:00
}
}