예제 #1
0
        public async Task <T> DeleteNamespacedAsync <T>(string ns, string name, CancellationToken cancel = default)
            where T : IKubernetesObject
        {
            var resp = await kubernetes.DeleteNamespacedCustomObjectWithHttpMessagesAsync(group, version, ns, plural, name, cancellationToken : cancel).ConfigureAwait(false);

            return(KubernetesJson.Deserialize <T>(resp.Body.ToString()));
        }
예제 #2
0
        public async Task <T> ListAsync <T>(CancellationToken cancel = default)
            where T : IKubernetesObject
        {
            var resp = await kubernetes.ListClusterCustomObjectWithHttpMessagesAsync(group, version, plural, cancellationToken : cancel).ConfigureAwait(false);

            return(KubernetesJson.Deserialize <T>(resp.Body.ToString()));
        }
예제 #3
0
        internal static async IAsyncEnumerable <(WatchEventType, T)> CreateWatchEventEnumerator(
            Func <Task <TextReader> > streamReaderCreator,
            Action <Exception> onError = null,
            [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            Task <TR> AttachCancellationToken <TR>(Task <TR> task)
            {
                if (!task.IsCompleted)
                {
                    // here to pass cancellationToken into task
                    return(task.ContinueWith(t => t.GetAwaiter().GetResult(), cancellationToken));
                }

                return(task);
            }

            using var streamReader = await AttachCancellationToken(streamReaderCreator ()).ConfigureAwait(false);

            for (; ;)
            {
                // ReadLineAsync will return null when we've reached the end of the stream.
                var line = await AttachCancellationToken(streamReader.ReadLineAsync()).ConfigureAwait(false);

                cancellationToken.ThrowIfCancellationRequested();

                if (line == null)
                {
                    yield break;
                }

                WatchEvent @event = null;

                try
                {
                    var genericEvent = KubernetesJson.Deserialize <Watcher <KubernetesObject> .WatchEvent>(line);

                    if (genericEvent.Object.Kind == "Status")
                    {
                        var statusEvent = KubernetesJson.Deserialize <Watcher <V1Status> .WatchEvent>(line);
                        var exception   = new KubernetesException(statusEvent.Object);
                        onError?.Invoke(exception);
                    }
                    else
                    {
                        @event = KubernetesJson.Deserialize <WatchEvent>(line);
                    }
                }
                catch (Exception e)
                {
                    onError?.Invoke(e);
                }


                if (@event != null)
                {
                    yield return(@event.Type, @event.Object);
                }
            }
        }
예제 #4
0
        /// <summary>
        /// Implementation of the proposal for out-of-tree client
        /// authentication providers as described here --
        /// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md
        /// Took inspiration from python exec_provider.py --
        /// https://github.com/kubernetes-client/python-base/blob/master/config/exec_provider.py
        /// </summary>
        /// <param name="config">The external command execution configuration</param>
        /// <returns>
        /// The token, client certificate data, and the client key data received from the external command execution
        /// </returns>
        public static ExecCredentialResponse ExecuteExternalCommand(ExternalExecution config)
        {
            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }

            var process = CreateRunnableExternalProcess(config);

            try
            {
                process.Start();
            }
            catch (Exception ex)
            {
                throw new KubeConfigException($"external exec failed due to: {ex.Message}");
            }

            var stdout = process.StandardOutput.ReadToEnd();
            var stderr = process.StandardError.ReadToEnd();

            if (string.IsNullOrWhiteSpace(stderr) == false)
            {
                throw new KubeConfigException($"external exec failed due to: {stderr}");
            }

            // Wait for a maximum of 5 seconds, if a response takes longer probably something went wrong...
            process.WaitForExit(5);

            try
            {
                var responseObject = KubernetesJson.Deserialize <ExecCredentialResponse>(stdout);
                if (responseObject == null || responseObject.ApiVersion != config.ApiVersion)
                {
                    throw new KubeConfigException(
                              $"external exec failed because api version {responseObject.ApiVersion} does not match {config.ApiVersion}");
                }

                if (responseObject.Status.IsValid())
                {
                    return(responseObject);
                }
                else
                {
                    throw new KubeConfigException($"external exec failed missing token or clientCertificateData field in plugin output");
                }
            }
            catch (JsonException ex)
            {
                throw new KubeConfigException($"external exec failed due to failed deserialization process: {ex}");
            }
            catch (Exception ex)
            {
                throw new KubeConfigException($"external exec failed due to uncaught exception: {ex}");
            }
        }
예제 #5
0
        public async Task <int> NamespacedPodExecAsync(string name, string @namespace, string container,
                                                       IEnumerable <string> command, bool tty, ExecAsyncCallback action, CancellationToken cancellationToken)
        {
            // All other parameters are being validated by MuxedStreamNamespacedPodExecAsync
            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            try
            {
                using (var muxedStream = await MuxedStreamNamespacedPodExecAsync(
                           name,
                           @namespace, command, container, tty: tty,
                           cancellationToken: cancellationToken).ConfigureAwait(false))
                    using (var stdIn = muxedStream.GetStream(null, ChannelIndex.StdIn))
                        using (var stdOut = muxedStream.GetStream(ChannelIndex.StdOut, null))
                            using (var stdErr = muxedStream.GetStream(ChannelIndex.StdErr, null))
                                using (var error = muxedStream.GetStream(ChannelIndex.Error, null))
                                    using (var errorReader = new StreamReader(error))
                                    {
                                        muxedStream.Start();

                                        await action(stdIn, stdOut, stdErr).ConfigureAwait(false);

                                        var errors = await errorReader.ReadToEndAsync().ConfigureAwait(false);

                                        // StatusError is defined here:
                                        // https://github.com/kubernetes/kubernetes/blob/068e1642f63a1a8c48c16c18510e8854a4f4e7c5/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go#L37
                                        var returnMessage = KubernetesJson.Deserialize <V1Status>(errors);
                                        return(GetExitCodeOrThrow(returnMessage));
                                    }
            }
            catch (HttpOperationException httpEx) when(httpEx.Body is V1Status)
            {
                throw new KubernetesException((V1Status)httpEx.Body, httpEx);
            }
        }
        protected async Task <WebSocket> StreamConnectAsync(Uri uri, string webSocketSubProtocol = null, Dictionary <string, List <string> > customHeaders = null, CancellationToken cancellationToken = default)
        {
            if (uri == null)
            {
                throw new ArgumentNullException(nameof(uri));
            }

            // Create WebSocket transport objects
            var webSocketBuilder = CreateWebSocketBuilder();

            // Set Headers
            if (customHeaders != null)
            {
                foreach (var header in customHeaders)
                {
                    webSocketBuilder.SetRequestHeader(header.Key, string.Join(" ", header.Value));
                }
            }

            // Set Credentials
            if (this.ClientCert != null)
            {
                webSocketBuilder.AddClientCertificate(this.ClientCert);
            }

            if (this.HttpClientHandler != null)
            {
#if NET5_0_OR_GREATER
                foreach (var cert in this.HttpClientHandler.SslOptions.ClientCertificates.OfType <X509Certificate2>())
#else
                foreach (var cert in this.HttpClientHandler.ClientCertificates.OfType <X509Certificate2>())
#endif
                {
                    webSocketBuilder.AddClientCertificate(cert);
                }
            }

            if (Credentials != null)
            {
                // Copy the default (credential-related) request headers from the HttpClient to the WebSocket
                var message = new HttpRequestMessage();
                await Credentials.ProcessHttpRequestAsync(message, cancellationToken).ConfigureAwait(false);

                foreach (var header in message.Headers)
                {
                    webSocketBuilder.SetRequestHeader(header.Key, string.Join(" ", header.Value));
                }
            }

            if (this.CaCerts != null)
            {
                webSocketBuilder.ExpectServerCertificate(this.CaCerts);
            }

            if (this.SkipTlsVerify)
            {
                webSocketBuilder.SkipServerCertificateValidation();
            }

            if (webSocketSubProtocol != null)
            {
                webSocketBuilder.Options.AddSubProtocol(webSocketSubProtocol);
            }

            // Send Request
            cancellationToken.ThrowIfCancellationRequested();

            WebSocket webSocket = null;
            try
            {
                webSocket = await webSocketBuilder.BuildAndConnectAsync(uri, CancellationToken.None)
                            .ConfigureAwait(false);
            }
            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 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
                {
#if NET5_0_OR_GREATER
                    var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
                    var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
                    // Try to parse the content as a V1Status object
                    var      genericObject = KubernetesJson.Deserialize <KubernetesObject>(content);
                    V1Status status        = null;

                    if (genericObject.ApiVersion == "v1" && genericObject.Kind == "Status")
                    {
                        status = KubernetesJson.Deserialize <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;
                }
            }
            catch (Exception)
            {
                throw;
            }

            return(webSocket);
        }