Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package k0sctl for openSUSE:Factory checked 
in at 2025-12-11 18:39:52
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/k0sctl (Old)
 and      /work/SRC/openSUSE:Factory/.k0sctl.new.1939 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "k0sctl"

Thu Dec 11 18:39:52 2025 rev:17 rq:1322121 version:0.27.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/k0sctl/k0sctl.changes    2025-10-29 
21:08:18.310782952 +0100
+++ /work/SRC/openSUSE:Factory/.k0sctl.new.1939/k0sctl.changes  2025-12-11 
18:41:44.870497814 +0100
@@ -1,0 +2,21 @@
+Thu Dec 11 06:48:33 UTC 2025 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.27.1:
+  * Bump go to v1.25.5 (#989)
+  * Bump golang.org/x/text from 0.31.0 to 0.32.0 (#988)
+  * Fix missing kubelet-dir from controller reset command (#987)
+  * Bump actions/checkout from 5 to 6 (#984)
+  * Fix URL in README docker example (#983)
+  * Set debug log level for config loading (#980)
+  * Bump golang.org/x/crypto from 0.42.0 to 0.45.0 (#982)
+  * Fix ineffective boolean options (#979)
+  * Bump k8s.io/client-go from 0.34.1 to 0.34.2 (#976)
+  * Bump golang.org/x/text from 0.30.0 to 0.31.0 (#975)
+  * Merge pull request #974 from
+    k0sproject/dependabot/github_actions/golangci/golangci-lint-action-9
+  * Improve config origin resolution cascade
+  * Remember config origin and use as base for relative paths
+  * Fix path file upload relative to config origin
+  * Bump actions/upload-artifact from 4 to 5 (#970)
+
+-------------------------------------------------------------------

Old:
----
  k0sctl-0.27.0.obscpio

New:
----
  k0sctl-0.27.1.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ k0sctl.spec ++++++
--- /var/tmp/diff_new_pack.UtFKfE/_old  2025-12-11 18:41:45.998545210 +0100
+++ /var/tmp/diff_new_pack.UtFKfE/_new  2025-12-11 18:41:46.002545378 +0100
@@ -18,7 +18,7 @@
 
 
 Name:           k0sctl
-Version:        0.27.0
+Version:        0.27.1
 Release:        0
 Summary:        A bootstrapping and management tool for k0s clusters
 License:        Apache-2.0
@@ -28,8 +28,8 @@
 Source1:        vendor.tar.gz
 BuildRequires:  bash-completion
 BuildRequires:  fish
-BuildRequires:  go >= 1.23.2
 BuildRequires:  zsh
+BuildRequires:  golang(API) >= 1.25
 
 %description
 k0sctl is a bootstrapping and management tool for k0s clusters.

++++++ _service ++++++
--- /var/tmp/diff_new_pack.UtFKfE/_old  2025-12-11 18:41:46.050547395 +0100
+++ /var/tmp/diff_new_pack.UtFKfE/_new  2025-12-11 18:41:46.054547562 +0100
@@ -2,7 +2,7 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://github.com/k0sproject/k0sctl.git</param>
     <param name="scm">git</param>
-    <param name="revision">v0.27.0</param>
+    <param name="revision">v0.27.1</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.UtFKfE/_old  2025-12-11 18:41:46.106549747 +0100
+++ /var/tmp/diff_new_pack.UtFKfE/_new  2025-12-11 18:41:46.110549916 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/k0sproject/k0sctl.git</param>
-              <param 
name="changesrevision">14ccbf5d395b90c683e8a4324a7b34e6b0bdb80e</param></service></servicedata>
+              <param 
name="changesrevision">c7ee91b2f1a36c447590993cbb133028882271df</param></service></servicedata>
 (No newline at EOF)
 

++++++ k0sctl-0.27.0.obscpio -> k0sctl-0.27.1.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/actionlint.yml 
new/k0sctl-0.27.1/.github/workflows/actionlint.yml
--- old/k0sctl-0.27.0/.github/workflows/actionlint.yml  2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/actionlint.yml  2025-12-09 
10:38:28.000000000 +0100
@@ -8,7 +8,7 @@
   actionlint:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: reviewdog/action-actionlint@v1
         with:
           fail_on_error: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/codeql-analysis.yml 
new/k0sctl-0.27.1/.github/workflows/codeql-analysis.yml
--- old/k0sctl-0.27.0/.github/workflows/codeql-analysis.yml     2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/codeql-analysis.yml     2025-12-09 
10:38:28.000000000 +0100
@@ -38,7 +38,7 @@
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v5
+      uses: actions/checkout@v6
 
     # Added as suggested in 
https://github.com/github/codeql-action/issues/1842 to bring in a newer go than 
exists
     # preinstalled on the runners
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/dco.yaml 
new/k0sctl-0.27.1/.github/workflows/dco.yaml
--- old/k0sctl-0.27.0/.github/workflows/dco.yaml        2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/dco.yaml        2025-12-09 
10:38:28.000000000 +0100
@@ -18,7 +18,7 @@
     runs-on: ubuntu-24.04
     steps:
       - name: Checkout k0s
-        uses: actions/checkout@v5
+        uses: actions/checkout@v6
 
       - name: Set up Python ${{ env.PYTHON_VERSION }}
         uses: actions/setup-python@v6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/go.yml 
new/k0sctl-0.27.1/.github/workflows/go.yml
--- old/k0sctl-0.27.0/.github/workflows/go.yml  2025-10-23 09:43:30.000000000 
+0200
+++ new/k0sctl-0.27.1/.github/workflows/go.yml  2025-12-09 10:38:28.000000000 
+0100
@@ -16,7 +16,7 @@
     runs-on: ${{ matrix.os }}
 
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
 
     - name: Set up Go
       uses: actions/setup-go@v6
@@ -30,7 +30,7 @@
   build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
 
     - name: Set up Go
       uses: actions/setup-go@v6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/golangci-lint.yml 
new/k0sctl-0.27.1/.github/workflows/golangci-lint.yml
--- old/k0sctl-0.27.0/.github/workflows/golangci-lint.yml       2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/golangci-lint.yml       2025-12-09 
10:38:28.000000000 +0100
@@ -10,7 +10,7 @@
     runs-on: ubuntu-latest
     steps:
       - name: Check out code into the Go module directory
-        uses: actions/checkout@v5
+        uses: actions/checkout@v6
 
       - name: Set up Go
         uses: actions/setup-go@v6
@@ -19,7 +19,7 @@
           check-latest: true
 
       - name: Run golangci-lint
-        uses: golangci/golangci-lint-action@v8
+        uses: golangci/golangci-lint-action@v9
         with:
           version: latest
           skip-cache: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/gomod-lint.yml 
new/k0sctl-0.27.1/.github/workflows/gomod-lint.yml
--- old/k0sctl-0.27.0/.github/workflows/gomod-lint.yml  2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/gomod-lint.yml  2025-12-09 
10:38:28.000000000 +0100
@@ -11,7 +11,7 @@
     runs-on: ubuntu-latest
     steps:
       - name: Check out code into the Go module directory
-        uses: actions/checkout@v5
+        uses: actions/checkout@v6
 
       - name: Set up Go
         uses: actions/setup-go@v6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/release.yml 
new/k0sctl-0.27.1/.github/workflows/release.yml
--- old/k0sctl-0.27.0/.github/workflows/release.yml     2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/release.yml     2025-12-09 
10:38:28.000000000 +0100
@@ -14,7 +14,7 @@
       packages: write # needed for ghcr access
     steps:
       - name: Check out code into the Go module directory
-        uses: actions/checkout@v5
+        uses: actions/checkout@v6
     
       - name: Tag name
         id: tag-name
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/.github/workflows/smoke.yml 
new/k0sctl-0.27.1/.github/workflows/smoke.yml
--- old/k0sctl-0.27.0/.github/workflows/smoke.yml       2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/smoke.yml       2025-12-09 
10:38:28.000000000 +0100
@@ -16,7 +16,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
 
     - name: Set up Go
       uses: actions/setup-go@v6
@@ -28,7 +28,7 @@
       run: make k0sctl
 
     - name: Stash the compiled binary for further testing
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v5
       with:
         name: k0sctl
         path: k0sctl
@@ -49,7 +49,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -68,7 +68,7 @@
 
     steps:
 
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -81,7 +81,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
 
       - name: Build image
@@ -102,7 +102,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -119,7 +119,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -137,7 +137,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         run: make smoke-files
@@ -152,7 +152,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         run: make smoke-dynamic
@@ -163,7 +163,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run OS override smoke test
         run: make smoke-os-override
@@ -174,7 +174,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run k0sDownloadURL smoke test
         run: make smoke-downloadurl
@@ -194,7 +194,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -215,7 +215,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -235,7 +235,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -255,7 +255,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -268,7 +268,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -286,7 +286,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -305,7 +305,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run smoke tests
         env:
@@ -318,7 +318,7 @@
     runs-on: ubuntu-24.04
 
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: ./.github/actions/smoke-test-cache
       - name: Run init smoke test
         run: make smoke-init
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/.github/workflows/update-latest-release.yml 
new/k0sctl-0.27.1/.github/workflows/update-latest-release.yml
--- old/k0sctl-0.27.0/.github/workflows/update-latest-release.yml       
2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/.github/workflows/update-latest-release.yml       
2025-12-09 10:38:28.000000000 +0100
@@ -20,7 +20,7 @@
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@v5
+        uses: actions/checkout@v6
         with:
           ref: ${{ github.sha }}
           fetch-depth: 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/README.md new/k0sctl-0.27.1/README.md
--- old/k0sctl-0.27.0/README.md 2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/README.md 2025-12-09 10:38:28.000000000 +0100
@@ -76,13 +76,13 @@
 
 ```sh
 # pull the image
-docker pull ghcr.io/k0sprojects/k0sctl:latest
+docker pull ghcr.io/k0sproject/k0sctl:latest
 
 # create a backup
 docker run -it --workdir /backup \
   -v ./backup:/backup \
   -v ./k0sctl.yaml:/etc/k0s/k0sctl.yaml \
-  ghcr.io/k0sprojects/k0sctl:latest k0sctl backup --config /etc/k0s/k0sctl.yaml
+  ghcr.io/k0sproject/k0sctl:latest k0sctl backup --config /etc/k0s/k0sctl.yaml
 ```
 
 #### Shell auto-completions
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/cmd/apply.go 
new/k0sctl-0.27.1/cmd/apply.go
--- old/k0sctl-0.27.0/cmd/apply.go      2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/cmd/apply.go      2025-12-09 10:38:28.000000000 +0100
@@ -113,7 +113,7 @@
                        KubeconfigAPIAddress:  
ctx.String("kubeconfig-api-address"),
                        KubeconfigUser:        ctx.String("kubeconfig-user"),
                        KubeconfigCluster:     ctx.String("kubeconfig-cluster"),
-                       NoWait:                ctx.Bool("no-wait") || 
!manager.Config.Spec.Options.Wait.Enabled,
+                       NoWait:                ctx.Bool("no-wait") || 
!manager.Config.Spec.Options.Wait.EnabledValue(),
                        NoDrain:               getNoDrainFlagOrConfig(ctx, 
manager.Config.Spec.Options.Drain),
                        DisableDowngradeCheck: 
ctx.Bool("disable-downgrade-check"),
                        RestoreFrom:           ctx.String("restore-from"),
@@ -134,5 +134,5 @@
        if ctx.IsSet("no-drain") {
                return ctx.Bool("no-drain")
        }
-       return !drain.Enabled
+       return !drain.EnabledValue()
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/cmd/apply_test.go 
new/k0sctl-0.27.1/cmd/apply_test.go
--- old/k0sctl-0.27.0/cmd/apply_test.go 2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/cmd/apply_test.go 2025-12-09 10:38:28.000000000 +0100
@@ -15,12 +15,12 @@
        app := cli.NewApp()
        ctx := cli.NewContext(app, set, nil)
 
-       cfg := cluster.DrainOption{Enabled: true}
+       cfg := cluster.DrainOption{Enabled: boolPtr(true)}
        if got := getNoDrainFlagOrConfig(ctx, cfg); got {
                t.Errorf("Expected false when config.Enabled is true and flag 
not set, got true")
        }
 
-       cfg.Enabled = false
+       cfg.Enabled = boolPtr(false)
        if got := getNoDrainFlagOrConfig(ctx, cfg); !got {
                t.Errorf("Expected true when config.Enabled is false and flag 
not set, got false")
        }
@@ -35,3 +35,7 @@
                t.Errorf("Expected false when flag is set to false, got true")
        }
 }
+
+func boolPtr(value bool) *bool {
+       return &value
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/cmd/flags.go 
new/k0sctl-0.27.1/cmd/flags.go
--- old/k0sctl-0.27.0/cmd/flags.go      2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/cmd/flags.go      2025-12-09 10:38:28.000000000 +0100
@@ -226,7 +226,22 @@
 
                log.Debugf("parsing configuration from %s", f)
 
-               if err := manifestReader.ParseBytes(subst); err != nil {
+               origin := cfgName
+               if cfgName == "-" {
+                       cwd, err := os.Getwd()
+                       if err != nil {
+                               return fmt.Errorf("failed to determine working 
directory: %w", err)
+                       }
+                       origin = cwd
+               } else if named, ok := cfgFile.(interface{ Name() string }); ok 
{
+                       if n := named.Name(); n != "" {
+                               origin = n
+                       }
+               } else if abs, err := filepath.Abs(origin); err == nil {
+                       origin = abs
+               }
+
+               if err := manifestReader.ParseBytesWithOrigin(subst, origin); 
err != nil {
                        return fmt.Errorf("failed to parse config: %w", err)
                }
 
@@ -280,6 +295,10 @@
        if err := ctlConfigs[0].Unmarshal(cfg); err != nil {
                return nil, fmt.Errorf("failed to unmarshal cluster config: 
%w", err)
        }
+       cfg.Origin = ctlConfigs[0].Origin
+       if err := cfg.Resolve(configBaseDir(cfg.Origin)); err != nil {
+               return nil, fmt.Errorf("failed to resolve upload file paths: 
%w", err)
+       }
        if k0sConfigs, err := mr.GetResources("k0s.k0sproject.io/v1beta1", 
"ClusterConfig"); err == nil && len(k0sConfigs) > 0 {
                if cfg.Spec.K0s.Config == nil {
                        cfg.Spec.K0s.Config = make(dig.Mapping)
@@ -318,6 +337,20 @@
        return cfg, nil
 }
 
+func configBaseDir(origin string) string {
+       if origin == "" {
+               return ""
+       }
+       cleaned := origin
+       if abs, err := filepath.Abs(origin); err == nil {
+               cleaned = abs
+       }
+       if info, err := os.Stat(cleaned); err == nil && info.IsDir() {
+               return filepath.ToSlash(cleaned)
+       }
+       return filepath.ToSlash(filepath.Dir(cleaned))
+}
+
 func initManager(ctx *cli.Context) error {
        cfg, err := readConfig(ctx)
        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/cmd/flags_test.go 
new/k0sctl-0.27.1/cmd/flags_test.go
--- old/k0sctl-0.27.0/cmd/flags_test.go 1970-01-01 01:00:00.000000000 +0100
+++ new/k0sctl-0.27.1/cmd/flags_test.go 2025-12-09 10:38:28.000000000 +0100
@@ -0,0 +1,79 @@
+package cmd
+
+import (
+       "context"
+       "flag"
+       "os"
+       "path/filepath"
+       "testing"
+
+       "github.com/k0sproject/k0sctl/pkg/manifest"
+       "github.com/stretchr/testify/require"
+       "github.com/urfave/cli/v2"
+)
+
+func TestReadConfigSetsOrigin(t *testing.T) {
+       origin := filepath.Join(t.TempDir(), "cluster.yaml")
+       clusterYAML := `apiVersion: k0sctl.k0sproject.io/v1beta1
+kind: Cluster
+metadata:
+  name: test
+spec:
+  hosts:
+    - role: controller
+      ssh:
+        address: 10.0.0.1
+`
+
+       mr := &manifest.Reader{}
+       require.NoError(t, mr.ParseBytesWithOrigin([]byte(clusterYAML), origin))
+
+       app := cli.NewApp()
+       flagSet := flag.NewFlagSet("test", flag.ContinueOnError)
+       ctx := cli.NewContext(app, flagSet, nil)
+       ctx.Context = context.WithValue(context.Background(), ctxConfigsKey{}, 
mr)
+
+       cfg, err := readConfig(ctx)
+       require.NoError(t, err)
+       require.Equal(t, origin, cfg.Origin)
+}
+
+func TestReadConfigResolvesUploadFilesRelativeToOrigin(t *testing.T) {
+       dir := t.TempDir()
+       assetsDir := filepath.Join(dir, "assets", "bin")
+       require.NoError(t, os.MkdirAll(assetsDir, 0o755))
+       require.NoError(t, os.WriteFile(filepath.Join(assetsDir, "script.sh"), 
[]byte("#!/bin/sh\n"), 0o755))
+
+       origin := filepath.Join(dir, "cluster.yaml")
+       clusterYAML := `apiVersion: k0sctl.k0sproject.io/v1beta1
+kind: Cluster
+metadata:
+  name: test
+spec:
+  hosts:
+    - role: controller
+      ssh:
+        address: 10.0.0.1
+      files:
+        - src: assets/bin/script.sh
+          dstDir: /tmp
+`
+       require.NoError(t, os.WriteFile(origin, []byte(clusterYAML), 0o644))
+
+       mr := &manifest.Reader{}
+       require.NoError(t, mr.ParseBytesWithOrigin([]byte(clusterYAML), origin))
+
+       app := cli.NewApp()
+       flagSet := flag.NewFlagSet("test", flag.ContinueOnError)
+       ctx := cli.NewContext(app, flagSet, nil)
+       ctx.Context = context.WithValue(context.Background(), ctxConfigsKey{}, 
mr)
+
+       cfg, err := readConfig(ctx)
+       require.NoError(t, err)
+       require.Equal(t, origin, cfg.Origin)
+       require.Len(t, cfg.Spec.Hosts, 1)
+       require.Len(t, cfg.Spec.Hosts[0].Files, 1)
+       require.Equal(t, filepath.ToSlash(assetsDir), 
cfg.Spec.Hosts[0].Files[0].Base)
+       require.Len(t, cfg.Spec.Hosts[0].Files[0].Sources, 1)
+       require.Equal(t, "script.sh", 
cfg.Spec.Hosts[0].Files[0].Sources[0].Path)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/go.mod new/k0sctl-0.27.1/go.mod
--- old/k0sctl-0.27.0/go.mod    2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/go.mod    2025-12-09 10:38:28.000000000 +0100
@@ -2,7 +2,7 @@
 
 go 1.25.0
 
-toolchain go1.25.3
+toolchain go1.25.5
 
 require (
        github.com/AlecAivazis/survey/v2 v2.3.7
@@ -23,11 +23,11 @@
        github.com/sirupsen/logrus v1.9.3
        github.com/stretchr/testify v1.11.1
        github.com/urfave/cli/v2 v2.27.7
-       golang.org/x/crypto v0.42.0 // indirect
-       golang.org/x/net v0.43.0 // indirect
-       golang.org/x/sys v0.36.0 // indirect
-       golang.org/x/term v0.35.0 // indirect
-       golang.org/x/text v0.30.0
+       golang.org/x/crypto v0.45.0 // indirect
+       golang.org/x/net v0.47.0 // indirect
+       golang.org/x/sys v0.38.0 // indirect
+       golang.org/x/term v0.37.0 // indirect
+       golang.org/x/text v0.32.0
        gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -38,7 +38,8 @@
        github.com/jellydator/validation v1.2.0
        github.com/k0sproject/version v0.8.0
        github.com/sergi/go-diff v1.4.0
-       k8s.io/client-go v0.34.1
+       k8s.io/apimachinery v0.34.2
+       k8s.io/client-go v0.34.2
 )
 
 require (
@@ -87,7 +88,6 @@
        golang.org/x/time v0.10.0 // indirect
        gopkg.in/inf.v0 v0.9.1 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
-       k8s.io/apimachinery v0.34.1 // indirect
        k8s.io/klog/v2 v2.130.1 // indirect
        k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
        sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/go.sum new/k0sctl-0.27.1/go.sum
--- old/k0sctl-0.27.0/go.sum    2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/go.sum    2025-12-09 10:38:28.000000000 +0100
@@ -203,8 +203,8 @@
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod 
h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.6.0/go.mod 
h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
-golang.org/x/crypto v0.42.0/go.mod 
h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod 
h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod 
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -217,8 +217,8 @@
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod 
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
-golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
 golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
 golang.org/x/oauth2 v0.27.0/go.mod 
h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -236,20 +236,20 @@
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
-golang.org/x/term v0.35.0/go.mod 
h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod 
h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod 
h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
+golang.org/x/text v0.32.0/go.mod 
h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
 golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
 golang.org/x/time v0.10.0/go.mod 
h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -276,12 +276,12 @@
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
-k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
-k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
-k8s.io/apimachinery v0.34.1/go.mod 
h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
-k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
-k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
+k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
+k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
+k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
+k8s.io/apimachinery v0.34.2/go.mod 
h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
+k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
+k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b 
h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/phase/manager.go 
new/k0sctl-0.27.1/phase/manager.go
--- old/k0sctl-0.27.0/phase/manager.go  2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/phase/manager.go  2025-12-09 10:38:28.000000000 +0100
@@ -192,7 +192,7 @@
                return fmt.Errorf("failed to set defaults: %w", err)
        }
        log.Debug("final configuration:")
-       log.Print(m.Config.String())
+       log.Debug(m.Config.String())
 
        defer func() {
                if m.DryRun {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/phase/reset_controllers.go 
new/k0sctl-0.27.1/phase/reset_controllers.go
--- old/k0sctl-0.27.0/phase/reset_controllers.go        2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/phase/reset_controllers.go        2025-12-09 
10:38:28.000000000 +0100
@@ -126,7 +126,7 @@
 
                log.Debugf("%s: resetting k0s...", h)
                var stdoutbuf, stderrbuf bytes.Buffer
-               cmd, err := h.ExecStreams(h.Configurer.K0sCmdf("reset 
--data-dir=%s", h.K0sDataDir()), nil, &stdoutbuf, &stderrbuf, exec.Sudo(h))
+               cmd, err := h.ExecStreams(h.K0sResetCommand(), nil, &stdoutbuf, 
&stderrbuf, exec.Sudo(h))
                if err != nil {
                        return fmt.Errorf("failed to run k0s reset: %w", err)
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go
--- old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go     
2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go     
2025-12-09 10:38:28.000000000 +0100
@@ -1,12 +1,12 @@
 package cluster
 
 import (
-    "context"
-    "fmt"
-    "net/url"
-    gos "os"
-    gopath "path"
-    "slices"
+       "context"
+       "fmt"
+       "net/url"
+       gos "os"
+       gopath "path"
+       "slices"
        "strings"
        "time"
 
@@ -132,6 +132,16 @@
        )
 }
 
+// ResolveUploadFiles resolves host file sources relative to baseDir.
+func (h *Host) ResolveUploadFiles(baseDir string) error {
+       for _, f := range h.Files {
+               if err := f.ResolveRelativeTo(baseDir); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
 type configurer interface {
        Kind() string
        CheckPrivilege(os.Host) error
@@ -203,6 +213,11 @@
        DryRunFakeLeader  bool
 }
 
+// Resolve prepares host-scoped data after unmarshalling by resolving upload 
files.
+func (h *Host) Resolve(baseDir string) error {
+       return h.ResolveUploadFiles(baseDir)
+}
+
 // UnmarshalYAML sets in some sane defaults when unmarshaling the data from 
yaml
 func (h *Host) UnmarshalYAML(unmarshal func(interface{}) error) error {
        type host Host
@@ -686,26 +701,26 @@
 
 // HasHooks returns true when the host has hooks defined for the action and 
stage.
 func (h *Host) HasHooks(action, stage string) bool {
-    return len(h.Hooks.ForActionAndStage(action, stage)) > 0
+       return len(h.Hooks.ForActionAndStage(action, stage)) > 0
 }
 
 // RunHooks runs the hooks for the given action and stage (such as "apply", 
"before" would run the "before apply" hooks).
 // It respects context cancellation between hook executions.
 func (h *Host) RunHooks(ctx context.Context, action, stage string) error {
-    commands := h.Hooks.ForActionAndStage(action, stage)
-    if len(commands) == 0 {
-        return nil
-    }
-    for _, cmd := range commands {
-        // Abort early if the context has been canceled.
-        if err := ctx.Err(); err != nil {
-            return err
-        }
-
-        log.Infof("%s: running %s %s hook: %q", h, stage, action, cmd)
-        if err := h.Exec(cmd); err != nil {
-            return fmt.Errorf("failed to execute hook %q for action %q stage 
%q on host %s: %w", cmd, action, stage, h.Address(), err)
-        }
-    }
-    return nil
+       commands := h.Hooks.ForActionAndStage(action, stage)
+       if len(commands) == 0 {
+               return nil
+       }
+       for _, cmd := range commands {
+               // Abort early if the context has been canceled.
+               if err := ctx.Err(); err != nil {
+                       return err
+               }
+
+               log.Infof("%s: running %s %s hook: %q", h, stage, action, cmd)
+               if err := h.Exec(cmd); err != nil {
+                       return fmt.Errorf("failed to execute hook %q for action 
%q stage %q on host %s: %w", cmd, action, stage, h.Address(), err)
+               }
+       }
+       return nil
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/hosts.go 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/hosts.go
--- old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/hosts.go    
2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/hosts.go    
2025-12-09 10:38:28.000000000 +0100
@@ -38,6 +38,16 @@
        return nil
 }
 
+// Resolve runs Host.Resolve for each host.
+func (hosts Hosts) Resolve(baseDir string) error {
+       for _, h := range hosts {
+               if err := h.Resolve(baseDir); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
 // First returns the first host
 func (hosts Hosts) First() *Host {
        if len(hosts) == 0 {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go
--- old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go  
2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/options.go  
2025-12-09 10:38:28.000000000 +0100
@@ -37,26 +37,43 @@
 
 // WaitOption controls the wait behavior for cluster operations.
 type WaitOption struct {
-       Enabled bool `yaml:"enabled" default:"true"`
+       Enabled *bool `yaml:"enabled" default:"true"`
 }
 
 // DrainOption controls the drain behavior for cluster operations.
 type DrainOption struct {
-       Enabled                  bool          `yaml:"enabled" default:"true"`
+       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"`
+       Force                    *bool         `yaml:"force" default:"true"`
+       IgnoreDaemonSets         *bool         `yaml:"ignoreDaemonSets" 
default:"true"`
+       DeleteEmptyDirData       *bool         `yaml:"deleteEmptyDirData" 
default:"true"`
        PodSelector              string        `yaml:"podSelector" default:""`
        SkipWaitForDeleteTimeout time.Duration `yaml:"skipWaitForDeleteTimeout" 
default:"0s"`
 }
 
+// EnabledValue returns the effective enabled flag, defaulting to true when 
unset.
+func (w WaitOption) EnabledValue() bool {
+       return boolPtrValue(w.Enabled, true)
+}
+
+// EnabledValue returns the effective enabled flag, defaulting to true when 
unset.
+func (d DrainOption) EnabledValue() bool {
+       return boolPtrValue(d.Enabled, true)
+}
+
+func boolPtrValue(value *bool, def bool) bool {
+       if value == nil {
+               return def
+       }
+       return *value
+}
+
 // ToKubectlArgs converts the DrainOption to kubectl arguments.
 func (d *DrainOption) ToKubectlArgs() string {
        args := []string{}
 
-       if d.Force {
+       if boolPtrValue(d.Force, true) {
                args = append(args, "--force")
        }
 
@@ -76,11 +93,11 @@
                args = append(args, 
fmt.Sprintf("--skip-wait-for-delete-timeout=%s", d.SkipWaitForDeleteTimeout))
        }
 
-       if d.DeleteEmptyDirData {
+       if boolPtrValue(d.DeleteEmptyDirData, true) {
                args = append(args, "--delete-emptydir-data")
        }
 
-       if d.IgnoreDaemonSets {
+       if boolPtrValue(d.IgnoreDaemonSets, true) {
                args = append(args, "--ignore-daemonsets")
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go
--- old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go     
2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go     
2025-12-09 10:38:28.000000000 +0100
@@ -96,6 +96,21 @@
        )
 }
 
+// ResolveUploadFilePaths resolves all host file sources relative to baseDir.
+func (s *Spec) ResolveUploadFilePaths(baseDir string) error {
+       for _, h := range s.Hosts {
+               if err := h.ResolveUploadFiles(baseDir); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// Resolve prepares spec-level data after unmarshalling by cascading to hosts.
+func (s *Spec) Resolve(baseDir string) error {
+       return s.ResolveUploadFilePaths(baseDir)
+}
+
 type k0sCPLBConfig struct {
        Spec struct {
                Network struct {
@@ -187,6 +202,8 @@
        return fmt.Sprintf("https://%s:%d";, formatIPV6(addr), s.APIPort())
 }
 
+// Resolve prepares spec-scoped resources after unmarshalling.
+// Currently cascades resolution into hosts using the given origin.
 func formatIPV6(address string) string {
        if strings.Contains(address, ":") {
                return fmt.Sprintf("[%s]", address)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go
--- 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go   
    2025-10-23 09:43:30.000000000 +0200
+++ 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go   
    2025-12-09 10:38:28.000000000 +0100
@@ -4,6 +4,7 @@
        "fmt"
        "os"
        "path"
+       "path/filepath"
        "strconv"
        "strings"
 
@@ -88,6 +89,7 @@
 }
 
 // UnmarshalYAML sets in some sane defaults when unmarshaling the data from 
yaml
+
 func (u *UploadFile) UnmarshalYAML(unmarshal func(interface{}) error) error {
        type uploadFile UploadFile
        yu := (*uploadFile)(u)
@@ -108,7 +110,7 @@
        }
        u.DirPermString = dp
 
-       return u.resolve()
+       return nil
 }
 
 // String returns the file bundle name or if it is empty, the source.
@@ -129,8 +131,8 @@
        return strings.ContainsAny(s, "*%?[]{}")
 }
 
-// sets the destination and resolves any globs/local paths into u.Sources
-func (u *UploadFile) resolve() error {
+// ResolveRelativeTo sets the destination and resolves globs/local paths 
relative to baseDir.
+func (u *UploadFile) ResolveRelativeTo(baseDir string) error {
        if u.IsURL() {
                if u.DestinationFile == "" {
                        if u.DestinationDir != "" {
@@ -146,27 +148,42 @@
                return nil
        }
 
+       u.Base = ""
+       u.Sources = nil
+
+       src := filepath.ToSlash(u.Source)
+       if src == "" {
+               return fmt.Errorf("failed to resolve local path for %s: empty 
source", u)
+       }
+       if !path.IsAbs(src) {
+               if baseDir != "" {
+                       src = path.Join(baseDir, src)
+               }
+       }
+       src = path.Clean(src)
+
        if isGlob(u.Source) {
-               return u.glob(u.Source)
+               return u.glob(src)
        }
 
-       stat, err := os.Stat(u.Source)
+       fsPath := filepath.FromSlash(src)
+       stat, err := os.Stat(fsPath)
        if err != nil {
                return fmt.Errorf("failed to stat local path for %s: %w", u, 
err)
        }
 
        if stat.IsDir() {
-               log.Tracef("source %s is a directory, assuming %s/**/*", 
u.Source, u.Source)
-               return u.glob(path.Join(u.Source, "**/*"))
+               log.Tracef("source %s is a directory, assuming %s/**/*", src, 
src)
+               return u.glob(path.Join(src, "**/*"))
        }
 
        perm := u.PermString
        if perm == "" {
                perm = fmt.Sprintf("%o", stat.Mode())
        }
-       u.Base = path.Dir(u.Source)
+       u.Base = path.Dir(src)
        u.Sources = []*LocalFile{
-               {Path: path.Base(u.Source), PermMode: perm},
+               {Path: path.Base(src), PermMode: perm},
        }
 
        return nil
@@ -176,7 +193,7 @@
 func (u *UploadFile) glob(src string) error {
        base, pattern := doublestar.SplitPattern(src)
        u.Base = base
-       fsys := os.DirFS(base)
+       fsys := os.DirFS(filepath.FromSlash(base))
        sources, err := doublestar.Glob(fsys, pattern)
        if err != nil {
                return err
@@ -185,7 +202,7 @@
        for _, s := range sources {
                abs := path.Join(base, s)
                log.Tracef("glob %s found: %s", abs, s)
-               stat, err := os.Stat(abs)
+               stat, err := os.Stat(filepath.FromSlash(abs))
                if err != nil {
                        return fmt.Errorf("failed to stat file %s: %w", u, err)
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile_test.go
 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile_test.go
--- 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile_test.go
  2025-10-23 09:43:30.000000000 +0200
+++ 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile_test.go
  2025-12-09 10:38:28.000000000 +0100
@@ -1,6 +1,8 @@
 package cluster
 
 import (
+       "os"
+       "path/filepath"
        "testing"
 
        "github.com/stretchr/testify/require"
@@ -93,3 +95,63 @@
        require.Error(t, err)
        require.Contains(t, err.Error(), "name or dst required for data")
 }
+
+func TestUploadFileResolveRelativeToBaseDir(t *testing.T) {
+       dir := t.TempDir()
+       srcDir := filepath.Join(dir, "files")
+       require.NoError(t, os.MkdirAll(srcDir, 0o755))
+       filePath := filepath.Join(srcDir, "example.txt")
+       require.NoError(t, os.WriteFile(filePath, []byte("data"), 0o644))
+
+       var u UploadFile
+       yml := []byte(`
+src: files/example.txt
+dstDir: /tmp
+`)
+       require.NoError(t, yaml.Unmarshal(yml, &u))
+       require.NoError(t, u.ResolveRelativeTo(filepath.ToSlash(dir)))
+
+       require.Equal(t, filepath.ToSlash(srcDir), u.Base)
+       require.Len(t, u.Sources, 1)
+       require.Equal(t, "example.txt", u.Sources[0].Path)
+}
+
+func TestUploadFileResolveGlobRelativeToBaseDir(t *testing.T) {
+       dir := t.TempDir()
+       srcDir := filepath.Join(dir, "files", "manifests")
+       require.NoError(t, os.MkdirAll(srcDir, 0o755))
+       require.NoError(t, os.WriteFile(filepath.Join(srcDir, "a.yaml"), 
[]byte("a"), 0o644))
+       require.NoError(t, os.WriteFile(filepath.Join(srcDir, "b.yaml"), 
[]byte("b"), 0o644))
+
+       var u UploadFile
+       yml := []byte(`
+src: files/**/*.yaml
+dstDir: /tmp
+`)
+       require.NoError(t, yaml.Unmarshal(yml, &u))
+       require.NoError(t, u.ResolveRelativeTo(filepath.ToSlash(dir)))
+
+       require.Equal(t, filepath.ToSlash(filepath.Join(dir, "files")), u.Base)
+       require.Len(t, u.Sources, 2)
+       require.ElementsMatch(t, []string{"manifests/a.yaml", 
"manifests/b.yaml"}, []string{u.Sources[0].Path, u.Sources[1].Path})
+}
+
+func TestUploadFileResolveRelativeURLSetsDestination(t *testing.T) {
+       u := &UploadFile{Source: "https://example.com/assets/app.tar.gz";, 
DestinationDir: "/opt"}
+       require.NoError(t, u.ResolveRelativeTo(""))
+       require.Equal(t, "/opt/app.tar.gz", u.DestinationFile)
+       require.Equal(t, "", u.Base)
+       require.Len(t, u.Sources, 0)
+}
+
+func TestUploadFileResolveRelativeSingleFile(t *testing.T) {
+       tmp := filepath.ToSlash(t.TempDir())
+       filePath := filepath.Join(tmp, "a.txt")
+       require.NoError(t, os.WriteFile(filePath, []byte("a"), 0o640))
+
+       u := &UploadFile{Source: "a.txt"}
+       require.NoError(t, u.ResolveRelativeTo(tmp))
+       require.Equal(t, tmp, u.Base)
+       require.Len(t, u.Sources, 1)
+       require.Equal(t, "a.txt", u.Sources[0].Path)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go 
new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go
--- old/k0sctl-0.27.0/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go  
2025-10-23 09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go  
2025-12-09 10:38:28.000000000 +0100
@@ -29,6 +29,7 @@
        Kind       string           `yaml:"kind"`
        Metadata   *ClusterMetadata `yaml:"metadata"`
        Spec       *cluster.Spec    `yaml:"spec"`
+       Origin     string           `yaml:"-"`
 }
 
 // UnmarshalYAML sets in some sane defaults when unmarshaling the data from 
yaml
@@ -110,3 +111,11 @@
        // default to etcd otherwise
        return "etcd"
 }
+
+// Resolve prepares cluster-level data after unmarshalling by cascading down 
to the spec.
+func (c *Cluster) Resolve(baseDir string) error {
+       if c.Spec == nil {
+               return nil
+       }
+       return c.Spec.Resolve(baseDir)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/pkg/manifest/reader.go 
new/k0sctl-0.27.1/pkg/manifest/reader.go
--- old/k0sctl-0.27.0/pkg/manifest/reader.go    2025-10-23 09:43:30.000000000 
+0200
+++ new/k0sctl-0.27.1/pkg/manifest/reader.go    2025-12-09 10:38:28.000000000 
+0100
@@ -6,7 +6,6 @@
        "errors"
        "fmt"
        "io"
-       "os"
        "path"
        "regexp"
        "strings"
@@ -73,14 +72,43 @@
 }
 
 func name(r io.Reader) string {
-       if n, ok := r.(*os.File); ok {
-               return n.Name()
+       type named interface {
+               Name() string
+       }
+       if n, ok := r.(named); ok {
+               if nn := n.Name(); nn != "" {
+                       return nn
+               }
        }
        return "manifest"
 }
 
+// ParseOption configures optional behavior for Parse.
+type ParseOption func(*parseOptions)
+
+type parseOptions struct {
+       origin string
+}
+
+// WithOrigin overrides the origin name for resources parsed from the reader.
+func WithOrigin(origin string) ParseOption {
+       return func(po *parseOptions) {
+               po.origin = origin
+       }
+}
+
 // Parse parses Kubernetes resource definitions from the provided input 
stream. They are then available via the Resources() or GetResources(apiVersion, 
kind) methods.
-func (r *Reader) Parse(input io.Reader) error {
+func (r *Reader) Parse(input io.Reader, opts ...ParseOption) error {
+       po := &parseOptions{}
+       for _, opt := range opts {
+               opt(po)
+       }
+
+       origin := po.origin
+       if origin == "" {
+               origin = name(input)
+       }
+
        yamlReader := yamlutil.NewYAMLReader(bufio.NewReader(input))
 
        for {
@@ -101,16 +129,17 @@
                        if r.IgnoreErrors {
                                continue
                        }
-                       return fmt.Errorf("failed to decode resource %s: %w", 
name(input), err)
+                       return fmt.Errorf("failed to decode resource %s: %w", 
origin, err)
                }
 
                if rd.APIVersion == "" || rd.Kind == "" {
                        if r.IgnoreErrors {
                                continue
                        }
-                       return fmt.Errorf("missing apiVersion or kind in 
resource %s", name(input))
+                       return fmt.Errorf("missing apiVersion or kind in 
resource %s", origin)
                }
 
+               rd.Origin = origin
                // Store the raw chunk
                rd.Raw = append([]byte{}, rawChunk...)
                r.manifests = append(r.manifests, rd)
@@ -120,13 +149,18 @@
 }
 
 // ParseString parses Kubernetes resource definitions from the provided string.
-func (r *Reader) ParseString(input string) error {
-       return r.Parse(strings.NewReader(input))
+func (r *Reader) ParseString(input string, opts ...ParseOption) error {
+       return r.Parse(strings.NewReader(input), opts...)
 }
 
 // ParseBytes parses Kubernetes resource definitions from the provided byte 
slice.
-func (r *Reader) ParseBytes(input []byte) error {
-       return r.Parse(bytes.NewReader(input))
+func (r *Reader) ParseBytes(input []byte, opts ...ParseOption) error {
+       return r.Parse(bytes.NewReader(input), opts...)
+}
+
+// ParseBytesWithOrigin parses raw bytes and attributes them to the provided 
origin.
+func (r *Reader) ParseBytesWithOrigin(input []byte, origin string) error {
+       return r.ParseBytes(input, WithOrigin(origin))
 }
 
 // Resources returns all parsed Kubernetes resource definitions.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.27.0/pkg/manifest/reader_test.go 
new/k0sctl-0.27.1/pkg/manifest/reader_test.go
--- old/k0sctl-0.27.0/pkg/manifest/reader_test.go       2025-10-23 
09:43:30.000000000 +0200
+++ new/k0sctl-0.27.1/pkg/manifest/reader_test.go       2025-12-09 
10:38:28.000000000 +0100
@@ -180,3 +180,18 @@
        require.Contains(t, parsed.Data, "payload")
        require.Len(t, parsed.Data["payload"], len(largeData))
 }
+
+func TestReader_ParseBytesWithOrigin(t *testing.T) {
+       origin := "/tmp/configs/config.yaml"
+       input := `apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: origin-test
+`
+       r := &manifest.Reader{}
+       require.NoError(t, r.ParseBytesWithOrigin([]byte(input), origin))
+
+       resources := r.Resources()
+       require.Len(t, resources, 1)
+       require.Equal(t, origin, resources[0].Origin)
+}

++++++ k0sctl.obsinfo ++++++
--- /var/tmp/diff_new_pack.UtFKfE/_old  2025-12-11 18:41:46.426563193 +0100
+++ /var/tmp/diff_new_pack.UtFKfE/_new  2025-12-11 18:41:46.430563360 +0100
@@ -1,5 +1,5 @@
 name: k0sctl
-version: 0.27.0
-mtime: 1761205410
-commit: 14ccbf5d395b90c683e8a4324a7b34e6b0bdb80e
+version: 0.27.1
+mtime: 1765273108
+commit: c7ee91b2f1a36c447590993cbb133028882271df
 

++++++ vendor.tar.gz ++++++
++++ 12990 lines of diff (skipped)

Reply via email to