Skip to content

[k8s]: refactor K8sHelper into focused extensions and add K8sWorker protocol #2102

Description

@jshi991

Summary

Two related improvements to ContainerK8s: split K8sHelper.swift into focused
extension files, and add a WorkerProvisioner protocol so plugin binaries can
provision and join external machines as worker nodes without modifying OSS sources.


Background

K8sHelper.swift is a 725-line file covering image management, node bootstrap,
kubeadm config, kubeconfig read/write/merge, FQDN detection, and readiness polling.
Its size makes it hard to navigate and review.

Separately, there is no clean extension point for joining an external machine as a
worker node after cluster creation. A protocol in the library gives any plugin a
first-class way to participate in the full provisioning lifecycle without forking
K8sCreate.


Changes

1. Split K8sHelper.swift into focused extension files

Move the MARK sections into extension files under Sources/ContainerK8s/Support/:

Sources/ContainerK8s/
└── Support/
    ├── K8sHelper+Image.swift       # ensureImage, isShortName, fqReference
    ├── K8sHelper+Networking.swift  # nodeProxyEnv, fqdn, detectFQDN, clusterPort
    ├── K8sHelper+Kubeconfig.swift  # fetchConfig, transformConfig, mergeConfig,
    │                               #   removeConfig, resolveKubeconfigMergePath
    ├── K8sHelper+Readiness.swift   # waitForNodeBooted, waitForReady
    └── TableOutput.swift           # ListDisplayable, TableOutput

K8sHelper.swift retains only constants, Defaults, defaultedResourceFlags,
prepareNode, bootstrapControlPlane, buildK8sRows, renderTable, and
K8sNodeResource. No public API or behaviour changes.

2. Add a WorkerProvisioner protocol

Add a public protocol to ContainerK8s that covers the full lifecycle of an
external worker node:

// Sources/ContainerK8s/WorkerProvisioner.swift
public protocol WorkerProvisioner: Sendable {
    var defaultNodeImage: String? { get }

    func provision(name: String, log: Logger) async throws
    func join(name: String, controlPlaneEndpoint: String,
              token: String, caCertHash: String, log: Logger) async throws
    func waitForReady(name: String, log: Logger) async throws
    func address(name: String, log: Logger) async throws -> String
    func teardown(name: String, log: Logger) async throws
}

K8sCreate calls the provisioner in this order:

  1. provision — sets up the machine before the cluster is initialized
  2. address — returns the worker IP, passed as a cert SAN to bootstrapControlPlane
  3. join — called with the kubeadm bootstrap token and CA cert hash after the
    control-plane is ready
  4. waitForReady — polls until the worker node is registered and Ready

If any step throws, the cluster is deleted before re-throwing. K8sDelete calls
teardown before removing cluster containers.

K8sCreate.run() and K8sDelete.run() each accept an optional
worker: (any WorkerProvisioner)? (default nil). The OSS code path is unchanged
when no worker is provided.


Acceptance criteria

  • swift build succeeds with no behaviour changes to existing commands
  • K8sHelper.swift is under 150 lines after the split
  • WorkerProvisioner is public and lives in ContainerK8s
  • K8sCreate and K8sDelete behave identically when no worker is passed
  • An external plugin can conform to WorkerProvisioner without modifying OSS sources

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions