From d346f1b7e469ab7e88be02d3edbd87ff0f837d5e Mon Sep 17 00:00:00 2001 From: Thomas Horrobin Date: Sun, 15 Aug 2021 23:25:51 +0200 Subject: [PATCH 1/4] added loadConfigFromPath hung loadConfigFromPath off of KubernetesClientConfig --- .../Config/KubernetesClientConfig.swift | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift index 24cdf9e..d8a239e 100644 --- a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift +++ b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift @@ -228,3 +228,51 @@ private extension AuthInfo { return nil } } + +public extension KubernetesClientConfig { + static func loadConfigFromPath(logger: Logger, path: String) throws -> KubernetesClientConfig? { + let decoder = YAMLDecoder() + + let fileURL = URL(fileURLWithPath: path) + + guard let contents = try? String(contentsOf: fileURL, encoding: .utf8) else { + return nil + } + + guard let kubeConfig = try? decoder.decode(KubeConfig.self, from: contents) else { + return nil + } + + guard let currentContext = kubeConfig.currentContext else { + return nil + } + + guard let context = kubeConfig.contexts?.filter({ $0.name == currentContext }).map(\.context).first else { + return nil + } + + guard let cluster = kubeConfig.clusters?.filter({ $0.name == context.cluster }).map(\.cluster).first else { + return nil + } + + guard let masterURL = URL(string: cluster.server) else { + return nil + } + + guard let authInfo = kubeConfig.users?.filter({ $0.name == context.user }).map(\.authInfo).first else { + return nil + } + + guard let authentication = authInfo.authentication(logger: logger) else { + return nil + } + + return KubernetesClientConfig( + masterURL: masterURL, + namespace: context.namespace ?? "default", + authentication: authentication, + trustRoots: cluster.trustRoots(logger: logger), + insecureSkipTLSVerify: cluster.insecureSkipTLSVerify ?? true + ) + } +} From 0ca450d8dbd73acd7b19460ba1cc0483622e0254 Mon Sep 17 00:00:00 2001 From: Thomas Horrobin Date: Sat, 21 Aug 2021 10:57:57 +0100 Subject: [PATCH 2/4] extract logic to load URLs into URLConfigLoader and added LocalKubeConfigLoader plus new convenience constructor This commit removes the KubernetesClientConfig.loadURL extension and moves the functionality to a struct that implements KubernetesClientConfigLoader. This is so the loaders are consistent which will help future proof the project. Also a convenience constructor has been added to take advantage of the new functionality in URLConfigLoader --- .../Client/KubernetesClient.swift | 29 ++++++- .../Config/KubernetesClientConfig.swift | 84 ++++++------------- 2 files changed, 54 insertions(+), 59 deletions(-) diff --git a/Sources/SwiftkubeClient/Client/KubernetesClient.swift b/Sources/SwiftkubeClient/Client/KubernetesClient.swift index 4c2da52..d8ee58a 100644 --- a/Sources/SwiftkubeClient/Client/KubernetesClient.swift +++ b/Sources/SwiftkubeClient/Client/KubernetesClient.swift @@ -100,7 +100,7 @@ public class KubernetesClient { guard let config = - (try? LocalFileConfigLoader().load(logger: logger)) ?? + (try? LocalKubeConfigLoader().load(logger: logger)) ?? (try? ServiceAccountConfigLoader().load(logger: logger)) else { return nil @@ -108,6 +108,33 @@ public class KubernetesClient { self.init(config: config, provider: provider, logger: logger) } + + /// Create a new instance of the Kubernetes client. + /// + /// - Parameters: + /// - fromURL: The url to load the configuration from for this client instance. It can be a local file or remote URL. + /// - provider: Specify how `EventLoopGroup` will be created. + /// - logger: The logger to use for this client. + public convenience init?( + fromURL: URL, + provider: HTTPClient.EventLoopGroupProvider = .shared(MultiThreadedEventLoopGroup(numberOfThreads: 1)), + logger: Logger? = nil + ) throws { + let logger = logger ?? KubernetesClient.loggingDisabled + + guard fromURL.isFileURL else { + logger.debug("URL must point to a file") + throw URLError(.unsupportedURL) + } + + guard + let config = try? LocalFileConfigLoader(fromURL: fromURL).load(logger: logger) + else { + return nil + } + + self.init(config: config, provider: provider, logger: logger) + } /// Create a new instance of the Kubernetes client. /// diff --git a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift index d8a239e..05b9aac 100644 --- a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift +++ b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift @@ -50,20 +50,14 @@ internal protocol KubernetesClientConfigLoader { func load(logger: Logger) throws -> KubernetesClientConfig? } -// MARK: - LocalFileConfigLoader +// MARK: - URLConfigLoader -internal struct LocalFileConfigLoader: KubernetesClientConfigLoader { +internal struct URLConfigLoader { - internal func load(logger: Logger) throws -> KubernetesClientConfig? { + internal func load(fromURL: URL, logger: Logger) throws -> KubernetesClientConfig? { let decoder = YAMLDecoder() - guard let homePath = ProcessInfo.processInfo.environment["HOME"] else { - logger.info("Skipping kubeconfig in $HOME/.kube/config because HOME env variable is not set.") - return nil - } - let fileURL = URL(fileURLWithPath: homePath + "/.kube/config") - - guard let contents = try? String(contentsOf: fileURL, encoding: .utf8) else { + guard let contents = try? String(contentsOf: fromURL, encoding: .utf8) else { return nil } @@ -105,6 +99,28 @@ internal struct LocalFileConfigLoader: KubernetesClientConfigLoader { } } +// MARK: - LocalFileConfigLoader + +internal struct LocalFileConfigLoader: KubernetesClientConfigLoader { + let fromURL: URL + func load(logger: Logger) throws -> KubernetesClientConfig? { + return try? URLConfigLoader().load(fromURL: fromURL, logger: logger) + } +} + +// MARK: - LocalKubeConfigLoader + +internal struct LocalKubeConfigLoader: KubernetesClientConfigLoader { + func load(logger: Logger) throws -> KubernetesClientConfig? { + guard let homePath = ProcessInfo.processInfo.environment["HOME"] else { + logger.info("Skipping kubeconfig in $HOME/.kube/config because HOME env variable is not set.") + return nil + } + let kubeConfigURL = URL(fileURLWithPath: homePath + "/.kube/config") + return try? URLConfigLoader().load(fromURL: kubeConfigURL, logger: logger) + } +} + // MARK: - ServiceAccountConfigLoader internal struct ServiceAccountConfigLoader: KubernetesClientConfigLoader { @@ -228,51 +244,3 @@ private extension AuthInfo { return nil } } - -public extension KubernetesClientConfig { - static func loadConfigFromPath(logger: Logger, path: String) throws -> KubernetesClientConfig? { - let decoder = YAMLDecoder() - - let fileURL = URL(fileURLWithPath: path) - - guard let contents = try? String(contentsOf: fileURL, encoding: .utf8) else { - return nil - } - - guard let kubeConfig = try? decoder.decode(KubeConfig.self, from: contents) else { - return nil - } - - guard let currentContext = kubeConfig.currentContext else { - return nil - } - - guard let context = kubeConfig.contexts?.filter({ $0.name == currentContext }).map(\.context).first else { - return nil - } - - guard let cluster = kubeConfig.clusters?.filter({ $0.name == context.cluster }).map(\.cluster).first else { - return nil - } - - guard let masterURL = URL(string: cluster.server) else { - return nil - } - - guard let authInfo = kubeConfig.users?.filter({ $0.name == context.user }).map(\.authInfo).first else { - return nil - } - - guard let authentication = authInfo.authentication(logger: logger) else { - return nil - } - - return KubernetesClientConfig( - masterURL: masterURL, - namespace: context.namespace ?? "default", - authentication: authentication, - trustRoots: cluster.trustRoots(logger: logger), - insecureSkipTLSVerify: cluster.insecureSkipTLSVerify ?? true - ) - } -} From d30296e2e5da708fdf0a11af0077730e331cdeec Mon Sep 17 00:00:00 2001 From: Thomas Horrobin Date: Wed, 25 Aug 2021 10:06:10 +0100 Subject: [PATCH 3/4] fixed issues whereby non file URLs were silently breaking --- Sources/SwiftkubeClient/Client/KubernetesClient.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/SwiftkubeClient/Client/KubernetesClient.swift b/Sources/SwiftkubeClient/Client/KubernetesClient.swift index d8ee58a..713a4b8 100644 --- a/Sources/SwiftkubeClient/Client/KubernetesClient.swift +++ b/Sources/SwiftkubeClient/Client/KubernetesClient.swift @@ -119,14 +119,9 @@ public class KubernetesClient { fromURL: URL, provider: HTTPClient.EventLoopGroupProvider = .shared(MultiThreadedEventLoopGroup(numberOfThreads: 1)), logger: Logger? = nil - ) throws { + ) { let logger = logger ?? KubernetesClient.loggingDisabled - guard fromURL.isFileURL else { - logger.debug("URL must point to a file") - throw URLError(.unsupportedURL) - } - guard let config = try? LocalFileConfigLoader(fromURL: fromURL).load(logger: logger) else { From a3810ab878ff3b67137739f6f5fe0622ca02a7b0 Mon Sep 17 00:00:00 2001 From: Thomas Horrobin Date: Wed, 25 Aug 2021 11:28:30 +0100 Subject: [PATCH 4/4] ran swiftformat --- Package.swift | 13 ++++++---- .../Client/KubernetesClient.swift | 4 +-- .../Config/KubernetesClientConfig.swift | 2 +- Tests/SwiftkubeClientTests/NodeTests.swift | 2 +- .../RequestBuilderTests.swift | 25 +++++++++---------- .../SwiftkubeClientTests/RetryStrategy.swift | 6 ++--- 6 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Package.swift b/Package.swift index b14bffe..eb37bb1 100644 --- a/Package.swift +++ b/Package.swift @@ -6,12 +6,13 @@ import PackageDescription let package = Package( name: "SwiftkubeClient", platforms: [ - .macOS(.v10_13), .iOS(.v12), .tvOS(.v12), .watchOS(.v5) + .macOS(.v10_13), .iOS(.v12), .tvOS(.v12), .watchOS(.v5), ], products: [ .library( name: "SwiftkubeClient", - targets: ["SwiftkubeClient"]), + targets: ["SwiftkubeClient"] + ), ], dependencies: [ .package(name: "SwiftkubeModel", url: "https://github.com/swiftkube/model.git", .upToNextMajor(from: "0.4.0")), @@ -29,11 +30,13 @@ let package = Package( .product(name: "Logging", package: "swift-log"), .product(name: "Metrics", package: "swift-metrics"), .product(name: "Yams", package: "Yams"), - ]), + ] + ), .testTarget( name: "SwiftkubeClientTests", dependencies: [ - "SwiftkubeClient" - ]), + "SwiftkubeClient", + ] + ), ] ) diff --git a/Sources/SwiftkubeClient/Client/KubernetesClient.swift b/Sources/SwiftkubeClient/Client/KubernetesClient.swift index 713a4b8..70c8a14 100644 --- a/Sources/SwiftkubeClient/Client/KubernetesClient.swift +++ b/Sources/SwiftkubeClient/Client/KubernetesClient.swift @@ -108,7 +108,7 @@ public class KubernetesClient { self.init(config: config, provider: provider, logger: logger) } - + /// Create a new instance of the Kubernetes client. /// /// - Parameters: @@ -121,7 +121,7 @@ public class KubernetesClient { logger: Logger? = nil ) { let logger = logger ?? KubernetesClient.loggingDisabled - + guard let config = try? LocalFileConfigLoader(fromURL: fromURL).load(logger: logger) else { diff --git a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift index 05b9aac..3ea260b 100644 --- a/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift +++ b/Sources/SwiftkubeClient/Config/KubernetesClientConfig.swift @@ -104,7 +104,7 @@ internal struct URLConfigLoader { internal struct LocalFileConfigLoader: KubernetesClientConfigLoader { let fromURL: URL func load(logger: Logger) throws -> KubernetesClientConfig? { - return try? URLConfigLoader().load(fromURL: fromURL, logger: logger) + try? URLConfigLoader().load(fromURL: fromURL, logger: logger) } } diff --git a/Tests/SwiftkubeClientTests/NodeTests.swift b/Tests/SwiftkubeClientTests/NodeTests.swift index 72feca5..4a6458f 100644 --- a/Tests/SwiftkubeClientTests/NodeTests.swift +++ b/Tests/SwiftkubeClientTests/NodeTests.swift @@ -16,8 +16,8 @@ import AsyncHTTPClient import NIO -import SwiftkubeModel import SwiftkubeClient +import SwiftkubeModel import XCTest final class NodeTests: XCTestCase { diff --git a/Tests/SwiftkubeClientTests/RequestBuilderTests.swift b/Tests/SwiftkubeClientTests/RequestBuilderTests.swift index 810f65b..954267d 100644 --- a/Tests/SwiftkubeClientTests/RequestBuilderTests.swift +++ b/Tests/SwiftkubeClientTests/RequestBuilderTests.swift @@ -17,8 +17,8 @@ import AsyncHTTPClient import NIO import NIOHTTP1 -import SwiftkubeModel @testable import SwiftkubeClient +import SwiftkubeModel import XCTest final class RequestBuilderTests: XCTestCase { @@ -85,7 +85,7 @@ final class RequestBuilderTests: XCTestCase { let request = try? builder.to(.GET).in(.default).with(options: [ .labelSelector(.eq(["app": "nginx"])), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "labelSelector=app%3Dnginx") } @@ -95,7 +95,7 @@ final class RequestBuilderTests: XCTestCase { let request = try? builder.to(.GET).in(.default).with(options: [ .labelSelector(.neq(["app": "nginx"])), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "labelSelector=app!%3Dnginx") } @@ -105,7 +105,7 @@ final class RequestBuilderTests: XCTestCase { let request = try? builder.to(.GET).in(.default).with(options: [ .labelSelector(.in(["env": ["dev", "staging"]])), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "labelSelector=env%20in%20(dev,staging)") } @@ -114,17 +114,16 @@ final class RequestBuilderTests: XCTestCase { let request = try? builder.to(.GET).in(.default).with(options: [ .labelSelector(.notIn(["env": ["dev", "staging"]])), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "labelSelector=env%20notin%20(dev,staging)") } - func testGetWithListOptions_Exists() { let builder = RequestBuilder(config: config, gvk: gvk) let request = try? builder.to(.GET).in(.default).with(options: [ .labelSelector(.exists(["app", "env"])), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "labelSelector=app,env") } @@ -134,7 +133,7 @@ final class RequestBuilderTests: XCTestCase { let request = try? builder.to(.GET).in(.default).with(options: [ .fieldSelector(.eq(["app": "nginx"])), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "fieldSelector=app%3Dnginx") } @@ -144,7 +143,7 @@ final class RequestBuilderTests: XCTestCase { let request = try? builder.to(.GET).in(.default).with(options: [ .fieldSelector(.neq(["app": "nginx"])), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "fieldSelector=app!%3Dnginx") } @@ -152,9 +151,9 @@ final class RequestBuilderTests: XCTestCase { func testGetWithListOptions_Limit() { let builder = RequestBuilder(config: config, gvk: gvk) let request = try? builder.to(.GET).in(.default).with(options: [ - .limit(2) + .limit(2), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "limit=2") } @@ -162,9 +161,9 @@ final class RequestBuilderTests: XCTestCase { func testGetWithListOptions_Version() { let builder = RequestBuilder(config: config, gvk: gvk) let request = try? builder.to(.GET).in(.default).with(options: [ - .resourceVersion("20") + .resourceVersion("20"), ]) - .build() + .build() XCTAssertEqual(request?.url.query, "resourceVersion=20") } diff --git a/Tests/SwiftkubeClientTests/RetryStrategy.swift b/Tests/SwiftkubeClientTests/RetryStrategy.swift index 54ad473..fe351bf 100644 --- a/Tests/SwiftkubeClientTests/RetryStrategy.swift +++ b/Tests/SwiftkubeClientTests/RetryStrategy.swift @@ -86,7 +86,7 @@ final class RetryStrategyTests: XCTestCase { XCTAssertEqual(attempts, [ RetryAttempt(attempt: 1, delay: 0.0), RetryAttempt(attempt: 2, delay: 0.0), - RetryAttempt(attempt: 3, delay: 0.0) + RetryAttempt(attempt: 3, delay: 0.0), ]) } @@ -97,7 +97,7 @@ final class RetryStrategyTests: XCTestCase { XCTAssertEqual(attempts, [ RetryAttempt(attempt: 1, delay: 10.0), RetryAttempt(attempt: 2, delay: 20.0), - RetryAttempt(attempt: 3, delay: 30.0) + RetryAttempt(attempt: 3, delay: 30.0), ]) } @@ -108,7 +108,7 @@ final class RetryStrategyTests: XCTestCase { XCTAssertEqual(attempts, [ RetryAttempt(attempt: 1, delay: 0.0), RetryAttempt(attempt: 2, delay: 0.0), - RetryAttempt(attempt: 3, delay: 0.0) + RetryAttempt(attempt: 3, delay: 0.0), ]) }