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
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ $(STAGING_DIR):
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/resources)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/k8s/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/k8s/resources)"

@install "$(BUILD_BIN_DIR)/container" "$(join $(STAGING_DIR), bin/container)"
@install "$(BUILD_BIN_DIR)/container-apiserver" "$(join $(STAGING_DIR), bin/container-apiserver)"
Expand All @@ -149,6 +150,7 @@ $(STAGING_DIR):
@install Sources/Plugins/MachineAPIServer/Resources/create-user.sh "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/resources/create-user.sh)"
@install "$(BUILD_BIN_DIR)/k8s" "$(join $(STAGING_DIR), libexec/container/plugins/k8s/bin/k8s)"
@install Sources/Plugins/K8s/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/k8s/config.toml)"
@install Sources/Plugins/K8s/Resources/kindnet.yaml "$(join $(STAGING_DIR), libexec/container/plugins/k8s/resources/kindnet.yaml)"

@echo Install update script
@install scripts/update-container.sh "$(join $(STAGING_DIR), bin/update-container.sh)"
Expand Down
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,18 +186,18 @@ let package = Package(
"ContainerAPIClient",
"ContainerLog",
"ContainerPersistence",
"ContainerPlugin",
"ContainerResource",
"ContainerVersion",
"TerminalProgress",
"Yams",
],
resources: [.process("Resources/kindnet.yaml")]
]
),
.executableTarget(
name: "k8s",
dependencies: ["ContainerK8s"],
path: "Sources/Plugins/K8s",
exclude: ["config.toml"]
exclude: ["config.toml", "Resources"]
),
.executableTarget(
name: "container-apiserver",
Expand Down
56 changes: 51 additions & 5 deletions Sources/ContainerK8s/K8sHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import ContainerAPIClient
import ContainerPersistence
import ContainerPlugin
import ContainerResource
import ContainerVersion
import ContainerizationError
Expand Down Expand Up @@ -257,7 +258,7 @@ struct K8sHelper {
arguments: ["taint", "nodes", "--all", "node-role.kubernetes.io/control-plane-"])

log.info("Applying kindnet CNI", metadata: ["node": "\(nodeID)"])
let manifest = try loadKindnetManifest()
let manifest = try await loadKindnetManifest(log: log)
let apply =
"cat > /tmp/kindnet.yaml <<'EOF'\n\(manifest)\nEOF\n"
+ "\(kubeconfigEnv) kubectl apply -f /tmp/kindnet.yaml"
Expand All @@ -269,15 +270,60 @@ struct K8sHelper {
}
}

private static func loadKindnetManifest() throws -> String {
guard let url = Bundle.module.url(forResource: "kindnet", withExtension: "yaml"),
let contents = try? String(contentsOf: url, encoding: .utf8)
private static func loadKindnetManifest(log: Logger) async throws -> String {
let pluginLoader = try await makePluginLoader(log: log)
guard let plugin = pluginLoader.findPlugin(forExecutable: CommandLine.executablePath),
let resourceURL = plugin.resourceURL
else {
throw ContainerizationError(.internalError, message: "kindnet manifest resource missing")
throw ContainerizationError(.internalError, message: "unable to locate k8s plugin installation or resources")
}
let url = resourceURL.appendingPathComponent("kindnet.yaml")
guard let contents = try? String(contentsOf: url, encoding: .utf8) else {
throw ContainerizationError(.internalError, message: "kindnet manifest resource missing at \(url.path)")
}
return contents
}

/// NOTE: This duplicates `Application.createPluginLoader()` in
/// Sources/ContainerCommands/Application.swift. `ContainerK8s` cannot depend on
/// `ContainerCommands`, so the plugin directory/factory list here is kept in sync
/// by hand — if that logic changes, update this copy too (or factor a shared
/// constructor into `ContainerPlugin`).
private static func makePluginLoader(log: Logger) async throws -> PluginLoader {
let health = try await ClientHealthCheck.ping(timeout: .seconds(10))

let installRootPath = FilePath(health.installRoot.path(percentEncoded: false))
let userPluginsURL = PluginLoader.userPluginsDir(installRoot: health.installRoot)
var directoryExists: ObjCBool = false
_ = FileManager.default.fileExists(atPath: userPluginsURL.path, isDirectory: &directoryExists)

let appBundlePluginsURL = Bundle.main.resourceURL?.appending(path: "plugins")
let installRootPluginsPath =
installRootPath
.appending(FilePath.Component("libexec"))
.appending(FilePath.Component("container"))
.appending(FilePath.Component("plugins"))
let installRootPluginsURL = URL(fileURLWithPath: installRootPluginsPath.string)

let pluginDirectories = [
directoryExists.boolValue ? userPluginsURL : nil,
appBundlePluginsURL,
installRootPluginsURL,
].compactMap { $0 }

return try PluginLoader(
appRoot: health.appRoot,
installRoot: health.installRoot,
logRoot: health.logRoot,
pluginDirectories: pluginDirectories,
pluginFactories: [
DefaultPluginFactory(logger: log),
AppBundlePluginFactory(logger: log),
],
log: log
)
}

private static let nodePrepScript: String = {
"""
set -e
Expand Down
27 changes: 27 additions & 0 deletions Sources/ContainerPlugin/PluginLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//

import ContainerizationOS
import Darwin
import Foundation
import Logging
import SystemPackage
Expand Down Expand Up @@ -198,6 +199,32 @@ extension PluginLoader {

return nil
}

/// Locate the plugin whose executable resolves to `path`, e.g. to let a
/// running plugin process identify its own `Plugin` (and thus its
/// `resourceURL`) from `CommandLine.executablePath`.
public func findPlugin(forExecutable path: FilePath) -> Plugin? {
guard let resolvedPath = Self.resolveSymlinks(path.string) else {
return nil
}
for plugin in findPlugins() {
guard let binaryPath = Self.resolveSymlinks(plugin.binaryURL.path(percentEncoded: false)) else {
continue
}
if binaryPath == resolvedPath {
return plugin
}
}
return nil
}

private static func resolveSymlinks(_ path: String) -> String? {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use resolvingSymlinks defined in FilePath+Symlink.swift?
Though we need to add dependency on ContainerPersistence for that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% the right idea.

We can live with this bit of non-DRY code for now.

How we're going to tackle this tech debt is to coalesce the different FilePath helpers we have to https://github.com/apple/containerization/blob/main/Sources/ContainerizationOS/FilePathOps.swift.

Then we address stuff like this as we migrate the parts of container that haven't yet been migrated from String/URL paths to FilePath.

guard let resolved = Darwin.realpath(path, nil) else {
return nil
}
defer { free(resolved) }
return String(cString: resolved)
}
}

extension PluginLoader {
Expand Down
6 changes: 3 additions & 3 deletions Sources/Plugins/K8s/K8sCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public struct K8sCommand: AsyncParsableCommand {

Load a local image into the cluster and run it:
$ container image pull docker.io/library/hello-world:latest
$ container image tag docker.io/library/hello-world:latest my-hello-world:latest
$ container k8s load-image --name my-cluster my-hello-world:latest
$ kubectl run hello-job --image=my-hello-world:latest --restart=Never --attach --rm -i
$ container image tag docker.io/library/hello-world:latest registry.example.com/max_mustermann/my-hello-world:latest
$ container k8s load-image --name my-cluster registry.example.com/max_mustermann/my-hello-world:latest
$ kubectl run hello-job --image=registry.example.com/max_mustermann/my-hello-world:latest --image-pull-policy=Never --restart=Never --attach --rm -i

Stop and delete the cluster:
$ container k8s delete --name my-cluster
Expand Down
Loading