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)
