Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions Sources/SwiftkubeClient/Client/KubernetesClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,34 +105,34 @@ public actor KubernetesClient {
#endif

#if os(Linux) || os(macOS)
/// Create a new instance of the Kubernetes client.
///
/// The client tries to resolve a `kube config` automatically from different sources in the following order:
///
/// - A Kube config file at path of environment variable `KUBECONFIG` (if set)
/// - A Kube config file in the user's `$HOME/.kube/config` directory
/// - `ServiceAccount` token located at `/var/run/secrets/kubernetes.io/serviceaccount/token` and a mounted CA certificate, if it's running in Kubernetes.
///
/// Returns `nil` if a configuration can't be found.
///
/// - Note: This initializer is only available on Linux and macOS. On iOS, tvOS, and watchOS, you must use
/// `init(config:provider:logger:)` with a manually configured `KubernetesClientConfig`.
///
/// - Parameters:
/// - provider: A ``EventLoopGroupProvider`` to specify how ``EventLoopGroup`` will be created.
/// - logger: The logger to use for this client.
public init?(
provider: HTTPClient.EventLoopGroupProvider = .shared(MultiThreadedEventLoopGroup(numberOfThreads: 1)),
logger: Logger? = nil
) {
guard
let config = try? KubernetesClientConfig.initialize(logger: logger)
else {
return nil
}
/// Create a new instance of the Kubernetes client.
///
/// The client tries to resolve a `kube config` automatically from different sources in the following order:
///
/// - A Kube config file at path of environment variable `KUBECONFIG` (if set)
/// - A Kube config file in the user's `$HOME/.kube/config` directory
/// - `ServiceAccount` token located at `/var/run/secrets/kubernetes.io/serviceaccount/token` and a mounted CA certificate, if it's running in Kubernetes.
///
/// Returns `nil` if a configuration can't be found.
///
/// - Note: This initializer is only available on Linux and macOS. On iOS, tvOS, and watchOS, you must use
/// `init(config:provider:logger:)` with a manually configured `KubernetesClientConfig`.
///
/// - Parameters:
/// - provider: A ``EventLoopGroupProvider`` to specify how ``EventLoopGroup`` will be created.
/// - logger: The logger to use for this client.
public init?(
provider: HTTPClient.EventLoopGroupProvider = .shared(MultiThreadedEventLoopGroup(numberOfThreads: 1)),
logger: Logger? = nil
) {
guard
let config = try? KubernetesClientConfig.initialize(logger: logger)
else {
return nil
}

self.init(config: config, provider: provider, logger: logger)
}
self.init(config: config, provider: provider, logger: logger)
}
#endif

/// Create a new instance of the Kubernetes client.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftkubeClient/Client/KubernetesRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public struct KubernetesRequest: Sendable {

// MARK: - RequestBody

internal enum RequestBody: Sendable {
internal enum RequestBody {
case resource(payload: any KubernetesAPIResource)
case subResource(type: ResourceType, payload: any KubernetesResource)

Expand Down
18 changes: 5 additions & 13 deletions Sources/SwiftkubeClient/Config/AuthInfo+Authentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,8 @@ public extension AuthInfo {
return .bearer(token: token)
}

do {
if let tokenFile = tokenFile {
let fileURL = URL(fileURLWithPath: tokenFile)
let token = try String(contentsOf: fileURL, encoding: .utf8)
return .bearer(token: token)
}
} catch {
logger?.warning(
"Error initializing authentication from token file \(String(describing: tokenFile)): \(error)"
)
if let tokenFile = tokenFile {
return .tokenFile(source: CachedFileTokenSource(path: tokenFile))
}

do {
Expand Down Expand Up @@ -122,9 +114,9 @@ public extension AuthInfo {

// MARK: - ExecCredential

// It seems that AWS doesn't implement properly the model for client.authentication.k8s.io/v1beta1
// Acordingly with the doc https://kubernetes.io/docs/reference/config-api/client-authentication.v1beta1/
// ExecCredential.Spec.interactive is required as long as the ones in the Status object.
/// It seems that AWS doesn't implement properly the model for client.authentication.k8s.io/v1beta1
/// Acordingly with the doc https://kubernetes.io/docs/reference/config-api/client-authentication.v1beta1/
/// ExecCredential.Spec.interactive is required as long as the ones in the Status object.
public struct ExecCredential: Codable {
let apiVersion: String
let kind: String
Expand Down
194 changes: 97 additions & 97 deletions Sources/SwiftkubeClient/Config/KubeConfig+Loaders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,122 +32,122 @@ public extension KubeConfig {
}

#if os(Linux) || os(macOS)
static func fromEnvironment(envVar: String = "KUBECONFIG", logger: Logger? = nil) throws -> KubeConfig? {
guard let varContent = ProcessInfo.processInfo.environment[envVar] else {
logger?.info("Skipping kubeconfig because environment variable \(envVar) is not set")
return nil
}
static func fromEnvironment(envVar: String = "KUBECONFIG", logger: Logger? = nil) throws -> KubeConfig? {
guard let varContent = ProcessInfo.processInfo.environment[envVar] else {
logger?.info("Skipping kubeconfig because environment variable \(envVar) is not set")
return nil
}

let expanded = varContent.stringByExpandingTildePath()
let kubeConfigURL = URL(fileURLWithPath: expanded)
logger?.info("Loading configuration from \(kubeConfigURL)")
let expanded = varContent.stringByExpandingTildePath()
let kubeConfigURL = URL(fileURLWithPath: expanded)
logger?.info("Loading configuration from \(kubeConfigURL)")

return try from(url: kubeConfigURL)
}
#endif

#if os(Linux) || os(macOS)
static func fromDefaultLocalConfig(logger: Logger? = nil) throws -> KubeConfig? {
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
return try from(url: kubeConfigURL)
}

let kubeConfigURL = URL(fileURLWithPath: homePath + "/.kube/config")
logger?.info("Loading configuration from \(kubeConfigURL)")

return try from(url: kubeConfigURL)
}
#endif

#if os(Linux) || os(macOS)
static func fromServiceAccount(logger: Logger? = nil) throws -> KubeConfig? {
guard
let host = ProcessInfo.processInfo.environment["KUBERNETES_SERVICE_HOST"],
let port = ProcessInfo.processInfo.environment["KUBERNETES_SERVICE_PORT"]
else {
logger?.warning("Skipping service account kubeconfig because either KUBERNETES_SERVICE_HOST or KUBERNETES_SERVICE_PORT is not set")
return nil
}
static func fromDefaultLocalConfig(logger: Logger? = nil) throws -> KubeConfig? {
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 apiServerUrl = if host.contains(":") {
"https://[\(host)]:\(port)"
} else {
"https://\(host):\(port)"
}
let kubeConfigURL = URL(fileURLWithPath: homePath + "/.kube/config")
logger?.info("Loading configuration from \(kubeConfigURL)")

let tokenFile = URL(fileURLWithPath: "/var/run/secrets/kubernetes.io/serviceaccount/token")
guard let token = try? String(contentsOf: tokenFile, encoding: .utf8) else {
logger?.warning("Did not find service account token at /var/run/secrets/kubernetes.io/serviceaccount/token")
return nil
return try from(url: kubeConfigURL)
}
#endif

let namespaceFile = URL(fileURLWithPath: "/var/run/secrets/kubernetes.io/serviceaccount/namespace")
let namespace = try? String(contentsOf: namespaceFile, encoding: .utf8)
if namespace == nil {
logger?.debug("Did not find service account namespace at /var/run/secrets/kubernetes.io/serviceaccount/namespace")
#if os(Linux) || os(macOS)
static func fromServiceAccount(logger: Logger? = nil) throws -> KubeConfig? {
guard
let host = ProcessInfo.processInfo.environment["KUBERNETES_SERVICE_HOST"],
let port = ProcessInfo.processInfo.environment["KUBERNETES_SERVICE_PORT"]
else {
logger?.warning("Skipping service account kubeconfig because either KUBERNETES_SERVICE_HOST or KUBERNETES_SERVICE_PORT is not set")
return nil
}

let apiServerUrl = if host.contains(":") {
"https://[\(host)]:\(port)"
} else {
"https://\(host):\(port)"
}

let tokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
guard FileManager.default.fileExists(atPath: tokenPath) else {
logger?.warning("Did not find service account token at \(tokenPath)")
return nil
}

let namespaceFile = URL(fileURLWithPath: "/var/run/secrets/kubernetes.io/serviceaccount/namespace")
let namespace = try? String(contentsOf: namespaceFile, encoding: .utf8)
if namespace == nil {
logger?.debug("Did not find service account namespace at /var/run/secrets/kubernetes.io/serviceaccount/namespace")
}

let certificateAuthorityFile = URL(fileURLWithPath: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
let certificateAuthorityData = try? Data(contentsOf: certificateAuthorityFile)

return KubeConfig(
kind: "Config",
apiVersion: "v1",
clusters: [
NamedCluster(
name: "kubernetes-cluster-local",
cluster: Cluster(
server: apiServerUrl,
insecureSkipTLSVerify: certificateAuthorityData == nil,
certificateAuthorityData: certificateAuthorityData
)
),
],
users: [
NamedAuthInfo(
name: "service-account-user",
authInfo: AuthInfo(
tokenFile: tokenPath
)
),
],
contexts: [
NamedContext(
name: "service-account-context",
context: Context(
cluster: "kubernetes-cluster-local",
user: "service-account-user",
namespace: namespace
)
),
],
currentContext: "service-account-context"
)
}

let certificateAuthorityFile = URL(fileURLWithPath: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
let certificateAuthorityData = try? Data(contentsOf: certificateAuthorityFile)

return KubeConfig(
kind: "Config",
apiVersion: "v1",
clusters: [
NamedCluster(
name: "kubernetes-cluster-local",
cluster: Cluster(
server: apiServerUrl,
insecureSkipTLSVerify: certificateAuthorityData == nil,
certificateAuthorityData: certificateAuthorityData
)
),
],
users: [
NamedAuthInfo(
name: "service-account-user",
authInfo: AuthInfo(
token: token
)
),
],
contexts: [
NamedContext(
name: "service-account-context",
context: Context(
cluster: "kubernetes-cluster-local",
user: "service-account-user",
namespace: namespace
)
),
],
currentContext: "service-account-context"
)
}
#endif
}

#if os(Linux) || os(macOS)
internal extension String {
internal extension String {

func stringByExpandingTildePath() -> String {
guard !isEmpty else {
return ""
}
func stringByExpandingTildePath() -> String {
guard !isEmpty else {
return ""
}

if self == "~" {
return FileManager.default.homeDirectoryForCurrentUser.path
}
if self == "~" {
return FileManager.default.homeDirectoryForCurrentUser.path
}

guard hasPrefix("~/") else {
return self
}
guard hasPrefix("~/") else {
return self
}

var relativePath = self
relativePath.removeFirst(2)
var relativePath = self
relativePath.removeFirst(2)

return FileManager.default.homeDirectoryForCurrentUser.path + "/" + relativePath
return FileManager.default.homeDirectoryForCurrentUser.path + "/" + relativePath
}
}
}
#endif
Loading
Loading