From d17d633d97ad39d376347913790cf133181d7f81 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Thu, 19 Feb 2026 12:56:26 +0200 Subject: [PATCH 1/3] Move/generate docs to docs/ from README and add "k0sctl docs" Signed-off-by: Kimmo Lehto --- Makefile | 8 + README.md | 643 +------------- cmd/apply.go | 9 + cmd/docs.go | 54 ++ cmd/root.go | 1 + docs/configuration.md | 794 ++++++++++++++++++ docs/docs.go | 14 + docs/k0sctl-schema.json | 723 ++++++++++++++++ go.mod | 26 + go.sum | 54 ++ internal/schemagen/appendix.md | 426 ++++++++++ internal/schemagen/main.go | 155 ++++ internal/schemagen/markdown.go | 133 +++ internal/schemagen/preamble.md | 43 + internal/schemagen/rig_schemas.go | 183 ++++ .../k0sctl.k0sproject.io/v1beta1/cluster.go | 18 +- .../v1beta1/cluster/host.go | 40 +- .../v1beta1/cluster/k0s.go | 15 +- .../v1beta1/cluster/options.go | 63 +- .../v1beta1/cluster/spec.go | 5 +- .../v1beta1/cluster/uploadfile.go | 18 +- 21 files changed, 2762 insertions(+), 663 deletions(-) create mode 100644 cmd/docs.go create mode 100644 docs/configuration.md create mode 100644 docs/docs.go create mode 100644 docs/k0sctl-schema.json create mode 100644 internal/schemagen/appendix.md create mode 100644 internal/schemagen/main.go create mode 100644 internal/schemagen/markdown.go create mode 100644 internal/schemagen/preamble.md create mode 100644 internal/schemagen/rig_schemas.go diff --git a/Makefile b/Makefile index 336568fbd..f31344500 100644 --- a/Makefile +++ b/Makefile @@ -108,6 +108,14 @@ lint: $(golint) test: $(GO_SRCS) $(GO_TESTS) go test -v ./... +.PHONY: docs +docs: + go run ./internal/schemagen + +.PHONY: check-docs +check-docs: docs + git diff --exit-code docs/ + .PHONY: install install: k0sctl install -d $(DESTDIR)$(PREFIX)/bin/ diff --git a/README.md b/README.md index 066b1f270..a417efbc7 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,10 @@ The configuration file is in YAML format and loosely resembles the syntax used i To generate a simple skeleton configuration file, you can use the `k0sctl init` subcommand. +For the complete configuration reference, see **[docs/configuration.md](docs/configuration.md)**. + +A [JSON Schema](docs/k0sctl-schema.json) is also available for editor validation and autocompletion (e.g. VS Code YAML extension). + Configuration example: ```yaml @@ -200,663 +204,28 @@ apiVersion: k0sctl.k0sproject.io/v1beta1 kind: Cluster metadata: name: my-k0s-cluster - user: admin spec: hosts: - role: controller - installFlags: - - --debug ssh: address: 10.0.0.1 user: root - port: 22 keyPath: ~/.ssh/id_rsa - role: worker - installFlags: - - --debug ssh: address: 10.0.0.2 k0s: - version: 0.10.0 - config: - apiVersion: k0s.k0sproject.io/v1beta1 - kind: ClusterConfig - metadata: - name: my-k0s-cluster - spec: - images: - calico: - cni: - image: calico/cni - version: v3.16.2 - options: - wait: - enabled: true - drain: - enabled: true - evictTaint: - enabled: false - taint: k0sctl.k0sproject.io/evict=true - effect: NoExecute - concurrency: - limit: 30 - uploads: 5 + version: 1.32.2+k0s.0 ``` ### Environment variable substitution -Simple bash-like expressions are supported in the configuration for environment variable substition. +Simple bash-like expressions are supported in the configuration for environment variable substitution. - `$VAR` or `${VAR}` value of `VAR` environment variable - `${var:-DEFAULT_VALUE}` will use `VAR` if non-empty, otherwise `DEFAULT_VALUE` - `$$var` - escape, result will be `$var`. - And [several other expressions](https://github.com/a8m/envsubst#docs) -### Configuration Header Fields - -###### `apiVersion` <string> (required) - -The configuration file syntax version. Currently the only supported version is `k0sctl.k0sproject.io/v1beta1`. - -###### `kind` <string> (required) - -In the future, some of the configuration APIs can support multiple types of objects. For now, the only supported kind is `Cluster`. - -###### `spec` <mapping> (required) - -The main object definition, see [below](#spec-fields) - -###### `metadata` <mapping> (optional) - -Information that can be used to uniquely identify the object. - -Example: - -```yaml -metadata: - name: k0s-cluster-name - user: kubernetes-admin -``` - -### Spec Fields - -##### `spec.hosts` <sequence> (required) - -A list of cluster hosts. Host requirements: - -* Linux nodes are supported for all roles. -* Windows nodes can join as `worker` hosts when reachable over SSH or WinRM. This support is experimental and requires k0s version >= 1.34. -* On Linux, the SSH user must either be root or have passwordless `sudo` (or `doas`) access. Windows workers must allow WinRM access for the configured user (defaults to `Administrator`). -* The host must fulfill the k0s system requirements - -See [host object documentation](#host-fields) below. - -##### `spec.k0s` <mapping> (optional) - -Settings related to the k0s cluster. - -See [k0s object documentation](#k0s-fields) below. - -### Host Fields - -###### `spec.hosts[*].role` <string> (required) - -One of: -- `controller` - a controller host -- `controller+worker` - a controller host that will also run workloads -- `single` - a [single-node cluster](https://docs.k0sproject.io/stable/k0s-single-node/) host, the configuration can only contain one host -- `worker` - a worker host - -###### `spec.hosts[*].noTaints` <boolean> (optional) (default: `false`) - -When `true` and used in conjuction with the `controller+worker` role, the default taints are disabled making regular workloads schedulable on the node. By default, k0s sets a node-role.kubernetes.io/master:NoSchedule taint on controller+worker nodes and only workloads with toleration for it will be scheduled. - -###### `spec.hosts[*].uploadBinary` <boolean> (optional) (default: `false`) - -When `true`, the k0s binaries for target host will be downloaded and cached on the local host and uploaded to the target. -When `false`, the k0s binary downloading is performed on the target host itself - -###### `spec.hosts[*].useExistingK0s` <boolean> (optional) (default: `false`) - -When `true`, k0sctl reuses the k0s binary that already exists on the host. No binary downloads or uploads are performed, and upgrades for the host are skipped. This option cannot be combined with `uploadBinary`, `k0sBinaryPath`, or `k0sDownloadURL`. - -###### `spec.hosts[*].k0sBinaryPath` <string> (optional) - -A path to a file on the local host that contains a k0s binary to be uploaded to the host. Can be used to test drive a custom development build of k0s. - -###### `spec.hosts[*].k0sInstallPath` <string> (optional) (default: depends on OS) - -A path on the node where to install the k0s binary. - -###### `spec.hosts[*].k0sDownloadURL` <string> (optional) - -A URL to download the k0s binary from. The default is to download from the [k0s repository](https://github.com/k0sproject/k0s). The URL can contain '%'-prefixed tokens that will be replaced with the host's information, see [tokens](#tokens). - -###### `spec.hosts[*].hostname` <string> (optional) - -Override host's hostname. When not set, the hostname reported by the operating system is used. - -###### `spec.hosts[*].dataDir` <string> (optional) (default: `/var/lib/k0s`) - -Set host's k0s data-dir. - -###### `spec.hosts[*].kubeletRootDir` <string> (optional) (default: `""`) - -Set host's k0s kubelet-root-dir. - -###### `spec.hosts[*].installFlags` <sequence> (optional) - -Extra flags passed to the `k0s install` command on the target host. See `k0s install --help` for a list of options. - -###### `spec.hosts[*].environment` <mapping> (optional) - -List of key-value pairs to set to the target host's environment variables. - -Example: - -```yaml -environment: - HTTP_PROXY: 10.0.0.1:443 -``` - -###### `spec.hosts[*].files` <sequence> (optional) - -List of files to be uploaded to the host. - -Example: - -```yaml -- name: image-bundle - src: airgap-images.tgz - dstDir: /var/lib/k0s/images/ - perm: 0600 -``` - -Inline data example: - -```yaml -- name: motd - data: | - Powered by k0s - dst: /etc/motd - perm: 0644 -``` - -* `name`: name of the file "bundle", used only for logging purposes (optional) -* `src`: File path, an URL or [Glob pattern](https://golang.org/pkg/path/filepath/#Match) to match files to be uploaded. URL sources will be directly downloaded using the target host. If the value is a URL, '%'-prefixed tokens can be used, see [tokens](#tokens). (required when `data` is not set) -* `data`: Inline file data to write to the destination. Use together with `dst` or `dst` + `dstDir`. (required when `src` is not set) -* `dstDir`: Destination directory for the file(s). `k0sctl` will create full directory structure if it does not already exist on the host (default: user home) -* `dst`: Destination filename for the file. Only usable for single file uploads (default: basename of file) -* `perm`: File permission mode for uploaded file(s) (default: same as local) -* `dirPerm`: Directory permission mode for created directories (default: 0755) -* `user`: User name of file/directory owner, must exist on the host (optional) -* `group`: Group name of file/directory owner, must exist on the host (optional) - -###### `spec.hosts[*].hooks` <mapping> (optional) - -Run a set of commands on the remote host during k0sctl operations. - -Example: - -```yaml -hooks: - connect: - after: - - echo "connected and detected" >> k0sctl-connect.log - upgrade: - before: - - echo "about to upgrade" >> k0sctl-upgrade.log - after: - - echo "upgraded" >> k0sctl-upgrade.log - apply: - before: - - date >> k0sctl-apply.log - after: - - echo "apply success" >> k0sctl-apply.log -``` - -The currently available "hook points" are: - -* `connect`: - - `after`: Runs immediately after OS detection completes -* `apply`: Runs during `k0sctl apply` - - `before`: Runs after configuration and host validation, right before configuring k0s on the host - - `after`: Runs before disconnecting from the host after a successful apply operation -* `upgrade`: Runs during `k0sctl apply` - - `before`: Runs for each host that is going to be upgraded, before the upgrade begins - - `after`: Runs for each host that was upgraded, after the upgrade completes -* `install`: Runs during `k0sctl apply` - - `before`: Runs on each host just before installing its k0s components. This includes the first controller (Initialize the k0s cluster), additional controllers, and workers. - - `after`: Runs on each host immediately after installing its k0s components (service started and ready checks done). -* `backup`: Runs during `k0s backup` - - `before`: Runs before k0sctl runs the `k0s backup` command - - `after`: Runs before disconnecting from the host after successfully taking a backup -* `reset`: Runs during `k0sctl reset` or when `k0sctl apply` resets a host. - - `before`: Runs after gathering information about the cluster, right before starting to remove the k0s installation. - - `after`: Runs before disconnecting from the host after a successful reset operation - -Notes: - -- Hooks run on each host that defines them, using the same remote user as the connection. If elevated privileges are required, prefix commands with `sudo`. -- In dry-run mode, hooks are not executed; k0sctl prints what would run on each host. -- Hooks execute only on hosts targeted by the related phase. For example, `upgrade` hooks run only for hosts that need upgrade. - -##### `spec.hosts[*].os` <string> (optional) (default: ``) - -Override OS distribution auto-detection. By default `k0sctl` detects the OS by reading `/etc/os-release` or `/usr/lib/os-release` files. In case your system is based on e.g. Debian but the OS release info has something else configured you can override `k0sctl` to use Debian based functionality for the node with: - -```yaml - - role: worker - os: debian - ssh: - address: 10.0.0.2 -``` - -##### `spec.hosts[*].privateInterface` <string> (optional) (default: ``) - -Override private network interface selected by host fact gathering. -Useful in case fact gathering picks the wrong private network interface. - -```yaml - - role: worker - os: debian - privateInterface: eth1 -``` - -##### `spec.hosts[*].privateAddress` <string> (optional) (default: ``) - -Override private IP address selected by host fact gathering. -Useful in case fact gathering picks the wrong IPAddress. - -##### `spec.hosts[*].reset` <boolean> (optional) (default: `false`) - -If set to `true` k0sctl will remove the node from kubernetes and reset k0s on the host. - -```yaml - - role: worker - os: debian - privateAddress: 10.0.0.2 -``` - -##### `spec.hosts[*].ssh` <mapping> (optional) - -SSH connection options. - -Example: - -```yaml -spec: - hosts: - - role: controller - ssh: - address: 10.0.0.2 - user: ubuntu - keyPath: ~/.ssh/id_rsa -``` - -Windows worker nodes can also use the SSH transport when an SSH server is available on the host. - -It's also possible to tunnel connections over SSH through a bastion host. The bastion configuration has all the same fields as any SSH connection: - -```yaml -spec: - hosts: - - role: controller - ssh: - address: 10.0.0.2 - user: ubuntu - keyPath: ~/.ssh/id_rsa - bastion: - address: 10.0.0.1 - user: root - keyPath: ~/.ssh/id_rsa2 -``` - -SSH agent and auth forwarding are also supported, a host without a keyfile: - -```yaml -spec: - hosts: - - role: controller - ssh: - address: 10.0.0.2 - user: ubuntu -``` - -```shell -$ ssh-add ~/.ssh/aws.pem -$ ssh -A user@jumphost -user@jumphost ~ $ k0sctl apply -``` - -Pageant or openssh-agent can be used on Windows. - -###### `spec.hosts[*].ssh.address` <string> (required) - -IP address of the host - -###### `spec.hosts[*].ssh.user` <string> (optional) (default: `root`) - -Username to log in as. - -###### `spec.hosts[*].ssh.port` <number> (required) - -TCP port of the SSH service on the host. - -###### `spec.hosts[*].ssh.keyPath` <string> (optional) (default: `~/.ssh/identity ~/.ssh/id_rsa ~/.ssh/id_dsa`) - -Path to an SSH key file. If a public key is used, ssh-agent is required. When left empty, the default value will first be looked for from the ssh configuration (default `~/.ssh/config`) `IdentityFile` parameter. - -##### `spec.hosts[*].localhost` <mapping> (optional) - -Localhost connection options. Can be used to use the local host running k0sctl as a node in the cluster. - -###### `spec.hosts[*].localhost.enabled` <boolean> (optional) (default: `false`) - -This must be set `true` to enable the localhost connection. - -##### `spec.hosts[*].openSSH` <mapping> (optional) - -An alternative SSH client protocol that uses the system's openssh client for connections. - -Example: - -```yaml -spec: - hosts: - - role: controller - openSSH: - address: 10.0.0.2 -``` - -The only required field is the `address` and it can also be a hostname that is found in the ssh config. All other options such as user, port and keypath will use the same defaults as if running `ssh` from the command-line or will use values found from the ssh config. - -An example SSH config: - -``` -Host controller1 - Hostname 10.0.0.1 - Port 2222 - IdentityFile ~/.ssh/id_cluster_esa -``` - -If this is in your `~/.ssh/config`, you can simply use the host alias as the address in your k0sctl config: - -```yaml -spec: - hosts: - - role: controller - openSSH: - address: controller1 - # if the ssh configuration is in a different file, you can use: - # configPath: /path/to/config -``` - -###### `spec.hosts[*].openSSH.address` <string> (required) - -IP address, hostname or ssh config host alias of the host - -###### `spec.hosts[*].openSSH.user` <string> (optional) - -Username to connect as. - -###### `spec.hosts[*].openSSH.port` <number> (optional) - -Remote port. - -###### `spec.hosts[*].openSSH.keyPath` <string> (optional) - -Path to private key. - -###### `spec.hosts[*].openSSH.configPath` <string> (optional) - -Path to ssh config, defaults to ~/.ssh/config with fallback to /etc/ssh/ssh_config. - -###### `spec.hosts[*].openSSH.disableMultiplexing` <boolean> (optional) - -The default mode of operation is to use connection multiplexing where a ControlMaster connection is opened and the subsequent connections to the same host use the master connection over a socket to communicate to the host. - -If this is disabled by setting `disableMultiplexing: true`, running every remote command will require reconnecting and reauthenticating to the host. - -###### `spec.hosts[*].openSSH.options` <mapping> (optional) - -Additional options as key/value pairs to use when running the ssh client. - -Example: - -```yaml -openSSH: - address: host - options: - ForwardAgent: true # -o ForwardAgent=yes - StrictHostkeyChecking: false # -o StrictHostkeyChecking: no -``` - -##### `spec.hosts[*].winrm` <mapping> (optional) - -WinRM connection options for Windows worker nodes. Use this transport when targeting Windows hosts that prefer WinRM instead of SSH. Windows support is limited to the `worker` role and requires k0s version >= 1.34. - -Example: - -```yaml -spec: - hosts: - - role: worker - winrm: - address: win-worker-1.internal - user: Administrator - password: ${WINRM_PASSWORD} - useHTTPS: true - insecure: false -``` - -###### `spec.hosts[*].winrm.address` <string> (required) - -IP address or hostname of the host. - -###### `spec.hosts[*].winrm.user` <string> (optional) (default: `Administrator`) - -WinRM user name. The user must have administrative privileges. Windows does not provide a built-in way to elevate privileges over WinRM, so the user must already have them. - -###### `spec.hosts[*].winrm.port` <number> (optional) (default: `5985`) - -TCP port for the WinRM endpoint. When `useHTTPS` is `true`, the default port automatically switches to `5986`. - -###### `spec.hosts[*].winrm.password` <string> (optional) - -Password for the WinRM user. Required unless certificate-based authentication is configured. Consider using environment variable substitution to avoid storing plaintext passwords in the configuration file. - -###### `spec.hosts[*].winrm.useHTTPS` <boolean> (optional) (default: `false`) - -Enable HTTPS for WinRM. When enabled, set `caCertPath` (and optionally `certPath`/`keyPath`) to verify the remote endpoint. - -###### `spec.hosts[*].winrm.insecure` <boolean> (optional) (default: `false`) - -Skip TLS certificate verification when connecting over HTTPS. Use only in trusted environments. - -###### `spec.hosts[*].winrm.useNTLM` <boolean> (optional) (default: `false`) - -Use NTLM authentication instead of basic authentication. - -###### `spec.hosts[*].winrm.caCertPath` <string> (optional) - -Path to a CA bundle used to validate the WinRM server certificate. - -###### `spec.hosts[*].winrm.certPath` <string> (optional) - -Client certificate for mutual TLS authentication. - -###### `spec.hosts[*].winrm.keyPath` <string> (optional) - -Private key that matches `certPath`. - -###### `spec.hosts[*].winrm.tlsServerName` <string> (optional) - -Override the TLS server name used during certificate verification. - -###### `spec.hosts[*].winrm.bastion` <mapping> (optional) - -SSH connection details for a bastion host to reach the host. The fields match those documented under `spec.hosts[*].ssh`. - -### K0s Fields - -##### `spec.k0s.version` <string> (optional) (default: auto-discovery) - -The version of k0s to deploy. When left out, k0sctl will default to using the latest released version of k0s or the version already running on the cluster. - -##### `spec.k0s.versionChannel` <string> (optional) (default: `stable`) - -Possible values are `stable` and `latest`. - -When `spec.k0s.version` is left undefined, this setting can be set to `latest` to allow k0sctl to include k0s pre-releases when looking for the latest version. The default is to only look for stable releases. - -##### `spec.k0s.dynamicConfig` <boolean> (optional) (default: false) - -Enable k0s dynamic config. The setting will be automatically set to true if: - -* Any controller node has `--enable-dynamic-config` in `installFlags` -* Any existing controller node has `--enable-dynamic-config` in run arguments (`k0s status -o json`) - -**Note:** When running k0s in dynamic config mode, k0sctl will ONLY configure the cluster-wide configuration during the first time initialization, after that the configuration has to be managed via `k0s config edit` or `k0sctl config edit`. The node specific configuration will be updated on each apply. - -See also: - -* [k0s Dynamic Configuration](https://docs.k0sproject.io/stable/dynamic-configuration/) - -##### `spec.k0s.config` <mapping> (optional) (default: auto-generated) - -Embedded k0s cluster configuration. See [k0s configuration documentation](https://docs.k0sproject.io/stable/configuration/) for details. - -When left out, the output of `k0s config create` will be used. - -You can also host the configuration in a separate file or as a separate YAML document in the same file in the standard k0s configuration format. - -```yaml -apiVersion: k0sctl.k0sproject.io/v1beta1 -kind: Cluster -spec: - hosts: - - role: single - ssh: - address: 10.0.0.1 ---- -apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -metadata: - name: my-k0s-cluster -spec: - api: - externalAddress: 10.0.0.2 -``` - -### Options Fields - -The `spec.options` field contains options that can be used to modify the behavior of k0sctl. - -Example: - -```yaml -spec: - options: - wait: - enabled: true - drain: - enabled: true - evictTaint: - enabled: false - taint: k0sctl.k0sproject.io/evict=true - effect: NoExecute - concurrency: - limit: 30 - workerDisruptionPercent: 10 - uploads: 5 -``` - -##### `spec.options.wait.enabled` <boolean> (optional) (default: true) - -If set to `false`, k0sctl will not wait for k0s to become ready after restarting the service. By default, k0sctl waits for nodes to become ready before continuing to the next operation. This is functionally the same as using `--no-wait` on the command line. - -##### `spec.options.drain.enabled` <boolean> (optional) (default: true) - -If set to `false`, k0sctl will skip draining nodes before performing disruptive operations like upgrade or reset. By default, nodes are drained to allow for graceful pod eviction. This is functionally the same as using `--no-drain` on the command line. - -##### `spec.options.drain.gracePeriod` <duration> (optional) (default: 2m) - -The duration to wait for pods to be evicted from the node before proceeding with the operation. - -##### `spec.options.drain.timeout` <duration> (optional) (default: 5m) - -The duration to wait for the drain operation to complete before timing out. - -##### `spec.options.drain.force` <boolean> (optional) (default: true) - -Use `--force` in kubectl when draining the node. - -##### `spec.options.drain.ignoreDaemonSets` <boolean> (optional) (default: true) - -Ignore DaemonSets when draining the node. - -##### `spec.options.drain.deleteEmptyDirData` <boolean> (optional) (default: true) - -Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained). - -##### `spec.options.drain.skipWaitForDeleteTimeout` <duration> (optional) (default: 0s) - -If pod DeletionTimestamp older than N seconds, skip waiting for the pod. Seconds must be greater than 0 to skip. - -##### `spec.options.drain.podSelector` <string> (optional) (default: ``) - -Label selector to filter pods on the node - -##### `spec.options.evictTaint.enabled` <boolean> (optional) (default: false) - -When enabled, k0sctl will apply a taint to nodes before service-affecting operations such as upgrade or reset. This is used to signal workloads to be evicted in advance of node disruption. You can also use the `--evict-taint=k0sctl.k0sproject.io/evic=true:NoExecute` command line option to enable this feature. - -##### `spec.options.evictTaint.taint` <string> (optional) (default: `k0sctl.k0sproject.io/evict=true`) - -The taint to apply when `evictTaint.enabled` is `true`. Must be in the format `key=value`. - -##### `spec.options.evictTaint.effect` <string> (optional) (default: `NoExecute`) - -The taint effect to apply. Must be one of: - -* `NoExecute` -* `NoSchedule` -* `PreferNoSchedule` - -##### `spec.options.evictTaint.controllerWorkers` <boolean> (optional) (default: false) - -Whether to also apply the taint to nodes with the controller+worker dual role. By default, taints are only applied to worker-only nodes. - -##### `spec.options.concurrency.limit` <integer> (optional) (default: 30) - -The maximum number of hosts to operate on concurrently during cluster operations. Same as the `--concurrency` command line option. - -##### `spec.options.concurrency.workerDisruptionPercent` <integer> (optional) (default: 10) - -The maximum percentage of worker nodes that can be disrupted at the same time during operations such as upgrade. This is used to ensure that a minimum number of worker nodes remain available during the operation. The value must be between 0 and 100. - -##### `spec.options.concurrency.uploads` <integer> (optional) (default: 5) - -The maximum number of concurrent file uploads to perform. Same as the `--concurrent-uploads` command line option. - -### Tokens - -The following tokens can be used in the `k0sDownloadURL` and `files.[*].src` fields: - -- `%%` - literal `%` -- `%p` - host architecture (arm, arm64, amd64) -- `%v` - k0s version (v1.21.0+k0s.0) -- `%x` - k0s binary extension (.exe on Windows, empty elsewhere) - -Any other tokens will be output as-is including the `%` character. - -Example: - -```yaml - - role: controller - k0sDownloadURL: https://files.example.com/k0s%20files/k0s-%v-%p%x - # Expands to https://files.example.com/k0s%20files/k0s-v1.21.0+k0s.0-amd64 -``` ## License [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fk0sproject%2Fk0sctl.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fk0sproject%2Fk0sctl?ref=badge_large) diff --git a/cmd/apply.go b/cmd/apply.go index d9024e684..5c2ffc3e7 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -54,6 +54,10 @@ var applyCommand = &cli.Command{ Usage: "Set kubernetes cluster name", DefaultText: "k0s-cluster", }, + &cli.BoolFlag{ + Name: "validate", + Usage: "Validate the configuration and exit without applying", + }, &cli.BoolFlag{ Name: "disable-downgrade-check", Usage: "Skip downgrade check", @@ -90,6 +94,11 @@ var applyCommand = &cli.Command{ kubeconfigOut = out } + if ctx.Bool("validate") { + fmt.Fprintln(ctx.App.Writer, "Configuration is valid") + return nil + } + manager, ok := ctx.Context.Value(ctxManagerKey{}).(*phase.Manager) if !ok { return fmt.Errorf("failed to retrieve manager from context") diff --git a/cmd/docs.go b/cmd/docs.go new file mode 100644 index 000000000..0f76865da --- /dev/null +++ b/cmd/docs.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "os" + + "github.com/charmbracelet/glamour" + k0sctldocs "github.com/k0sproject/k0sctl/docs" + "github.com/urfave/cli/v2" +) + +var docsCommand = &cli.Command{ + Name: "docs", + Usage: "Show configuration documentation", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "jsonschema", + Usage: "Output the JSON Schema for the configuration instead of the Markdown reference", + }, + }, + Action: func(ctx *cli.Context) error { + if ctx.Bool("jsonschema") { + _, err := ctx.App.Writer.Write(k0sctldocs.SchemaJSON) + return err + } + return showDocs(ctx) + }, +} + +// showDocs renders configuration.md as styled terminal output when stdout is a +// terminal, or writes the raw Markdown when piped/redirected. +func showDocs(ctx *cli.Context) error { + md := string(k0sctldocs.ConfigurationMD) + + outFile, isTerminal := ctx.App.Writer.(*os.File) + if isTerminal { + fi, err := outFile.Stat() + if err != nil || (fi.Mode()&os.ModeCharDevice) == 0 { + isTerminal = false + } + } + + if isTerminal { + // Render Markdown with ANSI styles for the terminal. + rendered, err := glamour.Render(md, "auto") + if err == nil { + _, err = outFile.WriteString(rendered) + return err + } + // Glamour failed — fall through to plain write below. + } + + _, err := ctx.App.Writer.Write(k0sctldocs.ConfigurationMD) + return err +} diff --git a/cmd/root.go b/cmd/root.go index 95fef4126..8676c4e4c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,6 +48,7 @@ func NewK0sctl(in io.Reader, out, errOut io.Writer) *cli.App { Commands: []*cli.Command{ versionCommand, applyCommand, + docsCommand, kubeconfigCommand, initCommand, resetCommand, diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 000000000..dd0920bbe --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,794 @@ +# k0sctl Configuration Reference + +> This document is auto-generated from the Go struct definitions. To regenerate, run `make docs`. + +The configuration file is in YAML format and loosely resembles the syntax used in Kubernetes. +YAML anchors and aliases can be used. + +Use `k0sctl init` to generate a skeleton configuration file. + +## Example + +```yaml +apiVersion: k0sctl.k0sproject.io/v1beta1 +kind: Cluster +metadata: + name: my-k0s-cluster +spec: + hosts: + - role: controller + ssh: + address: 10.0.0.1 + user: root + keyPath: ~/.ssh/id_rsa + - role: worker + ssh: + address: 10.0.0.2 + user: root + keyPath: ~/.ssh/id_rsa + k0s: + version: 1.32.2+k0s.0 + options: + wait: + enabled: true + drain: + enabled: true + evictTaint: + enabled: false + concurrency: + limit: 30 + workerDisruptionPercent: 10 + uploads: 5 +``` + +## Configuration Fields + +**`apiVersion`** (required) — Configuration file syntax version. Must be k0sctl.k0sproject.io/v1beta1. + +**`kind`** (required) — Object kind. Must be Cluster. + +**`metadata`** (optional) — Information that can be used to uniquely identify the object. + +**`spec`** (required) — Cluster specification. + +## `metadata` + +**`metadata.name`** (optional) (default: `k0s-cluster`) — Name of the cluster. + +**`metadata.user`** (optional) (default: `admin`) — Kubernetes admin user name. + +## `spec` + +**`spec.hosts`** (required) — A list of cluster hosts. + +**`spec.k0s`** (optional) — Settings related to the k0s cluster. + +**`spec.options`** (required) — Options for cluster operations. + +### `spec.hosts[*]` + +**`spec.hosts[*].winRM`** (optional) + +**`spec.hosts[*].ssh`** (optional) + +**`spec.hosts[*].localhost`** (optional) + +**`spec.hosts[*].openSSH`** (optional) + +**`spec.hosts[*].role`** (required) — Role of the host in the cluster. One of: + - controller — a controller-only node + - controller+worker — a controller that also runs workloads + - single — a single-node cluster; the configuration may only contain one host with this role + - worker — a worker node + +**`spec.hosts[*].reset`** (optional) — When true, k0sctl will remove the node from Kubernetes and reset k0s on the host. + +**`spec.hosts[*].privateInterface`** (optional) — Override the private network interface selected by host fact gathering. Useful when +fact gathering picks the wrong interface for intra-cluster communication. + +**`spec.hosts[*].privateAddress`** (optional) — Override the private IP address selected by host fact gathering. Useful when +fact gathering picks the wrong address for intra-cluster communication. + +**`spec.hosts[*].dataDir`** (optional) (default: `/var/lib/k0s`) — Override the k0s data directory on the host. + +**`spec.hosts[*].kubeletRootDir`** (optional) — Override the kubelet root directory on the host. + +**`spec.hosts[*].environment`** (optional) — Environment variables to set in the k0s service environment on the host. + +**`spec.hosts[*].uploadBinary`** (optional) — When true, the k0s binary is downloaded on the local machine and uploaded to the +target host. When false (the default), the binary is downloaded directly on the host. + +**`spec.hosts[*].useExistingK0s`** (optional) — When true, k0sctl reuses the k0s binary that already exists on the host without +downloading or uploading anything. Upgrades for this host are skipped. +Cannot be combined with uploadBinary, k0sBinaryPath, or k0sDownloadURL. + +**`spec.hosts[*].k0sBinaryPath`** (optional) — Path to a local k0s binary to upload to the host. Useful for testing a custom +or development build of k0s without publishing a release. + +**`spec.hosts[*].k0sInstallPath`** (optional) — Path on the host where the k0s binary will be installed. + +**`spec.hosts[*].k0sDownloadURL`** (optional) — URL to download the k0s binary from instead of the default k0s GitHub releases. +Supports %-prefixed tokens: %v (version), %p (arch), %x (.exe on Windows). + +**`spec.hosts[*].installFlags`** (optional) — Extra flags passed verbatim to the k0s install command on the host. +See k0s install --help for available options. + +**`spec.hosts[*].files`** (optional) — Files to upload to the host before k0s is configured. Supports local paths, +URLs, and glob patterns. See the file upload documentation for details. + +**`spec.hosts[*].os`** (optional) — Override OS distribution auto-detection. By default k0sctl reads /etc/os-release. +Set this when the release file does not reflect the true distribution, e.g. set +"debian" for a Debian-based image that reports a different OS ID. + +**`spec.hosts[*].hostname`** (optional) — Override the hostname reported by the OS. When not set, the OS hostname is used. + +**`spec.hosts[*].noTaints`** (optional) — When true and used with the controller+worker role, disables the default +node-role.kubernetes.io/master:NoSchedule taint so that regular workloads +can be scheduled on the node without requiring a toleration. + +**`spec.hosts[*].hooks`** (optional) — Commands to run on the host at specific points during k0sctl operations. +See the hooks documentation for available stages and timing details. + +#### `spec.hosts[*].winRM` + +**`spec.hosts[*].winRM.address`** (required) — IP address or hostname of the host. + +**`spec.hosts[*].winRM.user`** (optional) (default: `Administrator`) — WinRM user name. The user must have administrative privileges. + +**`spec.hosts[*].winRM.port`** (optional) (default: `5985`) — TCP port for the WinRM endpoint. When useHTTPS is true, the default port automatically switches to 5986. + +**`spec.hosts[*].winRM.password`** (optional) — Password for the WinRM user. Required unless certificate-based authentication is configured. + +**`spec.hosts[*].winRM.useHTTPS`** (optional) (default: `false`) — Enable HTTPS for WinRM. When enabled, set caCertPath (and optionally certPath/keyPath) to verify the remote endpoint. + +**`spec.hosts[*].winRM.insecure`** (optional) (default: `false`) — Skip TLS certificate verification when connecting over HTTPS. + +**`spec.hosts[*].winRM.useNTLM`** (optional) (default: `false`) — Use NTLM authentication instead of basic authentication. + +**`spec.hosts[*].winRM.caCertPath`** (optional) — Path to a CA bundle used to validate the WinRM server certificate. + +**`spec.hosts[*].winRM.certPath`** (optional) — Client certificate for mutual TLS authentication. + +**`spec.hosts[*].winRM.keyPath`** (optional) — Private key that matches certPath. + +**`spec.hosts[*].winRM.tlsServerName`** (optional) — Override the TLS server name used during certificate verification. + +**`spec.hosts[*].winRM.bastion`** (optional) — SSH bastion (jump host) configuration. + +#### `spec.hosts[*].winRM.bastion` + +**`spec.hosts[*].winRM.bastion.address`** (required) — IP address or hostname of the bastion host. + +**`spec.hosts[*].winRM.bastion.user`** (optional) (default: `root`) — SSH user for the bastion host. + +**`spec.hosts[*].winRM.bastion.port`** (optional) (default: `22`) — SSH port on the bastion host. + +**`spec.hosts[*].winRM.bastion.keyPath`** (optional) — Path to an SSH private key for the bastion host. + +#### `spec.hosts[*].ssh` + +**`spec.hosts[*].ssh.address`** (required) — IP address or hostname of the host. + +**`spec.hosts[*].ssh.user`** (optional) (default: `root`) — Username to log in as. + +**`spec.hosts[*].ssh.port`** (optional) (default: `22`) — TCP port of the SSH service on the host. + +**`spec.hosts[*].ssh.keyPath`** (optional) — Path to an SSH private key file. If a public key is used, ssh-agent is required. When left empty, the default value will first be looked for from the SSH configuration IdentityFile parameter. + +**`spec.hosts[*].ssh.bastion`** (optional) — SSH bastion (jump host) configuration. + +#### `spec.hosts[*].ssh.bastion` + +**`spec.hosts[*].ssh.bastion.address`** (required) — IP address or hostname of the bastion host. + +**`spec.hosts[*].ssh.bastion.user`** (optional) (default: `root`) — SSH user for the bastion host. + +**`spec.hosts[*].ssh.bastion.port`** (optional) (default: `22`) — SSH port on the bastion host. + +**`spec.hosts[*].ssh.bastion.keyPath`** (optional) — Path to an SSH private key for the bastion host. + +#### `spec.hosts[*].localhost` + +**`spec.hosts[*].localhost.enabled`** (optional) (default: `true`) — Must be set to true to enable the localhost connection. + +#### `spec.hosts[*].openSSH` + +**`spec.hosts[*].openSSH.address`** (required) — IP address, hostname, or ssh config host alias of the host. + +**`spec.hosts[*].openSSH.user`** (optional) — Username to connect as. + +**`spec.hosts[*].openSSH.port`** (optional) — Remote SSH port. + +**`spec.hosts[*].openSSH.keyPath`** (optional) — Path to an SSH private key. + +**`spec.hosts[*].openSSH.configPath`** (optional) — Path to ssh config. Defaults to ~/.ssh/config with fallback to /etc/ssh/ssh_config. + +**`spec.hosts[*].openSSH.options`** (optional) — Additional options as key/value pairs passed to the ssh client as -o flags. + +**`spec.hosts[*].openSSH.disableMultiplexing`** (optional) — Disable SSH connection multiplexing. When true, every remote command requires reconnecting to the host. + +#### `spec.hosts[*].files[*]` + +**`spec.hosts[*].files[*].name`** (optional) — Optional label for this upload entry, used only in log output. + +**`spec.hosts[*].files[*].src`** (optional) — Source file path, URL, or glob pattern. Required when data is not set. +Glob patterns follow the doublestar syntax. URL sources are downloaded directly +on the target host. Supports %v, %p, %x token expansion. + +**`spec.hosts[*].files[*].data`** (optional) — Inline file content to write to the destination. Required when src is not set. + +**`spec.hosts[*].files[*].dstDir`** (optional) — Destination directory on the host. k0sctl creates the full path if it does +not exist. Defaults to the remote user's home directory. + +**`spec.hosts[*].files[*].dst`** (optional) — Destination filename on the host. Only valid for single-file uploads. +Defaults to the source file's basename. + +**`spec.hosts[*].files[*].perm`** (optional) — Permission mode for the uploaded file(s), e.g. 0644. Defaults to the local +file's permission mode. + +**`spec.hosts[*].files[*].dirPerm`** (optional) — Permission mode for directories created by k0sctl during upload. + +**`spec.hosts[*].files[*].user`** (optional) — Owner user name for the uploaded file(s) and created directories. Must already +exist on the host. + +**`spec.hosts[*].files[*].group`** (optional) — Owner group name for the uploaded file(s) and created directories. Must already +exist on the host. + +#### `spec.hosts[*].hooks` + +**`spec.hosts[*].hooks.connect`** (optional) + +**`spec.hosts[*].hooks.apply`** (optional) + +**`spec.hosts[*].hooks.upgrade`** (optional) + +**`spec.hosts[*].hooks.install`** (optional) + +**`spec.hosts[*].hooks.backup`** (optional) + +**`spec.hosts[*].hooks.reset`** (optional) + +#### `spec.hosts[*].hooks.connect` + +**`spec.hosts[*].hooks.connect.before`** (optional) — Commands to run before the action. + +**`spec.hosts[*].hooks.connect.after`** (optional) — Commands to run after the action. + +#### `spec.hosts[*].hooks.apply` + +**`spec.hosts[*].hooks.apply.before`** (optional) — Commands to run before the action. + +**`spec.hosts[*].hooks.apply.after`** (optional) — Commands to run after the action. + +#### `spec.hosts[*].hooks.upgrade` + +**`spec.hosts[*].hooks.upgrade.before`** (optional) — Commands to run before the action. + +**`spec.hosts[*].hooks.upgrade.after`** (optional) — Commands to run after the action. + +#### `spec.hosts[*].hooks.install` + +**`spec.hosts[*].hooks.install.before`** (optional) — Commands to run before the action. + +**`spec.hosts[*].hooks.install.after`** (optional) — Commands to run after the action. + +#### `spec.hosts[*].hooks.backup` + +**`spec.hosts[*].hooks.backup.before`** (optional) — Commands to run before the action. + +**`spec.hosts[*].hooks.backup.after`** (optional) — Commands to run after the action. + +#### `spec.hosts[*].hooks.reset` + +**`spec.hosts[*].hooks.reset.before`** (optional) — Commands to run before the action. + +**`spec.hosts[*].hooks.reset.after`** (optional) — Commands to run after the action. + +### `spec.k0s` + +**`spec.k0s.version`** (optional) — Version of k0s to deploy. When omitted, k0sctl selects the latest stable release +(or the version already running on the cluster if one exists). + +**`spec.k0s.versionChannel`** (optional) (default: `stable`) — Version channel used when auto-discovering the k0s version. Set to "latest" to +allow k0sctl to select pre-release versions. Has no effect when version is set. + +**`spec.k0s.dynamicConfig`** (optional) — Enable k0s dynamic configuration. When true, k0sctl only pushes the cluster-wide +configuration on first-time initialisation; subsequent applies leave it unchanged. +Use k0sctl config edit or k0s config edit to manage it afterwards. +This flag is also auto-enabled when any controller has --enable-dynamic-config in +installFlags or in its running k0s arguments. + +**`spec.k0s.config`** (optional) — Embedded k0s cluster configuration. See https://docs.k0sproject.io/stable/configuration/ +for field reference. When omitted, the output of k0s config create is used. +The k0s config can also be placed as a separate YAML document in the same file, +using apiVersion: k0s.k0sproject.io/v1beta1 and kind: ClusterConfig. + +### `spec.options` + +**`spec.options.wait`** (required) — Controls wait behavior for cluster operations. + +**`spec.options.drain`** (required) — Controls drain behavior for cluster operations. + +**`spec.options.concurrency`** (required) — Controls how many hosts are operated on at once. + +**`spec.options.evictTaint`** (required) — Controls whether a taint is applied to nodes before disruptive operations. + +#### `spec.options.wait` + +**`spec.options.wait.enabled`** (optional) (default: `true`) — When false, k0sctl will not wait for k0s to become ready after restarting the +service. Equivalent to passing --no-wait on the command line. + +#### `spec.options.drain` + +**`spec.options.drain.enabled`** (optional) (default: `true`) — When false, k0sctl skips draining nodes before disruptive operations such as +upgrade or reset. Equivalent to passing --no-drain on the command line. + +**`spec.options.drain.gracePeriod`** (optional) (default: `120s`) — How long to wait for pods to be evicted from the node before proceeding. + +**`spec.options.drain.timeout`** (optional) (default: `300s`) — How long to wait for the entire drain operation to complete before timing out. + +**`spec.options.drain.force`** (optional) (default: `true`) — Pass --force to kubectl drain, allowing pods without a replication controller +to be evicted. + +**`spec.options.drain.ignoreDaemonSets`** (optional) (default: `true`) — Pass --ignore-daemonsets to kubectl drain so that DaemonSet-managed pods are +not considered when draining. + +**`spec.options.drain.deleteEmptyDirData`** (optional) (default: `true`) — Pass --delete-emptydir-data to kubectl drain, allowing pods that use emptyDir +volumes (whose data will be lost) to be evicted. + +**`spec.options.drain.podSelector`** (required) — Label selector passed to kubectl drain to restrict which pods are considered. + +**`spec.options.drain.skipWaitForDeleteTimeout`** (optional) (default: `0s`) — If a pod's DeletionTimestamp is older than this duration, skip waiting for it. +Must be greater than 0s to take effect. + +#### `spec.options.concurrency` + +**`spec.options.concurrency.limit`** (optional) (default: `30`) — Maximum number of hosts to configure concurrently. Equivalent to --concurrency +on the command line. Set to 0 for unlimited. + +**`spec.options.concurrency.workerDisruptionPercent`** (optional) (default: `10`) — Maximum percentage of worker nodes that may be disrupted simultaneously during +operations such as upgrade. Value must be between 0 and 100. This ensures a +minimum number of workers remain available during rolling operations. + +**`spec.options.concurrency.uploads`** (optional) (default: `5`) — Maximum number of file uploads to perform concurrently. Equivalent to +--concurrent-uploads on the command line. + +#### `spec.options.evictTaint` + +**`spec.options.evictTaint.enabled`** (optional) (default: `false`) — When true, k0sctl applies a taint to nodes before service-affecting operations +(upgrade, reset) to signal workloads to evacuate before the node is disrupted. +Can also be enabled at runtime with --evict-taint on the command line. + +**`spec.options.evictTaint.taint`** (optional) (default: `k0sctl.k0sproject.io/evict=true`) — Taint to apply when enabled is true. Must be in the format key=value. + +**`spec.options.evictTaint.effect`** (optional) (default: `NoExecute`) — Effect of the taint. Must be NoExecute, NoSchedule, or PreferNoSchedule. + +**`spec.options.evictTaint.controllerWorkers`** (optional) (default: `false`) — When true, the taint is also applied to controller+worker nodes. By default +only pure worker nodes are tainted. + +## Host Requirements + +- Linux nodes are supported for all roles. +- Windows nodes can join as `worker` hosts when reachable over SSH or WinRM. This support is + experimental and requires k0s version >= 1.34. +- On Linux, the SSH user must either be `root` or have passwordless `sudo` (or `doas`) access. + Windows workers must allow WinRM access for the configured user (defaults to `Administrator`). +- The host must fulfil the [k0s system requirements](https://docs.k0sproject.io/stable/system-requirements/). + +## Host Connection Types + +Each host entry must specify exactly one connection type: `ssh`, `openSSH`, `winRM`, or `localhost`. + +### SSH + +The built-in SSH client. No external tooling required. Windows worker nodes can also use SSH +when an SSH server is available on the host. + +```yaml +- role: worker + ssh: + address: 10.0.0.2 + user: ubuntu + port: 22 + keyPath: ~/.ssh/id_rsa +``` + +**Bastion (jump host):** tunnel connections through an intermediate host by adding a `bastion` +block. The bastion fields are identical to the SSH connection fields. + +```yaml +- role: controller + ssh: + address: 10.0.0.2 + user: ubuntu + keyPath: ~/.ssh/id_rsa + bastion: + address: 10.0.0.1 + user: root + keyPath: ~/.ssh/id_rsa2 +``` + +**SSH agent / auth forwarding:** a host without a `keyPath` will use the running ssh-agent. +Pageant or openssh-agent can be used on Windows. + +```yaml +- role: controller + ssh: + address: 10.0.0.2 + user: ubuntu +``` + +```shell +$ ssh-add ~/.ssh/aws.pem +$ ssh -A user@jumphost +user@jumphost ~ $ k0sctl apply +``` + +### OpenSSH + +Delegates connections to the system `ssh` binary. Inherits `~/.ssh/config`, agent forwarding, +multiplexing, and all other OpenSSH features. The `address` can be an IP, hostname, or any +host alias defined in `~/.ssh/config`. + +```yaml +- role: controller + openSSH: + address: controller1 # alias from ~/.ssh/config +``` + +Example `~/.ssh/config` entry that the above would pick up automatically: + +``` +Host controller1 + Hostname 10.0.0.1 + Port 2222 + IdentityFile ~/.ssh/id_cluster +``` + +Additional `ssh -o` flags can be passed via `options`: + +```yaml +openSSH: + address: 10.0.0.2 + options: + ForwardAgent: "yes" + StrictHostKeyChecking: "no" +``` + +By default, a ControlMaster connection is opened and subsequent commands reuse it. Set +`disableMultiplexing: true` to reconnect for every remote command (slower, but useful for +debugging or hosts that reject multiplexing). + +```yaml +openSSH: + address: 10.0.0.2 + disableMultiplexing: true +``` + +### WinRM + +Connects to Windows hosts via WinRM. Requires WinRM to be enabled on the target. +Windows support is limited to the `worker` role and requires k0s >= 1.34. + +```yaml +- role: worker + winRM: + address: win-worker-1.internal + user: Administrator + password: ${WINRM_PASSWORD} + useHTTPS: true + insecure: false +``` + +The user must have administrative privileges. Windows does not provide a built-in way to +elevate privileges over WinRM, so the user must already have them. + +When `useHTTPS` is `true` the default port switches from 5985 to 5986. Set `caCertPath` to +verify the server certificate, or `insecure: true` to skip verification in trusted environments. + +To reach a WinRM host through an SSH bastion: + +```yaml +- role: worker + winRM: + address: 10.0.0.20 + user: Administrator + password: ${WINRM_PASSWORD} + bastion: + address: bastion.example.com + user: ubuntu + keyPath: ~/.ssh/id_rsa +``` + +### Localhost + +Runs k0s directly on the machine executing k0sctl without any remote connection. + +```yaml +- role: single + localhost: + enabled: true +``` + +## Commonly Used Host Fields + +### OS Override + +By default k0sctl detects the OS by reading `/etc/os-release`. Use `os` to override when the +release file does not reflect the true distribution (e.g. a Debian-based image with a custom +OS ID): + +```yaml +- role: worker + os: debian + ssh: + address: 10.0.0.2 +``` + +### Private Interface / Address + +Override which network interface or IP address k0sctl uses for intra-cluster communication +when fact gathering picks the wrong one: + +```yaml +- role: worker + privateInterface: eth1 + privateAddress: 10.0.0.5 + ssh: + address: 10.0.0.2 +``` + +### Install Flags + +Extra flags passed verbatim to `k0s install` on each host. See `k0s install --help` for all +available options. + +```yaml +- role: controller + installFlags: + - --debug + - --enable-dynamic-config + ssh: + address: 10.0.0.1 +``` + +### Environment Variables + +Key-value pairs set in the k0s service environment on the host: + +```yaml +- role: worker + environment: + HTTP_PROXY: http://proxy.example.com:3128 + NO_PROXY: 10.0.0.0/8 + ssh: + address: 10.0.0.2 +``` + +## Hooks + +Hooks run shell commands on the remote host at specific points during k0sctl operations. +They execute using the same remote user as the connection; prefix commands with `sudo` if +elevated privileges are required. In dry-run mode hooks are printed but not executed. + +```yaml +- role: worker + ssh: + address: 10.0.0.3 + hooks: + connect: + after: + - echo "connected to $(hostname)" >> /tmp/k0sctl.log + apply: + before: + - apt-get install -y nfs-common + after: + - echo "apply done on $(hostname)" >> /tmp/k0sctl.log + upgrade: + before: + - echo "upgrading $(hostname)" >> /tmp/k0sctl.log + after: + - echo "upgrade done" >> /tmp/k0sctl.log + reset: + before: + - echo "resetting $(hostname)" >> /tmp/k0sctl.log +``` + +Available hook stages and when they fire: + +| Stage | Point | +|-------|-------| +| `connect.after` | Immediately after OS detection completes | +| `apply.before` | After validation, right before configuring k0s on the host | +| `apply.after` | Before disconnecting after a successful apply | +| `upgrade.before` | Before the upgrade begins on this host | +| `upgrade.after` | After the upgrade completes on this host | +| `install.before` | Just before installing k0s components (controller init, join, worker) | +| `install.after` | Immediately after k0s components are installed and ready | +| `backup.before` | Before running `k0s backup` | +| `backup.after` | Before disconnecting after a successful backup | +| `reset.before` | After gathering cluster info, right before removing k0s | +| `reset.after` | Before disconnecting after a successful reset | + +## Uploading Files + +The `files` list uploads local files or directories to the host before k0s is configured. +`src` supports file paths, URLs, and glob patterns. `%p`, `%v`, `%x` tokens (see Tokens) +are expanded in `src` and `k0sDownloadURL`. + +```yaml +- role: controller + ssh: + address: 10.0.0.1 + files: + - name: image-bundle # optional label used in log output + src: airgap-images.tgz + dstDir: /var/lib/k0s/images/ + perm: 0600 + - name: manifests + src: ./manifests/*.yaml + dstDir: /var/lib/k0s/manifests/myapp + perm: 0644 + - name: motd + data: | + Powered by k0s + dst: /etc/motd + perm: 0644 +``` + +Field summary: + +| Field | Description | +|-------|-------------| +| `name` | Label for log output (optional) | +| `src` | Local path, URL, or glob — required when `data` is not set | +| `data` | Inline file content — required when `src` is not set | +| `dstDir` | Destination directory; created if it does not exist (default: user home) | +| `dst` | Destination filename; only valid for single-file uploads (default: source basename) | +| `perm` | File permission mode (default: same as local file) | +| `dirPerm` | Permission mode for created directories (default: 0755) | +| `user` | Owner user name — must already exist on the host | +| `group` | Owner group name — must already exist on the host | + +## k0s Configuration + +### Version Auto-discovery + +When `spec.k0s.version` is omitted, k0sctl queries the k0s GitHub releases API and selects +the latest stable release (or the version already running on the cluster). Set +`spec.k0s.versionChannel: latest` to include pre-releases in the search. + +### Dynamic Config + +When `spec.k0s.dynamicConfig` is enabled (or auto-detected because any controller has +`--enable-dynamic-config` in `installFlags` or in its running arguments), k0sctl only +pushes the cluster-wide configuration during **first-time initialisation**. Subsequent +applies do not update it; use `k0sctl config edit` or `k0s config edit` to manage it +instead. Node-specific configuration is always updated on each apply. + +See [k0s Dynamic Configuration](https://docs.k0sproject.io/stable/dynamic-configuration/). + +### Separate k0s Config Document + +Instead of embedding the k0s cluster configuration inside `spec.k0s.config`, you can +place it as a second YAML document in the same file (or load it via a separate `--config` +flag): + +```yaml +apiVersion: k0sctl.k0sproject.io/v1beta1 +kind: Cluster +spec: + hosts: + - role: single + ssh: + address: 10.0.0.1 +--- +apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +metadata: + name: my-k0s-cluster +spec: + api: + externalAddress: 10.0.0.2 +``` + +## Options + +`spec.options` controls global behaviour for cluster operations. + +```yaml +spec: + options: + wait: + enabled: true + drain: + enabled: true + gracePeriod: 120s + timeout: 300s + force: true + ignoreDaemonSets: true + deleteEmptyDirData: true + skipWaitForDeleteTimeout: 0s + evictTaint: + enabled: false + taint: k0sctl.k0sproject.io/evict=true + effect: NoExecute + controllerWorkers: false + concurrency: + limit: 30 + workerDisruptionPercent: 10 + uploads: 5 +``` + +### Wait + +`options.wait.enabled` (default `true`) — when `false`, k0sctl does not wait for k0s to +become ready after restarting the service. Equivalent to `--no-wait` on the command line. + +### Drain + +`options.drain.enabled` (default `true`) — when `false`, k0sctl skips draining nodes before +disruptive operations. Equivalent to `--no-drain` on the command line. + +### EvictTaint + +When `options.evictTaint.enabled` is `true`, k0sctl applies a taint to nodes before +service-affecting operations (upgrade, reset) to signal workloads to evacuate in advance. +By default only `worker` nodes are tainted; set `controllerWorkers: true` to also taint +`controller+worker` nodes. + +The `--evict-taint` command-line flag can also enable this at runtime. + +### Concurrency + +`options.concurrency.workerDisruptionPercent` (default `10`) — the maximum percentage of +worker nodes that may be disrupted simultaneously during operations such as upgrade. Set to +`0` to allow all workers at once, or to `100` to process them one at a time (the maximum +value is treated as "unlimited" workers simultaneously, which may be confusing — use a small +value for conservative upgrades). + +## Tokens + +The following tokens are expanded in `k0sDownloadURL` and `files[*].src`: + +| Token | Meaning | +|-------|---------| +| `%%` | Literal `%` | +| `%p` | Host CPU architecture (`arm`, `arm64`, `amd64`) | +| `%v` | k0s version string (e.g. `v1.32.2+k0s.0`) | +| `%x` | Binary extension (`.exe` on Windows, empty elsewhere) | + +Any other `%X` sequence is passed through unchanged. + +Example: + +```yaml +- role: controller + k0sDownloadURL: https://files.example.com/k0s-files/k0s-%v-%p%x + # Expands to: https://files.example.com/k0s-files/k0s-v1.32.2+k0s.0-amd64 +``` + +## Environment Variable Substitution + +Bash-style variable substitution is applied to the entire configuration file before parsing. + +| Expression | Meaning | +|------------|---------| +| `$VAR` / `${VAR}` | Value of `VAR` | +| `${VAR:-default}` | Value of `VAR`, or `default` if unset or empty | +| `$$VAR` | Literal `$VAR` (escapes substitution) | + +See [a8m/envsubst](https://github.com/a8m/envsubst#docs) for the full expression reference. + +```yaml +spec: + hosts: + - role: controller + ssh: + address: ${CONTROLLER_IP} + user: ${SSH_USER:-root} + - role: worker + winRM: + address: ${WORKER_IP} + password: ${WINRM_PASSWORD} +``` diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 000000000..8e69f064d --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,14 @@ +// Package docs provides the embedded k0sctl configuration documentation. +package docs + +import _ "embed" + +// ConfigurationMD contains the configuration reference in Markdown format. +// +//go:embed configuration.md +var ConfigurationMD []byte + +// SchemaJSON contains the JSON Schema for the k0sctl configuration. +// +//go:embed k0sctl-schema.json +var SchemaJSON []byte diff --git a/docs/k0sctl-schema.json b/docs/k0sctl-schema.json new file mode 100644 index 000000000..928e020a9 --- /dev/null +++ b/docs/k0sctl-schema.json @@ -0,0 +1,723 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster", + "$ref": "#/$defs/Cluster", + "$defs": { + "Cluster": { + "properties": { + "apiVersion": { + "type": "string", + "description": "Configuration file syntax version. Must be k0sctl.k0sproject.io/v1beta1." + }, + "kind": { + "type": "string", + "description": "Object kind. Must be Cluster." + }, + "metadata": { + "$ref": "#/$defs/ClusterMetadata", + "description": "Information that can be used to uniquely identify the object." + }, + "spec": { + "$ref": "#/$defs/Spec", + "description": "Cluster specification." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "apiVersion", + "kind", + "spec" + ], + "description": "Cluster describes launchpad.yaml configuration" + }, + "ClusterMetadata": { + "properties": { + "name": { + "type": "string", + "description": "Name of the cluster.", + "default": "k0s-cluster" + }, + "user": { + "type": "string", + "description": "Kubernetes admin user name.", + "default": "admin" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "user" + ], + "description": "ClusterMetadata defines cluster metadata" + }, + "ConcurrencyOption": { + "properties": { + "limit": { + "type": "integer", + "description": "Maximum number of hosts to configure concurrently. Equivalent to --concurrency\non the command line. Set to 0 for unlimited.", + "default": 30 + }, + "workerDisruptionPercent": { + "type": "integer", + "description": "Maximum percentage of worker nodes that may be disrupted simultaneously during\noperations such as upgrade. Value must be between 0 and 100. This ensures a\nminimum number of workers remain available during rolling operations.", + "default": 10 + }, + "uploads": { + "type": "integer", + "description": "Maximum number of file uploads to perform concurrently. Equivalent to\n--concurrent-uploads on the command line.", + "default": 5 + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "limit", + "workerDisruptionPercent", + "uploads" + ], + "description": "ConcurrencyOption controls how many hosts are operated on at once." + }, + "DrainOption": { + "properties": { + "enabled": { + "type": "boolean", + "description": "When false, k0sctl skips draining nodes before disruptive operations such as\nupgrade or reset. Equivalent to passing --no-drain on the command line.", + "default": true + }, + "gracePeriod": { + "type": "string", + "description": "How long to wait for pods to be evicted from the node before proceeding.", + "default": "120s" + }, + "timeout": { + "type": "string", + "description": "How long to wait for the entire drain operation to complete before timing out.", + "default": "300s" + }, + "force": { + "type": "boolean", + "description": "Pass --force to kubectl drain, allowing pods without a replication controller\nto be evicted.", + "default": true + }, + "ignoreDaemonSets": { + "type": "boolean", + "description": "Pass --ignore-daemonsets to kubectl drain so that DaemonSet-managed pods are\nnot considered when draining.", + "default": true + }, + "deleteEmptyDirData": { + "type": "boolean", + "description": "Pass --delete-emptydir-data to kubectl drain, allowing pods that use emptyDir\nvolumes (whose data will be lost) to be evicted.", + "default": true + }, + "podSelector": { + "type": "string", + "description": "Label selector passed to kubectl drain to restrict which pods are considered." + }, + "skipWaitForDeleteTimeout": { + "type": "string", + "description": "If a pod's DeletionTimestamp is older than this duration, skip waiting for it.\nMust be greater than 0s to take effect.", + "default": "0s" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "enabled", + "gracePeriod", + "timeout", + "force", + "ignoreDaemonSets", + "deleteEmptyDirData", + "podSelector", + "skipWaitForDeleteTimeout" + ], + "description": "DrainOption controls the drain behavior for cluster operations." + }, + "EvictTaintOption": { + "properties": { + "enabled": { + "type": "boolean", + "description": "When true, k0sctl applies a taint to nodes before service-affecting operations\n(upgrade, reset) to signal workloads to evacuate before the node is disrupted.\nCan also be enabled at runtime with --evict-taint on the command line.", + "default": false + }, + "taint": { + "type": "string", + "description": "Taint to apply when enabled is true. Must be in the format key=value.", + "default": "k0sctl.k0sproject.io/evict=true" + }, + "effect": { + "type": "string", + "enum": [ + "NoExecute", + "NoSchedule", + "PreferNoSchedule" + ], + "description": "Effect of the taint. Must be NoExecute, NoSchedule, or PreferNoSchedule.", + "default": "NoExecute" + }, + "controllerWorkers": { + "type": "boolean", + "description": "When true, the taint is also applied to controller+worker nodes. By default\nonly pure worker nodes are tainted.", + "default": false + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "enabled", + "taint", + "effect", + "controllerWorkers" + ], + "description": "EvictTaintOption controls whether and how a taint is applied to nodes before service-affecting operations like upgrade or reset." + }, + "Host": { + "properties": { + "winRM": { + "properties": { + "address": { + "type": "string", + "description": "IP address or hostname of the host." + }, + "user": { + "type": "string", + "description": "WinRM user name. The user must have administrative privileges.", + "default": "Administrator" + }, + "port": { + "type": "integer", + "description": "TCP port for the WinRM endpoint. When useHTTPS is true, the default port automatically switches to 5986.", + "default": 5985 + }, + "password": { + "type": "string", + "description": "Password for the WinRM user. Required unless certificate-based authentication is configured." + }, + "useHTTPS": { + "type": "boolean", + "description": "Enable HTTPS for WinRM. When enabled, set caCertPath (and optionally certPath/keyPath) to verify the remote endpoint.", + "default": false + }, + "insecure": { + "type": "boolean", + "description": "Skip TLS certificate verification when connecting over HTTPS.", + "default": false + }, + "useNTLM": { + "type": "boolean", + "description": "Use NTLM authentication instead of basic authentication.", + "default": false + }, + "caCertPath": { + "type": "string", + "description": "Path to a CA bundle used to validate the WinRM server certificate." + }, + "certPath": { + "type": "string", + "description": "Client certificate for mutual TLS authentication." + }, + "keyPath": { + "type": "string", + "description": "Private key that matches certPath." + }, + "tlsServerName": { + "type": "string", + "description": "Override the TLS server name used during certificate verification." + }, + "bastion": { + "properties": { + "address": { + "type": "string", + "description": "IP address or hostname of the bastion host." + }, + "user": { + "type": "string", + "description": "SSH user for the bastion host.", + "default": "root" + }, + "port": { + "type": "integer", + "description": "SSH port on the bastion host.", + "default": 22 + }, + "keyPath": { + "type": "string", + "description": "Path to an SSH private key for the bastion host." + } + }, + "type": "object", + "required": [ + "address" + ], + "description": "SSH bastion (jump host) configuration." + } + }, + "type": "object", + "required": [ + "address" + ] + }, + "ssh": { + "properties": { + "address": { + "type": "string", + "description": "IP address or hostname of the host." + }, + "user": { + "type": "string", + "description": "Username to log in as.", + "default": "root" + }, + "port": { + "type": "integer", + "description": "TCP port of the SSH service on the host.", + "default": 22 + }, + "keyPath": { + "type": "string", + "description": "Path to an SSH private key file. If a public key is used, ssh-agent is required. When left empty, the default value will first be looked for from the SSH configuration IdentityFile parameter." + }, + "bastion": { + "properties": { + "address": { + "type": "string", + "description": "IP address or hostname of the bastion host." + }, + "user": { + "type": "string", + "description": "SSH user for the bastion host.", + "default": "root" + }, + "port": { + "type": "integer", + "description": "SSH port on the bastion host.", + "default": 22 + }, + "keyPath": { + "type": "string", + "description": "Path to an SSH private key for the bastion host." + } + }, + "type": "object", + "required": [ + "address" + ], + "description": "SSH bastion (jump host) configuration." + } + }, + "type": "object", + "required": [ + "address" + ] + }, + "localhost": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Must be set to true to enable the localhost connection.", + "default": true + } + }, + "type": "object", + "required": [ + "enabled" + ] + }, + "openSSH": { + "properties": { + "address": { + "type": "string", + "description": "IP address, hostname, or ssh config host alias of the host." + }, + "user": { + "type": "string", + "description": "Username to connect as." + }, + "port": { + "type": "integer", + "description": "Remote SSH port." + }, + "keyPath": { + "type": "string", + "description": "Path to an SSH private key." + }, + "configPath": { + "type": "string", + "description": "Path to ssh config. Defaults to ~/.ssh/config with fallback to /etc/ssh/ssh_config." + }, + "options": { + "additionalProperties": false, + "type": "object", + "description": "Additional options as key/value pairs passed to the ssh client as -o flags." + }, + "disableMultiplexing": { + "type": "boolean", + "description": "Disable SSH connection multiplexing. When true, every remote command requires reconnecting to the host." + } + }, + "type": "object", + "required": [ + "address" + ] + }, + "role": { + "type": "string", + "enum": [ + "controller", + "controller+worker", + "single", + "worker" + ], + "description": "Role of the host in the cluster. One of:\n - controller — a controller-only node\n - controller+worker — a controller that also runs workloads\n - single — a single-node cluster; the configuration may only contain one host with this role\n - worker — a worker node" + }, + "reset": { + "type": "boolean", + "description": "When true, k0sctl will remove the node from Kubernetes and reset k0s on the host." + }, + "privateInterface": { + "type": "string", + "description": "Override the private network interface selected by host fact gathering. Useful when\nfact gathering picks the wrong interface for intra-cluster communication." + }, + "privateAddress": { + "type": "string", + "description": "Override the private IP address selected by host fact gathering. Useful when\nfact gathering picks the wrong address for intra-cluster communication." + }, + "dataDir": { + "type": "string", + "description": "Override the k0s data directory on the host.", + "default": "/var/lib/k0s" + }, + "kubeletRootDir": { + "type": "string", + "description": "Override the kubelet root directory on the host." + }, + "environment": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Environment variables to set in the k0s service environment on the host." + }, + "uploadBinary": { + "type": "boolean", + "description": "When true, the k0s binary is downloaded on the local machine and uploaded to the\ntarget host. When false (the default), the binary is downloaded directly on the host." + }, + "useExistingK0s": { + "type": "boolean", + "description": "When true, k0sctl reuses the k0s binary that already exists on the host without\ndownloading or uploading anything. Upgrades for this host are skipped.\nCannot be combined with uploadBinary, k0sBinaryPath, or k0sDownloadURL." + }, + "k0sBinaryPath": { + "type": "string", + "description": "Path to a local k0s binary to upload to the host. Useful for testing a custom\nor development build of k0s without publishing a release." + }, + "k0sInstallPath": { + "type": "string", + "description": "Path on the host where the k0s binary will be installed." + }, + "k0sDownloadURL": { + "type": "string", + "description": "URL to download the k0s binary from instead of the default k0s GitHub releases.\nSupports %-prefixed tokens: %v (version), %p (arch), %x (.exe on Windows)." + }, + "installFlags": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Extra flags passed verbatim to the k0s install command on the host.\nSee k0s install --help for available options." + }, + "files": { + "items": { + "$ref": "#/$defs/UploadFile" + }, + "type": "array", + "description": "Files to upload to the host before k0s is configured. Supports local paths,\nURLs, and glob patterns. See the file upload documentation for details." + }, + "os": { + "type": "string", + "description": "Override OS distribution auto-detection. By default k0sctl reads /etc/os-release.\nSet this when the release file does not reflect the true distribution, e.g. set\n\"debian\" for a Debian-based image that reports a different OS ID." + }, + "hostname": { + "type": "string", + "description": "Override the hostname reported by the OS. When not set, the OS hostname is used." + }, + "noTaints": { + "type": "boolean", + "description": "When true and used with the controller+worker role, disables the default\nnode-role.kubernetes.io/master:NoSchedule taint so that regular workloads\ncan be scheduled on the node without requiring a toleration." + }, + "hooks": { + "properties": { + "connect": { + "properties": { + "before": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run before the action." + }, + "after": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run after the action." + } + }, + "type": "object" + }, + "apply": { + "properties": { + "before": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run before the action." + }, + "after": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run after the action." + } + }, + "type": "object" + }, + "upgrade": { + "properties": { + "before": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run before the action." + }, + "after": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run after the action." + } + }, + "type": "object" + }, + "install": { + "properties": { + "before": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run before the action." + }, + "after": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run after the action." + } + }, + "type": "object" + }, + "backup": { + "properties": { + "before": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run before the action." + }, + "after": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run after the action." + } + }, + "type": "object" + }, + "reset": { + "properties": { + "before": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run before the action." + }, + "after": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Commands to run after the action." + } + }, + "type": "object" + } + }, + "type": "object", + "description": "Commands to run on the host at specific points during k0sctl operations.\nSee the hooks documentation for available stages and timing details." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "role" + ], + "description": "Host contains all the needed details to work with hosts" + }, + "Hosts": { + "items": { + "$ref": "#/$defs/Host" + }, + "type": "array", + "description": "Hosts are destnation hosts" + }, + "K0s": { + "properties": { + "version": { + "type": "string", + "description": "Version of k0s to deploy. When omitted, k0sctl selects the latest stable release\n(or the version already running on the cluster if one exists)." + }, + "versionChannel": { + "type": "string", + "enum": [ + "stable", + "latest" + ], + "description": "Version channel used when auto-discovering the k0s version. Set to \"latest\" to\nallow k0sctl to select pre-release versions. Has no effect when version is set.", + "default": "stable" + }, + "dynamicConfig": { + "type": "boolean", + "description": "Enable k0s dynamic configuration. When true, k0sctl only pushes the cluster-wide\nconfiguration on first-time initialisation; subsequent applies leave it unchanged.\nUse k0sctl config edit or k0s config edit to manage it afterwards.\nThis flag is also auto-enabled when any controller has --enable-dynamic-config in\ninstallFlags or in its running k0s arguments." + }, + "config": { + "type": "object", + "description": "Embedded k0s cluster configuration. See https://docs.k0sproject.io/stable/configuration/\nfor field reference. When omitted, the output of k0s config create is used.\nThe k0s config can also be placed as a separate YAML document in the same file,\nusing apiVersion: k0s.k0sproject.io/v1beta1 and kind: ClusterConfig." + } + }, + "additionalProperties": false, + "type": "object", + "description": "K0s holds configuration for bootstraping a k0s cluster" + }, + "Options": { + "properties": { + "wait": { + "$ref": "#/$defs/WaitOption", + "description": "Controls wait behavior for cluster operations." + }, + "drain": { + "$ref": "#/$defs/DrainOption", + "description": "Controls drain behavior for cluster operations." + }, + "concurrency": { + "$ref": "#/$defs/ConcurrencyOption", + "description": "Controls how many hosts are operated on at once." + }, + "evictTaint": { + "$ref": "#/$defs/EvictTaintOption", + "description": "Controls whether a taint is applied to nodes before disruptive operations." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "wait", + "drain", + "concurrency", + "evictTaint" + ], + "description": "Options for cluster operations." + }, + "Spec": { + "properties": { + "hosts": { + "$ref": "#/$defs/Hosts", + "description": "A list of cluster hosts." + }, + "k0s": { + "$ref": "#/$defs/K0s", + "description": "Settings related to the k0s cluster." + }, + "options": { + "$ref": "#/$defs/Options", + "description": "Options for cluster operations." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "hosts", + "options" + ], + "description": "Spec defines cluster config spec section" + }, + "UploadFile": { + "properties": { + "name": { + "type": "string", + "description": "Optional label for this upload entry, used only in log output." + }, + "src": { + "type": "string", + "description": "Source file path, URL, or glob pattern. Required when data is not set.\nGlob patterns follow the doublestar syntax. URL sources are downloaded directly\non the target host. Supports %v, %p, %x token expansion." + }, + "data": { + "type": "string", + "description": "Inline file content to write to the destination. Required when src is not set." + }, + "dstDir": { + "type": "string", + "description": "Destination directory on the host. k0sctl creates the full path if it does\nnot exist. Defaults to the remote user's home directory." + }, + "dst": { + "type": "string", + "description": "Destination filename on the host. Only valid for single-file uploads.\nDefaults to the source file's basename." + }, + "perm": { + "description": "Permission mode for the uploaded file(s), e.g. 0644. Defaults to the local\nfile's permission mode." + }, + "dirPerm": { + "description": "Permission mode for directories created by k0sctl during upload." + }, + "user": { + "type": "string", + "description": "Owner user name for the uploaded file(s) and created directories. Must already\nexist on the host." + }, + "group": { + "type": "string", + "description": "Owner group name for the uploaded file(s) and created directories. Must already\nexist on the host." + } + }, + "additionalProperties": false, + "type": "object", + "description": "UploadFile describes a file to be uploaded for the host" + }, + "WaitOption": { + "properties": { + "enabled": { + "type": "boolean", + "description": "When false, k0sctl will not wait for k0s to become ready after restarting the\nservice. Equivalent to passing --no-wait on the command line.", + "default": true + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "enabled" + ], + "description": "WaitOption controls the wait behavior for cluster operations." + } + }, + "title": "k0sctl configuration", + "description": "Configuration file for k0sctl - a bootstrapping and management tool for k0s clusters." +} diff --git a/go.mod b/go.mod index 27e86cd27..5282bc015 100644 --- a/go.mod +++ b/go.mod @@ -35,9 +35,11 @@ require ( al.essio.dev/pkg/shellescape v1.6.0 github.com/carlmjohnson/versioninfo v0.22.5 github.com/go-playground/validator/v10 v10.30.1 + github.com/invopop/jsonschema v0.13.0 github.com/jellydator/validation v1.2.0 github.com/k0sproject/version v0.8.0 github.com/sergi/go-diff v1.4.0 + github.com/wk8/go-ordered-map/v2 v2.1.8 k8s.io/apimachinery v0.35.0 k8s.io/client-go v0.35.0 ) @@ -45,17 +47,31 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b // indirect github.com/bodgit/windows v1.0.1 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/glamour v0.10.0 // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect @@ -69,19 +85,29 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/yuin/goldmark v1.7.8 // indirect + github.com/yuin/goldmark-emoji v1.0.5 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/time v0.10.0 // indirect diff --git a/go.sum b/go.sum index 947a96a57..1fafb8ff8 100644 --- a/go.sum +++ b/go.sum @@ -16,17 +16,41 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b h1:baFN6AnR0SeC194X2D292IUZcHDs4JjStpqtE70fjXE= github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b/go.mod h1:Ram6ngyPDmP+0t6+4T2rymv0w0BS9N8Ch5vvUJccw5o= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= +github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= +github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -40,6 +64,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= @@ -73,6 +99,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= @@ -84,6 +112,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -122,6 +152,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg= @@ -134,11 +166,16 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -147,11 +184,19 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= @@ -179,11 +224,20 @@ github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTw github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= +github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= diff --git a/internal/schemagen/appendix.md b/internal/schemagen/appendix.md new file mode 100644 index 000000000..80ad8578e --- /dev/null +++ b/internal/schemagen/appendix.md @@ -0,0 +1,426 @@ +## Host Requirements + +- Linux nodes are supported for all roles. +- Windows nodes can join as `worker` hosts when reachable over SSH or WinRM. This support is + experimental and requires k0s version >= 1.34. +- On Linux, the SSH user must either be `root` or have passwordless `sudo` (or `doas`) access. + Windows workers must allow WinRM access for the configured user (defaults to `Administrator`). +- The host must fulfil the [k0s system requirements](https://docs.k0sproject.io/stable/system-requirements/). + +## Host Connection Types + +Each host entry must specify exactly one connection type: `ssh`, `openSSH`, `winRM`, or `localhost`. + +### SSH + +The built-in SSH client. No external tooling required. Windows worker nodes can also use SSH +when an SSH server is available on the host. + +```yaml +- role: worker + ssh: + address: 10.0.0.2 + user: ubuntu + port: 22 + keyPath: ~/.ssh/id_rsa +``` + +**Bastion (jump host):** tunnel connections through an intermediate host by adding a `bastion` +block. The bastion fields are identical to the SSH connection fields. + +```yaml +- role: controller + ssh: + address: 10.0.0.2 + user: ubuntu + keyPath: ~/.ssh/id_rsa + bastion: + address: 10.0.0.1 + user: root + keyPath: ~/.ssh/id_rsa2 +``` + +**SSH agent / auth forwarding:** a host without a `keyPath` will use the running ssh-agent. +Pageant or openssh-agent can be used on Windows. + +```yaml +- role: controller + ssh: + address: 10.0.0.2 + user: ubuntu +``` + +```shell +$ ssh-add ~/.ssh/aws.pem +$ ssh -A user@jumphost +user@jumphost ~ $ k0sctl apply +``` + +### OpenSSH + +Delegates connections to the system `ssh` binary. Inherits `~/.ssh/config`, agent forwarding, +multiplexing, and all other OpenSSH features. The `address` can be an IP, hostname, or any +host alias defined in `~/.ssh/config`. + +```yaml +- role: controller + openSSH: + address: controller1 # alias from ~/.ssh/config +``` + +Example `~/.ssh/config` entry that the above would pick up automatically: + +``` +Host controller1 + Hostname 10.0.0.1 + Port 2222 + IdentityFile ~/.ssh/id_cluster +``` + +Additional `ssh -o` flags can be passed via `options`: + +```yaml +openSSH: + address: 10.0.0.2 + options: + ForwardAgent: "yes" + StrictHostKeyChecking: "no" +``` + +By default, a ControlMaster connection is opened and subsequent commands reuse it. Set +`disableMultiplexing: true` to reconnect for every remote command (slower, but useful for +debugging or hosts that reject multiplexing). + +```yaml +openSSH: + address: 10.0.0.2 + disableMultiplexing: true +``` + +### WinRM + +Connects to Windows hosts via WinRM. Requires WinRM to be enabled on the target. +Windows support is limited to the `worker` role and requires k0s >= 1.34. + +```yaml +- role: worker + winRM: + address: win-worker-1.internal + user: Administrator + password: ${WINRM_PASSWORD} + useHTTPS: true + insecure: false +``` + +The user must have administrative privileges. Windows does not provide a built-in way to +elevate privileges over WinRM, so the user must already have them. + +When `useHTTPS` is `true` the default port switches from 5985 to 5986. Set `caCertPath` to +verify the server certificate, or `insecure: true` to skip verification in trusted environments. + +To reach a WinRM host through an SSH bastion: + +```yaml +- role: worker + winRM: + address: 10.0.0.20 + user: Administrator + password: ${WINRM_PASSWORD} + bastion: + address: bastion.example.com + user: ubuntu + keyPath: ~/.ssh/id_rsa +``` + +### Localhost + +Runs k0s directly on the machine executing k0sctl without any remote connection. + +```yaml +- role: single + localhost: + enabled: true +``` + +## Commonly Used Host Fields + +### OS Override + +By default k0sctl detects the OS by reading `/etc/os-release`. Use `os` to override when the +release file does not reflect the true distribution (e.g. a Debian-based image with a custom +OS ID): + +```yaml +- role: worker + os: debian + ssh: + address: 10.0.0.2 +``` + +### Private Interface / Address + +Override which network interface or IP address k0sctl uses for intra-cluster communication +when fact gathering picks the wrong one: + +```yaml +- role: worker + privateInterface: eth1 + privateAddress: 10.0.0.5 + ssh: + address: 10.0.0.2 +``` + +### Install Flags + +Extra flags passed verbatim to `k0s install` on each host. See `k0s install --help` for all +available options. + +```yaml +- role: controller + installFlags: + - --debug + - --enable-dynamic-config + ssh: + address: 10.0.0.1 +``` + +### Environment Variables + +Key-value pairs set in the k0s service environment on the host: + +```yaml +- role: worker + environment: + HTTP_PROXY: http://proxy.example.com:3128 + NO_PROXY: 10.0.0.0/8 + ssh: + address: 10.0.0.2 +``` + +## Hooks + +Hooks run shell commands on the remote host at specific points during k0sctl operations. +They execute using the same remote user as the connection; prefix commands with `sudo` if +elevated privileges are required. In dry-run mode hooks are printed but not executed. + +```yaml +- role: worker + ssh: + address: 10.0.0.3 + hooks: + connect: + after: + - echo "connected to $(hostname)" >> /tmp/k0sctl.log + apply: + before: + - apt-get install -y nfs-common + after: + - echo "apply done on $(hostname)" >> /tmp/k0sctl.log + upgrade: + before: + - echo "upgrading $(hostname)" >> /tmp/k0sctl.log + after: + - echo "upgrade done" >> /tmp/k0sctl.log + reset: + before: + - echo "resetting $(hostname)" >> /tmp/k0sctl.log +``` + +Available hook stages and when they fire: + +| Stage | Point | +|-------|-------| +| `connect.after` | Immediately after OS detection completes | +| `apply.before` | After validation, right before configuring k0s on the host | +| `apply.after` | Before disconnecting after a successful apply | +| `upgrade.before` | Before the upgrade begins on this host | +| `upgrade.after` | After the upgrade completes on this host | +| `install.before` | Just before installing k0s components (controller init, join, worker) | +| `install.after` | Immediately after k0s components are installed and ready | +| `backup.before` | Before running `k0s backup` | +| `backup.after` | Before disconnecting after a successful backup | +| `reset.before` | After gathering cluster info, right before removing k0s | +| `reset.after` | Before disconnecting after a successful reset | + +## Uploading Files + +The `files` list uploads local files or directories to the host before k0s is configured. +`src` supports file paths, URLs, and glob patterns. `%p`, `%v`, `%x` tokens (see Tokens) +are expanded in `src` and `k0sDownloadURL`. + +```yaml +- role: controller + ssh: + address: 10.0.0.1 + files: + - name: image-bundle # optional label used in log output + src: airgap-images.tgz + dstDir: /var/lib/k0s/images/ + perm: 0600 + - name: manifests + src: ./manifests/*.yaml + dstDir: /var/lib/k0s/manifests/myapp + perm: 0644 + - name: motd + data: | + Powered by k0s + dst: /etc/motd + perm: 0644 +``` + +Field summary: + +| Field | Description | +|-------|-------------| +| `name` | Label for log output (optional) | +| `src` | Local path, URL, or glob — required when `data` is not set | +| `data` | Inline file content — required when `src` is not set | +| `dstDir` | Destination directory; created if it does not exist (default: user home) | +| `dst` | Destination filename; only valid for single-file uploads (default: source basename) | +| `perm` | File permission mode (default: same as local file) | +| `dirPerm` | Permission mode for created directories (default: 0755) | +| `user` | Owner user name — must already exist on the host | +| `group` | Owner group name — must already exist on the host | + +## k0s Configuration + +### Version Auto-discovery + +When `spec.k0s.version` is omitted, k0sctl queries the k0s GitHub releases API and selects +the latest stable release (or the version already running on the cluster). Set +`spec.k0s.versionChannel: latest` to include pre-releases in the search. + +### Dynamic Config + +When `spec.k0s.dynamicConfig` is enabled (or auto-detected because any controller has +`--enable-dynamic-config` in `installFlags` or in its running arguments), k0sctl only +pushes the cluster-wide configuration during **first-time initialisation**. Subsequent +applies do not update it; use `k0sctl config edit` or `k0s config edit` to manage it +instead. Node-specific configuration is always updated on each apply. + +See [k0s Dynamic Configuration](https://docs.k0sproject.io/stable/dynamic-configuration/). + +### Separate k0s Config Document + +Instead of embedding the k0s cluster configuration inside `spec.k0s.config`, you can +place it as a second YAML document in the same file (or load it via a separate `--config` +flag): + +```yaml +apiVersion: k0sctl.k0sproject.io/v1beta1 +kind: Cluster +spec: + hosts: + - role: single + ssh: + address: 10.0.0.1 +--- +apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +metadata: + name: my-k0s-cluster +spec: + api: + externalAddress: 10.0.0.2 +``` + +## Options + +`spec.options` controls global behaviour for cluster operations. + +```yaml +spec: + options: + wait: + enabled: true + drain: + enabled: true + gracePeriod: 120s + timeout: 300s + force: true + ignoreDaemonSets: true + deleteEmptyDirData: true + skipWaitForDeleteTimeout: 0s + evictTaint: + enabled: false + taint: k0sctl.k0sproject.io/evict=true + effect: NoExecute + controllerWorkers: false + concurrency: + limit: 30 + workerDisruptionPercent: 10 + uploads: 5 +``` + +### Wait + +`options.wait.enabled` (default `true`) — when `false`, k0sctl does not wait for k0s to +become ready after restarting the service. Equivalent to `--no-wait` on the command line. + +### Drain + +`options.drain.enabled` (default `true`) — when `false`, k0sctl skips draining nodes before +disruptive operations. Equivalent to `--no-drain` on the command line. + +### EvictTaint + +When `options.evictTaint.enabled` is `true`, k0sctl applies a taint to nodes before +service-affecting operations (upgrade, reset) to signal workloads to evacuate in advance. +By default only `worker` nodes are tainted; set `controllerWorkers: true` to also taint +`controller+worker` nodes. + +The `--evict-taint` command-line flag can also enable this at runtime. + +### Concurrency + +`options.concurrency.workerDisruptionPercent` (default `10`) — the maximum percentage of +worker nodes that may be disrupted simultaneously during operations such as upgrade. Set to +`0` to allow all workers at once, or to `100` to process them one at a time (the maximum +value is treated as "unlimited" workers simultaneously, which may be confusing — use a small +value for conservative upgrades). + +## Tokens + +The following tokens are expanded in `k0sDownloadURL` and `files[*].src`: + +| Token | Meaning | +|-------|---------| +| `%%` | Literal `%` | +| `%p` | Host CPU architecture (`arm`, `arm64`, `amd64`) | +| `%v` | k0s version string (e.g. `v1.32.2+k0s.0`) | +| `%x` | Binary extension (`.exe` on Windows, empty elsewhere) | + +Any other `%X` sequence is passed through unchanged. + +Example: + +```yaml +- role: controller + k0sDownloadURL: https://files.example.com/k0s-files/k0s-%v-%p%x + # Expands to: https://files.example.com/k0s-files/k0s-v1.32.2+k0s.0-amd64 +``` + +## Environment Variable Substitution + +Bash-style variable substitution is applied to the entire configuration file before parsing. + +| Expression | Meaning | +|------------|---------| +| `$VAR` / `${VAR}` | Value of `VAR` | +| `${VAR:-default}` | Value of `VAR`, or `default` if unset or empty | +| `$$VAR` | Literal `$VAR` (escapes substitution) | + +See [a8m/envsubst](https://github.com/a8m/envsubst#docs) for the full expression reference. + +```yaml +spec: + hosts: + - role: controller + ssh: + address: ${CONTROLLER_IP} + user: ${SSH_USER:-root} + - role: worker + winRM: + address: ${WORKER_IP} + password: ${WINRM_PASSWORD} +``` diff --git a/internal/schemagen/main.go b/internal/schemagen/main.go new file mode 100644 index 000000000..3e7b0b5af --- /dev/null +++ b/internal/schemagen/main.go @@ -0,0 +1,155 @@ +// Package main generates JSON Schema and Markdown documentation from the +// k0sctl configuration Go structs. +// +// Usage: go run ./internal/schemagen +package main + +import ( + "encoding/json" + "fmt" + "os" + "reflect" + "time" + + "github.com/invopop/jsonschema" + "github.com/k0sproject/dig" + "github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1" + "github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster" + "github.com/k0sproject/rig" + "github.com/k0sproject/version" +) + +func main() { + r := &jsonschema.Reflector{ + FieldNameTag: "yaml", + Mapper: mapCustomTypes, + } + + // base must be the module root path; AddGoComments does path.Join(base, walkPath) + // for each directory it visits, which must equal the package's t.PkgPath(). + if err := r.AddGoComments("github.com/k0sproject/k0sctl", "./pkg/apis/k0sctl.k0sproject.io/v1beta1"); err != nil { + fmt.Fprintf(os.Stderr, "load go comments: %v\n", err) + os.Exit(1) + } + + schema := r.Reflect(&v1beta1.Cluster{}) + schema.Title = "k0sctl configuration" + schema.Description = "Configuration file for k0sctl - a bootstrapping and management tool for k0s clusters." + + injectDefaults(schema) + + if err := os.MkdirAll("docs", 0o755); err != nil { + fmt.Fprintf(os.Stderr, "mkdir docs: %v\n", err) + os.Exit(1) + } + + // Write JSON Schema. + jsonBytes, err := json.MarshalIndent(schema, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "marshal schema: %v\n", err) + os.Exit(1) + } + jsonBytes = append(jsonBytes, '\n') + if err := os.WriteFile("docs/k0sctl-schema.json", jsonBytes, 0o644); err != nil { + fmt.Fprintf(os.Stderr, "write schema: %v\n", err) + os.Exit(1) + } + + // Write Markdown. + md := renderMarkdown(schema) + if err := os.WriteFile("docs/configuration.md", []byte(md), 0o644); err != nil { + fmt.Fprintf(os.Stderr, "write markdown: %v\n", err) + os.Exit(1) + } + + fmt.Println("Generated docs/k0sctl-schema.json and docs/configuration.md") +} + +// mapCustomTypes returns manual schema definitions for types that the +// reflector cannot handle automatically (external rig types, dig.Mapping, etc.). +func mapCustomTypes(t reflect.Type) *jsonschema.Schema { + switch t { + case reflect.TypeOf(dig.Mapping{}): + return &jsonschema.Schema{ + Type: "object", + Description: "Arbitrary key-value mapping.", + } + case reflect.TypeOf(version.Version{}): + return &jsonschema.Schema{Type: "string"} + case reflect.TypeOf(time.Duration(0)): + return &jsonschema.Schema{ + Type: "string", + Description: "Duration string (e.g. \"120s\", \"5m\").", + } + case reflect.TypeOf(rig.SSH{}): + return sshSchema() + case reflect.TypeOf(rig.WinRM{}): + return winrmSchema() + case reflect.TypeOf(rig.OpenSSH{}): + return openSSHSchema() + case reflect.TypeOf(rig.Localhost{}): + return localhostSchema() + case reflect.TypeOf(cluster.Hooks{}): + return hooksSchema() + case reflect.TypeOf(cluster.Flags{}): + return &jsonschema.Schema{ + Type: "array", + Items: &jsonschema.Schema{Type: "string"}, + } + } + return nil +} + +func hooksSchema() *jsonschema.Schema { + stageProps := jsonschema.NewProperties() + prop(stageProps, "before", &jsonschema.Schema{ + Type: "array", + Items: &jsonschema.Schema{Type: "string"}, + Description: "Commands to run before the action.", + }) + prop(stageProps, "after", &jsonschema.Schema{ + Type: "array", + Items: &jsonschema.Schema{Type: "string"}, + Description: "Commands to run after the action.", + }) + stageSchema := &jsonschema.Schema{ + Type: "object", + Properties: stageProps, + } + + props := jsonschema.NewProperties() + for _, action := range []string{"connect", "apply", "upgrade", "install", "backup", "reset"} { + prop(props, action, stageSchema) + } + return &jsonschema.Schema{ + Type: "object", + Description: "Hook commands to run on the host during k0sctl operations.", + Properties: props, + } +} + +// injectDefaults walks the schema definitions and copies `default` values from +// Go struct tags (already parsed by the reflector into Extras) into the Default +// field. +func injectDefaults(schema *jsonschema.Schema) { + if schema.Definitions != nil { + for _, def := range schema.Definitions { + injectDefaultsInSchema(def) + } + } + injectDefaultsInSchema(schema) +} + +func injectDefaultsInSchema(s *jsonschema.Schema) { + if s == nil { + return + } + if s.Properties != nil { + for pair := s.Properties.Oldest(); pair != nil; pair = pair.Next() { + injectDefaultsInSchema(pair.Value) + } + } + if s.Items != nil { + injectDefaultsInSchema(s.Items) + } +} diff --git a/internal/schemagen/markdown.go b/internal/schemagen/markdown.go new file mode 100644 index 000000000..7a4202b20 --- /dev/null +++ b/internal/schemagen/markdown.go @@ -0,0 +1,133 @@ +package main + +import ( + _ "embed" + "fmt" + "slices" + "strings" + + "github.com/invopop/jsonschema" +) + +//go:embed preamble.md +var preambleMD string + +//go:embed appendix.md +var appendixMD string + +func renderMarkdown(schema *jsonschema.Schema) string { + var b strings.Builder + + b.WriteString(preambleMD) + + // Auto-generated field reference. + defs := schema.Definitions + root := resolveRef(schema, defs) + if root.Properties != nil { + renderProperties(&b, root, defs, "", root.Required) + } + + b.WriteString(appendixMD) + + return b.String() +} + +func resolveRef(s *jsonschema.Schema, defs jsonschema.Definitions) *jsonschema.Schema { + if s == nil { + return nil + } + if s.Ref != "" { + name := strings.TrimPrefix(s.Ref, "#/$defs/") + if d, ok := defs[name]; ok { + return d + } + } + return s +} + +func schemaType(s *jsonschema.Schema, defs jsonschema.Definitions) string { + s = resolveRef(s, defs) + if s == nil { + return "any" + } + if s.Type != "" { + if s.Type == "array" && s.Items != nil { + inner := resolveRef(s.Items, defs) + if inner != nil && inner.Type != "" { + return inner.Type + "[]" + } + return "array" + } + return s.Type + } + return "any" +} + +func renderProperties(b *strings.Builder, schema *jsonschema.Schema, defs jsonschema.Definitions, prefix string, parentRequired []string) { + if schema.Properties == nil { + return + } + + // Emit a heading for each section. + switch prefix { + case "": + b.WriteString("## Configuration Fields\n\n") + default: + heading := strings.TrimSuffix(prefix, ".") + level := strings.Count(heading, ".") + 2 + if level > 4 { + level = 4 + } + fmt.Fprintf(b, "%s `%s`\n\n", strings.Repeat("#", level), heading) + } + + for pair := schema.Properties.Oldest(); pair != nil; pair = pair.Next() { + name := pair.Key + raw := pair.Value + fieldSchema := resolveRef(raw, defs) + fullPath := prefix + name + + required := slices.Contains(parentRequired, name) + typ := schemaType(raw, defs) + + reqStr := "optional" + if required && fieldSchema.Default == nil { + reqStr = "required" + } + + defStr := "" + if fieldSchema.Default != nil { + defStr = fmt.Sprintf(" (default: `%v`)", fieldSchema.Default) + } + + desc := raw.Description + if desc == "" { + desc = fieldSchema.Description + } + + fmt.Fprintf(b, "**`%s`** <%s> (%s)%s", fullPath, typ, reqStr, defStr) + if desc != "" { + b.WriteString(" — ") + b.WriteString(desc) + } + b.WriteString("\n\n") + } + + // Recurse into object children. + for pair := schema.Properties.Oldest(); pair != nil; pair = pair.Next() { + name := pair.Key + fieldSchema := resolveRef(pair.Value, defs) + fullPath := prefix + name + + if fieldSchema.Type == "object" && fieldSchema.Properties != nil { + renderProperties(b, fieldSchema, defs, fullPath+".", fieldSchema.Required) + } + + if fieldSchema.Type == "array" && fieldSchema.Items != nil { + itemSchema := resolveRef(fieldSchema.Items, defs) + if itemSchema != nil && itemSchema.Type == "object" && itemSchema.Properties != nil { + renderProperties(b, itemSchema, defs, fullPath+"[*].", itemSchema.Required) + } + } + } +} diff --git a/internal/schemagen/preamble.md b/internal/schemagen/preamble.md new file mode 100644 index 000000000..15022d4b4 --- /dev/null +++ b/internal/schemagen/preamble.md @@ -0,0 +1,43 @@ +# k0sctl Configuration Reference + +> This document is auto-generated from the Go struct definitions. To regenerate, run `make docs`. + +The configuration file is in YAML format and loosely resembles the syntax used in Kubernetes. +YAML anchors and aliases can be used. + +Use `k0sctl init` to generate a skeleton configuration file. + +## Example + +```yaml +apiVersion: k0sctl.k0sproject.io/v1beta1 +kind: Cluster +metadata: + name: my-k0s-cluster +spec: + hosts: + - role: controller + ssh: + address: 10.0.0.1 + user: root + keyPath: ~/.ssh/id_rsa + - role: worker + ssh: + address: 10.0.0.2 + user: root + keyPath: ~/.ssh/id_rsa + k0s: + version: 1.32.2+k0s.0 + options: + wait: + enabled: true + drain: + enabled: true + evictTaint: + enabled: false + concurrency: + limit: 30 + workerDisruptionPercent: 10 + uploads: 5 +``` + diff --git a/internal/schemagen/rig_schemas.go b/internal/schemagen/rig_schemas.go new file mode 100644 index 000000000..64ddf6e32 --- /dev/null +++ b/internal/schemagen/rig_schemas.go @@ -0,0 +1,183 @@ +package main + +import ( + orderedmap "github.com/wk8/go-ordered-map/v2" + + "github.com/invopop/jsonschema" +) + +// prop is a helper to add a property to an ordered map. +func prop(m *orderedmap.OrderedMap[string, *jsonschema.Schema], key string, s *jsonschema.Schema) { + m.Set(key, s) +} + +func sshBastionSchema() *jsonschema.Schema { + props := jsonschema.NewProperties() + prop(props, "address", &jsonschema.Schema{ + Type: "string", + Description: "IP address or hostname of the bastion host.", + }) + prop(props, "user", &jsonschema.Schema{ + Type: "string", + Description: "SSH user for the bastion host.", + Default: "root", + }) + prop(props, "port", &jsonschema.Schema{ + Type: "integer", + Description: "SSH port on the bastion host.", + Default: 22, + }) + prop(props, "keyPath", &jsonschema.Schema{ + Type: "string", + Description: "Path to an SSH private key for the bastion host.", + }) + return &jsonschema.Schema{ + Type: "object", + Description: "SSH bastion (jump host) configuration.", + Properties: props, + Required: []string{"address"}, + } +} + +func sshSchema() *jsonschema.Schema { + props := jsonschema.NewProperties() + prop(props, "address", &jsonschema.Schema{ + Type: "string", + Description: "IP address or hostname of the host.", + }) + prop(props, "user", &jsonschema.Schema{ + Type: "string", + Description: "Username to log in as.", + Default: "root", + }) + prop(props, "port", &jsonschema.Schema{ + Type: "integer", + Description: "TCP port of the SSH service on the host.", + Default: 22, + }) + prop(props, "keyPath", &jsonschema.Schema{ + Type: "string", + Description: "Path to an SSH private key file. If a public key is used, ssh-agent is required. When left empty, the default value will first be looked for from the SSH configuration IdentityFile parameter.", + }) + prop(props, "bastion", sshBastionSchema()) + return &jsonschema.Schema{ + Type: "object", + Description: "SSH connection options.", + Properties: props, + Required: []string{"address"}, + } +} + +func winrmSchema() *jsonschema.Schema { + props := jsonschema.NewProperties() + prop(props, "address", &jsonschema.Schema{ + Type: "string", + Description: "IP address or hostname of the host.", + }) + prop(props, "user", &jsonschema.Schema{ + Type: "string", + Description: "WinRM user name. The user must have administrative privileges.", + Default: "Administrator", + }) + prop(props, "port", &jsonschema.Schema{ + Type: "integer", + Description: "TCP port for the WinRM endpoint. When useHTTPS is true, the default port automatically switches to 5986.", + Default: 5985, + }) + prop(props, "password", &jsonschema.Schema{ + Type: "string", + Description: "Password for the WinRM user. Required unless certificate-based authentication is configured.", + }) + prop(props, "useHTTPS", &jsonschema.Schema{ + Type: "boolean", + Description: "Enable HTTPS for WinRM. When enabled, set caCertPath (and optionally certPath/keyPath) to verify the remote endpoint.", + Default: false, + }) + prop(props, "insecure", &jsonschema.Schema{ + Type: "boolean", + Description: "Skip TLS certificate verification when connecting over HTTPS.", + Default: false, + }) + prop(props, "useNTLM", &jsonschema.Schema{ + Type: "boolean", + Description: "Use NTLM authentication instead of basic authentication.", + Default: false, + }) + prop(props, "caCertPath", &jsonschema.Schema{ + Type: "string", + Description: "Path to a CA bundle used to validate the WinRM server certificate.", + }) + prop(props, "certPath", &jsonschema.Schema{ + Type: "string", + Description: "Client certificate for mutual TLS authentication.", + }) + prop(props, "keyPath", &jsonschema.Schema{ + Type: "string", + Description: "Private key that matches certPath.", + }) + prop(props, "tlsServerName", &jsonschema.Schema{ + Type: "string", + Description: "Override the TLS server name used during certificate verification.", + }) + prop(props, "bastion", sshBastionSchema()) + return &jsonschema.Schema{ + Type: "object", + Description: "WinRM connection options for Windows worker nodes.", + Properties: props, + Required: []string{"address"}, + } +} + +func openSSHSchema() *jsonschema.Schema { + props := jsonschema.NewProperties() + prop(props, "address", &jsonschema.Schema{ + Type: "string", + Description: "IP address, hostname, or ssh config host alias of the host.", + }) + prop(props, "user", &jsonschema.Schema{ + Type: "string", + Description: "Username to connect as.", + }) + prop(props, "port", &jsonschema.Schema{ + Type: "integer", + Description: "Remote SSH port.", + }) + prop(props, "keyPath", &jsonschema.Schema{ + Type: "string", + Description: "Path to an SSH private key.", + }) + prop(props, "configPath", &jsonschema.Schema{ + Type: "string", + Description: "Path to ssh config. Defaults to ~/.ssh/config with fallback to /etc/ssh/ssh_config.", + }) + prop(props, "options", &jsonschema.Schema{ + Type: "object", + Description: "Additional options as key/value pairs passed to the ssh client as -o flags.", + AdditionalProperties: jsonschema.FalseSchema, + }) + prop(props, "disableMultiplexing", &jsonschema.Schema{ + Type: "boolean", + Description: "Disable SSH connection multiplexing. When true, every remote command requires reconnecting to the host.", + }) + return &jsonschema.Schema{ + Type: "object", + Description: "OpenSSH client connection options. Uses the system's openssh client for connections.", + Properties: props, + Required: []string{"address"}, + } +} + +func localhostSchema() *jsonschema.Schema { + props := jsonschema.NewProperties() + prop(props, "enabled", &jsonschema.Schema{ + Type: "boolean", + Description: "Must be set to true to enable the localhost connection.", + Default: true, + }) + return &jsonschema.Schema{ + Type: "object", + Description: "Localhost connection options. Can be used to use the local host running k0sctl as a node in the cluster.", + Properties: props, + Required: []string{"enabled"}, + } +} diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go index a46de15cd..05219b40b 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go @@ -16,8 +16,10 @@ const APIVersion = "k0sctl.k0sproject.io/v1beta1" // ClusterMetadata defines cluster metadata type ClusterMetadata struct { - Name string `yaml:"name" validate:"required" default:"k0s-cluster"` - User string `yaml:"user" default:"admin"` + // Name of the cluster. + Name string `yaml:"name" validate:"required" default:"k0s-cluster" jsonschema:"default=k0s-cluster"` + // Kubernetes admin user name. + User string `yaml:"user" default:"admin" jsonschema:"default=admin"` Kubeconfig string `yaml:"-"` EtcdMembers []string `yaml:"-"` Manifests map[string][]byte `yaml:"-"` @@ -25,10 +27,14 @@ type ClusterMetadata struct { // Cluster describes launchpad.yaml configuration type Cluster struct { - APIVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Metadata *ClusterMetadata `yaml:"metadata"` - Spec *cluster.Spec `yaml:"spec"` + // Configuration file syntax version. Must be k0sctl.k0sproject.io/v1beta1. + APIVersion string `yaml:"apiVersion" jsonschema:"required"` + // Object kind. Must be Cluster. + Kind string `yaml:"kind" jsonschema:"required"` + // Information that can be used to uniquely identify the object. + Metadata *ClusterMetadata `yaml:"metadata,omitempty"` + // Cluster specification. + Spec *cluster.Spec `yaml:"spec" jsonschema:"required"` Origin string `yaml:"-"` } diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go index 4ce630f9d..af07bd8bc 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go @@ -27,23 +27,59 @@ var K0sForceFlagSince = version.MustParse("v1.27.4+k0s.0") type Host struct { rig.Connection `yaml:",inline"` - Role string `yaml:"role"` + // Role of the host in the cluster. One of: + // - controller — a controller-only node + // - controller+worker — a controller that also runs workloads + // - single — a single-node cluster; the configuration may only contain one host with this role + // - worker — a worker node + Role string `yaml:"role" jsonschema:"required,enum=controller,enum=controller+worker,enum=single,enum=worker"` + // When true, k0sctl will remove the node from Kubernetes and reset k0s on the host. Reset bool `yaml:"reset,omitempty"` + // Override the private network interface selected by host fact gathering. Useful when + // fact gathering picks the wrong interface for intra-cluster communication. PrivateInterface string `yaml:"privateInterface,omitempty"` + // Override the private IP address selected by host fact gathering. Useful when + // fact gathering picks the wrong address for intra-cluster communication. PrivateAddress string `yaml:"privateAddress,omitempty"` - DataDir string `yaml:"dataDir,omitempty"` + // Override the k0s data directory on the host. + DataDir string `yaml:"dataDir,omitempty" jsonschema:"default=/var/lib/k0s"` + // Override the kubelet root directory on the host. KubeletRootDir string `yaml:"kubeletRootDir,omitempty"` + // Environment variables to set in the k0s service environment on the host. Environment map[string]string `yaml:"environment,flow,omitempty"` + // When true, the k0s binary is downloaded on the local machine and uploaded to the + // target host. When false (the default), the binary is downloaded directly on the host. UploadBinary bool `yaml:"uploadBinary,omitempty"` + // When true, k0sctl reuses the k0s binary that already exists on the host without + // downloading or uploading anything. Upgrades for this host are skipped. + // Cannot be combined with uploadBinary, k0sBinaryPath, or k0sDownloadURL. UseExistingK0s bool `yaml:"useExistingK0s,omitempty"` + // Path to a local k0s binary to upload to the host. Useful for testing a custom + // or development build of k0s without publishing a release. K0sBinaryPath string `yaml:"k0sBinaryPath,omitempty"` + // Path on the host where the k0s binary will be installed. K0sInstallPath string `yaml:"k0sInstallPath,omitempty"` + // URL to download the k0s binary from instead of the default k0s GitHub releases. + // Supports %-prefixed tokens: %v (version), %p (arch), %x (.exe on Windows). K0sDownloadURLOverride string `yaml:"k0sDownloadURL,omitempty"` + // Extra flags passed verbatim to the k0s install command on the host. + // See k0s install --help for available options. InstallFlags Flags `yaml:"installFlags,omitempty"` + // Files to upload to the host before k0s is configured. Supports local paths, + // URLs, and glob patterns. See the file upload documentation for details. Files []*UploadFile `yaml:"files,omitempty"` + // Override OS distribution auto-detection. By default k0sctl reads /etc/os-release. + // Set this when the release file does not reflect the true distribution, e.g. set + // "debian" for a Debian-based image that reports a different OS ID. OSIDOverride string `yaml:"os,omitempty"` + // Override the hostname reported by the OS. When not set, the OS hostname is used. HostnameOverride string `yaml:"hostname,omitempty"` + // When true and used with the controller+worker role, disables the default + // node-role.kubernetes.io/master:NoSchedule taint so that regular workloads + // can be scheduled on the node without requiring a toleration. NoTaints bool `yaml:"noTaints,omitempty"` + // Commands to run on the host at specific points during k0sctl operations. + // See the hooks documentation for available stages and timing details. Hooks Hooks `yaml:"hooks,omitempty"` UploadBinaryPath string `yaml:"-"` diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/k0s.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/k0s.go index 02d14665c..511d831ad 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/k0s.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/k0s.go @@ -30,9 +30,22 @@ var ( // K0s holds configuration for bootstraping a k0s cluster type K0s struct { + // Version of k0s to deploy. When omitted, k0sctl selects the latest stable release + // (or the version already running on the cluster if one exists). Version *version.Version `yaml:"version,omitempty"` - VersionChannel string `yaml:"versionChannel,omitempty"` + // Version channel used when auto-discovering the k0s version. Set to "latest" to + // allow k0sctl to select pre-release versions. Has no effect when version is set. + VersionChannel string `yaml:"versionChannel,omitempty" jsonschema:"default=stable,enum=stable,enum=latest"` + // Enable k0s dynamic configuration. When true, k0sctl only pushes the cluster-wide + // configuration on first-time initialisation; subsequent applies leave it unchanged. + // Use k0sctl config edit or k0s config edit to manage it afterwards. + // This flag is also auto-enabled when any controller has --enable-dynamic-config in + // installFlags or in its running k0s arguments. DynamicConfig bool `yaml:"dynamicConfig,omitempty" default:"false"` + // Embedded k0s cluster configuration. See https://docs.k0sproject.io/stable/configuration/ + // for field reference. When omitted, the output of k0s config create is used. + // The k0s config can also be placed as a separate YAML document in the same file, + // using apiVersion: k0s.k0sproject.io/v1beta1 and kind: ClusterConfig. Config dig.Mapping `yaml:"config,omitempty"` Metadata K0sMetadata `yaml:"-"` } diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go index 46205a187..cb608489f 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go @@ -12,9 +12,13 @@ import ( // Options for cluster operations. type Options struct { + // Controls wait behavior for cluster operations. Wait WaitOption `yaml:"wait"` + // Controls drain behavior for cluster operations. Drain DrainOption `yaml:"drain"` + // Controls how many hosts are operated on at once. Concurrency ConcurrencyOption `yaml:"concurrency"` + // Controls whether a taint is applied to nodes before disruptive operations. EvictTaint EvictTaintOption `yaml:"evictTaint"` } @@ -37,19 +41,34 @@ func (o *Options) UnmarshalYAML(unmarshal func(any) error) error { // WaitOption controls the wait behavior for cluster operations. type WaitOption struct { - Enabled *bool `yaml:"enabled" default:"true"` + // When false, k0sctl will not wait for k0s to become ready after restarting the + // service. Equivalent to passing --no-wait on the command line. + Enabled *bool `yaml:"enabled" default:"true" jsonschema:"default=true"` } // DrainOption controls the drain behavior for cluster operations. type DrainOption struct { - Enabled *bool `yaml:"enabled" default:"true"` - GracePeriod time.Duration `yaml:"gracePeriod" default:"120s"` - Timeout time.Duration `yaml:"timeout" default:"300s"` - Force *bool `yaml:"force" default:"true"` - IgnoreDaemonSets *bool `yaml:"ignoreDaemonSets" default:"true"` - DeleteEmptyDirData *bool `yaml:"deleteEmptyDirData" default:"true"` + // When false, k0sctl skips draining nodes before disruptive operations such as + // upgrade or reset. Equivalent to passing --no-drain on the command line. + Enabled *bool `yaml:"enabled" default:"true" jsonschema:"default=true"` + // How long to wait for pods to be evicted from the node before proceeding. + GracePeriod time.Duration `yaml:"gracePeriod" default:"120s" jsonschema:"default=120s"` + // How long to wait for the entire drain operation to complete before timing out. + Timeout time.Duration `yaml:"timeout" default:"300s" jsonschema:"default=300s"` + // Pass --force to kubectl drain, allowing pods without a replication controller + // to be evicted. + Force *bool `yaml:"force" default:"true" jsonschema:"default=true"` + // Pass --ignore-daemonsets to kubectl drain so that DaemonSet-managed pods are + // not considered when draining. + IgnoreDaemonSets *bool `yaml:"ignoreDaemonSets" default:"true" jsonschema:"default=true"` + // Pass --delete-emptydir-data to kubectl drain, allowing pods that use emptyDir + // volumes (whose data will be lost) to be evicted. + DeleteEmptyDirData *bool `yaml:"deleteEmptyDirData" default:"true" jsonschema:"default=true"` + // Label selector passed to kubectl drain to restrict which pods are considered. PodSelector string `yaml:"podSelector" default:""` - SkipWaitForDeleteTimeout time.Duration `yaml:"skipWaitForDeleteTimeout" default:"0s"` + // If a pod's DeletionTimestamp is older than this duration, skip waiting for it. + // Must be greater than 0s to take effect. + SkipWaitForDeleteTimeout time.Duration `yaml:"skipWaitForDeleteTimeout" default:"0s" jsonschema:"default=0s"` } // EnabledValue returns the effective enabled flag, defaulting to true when unset. @@ -123,9 +142,16 @@ func (d *DrainOption) UnmarshalYAML(unmarshal func(any) error) error { // ConcurrencyOption controls how many hosts are operated on at once. type ConcurrencyOption struct { - Limit int `yaml:"limit" default:"30"` // Max number of hosts to operate on at once - WorkerDisruptionPercent int `yaml:"workerDisruptionPercent" default:"10"` // Max percentage of hosts to disrupt at once - Uploads int `yaml:"uploads" default:"5"` // Max concurrent file uploads + // Maximum number of hosts to configure concurrently. Equivalent to --concurrency + // on the command line. Set to 0 for unlimited. + Limit int `yaml:"limit" default:"30" jsonschema:"default=30"` + // Maximum percentage of worker nodes that may be disrupted simultaneously during + // operations such as upgrade. Value must be between 0 and 100. This ensures a + // minimum number of workers remain available during rolling operations. + WorkerDisruptionPercent int `yaml:"workerDisruptionPercent" default:"10" jsonschema:"default=10"` + // Maximum number of file uploads to perform concurrently. Equivalent to + // --concurrent-uploads on the command line. + Uploads int `yaml:"uploads" default:"5" jsonschema:"default=5"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for ConcurrencyOption. @@ -148,10 +174,17 @@ func (c *ConcurrencyOption) UnmarshalYAML(unmarshal func(any) error) error { // EvictTaintOption controls whether and how a taint is applied to nodes // before service-affecting operations like upgrade or reset. type EvictTaintOption struct { - Enabled bool `yaml:"enabled" default:"false"` - Taint string `yaml:"taint" default:"k0sctl.k0sproject.io/evict=true"` - Effect string `yaml:"effect" default:"NoExecute"` - ControllerWorkers bool `yaml:"controllerWorkers" default:"false"` + // When true, k0sctl applies a taint to nodes before service-affecting operations + // (upgrade, reset) to signal workloads to evacuate before the node is disrupted. + // Can also be enabled at runtime with --evict-taint on the command line. + Enabled bool `yaml:"enabled" default:"false" jsonschema:"default=false"` + // Taint to apply when enabled is true. Must be in the format key=value. + Taint string `yaml:"taint" default:"k0sctl.k0sproject.io/evict=true" jsonschema:"default=k0sctl.k0sproject.io/evict=true"` + // Effect of the taint. Must be NoExecute, NoSchedule, or PreferNoSchedule. + Effect string `yaml:"effect" default:"NoExecute" jsonschema:"default=NoExecute,enum=NoExecute,enum=NoSchedule,enum=PreferNoSchedule"` + // When true, the taint is also applied to controller+worker nodes. By default + // only pure worker nodes are tainted. + ControllerWorkers bool `yaml:"controllerWorkers" default:"false" jsonschema:"default=false"` } // String returns a string representation of the EvictTaintOption (:) diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go index bb296d465..ca34240f2 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go @@ -11,8 +11,11 @@ import ( // Spec defines cluster config spec section type Spec struct { - Hosts Hosts `yaml:"hosts,omitempty"` + // A list of cluster hosts. + Hosts Hosts `yaml:"hosts,omitempty" jsonschema:"required"` + // Settings related to the k0s cluster. K0s *K0s `yaml:"k0s,omitempty"` + // Options for cluster operations. Options Options `yaml:"options"` k0sLeader *Host diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go index 1af95a920..cc75efc02 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go @@ -20,14 +20,30 @@ type LocalFile struct { // UploadFile describes a file to be uploaded for the host type UploadFile struct { + // Optional label for this upload entry, used only in log output. Name string `yaml:"name,omitempty"` + // Source file path, URL, or glob pattern. Required when data is not set. + // Glob patterns follow the doublestar syntax. URL sources are downloaded directly + // on the target host. Supports %v, %p, %x token expansion. Source string `yaml:"src,omitempty"` + // Inline file content to write to the destination. Required when src is not set. Data string `yaml:"data,omitempty"` + // Destination directory on the host. k0sctl creates the full path if it does + // not exist. Defaults to the remote user's home directory. DestinationDir string `yaml:"dstDir,omitempty"` + // Destination filename on the host. Only valid for single-file uploads. + // Defaults to the source file's basename. DestinationFile string `yaml:"dst,omitempty"` + // Permission mode for the uploaded file(s), e.g. 0644. Defaults to the local + // file's permission mode. PermMode any `yaml:"perm,omitempty"` - DirPermMode any `yaml:"dirPerm,omitempty"` + // Permission mode for directories created by k0sctl during upload. + DirPermMode any `yaml:"dirPerm,omitempty" jsonschema:"default=0755"` + // Owner user name for the uploaded file(s) and created directories. Must already + // exist on the host. User string `yaml:"user,omitempty"` + // Owner group name for the uploaded file(s) and created directories. Must already + // exist on the host. Group string `yaml:"group,omitempty"` PermString string `yaml:"-"` DirPermString string `yaml:"-"` From bfdd5da297783cc6c4e8eaac0027c6d23e13bc52 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Thu, 19 Feb 2026 13:35:11 +0200 Subject: [PATCH 2/3] Add "k0sctl validate" Signed-off-by: Kimmo Lehto --- README.md | 10 +++++ cmd/apply.go | 10 ----- cmd/root.go | 1 + cmd/validate.go | 24 +++++++++++ docs/configuration.md | 12 +++--- docs/k0sctl-schema.json | 41 ++----------------- internal/schemagen/main.go | 5 ++- internal/schemagen/rig_schemas.go | 8 ++-- .../k0sctl.k0sproject.io/v1beta1/cluster.go | 14 +++---- .../v1beta1/cluster/options.go | 34 +++++++-------- .../v1beta1/cluster/spec.go | 6 +-- 11 files changed, 80 insertions(+), 85 deletions(-) create mode 100644 cmd/validate.go diff --git a/README.md b/README.md index a417efbc7..86b16e7cc 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ For repository layout, development, and testing guidelines (including notes for The main function of k0sctl is the `k0sctl apply` subcommand. Provided a configuration file describing the desired cluster state, k0sctl will connect to the listed hosts, determines the current state of the hosts and configures them as needed to form a k0s cluster. +Use [`k0sctl validate`](#k0sctl-validate) to verify the configuration file before running apply. + The default location for the configuration file is `k0sctl.yaml` in the current working directory. To load a configuration from a different location, use: ```sh @@ -130,6 +132,14 @@ k0sctl apply --config path/to/k0sctl.yaml If the configuration cluster version `spec.k0s.version` is greater than the version detected on the cluster, a cluster upgrade will be performed. If the configuration lists hosts that are not part of the cluster, they will be configured to run k0s and will be joined to the cluster. +### `k0sctl validate` + +The `k0sctl validate` subcommand loads and validates a configuration file without contacting any hosts. This is useful in CI pipelines or when iterating on the configuration locally. + +```sh +k0sctl validate --config path/to/k0sctl.yaml +``` + ### `k0sctl init` Generate a configuration template. Use `--k0s` to include an example `spec.k0s.config` k0s configuration block. You can also supply a list of host addresses via arguments or stdin. diff --git a/cmd/apply.go b/cmd/apply.go index 5c2ffc3e7..096750c56 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -54,10 +54,6 @@ var applyCommand = &cli.Command{ Usage: "Set kubernetes cluster name", DefaultText: "k0s-cluster", }, - &cli.BoolFlag{ - Name: "validate", - Usage: "Validate the configuration and exit without applying", - }, &cli.BoolFlag{ Name: "disable-downgrade-check", Usage: "Skip downgrade check", @@ -79,7 +75,6 @@ var applyCommand = &cli.Command{ After: actions(cancelTimeout), Action: func(ctx *cli.Context) error { var kubeconfigOut io.Writer - if kc := ctx.String("kubeconfig-out"); kc != "" { out, err := os.OpenFile(kc, os.O_CREATE|os.O_WRONLY, 0o600) if err != nil { @@ -94,11 +89,6 @@ var applyCommand = &cli.Command{ kubeconfigOut = out } - if ctx.Bool("validate") { - fmt.Fprintln(ctx.App.Writer, "Configuration is valid") - return nil - } - manager, ok := ctx.Context.Value(ctxManagerKey{}).(*phase.Manager) if !ok { return fmt.Errorf("failed to retrieve manager from context") diff --git a/cmd/root.go b/cmd/root.go index 8676c4e4c..6e9c19979 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,6 +48,7 @@ func NewK0sctl(in io.Reader, out, errOut io.Writer) *cli.App { Commands: []*cli.Command{ versionCommand, applyCommand, + validateCommand, docsCommand, kubeconfigCommand, initCommand, diff --git a/cmd/validate.go b/cmd/validate.go new file mode 100644 index 000000000..96c2b9af1 --- /dev/null +++ b/cmd/validate.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var validateCommand = &cli.Command{ + Name: "validate", + Usage: "Validate a k0sctl configuration", + Flags: []cli.Flag{ + configFlag, + }, + Before: actions(initLogging, initConfig, displayLogo, displayCopyright, warnOldCache), + After: actions(cancelTimeout), + Action: func(ctx *cli.Context) error { + if _, err := readConfig(ctx); err != nil { + return fmt.Errorf("configuration validation failed: %w", err) + } + fmt.Fprintln(ctx.App.Writer, "Configuration is valid") + return nil + }, +} diff --git a/docs/configuration.md b/docs/configuration.md index dd0920bbe..3d32b142a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -63,7 +63,7 @@ spec: **`spec.k0s`** (optional) — Settings related to the k0s cluster. -**`spec.options`** (required) — Options for cluster operations. +**`spec.options`** (optional) — Options for cluster operations. ### `spec.hosts[*]` @@ -305,13 +305,13 @@ using apiVersion: k0s.k0sproject.io/v1beta1 and kind: ClusterConfig. ### `spec.options` -**`spec.options.wait`** (required) — Controls wait behavior for cluster operations. +**`spec.options.wait`** (optional) — Controls wait behavior for cluster operations. -**`spec.options.drain`** (required) — Controls drain behavior for cluster operations. +**`spec.options.drain`** (optional) — Controls drain behavior for cluster operations. -**`spec.options.concurrency`** (required) — Controls how many hosts are operated on at once. +**`spec.options.concurrency`** (optional) — Controls how many hosts are operated on at once. -**`spec.options.evictTaint`** (required) — Controls whether a taint is applied to nodes before disruptive operations. +**`spec.options.evictTaint`** (optional) — Controls whether a taint is applied to nodes before disruptive operations. #### `spec.options.wait` @@ -336,7 +336,7 @@ not considered when draining. **`spec.options.drain.deleteEmptyDirData`** (optional) (default: `true`) — Pass --delete-emptydir-data to kubectl drain, allowing pods that use emptyDir volumes (whose data will be lost) to be evicted. -**`spec.options.drain.podSelector`** (required) — Label selector passed to kubectl drain to restrict which pods are considered. +**`spec.options.drain.podSelector`** (optional) — Label selector passed to kubectl drain to restrict which pods are considered. **`spec.options.drain.skipWaitForDeleteTimeout`** (optional) (default: `0s`) — If a pod's DeletionTimestamp is older than this duration, skip waiting for it. Must be greater than 0s to take effect. diff --git a/docs/k0sctl-schema.json b/docs/k0sctl-schema.json index 928e020a9..6630fd0fc 100644 --- a/docs/k0sctl-schema.json +++ b/docs/k0sctl-schema.json @@ -46,10 +46,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "name", - "user" - ], "description": "ClusterMetadata defines cluster metadata" }, "ConcurrencyOption": { @@ -72,11 +68,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "limit", - "workerDisruptionPercent", - "uploads" - ], "description": "ConcurrencyOption controls how many hosts are operated on at once." }, "DrainOption": { @@ -123,16 +114,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "enabled", - "gracePeriod", - "timeout", - "force", - "ignoreDaemonSets", - "deleteEmptyDirData", - "podSelector", - "skipWaitForDeleteTimeout" - ], "description": "DrainOption controls the drain behavior for cluster operations." }, "EvictTaintOption": { @@ -165,12 +146,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "enabled", - "taint", - "effect", - "controllerWorkers" - ], "description": "EvictTaintOption controls whether and how a taint is applied to nodes before service-affecting operations like upgrade or reset." }, "Host": { @@ -348,7 +323,9 @@ "description": "Path to ssh config. Defaults to ~/.ssh/config with fallback to /etc/ssh/ssh_config." }, "options": { - "additionalProperties": false, + "additionalProperties": { + "type": "string" + }, "type": "object", "description": "Additional options as key/value pairs passed to the ssh client as -o flags." }, @@ -630,12 +607,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "wait", - "drain", - "concurrency", - "evictTaint" - ], "description": "Options for cluster operations." }, "Spec": { @@ -656,8 +627,7 @@ "additionalProperties": false, "type": "object", "required": [ - "hosts", - "options" + "hosts" ], "description": "Spec defines cluster config spec section" }, @@ -712,9 +682,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "enabled" - ], "description": "WaitOption controls the wait behavior for cluster operations." } }, diff --git a/internal/schemagen/main.go b/internal/schemagen/main.go index 3e7b0b5af..b1a993a6a 100644 --- a/internal/schemagen/main.go +++ b/internal/schemagen/main.go @@ -21,8 +21,9 @@ import ( func main() { r := &jsonschema.Reflector{ - FieldNameTag: "yaml", - Mapper: mapCustomTypes, + FieldNameTag: "yaml", + Mapper: mapCustomTypes, + RequiredFromJSONSchemaTags: true, } // base must be the module root path; AddGoComments does path.Join(base, walkPath) diff --git a/internal/schemagen/rig_schemas.go b/internal/schemagen/rig_schemas.go index 64ddf6e32..a3e9b2af0 100644 --- a/internal/schemagen/rig_schemas.go +++ b/internal/schemagen/rig_schemas.go @@ -151,9 +151,11 @@ func openSSHSchema() *jsonschema.Schema { Description: "Path to ssh config. Defaults to ~/.ssh/config with fallback to /etc/ssh/ssh_config.", }) prop(props, "options", &jsonschema.Schema{ - Type: "object", - Description: "Additional options as key/value pairs passed to the ssh client as -o flags.", - AdditionalProperties: jsonschema.FalseSchema, + Type: "object", + Description: "Additional options as key/value pairs passed to the ssh client as -o flags.", + AdditionalProperties: &jsonschema.Schema{ + Type: "string", + }, }) prop(props, "disableMultiplexing", &jsonschema.Schema{ Type: "boolean", diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go index 05219b40b..36cbd2b4b 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go @@ -17,9 +17,9 @@ const APIVersion = "k0sctl.k0sproject.io/v1beta1" // ClusterMetadata defines cluster metadata type ClusterMetadata struct { // Name of the cluster. - Name string `yaml:"name" validate:"required" default:"k0s-cluster" jsonschema:"default=k0s-cluster"` + Name string `yaml:"name" validate:"required" default:"k0s-cluster" jsonschema:"default=k0s-cluster"` // Kubernetes admin user name. - User string `yaml:"user" default:"admin" jsonschema:"default=admin"` + User string `yaml:"user,omitempty" default:"admin" jsonschema:"default=admin"` Kubeconfig string `yaml:"-"` EtcdMembers []string `yaml:"-"` Manifests map[string][]byte `yaml:"-"` @@ -28,14 +28,14 @@ type ClusterMetadata struct { // Cluster describes launchpad.yaml configuration type Cluster struct { // Configuration file syntax version. Must be k0sctl.k0sproject.io/v1beta1. - APIVersion string `yaml:"apiVersion" jsonschema:"required"` + APIVersion string `yaml:"apiVersion" jsonschema:"required"` // Object kind. Must be Cluster. - Kind string `yaml:"kind" jsonschema:"required"` + Kind string `yaml:"kind" jsonschema:"required"` // Information that can be used to uniquely identify the object. - Metadata *ClusterMetadata `yaml:"metadata,omitempty"` + Metadata *ClusterMetadata `yaml:"metadata,omitempty"` // Cluster specification. - Spec *cluster.Spec `yaml:"spec" jsonschema:"required"` - Origin string `yaml:"-"` + Spec *cluster.Spec `yaml:"spec" jsonschema:"required"` + Origin string `yaml:"-"` } // UnmarshalYAML sets in some sane defaults when unmarshaling the data from yaml diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go index cb608489f..c4bd6f866 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go @@ -13,13 +13,13 @@ import ( // Options for cluster operations. type Options struct { // Controls wait behavior for cluster operations. - Wait WaitOption `yaml:"wait"` + Wait WaitOption `yaml:"wait,omitempty"` // Controls drain behavior for cluster operations. - Drain DrainOption `yaml:"drain"` + Drain DrainOption `yaml:"drain,omitempty"` // Controls how many hosts are operated on at once. - Concurrency ConcurrencyOption `yaml:"concurrency"` + Concurrency ConcurrencyOption `yaml:"concurrency,omitempty"` // Controls whether a taint is applied to nodes before disruptive operations. - EvictTaint EvictTaintOption `yaml:"evictTaint"` + EvictTaint EvictTaintOption `yaml:"evictTaint,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for Options. @@ -50,22 +50,22 @@ type WaitOption struct { type DrainOption struct { // When false, k0sctl skips draining nodes before disruptive operations such as // upgrade or reset. Equivalent to passing --no-drain on the command line. - Enabled *bool `yaml:"enabled" default:"true" jsonschema:"default=true"` + Enabled *bool `yaml:"enabled" default:"true" jsonschema:"default=true"` // How long to wait for pods to be evicted from the node before proceeding. - GracePeriod time.Duration `yaml:"gracePeriod" default:"120s" jsonschema:"default=120s"` + GracePeriod time.Duration `yaml:"gracePeriod" default:"120s" jsonschema:"default=120s"` // How long to wait for the entire drain operation to complete before timing out. - Timeout time.Duration `yaml:"timeout" default:"300s" jsonschema:"default=300s"` + Timeout time.Duration `yaml:"timeout" default:"300s" jsonschema:"default=300s"` // Pass --force to kubectl drain, allowing pods without a replication controller // to be evicted. - Force *bool `yaml:"force" default:"true" jsonschema:"default=true"` + Force *bool `yaml:"force" default:"true" jsonschema:"default=true"` // Pass --ignore-daemonsets to kubectl drain so that DaemonSet-managed pods are // not considered when draining. - IgnoreDaemonSets *bool `yaml:"ignoreDaemonSets" default:"true" jsonschema:"default=true"` + IgnoreDaemonSets *bool `yaml:"ignoreDaemonSets" default:"true" jsonschema:"default=true"` // Pass --delete-emptydir-data to kubectl drain, allowing pods that use emptyDir // volumes (whose data will be lost) to be evicted. - DeleteEmptyDirData *bool `yaml:"deleteEmptyDirData" default:"true" jsonschema:"default=true"` + DeleteEmptyDirData *bool `yaml:"deleteEmptyDirData" default:"true" jsonschema:"default=true"` // Label selector passed to kubectl drain to restrict which pods are considered. - PodSelector string `yaml:"podSelector" default:""` + PodSelector string `yaml:"podSelector" default:""` // If a pod's DeletionTimestamp is older than this duration, skip waiting for it. // Must be greater than 0s to take effect. SkipWaitForDeleteTimeout time.Duration `yaml:"skipWaitForDeleteTimeout" default:"0s" jsonschema:"default=0s"` @@ -144,14 +144,14 @@ func (d *DrainOption) UnmarshalYAML(unmarshal func(any) error) error { type ConcurrencyOption struct { // Maximum number of hosts to configure concurrently. Equivalent to --concurrency // on the command line. Set to 0 for unlimited. - Limit int `yaml:"limit" default:"30" jsonschema:"default=30"` + Limit int `yaml:"limit" default:"30" jsonschema:"default=30"` // Maximum percentage of worker nodes that may be disrupted simultaneously during // operations such as upgrade. Value must be between 0 and 100. This ensures a // minimum number of workers remain available during rolling operations. WorkerDisruptionPercent int `yaml:"workerDisruptionPercent" default:"10" jsonschema:"default=10"` // Maximum number of file uploads to perform concurrently. Equivalent to // --concurrent-uploads on the command line. - Uploads int `yaml:"uploads" default:"5" jsonschema:"default=5"` + Uploads int `yaml:"uploads" default:"5" jsonschema:"default=5"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for ConcurrencyOption. @@ -177,14 +177,14 @@ type EvictTaintOption struct { // When true, k0sctl applies a taint to nodes before service-affecting operations // (upgrade, reset) to signal workloads to evacuate before the node is disrupted. // Can also be enabled at runtime with --evict-taint on the command line. - Enabled bool `yaml:"enabled" default:"false" jsonschema:"default=false"` + Enabled bool `yaml:"enabled" default:"false" jsonschema:"default=false"` // Taint to apply when enabled is true. Must be in the format key=value. - Taint string `yaml:"taint" default:"k0sctl.k0sproject.io/evict=true" jsonschema:"default=k0sctl.k0sproject.io/evict=true"` + Taint string `yaml:"taint" default:"k0sctl.k0sproject.io/evict=true" jsonschema:"default=k0sctl.k0sproject.io/evict=true"` // Effect of the taint. Must be NoExecute, NoSchedule, or PreferNoSchedule. - Effect string `yaml:"effect" default:"NoExecute" jsonschema:"default=NoExecute,enum=NoExecute,enum=NoSchedule,enum=PreferNoSchedule"` + Effect string `yaml:"effect" default:"NoExecute" jsonschema:"default=NoExecute,enum=NoExecute,enum=NoSchedule,enum=PreferNoSchedule"` // When true, the taint is also applied to controller+worker nodes. By default // only pure worker nodes are tainted. - ControllerWorkers bool `yaml:"controllerWorkers" default:"false" jsonschema:"default=false"` + ControllerWorkers bool `yaml:"controllerWorkers" default:"false" jsonschema:"default=false"` } // String returns a string representation of the EvictTaintOption (:) diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go index ca34240f2..e762c2b1c 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go @@ -12,11 +12,11 @@ import ( // Spec defines cluster config spec section type Spec struct { // A list of cluster hosts. - Hosts Hosts `yaml:"hosts,omitempty" jsonschema:"required"` + Hosts Hosts `yaml:"hosts,omitempty" jsonschema:"required"` // Settings related to the k0s cluster. - K0s *K0s `yaml:"k0s,omitempty"` + K0s *K0s `yaml:"k0s,omitempty"` // Options for cluster operations. - Options Options `yaml:"options"` + Options Options `yaml:"options,omitempty"` k0sLeader *Host } From 94786b70175090fcf57cdf12b7afc6b4c07af02a Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Thu, 19 Feb 2026 13:46:14 +0200 Subject: [PATCH 3/3] go.mod sync Signed-off-by: Kimmo Lehto --- go.mod | 2 +- go.sum | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5282bc015..626a88d36 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( require ( al.essio.dev/pkg/shellescape v1.6.0 github.com/carlmjohnson/versioninfo v0.22.5 + github.com/charmbracelet/glamour v0.10.0 github.com/go-playground/validator/v10 v10.30.1 github.com/invopop/jsonschema v0.13.0 github.com/jellydator/validation v1.2.0 @@ -56,7 +57,6 @@ require ( github.com/bodgit/windows v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/glamour v0.10.0 // indirect github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect diff --git a/go.sum b/go.sum index 1fafb8ff8..d598a4fcf 100644 --- a/go.sum +++ b/go.sum @@ -16,13 +16,19 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -47,6 +53,8 @@ github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2ll github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= @@ -110,6 +118,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= @@ -248,6 +258,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=