From 4a0a3948454f8a8f22a9e7f5604cc980ceb8e6fd Mon Sep 17 00:00:00 2001 From: Iskandar Abudiab Date: Thu, 26 Nov 2020 20:21:20 +0100 Subject: [PATCH] Add Pod follow logs and status API --- .../Client/GenericKubernetesClient.swift | 42 ++++++++++++++++--- ...amespacedGenericKubernetesClient+Pod.swift | 34 +++++++++++++++ .../NamespacedGenericKubernetesClient.swift | 6 +-- .../Client/RequestBuilder.swift | 34 +++++++++------ 4 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient+Pod.swift diff --git a/Sources/SwiftkubeClient/Client/GenericKubernetesClient.swift b/Sources/SwiftkubeClient/Client/GenericKubernetesClient.swift index 7e0d604..e0a2d96 100644 --- a/Sources/SwiftkubeClient/Client/GenericKubernetesClient.swift +++ b/Sources/SwiftkubeClient/Client/GenericKubernetesClient.swift @@ -144,13 +144,14 @@ public class GenericKubernetesClient { } } - private func makeRequest() -> RequestBuilder { - return RequestBuilder(config: config, gvk: gvk) - } } internal extension GenericKubernetesClient { + func makeRequest() -> RequestBuilder { + return RequestBuilder(config: config, gvk: gvk) + } + func handle(_ response: HTTPClient.Response, eventLoop: EventLoop) -> EventLoopFuture { return handleResourceOrStatus(response, eventLoop: eventLoop).flatMap { (result: ResourceOrStatus) -> EventLoopFuture in guard case let ResourceOrStatus.resource(resource) = result else { @@ -203,16 +204,45 @@ public extension GenericKubernetesClient where Resource: ListableResource { } } -public extension GenericKubernetesClient { +internal extension GenericKubernetesClient { + + func status(in namespace: NamespaceSelector, name: String) throws -> EventLoopFuture { + do { + let eventLoop = httpClient.eventLoopGroup.next() + let request = try makeRequest().to(.GET).resource(withName: name).status().in(namespace).build() + + return httpClient.execute(request: request, logger: logger).flatMap { response in + self.handle(response, eventLoop: eventLoop) + } + } catch { + return httpClient.eventLoopGroup.next().makeFailedFuture(error) + } + } + + func updateStatus(in namespace: NamespaceSelector, _ resource: Resource) throws -> EventLoopFuture { + do { + let eventLoop = httpClient.eventLoopGroup.next() + let request = try makeRequest().to(.PUT).resource(resource).status().in(namespace).build() + + return httpClient.execute(request: request, logger: logger).flatMap { response in + self.handle(response, eventLoop: eventLoop) + } + } catch { + return httpClient.eventLoopGroup.next().makeFailedFuture(error) + } + } +} + +internal extension GenericKubernetesClient { - internal func watch(in namespace: NamespaceSelector, using watch: ResourceWatch) throws -> HTTPClient.Task { + func watch(in namespace: NamespaceSelector, using watch: ResourceWatch) throws -> HTTPClient.Task { let request = try makeRequest().toWatch().in(namespace).build() let delegate = WatchDelegate(watch: watch, logger: logger) return httpClient.execute(request: request, delegate: delegate, logger: logger) } - internal func follow(in namespace: NamespaceSelector, name: String, container: String?, using watch: LogWatch) throws -> HTTPClient.Task { + func follow(in namespace: NamespaceSelector, name: String, container: String?, using watch: LogWatch) throws -> HTTPClient.Task { let request = try makeRequest().toFollow(pod: name, container: container).in(namespace).build() let delegate = WatchDelegate(watch: watch, logger: logger) diff --git a/Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient+Pod.swift b/Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient+Pod.swift new file mode 100644 index 0000000..87c9173 --- /dev/null +++ b/Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient+Pod.swift @@ -0,0 +1,34 @@ +// +// Copyright 2020 Iskandar Abudiab (iabudiab.dev) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AsyncHTTPClient +import NIO +import SwiftkubeModel + +public extension NamespacedGenericKubernetesClient where Resource == core.v1.Pod { + + func follow(in namespace: NamespaceSelector? = nil, name: String, container: String?, lineHandler: @escaping LogWatch.LineHandler) throws -> HTTPClient.Task { + return try super.follow(in: namespace ?? .namespace(self.config.namespace), name: name, container: container, using: LogWatch(logger: logger, lineHandler)) + } + + func status(in namespace: NamespaceSelector? = nil, name: String) throws -> EventLoopFuture { + return try super.status(in: namespace ?? .namespace(self.config.namespace), name: name) + } + + func updateStatus(in namespace: NamespaceSelector? = nil, _ pod: core.v1.Pod) throws -> EventLoopFuture { + return try super.updateStatus(in: namespace ?? .namespace(self.config.namespace), pod) + } +} diff --git a/Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient.swift b/Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient.swift index 891fb3b..57ed30b 100644 --- a/Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient.swift +++ b/Sources/SwiftkubeClient/Client/NamespacedGenericKubernetesClient.swift @@ -28,11 +28,7 @@ public extension NamespacedGenericKubernetesClient where Resource: ReadableResou } func watch(in namespace: NamespaceSelector? = nil, eventHandler: @escaping ResourceWatch.EventHandler) throws -> HTTPClient.Task { - return try super.watch(in: namespace ?? NamespaceSelector.allNamespaces, using: ResourceWatch(logger: logger, eventHandler)) - } - - func follow(in namespace: NamespaceSelector? = nil, name: String, container: String?, lineHandler: @escaping LogWatch.LineHandler) throws -> HTTPClient.Task { - return try super.follow(in: namespace ?? NamespaceSelector.allNamespaces, name: name, container: container, using: LogWatch(logger: logger, lineHandler)) + return try super.watch(in: namespace ?? .namespace(self.config.namespace), using: ResourceWatch(logger: logger, eventHandler)) } } diff --git a/Sources/SwiftkubeClient/Client/RequestBuilder.swift b/Sources/SwiftkubeClient/Client/RequestBuilder.swift index b25a73e..9173664 100644 --- a/Sources/SwiftkubeClient/Client/RequestBuilder.swift +++ b/Sources/SwiftkubeClient/Client/RequestBuilder.swift @@ -41,8 +41,9 @@ internal class RequestBuilder { var listOptions: [ListOption]? var method: HTTPMethod! var namespace: NamespaceSelector! - var watch: Bool = false - var follow: Bool = false + var statusRequest: Bool = false + var watchRequest: Bool = false + var followRequest: Bool = false var container: String? init(config: KubernetesClientConfig, gvk: GroupVersionKind) { @@ -55,9 +56,14 @@ internal class RequestBuilder { return self } + func status() -> RequestBuilder { + self.statusRequest = true + return self + } + func toWatch() -> RequestBuilder { self.method = .GET - self.watch = true + self.watchRequest = true return self } @@ -65,7 +71,7 @@ internal class RequestBuilder { self.method = .GET self.resourceName = pod self.container = container - self.follow = true + self.followRequest = true return self } @@ -91,7 +97,15 @@ internal class RequestBuilder { func build() throws -> HTTPClient.Request { var components = URLComponents(url: config.masterURL, resolvingAgainstBaseURL: false) - components?.path = urlPath(forNamespace: namespace, name: resourceName, follow: follow) + components?.path = urlPath(forNamespace: namespace, name: resourceName) + + if statusRequest { + components?.path += "/status" + } + + if followRequest { + components?.path += "/log" + } guard !(method.hasRequestBody && resourceName == nil) else { throw SwiftkubeClientError.badRequest("Resource `metadata.name` must be set.") @@ -103,11 +117,11 @@ internal class RequestBuilder { components?.queryItems?.append(contentsOf: listOptions.map { URLQueryItem(name: $0.name, value: $0.value) }) } - if watch { + if watchRequest { components?.queryItems?.append(URLQueryItem(name: "watch", value: "true")) } - if follow { + if followRequest { components?.queryItems?.append(URLQueryItem(name: "follow", value: "true")) } @@ -130,7 +144,7 @@ internal class RequestBuilder { return try HTTPClient.Request(url: url, method: method, headers: headers, body: body) } - func urlPath(forNamespace namespace: NamespaceSelector, name: String?, follow: Bool) -> String { + func urlPath(forNamespace namespace: NamespaceSelector, name: String?) -> String { var url: String if case NamespaceSelector.allNamespaces = namespace { @@ -143,10 +157,6 @@ internal class RequestBuilder { url += "/\(name)" } - if follow { - url += "/log" - } - return url }