Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package k0sctl for openSUSE:Factory checked 
in at 2024-11-14 16:09:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/k0sctl (Old)
 and      /work/SRC/openSUSE:Factory/.k0sctl.new.2017 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "k0sctl"

Thu Nov 14 16:09:02 2024 rev:8 rq:1224001 version:0.19.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/k0sctl/k0sctl.changes    2024-10-27 
11:25:47.287680621 +0100
+++ /work/SRC/openSUSE:Factory/.k0sctl.new.2017/k0sctl.changes  2024-11-14 
16:10:03.974228314 +0100
@@ -1,0 +2,20 @@
+Tue Nov 12 13:04:18 UTC 2024 - opensuse_buildserv...@ojkastl.de
+
+- Update to version 0.19.4:
+  * Bump github.com/k0sproject/dig from 0.2.0 to 0.3.1 (#791)
+  * Bump github.com/adrg/xdg from 0.5.1 to 0.5.3 (#788)
+  * Bump k8s.io/client-go from 0.31.1 to 0.31.2 (#786)
+  * Bump github.com/k0sproject/rig from 0.18.8 to 0.19.0 (#789)
+
+-------------------------------------------------------------------
+Tue Nov 12 08:16:33 UTC 2024 - opensuse_buildserv...@ojkastl.de
+
+- Update to version 0.19.3:
+  * Restore k0s spec.api.sans updating that was removed in #772
+    (#783)
+  * Fix installflags change detection (#784)
+  * Default apiserver address to privateAddress if
+    onlyBindToAddress (#777)
+  * Fix KubeAPIURL for CPLB (#781)
+
+-------------------------------------------------------------------

Old:
----
  k0sctl-0.19.2.obscpio

New:
----
  k0sctl-0.19.4.obscpio

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

Other differences:
------------------
++++++ k0sctl.spec ++++++
--- /var/tmp/diff_new_pack.frJlpH/_old  2024-11-14 16:10:05.870307497 +0100
+++ /var/tmp/diff_new_pack.frJlpH/_new  2024-11-14 16:10:05.870307497 +0100
@@ -18,7 +18,7 @@
 
 
 Name:           k0sctl
-Version:        0.19.2
+Version:        0.19.4
 Release:        0
 Summary:        A bootstrapping and management tool for k0s clusters
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.frJlpH/_old  2024-11-14 16:10:05.898308666 +0100
+++ /var/tmp/diff_new_pack.frJlpH/_new  2024-11-14 16:10:05.902308833 +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.19.2</param>
+    <param name="revision">v0.19.4</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.frJlpH/_old  2024-11-14 16:10:05.918309502 +0100
+++ /var/tmp/diff_new_pack.frJlpH/_new  2024-11-14 16:10:05.922309668 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/k0sproject/k0sctl.git</param>
-              <param 
name="changesrevision">081dfeb085418df4824e00778b092e77f84715ad</param></service></servicedata>
+              <param 
name="changesrevision">a06d3f6c227d15c3c7f1b87205ee6b32a2000521</param></service></servicedata>
 (No newline at EOF)
 

++++++ k0sctl-0.19.2.obscpio -> k0sctl-0.19.4.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/go.mod new/k0sctl-0.19.4/go.mod
--- old/k0sctl-0.19.2/go.mod    2024-10-25 12:43:06.000000000 +0200
+++ new/k0sctl-0.19.4/go.mod    2024-11-12 13:03:52.000000000 +0100
@@ -7,14 +7,14 @@
        github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // 
indirect
        github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 // 
indirect
        github.com/a8m/envsubst v1.4.2
-       github.com/adrg/xdg v0.5.1
+       github.com/adrg/xdg v0.5.3
        github.com/bmatcuk/doublestar/v4 v4.7.1
        github.com/creasty/defaults v1.8.0
        github.com/denisbrodbeck/machineid v1.0.1
        github.com/gofrs/uuid v4.4.0+incompatible // indirect
        github.com/hashicorp/go-version v1.7.0 // indirect
-       github.com/k0sproject/dig v0.2.0
-       github.com/k0sproject/rig v0.18.8
+       github.com/k0sproject/dig v0.3.1
+       github.com/k0sproject/rig v0.19.0
        github.com/logrusorgru/aurora v2.0.3+incompatible
        github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // 
indirect
        github.com/masterzen/winrm v0.0.0-20240702205601-3fad6e106085 // 
indirect
@@ -26,11 +26,11 @@
        github.com/stretchr/testify v1.9.0
        github.com/urfave/cli/v2 v2.27.5
        github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
-       golang.org/x/crypto v0.28.0 // indirect
+       golang.org/x/crypto v0.29.0 // indirect
        golang.org/x/net v0.30.0 // indirect
-       golang.org/x/sys v0.26.0 // indirect
-       golang.org/x/term v0.25.0 // indirect
-       golang.org/x/text v0.19.0
+       golang.org/x/sys v0.27.0 // indirect
+       golang.org/x/term v0.26.0 // indirect
+       golang.org/x/text v0.20.0
        gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -41,7 +41,7 @@
        github.com/jellydator/validation v1.1.0
        github.com/k0sproject/version v0.6.0
        github.com/sergi/go-diff v1.3.1
-       k8s.io/client-go v0.31.1
+       k8s.io/client-go v0.31.2
 )
 
 require (
@@ -90,7 +90,7 @@
        golang.org/x/time v0.7.0 // indirect
        gopkg.in/inf.v0 v0.9.1 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
-       k8s.io/apimachinery v0.31.1 // indirect
+       k8s.io/apimachinery v0.31.2 // indirect
        k8s.io/klog/v2 v2.130.1 // indirect
        k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect
        sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/go.sum new/k0sctl-0.19.4/go.sum
--- old/k0sctl-0.19.2/go.sum    2024-10-25 12:43:06.000000000 +0200
+++ new/k0sctl-0.19.4/go.sum    2024-11-12 13:03:52.000000000 +0100
@@ -12,8 +12,8 @@
 github.com/a8m/envsubst v1.4.2/go.mod 
h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d 
h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod 
h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
-github.com/adrg/xdg v0.5.1 h1:Im8iDbEFARltY09yOJlSGu4Asjk2vF85+3Dyru8uJ0U=
-github.com/adrg/xdg v0.5.1/go.mod 
h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
+github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
+github.com/adrg/xdg v0.5.3/go.mod 
h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
 github.com/alessio/shellescape v1.4.2 
h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
 github.com/alessio/shellescape v1.4.2/go.mod 
h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod 
h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
@@ -113,10 +113,10 @@
 github.com/josharian/intern v1.0.0/go.mod 
h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.12 
h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod 
h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/k0sproject/dig v0.2.0 
h1:cNxEIl96g9kqSMfPSZLhpnZ0P8bWXKv08nxvsMHop5w=
-github.com/k0sproject/dig v0.2.0/go.mod 
h1:rBcqaQlJpcKdt2x/OE/lPvhGU50u/e95CSm5g/r4s78=
-github.com/k0sproject/rig v0.18.8 
h1:Dudzljvztdk5f5DGV8f07eRZBIXRTxpIslEMhRthLNA=
-github.com/k0sproject/rig v0.18.8/go.mod 
h1:rV9v56TQ6e62jgpAO1kEuoMMczwNH/I1MIxiV8gsvmg=
+github.com/k0sproject/dig v0.3.1 
h1:/QK40lXQ/HEE3LMT3r/kST1ANhMVZiajNDXI+spbL9o=
+github.com/k0sproject/dig v0.3.1/go.mod 
h1:rlZ7N7ZEcB4Fi96TPXkZ4dqyAiDWOGLapyL9YpZ7Qz4=
+github.com/k0sproject/rig v0.19.0 
h1:aF/wJDfK45Ho2Z75Uap+u4Q4jHgr/1WfrHcOg2U9/n0=
+github.com/k0sproject/rig v0.19.0/go.mod 
h1:SNa9+xeVA6zQVYx+SINaa4ZihFPWrmo/6crHcdvJRFI=
 github.com/k0sproject/version v0.6.0 
h1:Wi8wu9j+H36+okIQA47o/YHbzNpKeIYj8IjGdJOdqsI=
 github.com/k0sproject/version v0.6.0/go.mod 
h1:5/7Js62gDCLBP6mEs0mUcYEEkYneM5qXDKN/hyFlQTM=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 
h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
@@ -213,8 +213,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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
-golang.org/x/crypto v0.28.0/go.mod 
h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
+golang.org/x/crypto v0.29.0/go.mod 
h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
 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=
@@ -247,20 +247,20 @@
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
+golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
-golang.org/x/term v0.25.0/go.mod 
h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
+golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
+golang.org/x/term v0.26.0/go.mod 
h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
-golang.org/x/text v0.19.0/go.mod 
h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
+golang.org/x/text v0.20.0/go.mod 
h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
 golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
 golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -286,12 +286,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.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
-k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
-k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
-k8s.io/apimachinery v0.31.1/go.mod 
h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
-k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
-k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
+k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
+k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
+k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
+k8s.io/apimachinery v0.31.2/go.mod 
h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
+k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
+k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs=
 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-20240228011516-70dd3763d340 
h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/internal/shell/split.go 
new/k0sctl-0.19.4/internal/shell/split.go
--- old/k0sctl-0.19.2/internal/shell/split.go   1970-01-01 01:00:00.000000000 
+0100
+++ new/k0sctl-0.19.4/internal/shell/split.go   2024-11-12 13:03:52.000000000 
+0100
@@ -0,0 +1,62 @@
+package shell
+
+// this is borrowed as-is from rig v2 until k0sctl is updated to use it
+
+import (
+       "fmt"
+       "strings"
+)
+
+// Split splits the input string respecting shell-like quoted segments.
+func Split(input string) ([]string, error) { //nolint:cyclop
+       var segments []string
+
+       currentSegment, ok := builderPool.Get().(*strings.Builder)
+       if !ok {
+               currentSegment = &strings.Builder{}
+       }
+       defer builderPool.Put(currentSegment)
+       defer currentSegment.Reset()
+
+       var inDoubleQuotes, inSingleQuotes, isEscaped bool
+
+       for i := range len(input) {
+               currentChar := input[i]
+
+               if isEscaped {
+                       currentSegment.WriteByte(currentChar)
+                       isEscaped = false
+                       continue
+               }
+
+               switch {
+               case currentChar == '\\' && !inSingleQuotes:
+                       isEscaped = true
+               case currentChar == '"' && !inSingleQuotes:
+                       inDoubleQuotes = !inDoubleQuotes
+               case currentChar == '\'' && !inDoubleQuotes:
+                       inSingleQuotes = !inSingleQuotes
+               case currentChar == ' ' && !inDoubleQuotes && !inSingleQuotes:
+                       // Space outside quotes; delimiter for a new segment
+                       segments = append(segments, currentSegment.String())
+                       currentSegment.Reset()
+               default:
+                       currentSegment.WriteByte(currentChar)
+               }
+       }
+
+       if inDoubleQuotes || inSingleQuotes {
+               return nil, fmt.Errorf("split `%q`: %w", input, 
ErrMismatchedQuotes)
+       }
+
+       if isEscaped {
+               return nil, fmt.Errorf("split `%q`: %w", input, 
ErrTrailingBackslash)
+       }
+
+       // Add the last segment if present
+       if currentSegment.Len() > 0 {
+               segments = append(segments, currentSegment.String())
+       }
+
+       return segments, nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/internal/shell/unquote.go 
new/k0sctl-0.19.4/internal/shell/unquote.go
--- old/k0sctl-0.19.2/internal/shell/unquote.go 1970-01-01 01:00:00.000000000 
+0100
+++ new/k0sctl-0.19.4/internal/shell/unquote.go 2024-11-12 13:03:52.000000000 
+0100
@@ -0,0 +1,80 @@
+package shell
+
+import (
+       "errors"
+       "fmt"
+       "strings"
+       "sync"
+)
+
+// This is borrowed from rig v2 until k0sctl is updated to use it
+
+var (
+       builderPool = sync.Pool{
+               New: func() interface{} {
+                       return &strings.Builder{}
+               },
+       }
+
+       // ErrMismatchedQuotes is returned when the input string has mismatched 
quotes when unquoting.
+       ErrMismatchedQuotes = errors.New("mismatched quotes")
+
+       // ErrTrailingBackslash is returned when the input string ends with a 
trailing backslash.
+       ErrTrailingBackslash = errors.New("trailing backslash")
+)
+
+// Unquote is a mostly POSIX compliant implementation of unquoting a string 
the same way a shell would.
+// Variables and command substitutions are not handled.
+func Unquote(input string) (string, error) { //nolint:cyclop
+       sb, ok := builderPool.Get().(*strings.Builder)
+       if !ok {
+               sb = &strings.Builder{}
+       }
+       defer builderPool.Put(sb)
+       defer sb.Reset()
+
+       var inDoubleQuotes, inSingleQuotes, isEscaped bool
+
+       for i := range len(input) {
+               currentChar := input[i]
+
+               if isEscaped {
+                       sb.WriteByte(currentChar)
+                       isEscaped = false
+                       continue
+               }
+
+               switch currentChar {
+               case '\\':
+                       if !inSingleQuotes { // Escape works in double quotes 
or outside any quotes
+                               isEscaped = true
+                       } else {
+                               sb.WriteByte(currentChar) // Treat as a regular 
character within single quotes
+                       }
+               case '"':
+                       if !inSingleQuotes { // Toggle double quotes only if 
not in single quotes
+                               inDoubleQuotes = !inDoubleQuotes
+                       } else {
+                               sb.WriteByte(currentChar) // Treat as a regular 
character within single quotes
+                       }
+               case '\'':
+                       if !inDoubleQuotes { // Toggle single quotes only if 
not in double quotes
+                               inSingleQuotes = !inSingleQuotes
+                       } else {
+                               sb.WriteByte(currentChar) // Treat as a regular 
character within double quotes
+                       }
+               default:
+                       sb.WriteByte(currentChar)
+               }
+       }
+
+       if inDoubleQuotes || inSingleQuotes {
+               return "", fmt.Errorf("unquote `%q`: %w", input, 
ErrMismatchedQuotes)
+       }
+
+       if isEscaped {
+               return "", fmt.Errorf("unquote `%q`: %w", input, 
ErrTrailingBackslash)
+       }
+
+       return sb.String(), nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/internal/shell/unquote_test.go 
new/k0sctl-0.19.4/internal/shell/unquote_test.go
--- old/k0sctl-0.19.2/internal/shell/unquote_test.go    1970-01-01 
01:00:00.000000000 +0100
+++ new/k0sctl-0.19.4/internal/shell/unquote_test.go    2024-11-12 
13:03:52.000000000 +0100
@@ -0,0 +1,40 @@
+package shell_test
+
+import (
+       "testing"
+
+       "github.com/k0sproject/k0sctl/internal/shell"
+       "github.com/stretchr/testify/require"
+)
+
+func TestUnquote(t *testing.T) {
+       t.Run("no quotes", func(t *testing.T) {
+               out, err := shell.Unquote("foo bar")
+               require.NoError(t, err)
+               require.Equal(t, "foo bar", out)
+       })
+
+       t.Run("simple quotes", func(t *testing.T) {
+               out, err := shell.Unquote("\"foo\" 'bar'")
+               require.NoError(t, err)
+               require.Equal(t, "foo bar", out)
+       })
+
+       t.Run("mid-word quotes", func(t *testing.T) {
+               out, err := shell.Unquote("f\"o\"o b'a'r")
+               require.NoError(t, err)
+               require.Equal(t, "foo bar", out)
+       })
+
+       t.Run("complex quotes", func(t *testing.T) {
+               out, err := shell.Unquote(`'"'"'foo'"'"'`)
+               require.NoError(t, err)
+               require.Equal(t, `"'foo'"`, out)
+       })
+
+       t.Run("escaped quotes", func(t *testing.T) {
+               out, err := shell.Unquote("\\'foo\\' 'bar'")
+               require.NoError(t, err)
+               require.Equal(t, "'foo' bar", out)
+       })
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/phase/configure_k0s.go 
new/k0sctl-0.19.4/phase/configure_k0s.go
--- old/k0sctl-0.19.2/phase/configure_k0s.go    2024-10-25 12:43:06.000000000 
+0200
+++ new/k0sctl-0.19.4/phase/configure_k0s.go    2024-11-12 13:03:52.000000000 
+0100
@@ -5,6 +5,7 @@
        "context"
        "fmt"
        gopath "path"
+       "slices"
        "time"
 
        "github.com/k0sproject/dig"
@@ -74,6 +75,42 @@
                }
        }
 
+       // convert sans from unmarshaled config into []string
+       var sans []string
+       oldsans := p.newBaseConfig.Dig("spec", "api", "sans")
+       switch oldsans := oldsans.(type) {
+       case []interface{}:
+               for _, v := range oldsans {
+                       if s, ok := v.(string); ok {
+                               sans = append(sans, s)
+                       }
+               }
+               log.Tracef("converted sans from %T to []string", oldsans)
+       case []string:
+               sans = append(sans, oldsans...)
+               log.Tracef("sans was readily %T", oldsans)
+       default:
+               // do nothing - base k0s config does not contain any existing 
SANs
+       }
+
+       // populate SANs with all controller addresses
+       for i, c := range p.Config.Spec.Hosts.Controllers() {
+               if c.Reset {
+                       continue
+               }
+               if !slices.Contains(sans, c.Address()) {
+                       sans = append(sans, c.Address())
+                       log.Debugf("added controller %d address %s to 
spec.api.sans", i+1, c.Address())
+               }
+               if c.PrivateAddress != "" && !slices.Contains(sans, 
c.PrivateAddress) {
+                       sans = append(sans, c.PrivateAddress)
+                       log.Debugf("added controller %d private address %s to 
spec.api.sans", i+1, c.PrivateAddress)
+               }
+       }
+
+       // assign populated sans to the base config
+       p.newBaseConfig.DigMapping("spec", "api")["sans"] = sans
+
        for _, h := range p.Config.Spec.Hosts.Controllers() {
                if h.Reset {
                        continue
@@ -286,12 +323,19 @@
        }
 
        var addr string
+
        if h.PrivateAddress != "" {
                addr = h.PrivateAddress
        } else {
                addr = h.Address()
        }
 
+       if cfg.DigString("spec", "api", "address") == "" {
+               if onlyBindAddr, ok := cfg.Dig("spec", "api", 
"onlyBindToAddress").(bool); ok && onlyBindAddr {
+                       cfg.DigMapping("spec", "api")["address"] = addr
+               }
+       }
+
        if cfg.Dig("spec", "storage", "etcd", "peerAddress") != nil || 
h.PrivateAddress != "" {
                cfg.DigMapping("spec", "storage", "etcd")["peerAddress"] = addr
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/phase/gather_k0s_facts.go 
new/k0sctl-0.19.4/phase/gather_k0s_facts.go
--- old/k0sctl-0.19.2/phase/gather_k0s_facts.go 2024-10-25 12:43:06.000000000 
+0200
+++ new/k0sctl-0.19.4/phase/gather_k0s_facts.go 2024-11-12 13:03:52.000000000 
+0100
@@ -272,10 +272,14 @@
 
        h.Metadata.NeedsUpgrade = p.needsUpgrade(h)
 
+       var args cluster.Flags
        if len(status.Args) > 2 {
                // status.Args contains the binary path and the role as the 
first two elements, which we can ignore here.
-               h.Metadata.K0sStatusArgs = status.Args[2:]
+               for _, a := range status.Args[2:] {
+                       args.Add(a)
+               }
        }
+       h.Metadata.K0sStatusArgs = args
 
        log.Infof("%s: is running k0s %s version %s", h, h.Role, 
h.Metadata.K0sRunningVersion)
        if h.IsController() {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k0sctl-0.19.2/phase/reinstall.go 
new/k0sctl-0.19.4/phase/reinstall.go
--- old/k0sctl-0.19.2/phase/reinstall.go        2024-10-25 12:43:06.000000000 
+0200
+++ new/k0sctl-0.19.4/phase/reinstall.go        2024-11-12 13:03:52.000000000 
+0100
@@ -77,7 +77,7 @@
                h.InstallFlags.AddOrReplace("--enable-dynamic-config")
        }
 
-       h.InstallFlags.AddOrReplace("--force")
+       h.InstallFlags.AddOrReplace("--force=true")
 
        cmd, err := h.K0sInstallCommand()
        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags.go 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags.go
--- old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags.go    
2024-10-25 12:43:06.000000000 +0200
+++ new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags.go    
2024-11-12 13:03:52.000000000 +0100
@@ -1,8 +1,12 @@
 package cluster
 
 import (
+       "fmt"
        "strconv"
        "strings"
+
+       "github.com/alessio/shellescape"
+       "github.com/k0sproject/k0sctl/internal/shell"
 )
 
 // Flags is a slice of strings with added functions to ease manipulating lists 
of command-line flags
@@ -10,30 +14,42 @@
 
 // Add adds a flag regardless if it exists already or not
 func (f *Flags) Add(s string) {
+       if ns, err := shell.Unquote(s); err == nil {
+               s = ns
+       }
        *f = append(*f, s)
 }
 
 // Add a flag with a value
 func (f *Flags) AddWithValue(key, value string) {
-       *f = append(*f, key+" "+value)
+       if nv, err := shell.Unquote(value); err == nil {
+               value = nv
+       }
+       *f = append(*f, key+"="+value)
 }
 
 // AddUnlessExist adds a flag unless one with the same prefix exists
 func (f *Flags) AddUnlessExist(s string) {
+       if ns, err := shell.Unquote(s); err == nil {
+               s = ns
+       }
        if f.Include(s) {
                return
        }
-       *f = append(*f, s)
+       f.Add(s)
 }
 
 // AddOrReplace replaces a flag with the same prefix or adds a new one if one 
does not exist
 func (f *Flags) AddOrReplace(s string) {
+       if ns, err := shell.Unquote(s); err == nil {
+               s = ns
+       }
        idx := f.Index(s)
        if idx > -1 {
                (*f)[idx] = s
                return
        }
-       *f = append(*f, s)
+       f.Add(s)
 }
 
 // Include returns true if a flag with a matching prefix can be found
@@ -43,6 +59,9 @@
 
 // Index returns an index to a flag with a matching prefix
 func (f Flags) Index(s string) int {
+       if ns, err := shell.Unquote(s); err == nil {
+               s = ns
+       }
        var flag string
        sepidx := strings.IndexAny(s, "= ")
        if sepidx < 0 {
@@ -73,6 +92,9 @@
        if fl == "" {
                return ""
        }
+       if nfl, err := shell.Unquote(fl); err == nil {
+               fl = nfl
+       }
 
        idx := strings.IndexAny(fl, "= ")
        if idx < 0 {
@@ -80,10 +102,6 @@
        }
 
        val := fl[idx+1:]
-       s, err := strconv.Unquote(val)
-       if err == nil {
-               return s
-       }
 
        return val
 }
@@ -137,5 +155,59 @@
 
 // Join creates a string separated by spaces
 func (f *Flags) Join() string {
-       return strings.Join(*f, " ")
+       var parts []string
+       f.Each(func(k, v string) {
+               if v == "" && k != "" {
+                       parts = append(parts, shellescape.Quote(k))
+               } else {
+                       parts = append(parts, fmt.Sprintf("%s=%s", k, 
shellescape.Quote(v)))
+               }
+       })
+       return strings.Join(parts, " ")
+}
+
+// Each iterates over each flag and calls the function with the flag key and 
value as arguments
+func (f Flags) Each(fn func(string, string)) {
+       for _, flag := range f {
+               sepidx := strings.IndexAny(flag, "= ")
+               if sepidx < 0 {
+                       if flag == "" {
+                               continue
+                       }
+                       fn(flag, "")
+               } else {
+                       key, value := flag[:sepidx], flag[sepidx+1:]
+                       if unq, err := shell.Unquote(value); err == nil {
+                               value = unq
+                       }
+                       fn(key, value)
+               }
+       }
+}
+
+// Map returns a map[string]string of the flags where the key is the flag and 
the value is the value
+func (f Flags) Map() map[string]string {
+       res := make(map[string]string)
+       f.Each(func(k, v string) {
+               res[k] = v
+       })
+       return res
+}
+
+// Equals compares the flags with another Flags and returns true if they have 
the same flags and values, ignoring order
+func (f Flags) Equals(b Flags) bool {
+       if len(f) != len(b) {
+               return false
+       }
+       for _, flag := range f {
+               if !b.Include(flag) {
+                       return false
+               }
+               ourValue := f.GetValue(flag)
+               theirValue := b.GetValue(flag)
+               if ourValue != theirValue {
+                       return false
+               }
+       }
+       return true
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags_test.go 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags_test.go
--- 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags_test.go   
    2024-10-25 12:43:06.000000000 +0200
+++ 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/flags_test.go   
    2024-11-12 13:03:52.000000000 +0100
@@ -99,6 +99,39 @@
                result, err := flags.GetBoolean("--flag3")
                require.NoError(t, err)
                require.Equal(t, result, false)
+       })
+}
 
+func TestEach(t *testing.T) {
+       flags := Flags{"--flag1", "--flag2=foo", "--flag3=bar"}
+       var countF, countV int
+       flags.Each(func(flag string, value string) {
+               countF++
+               if value != "" {
+                       countV++
+               }
        })
+       require.Equal(t, 3, countF)
+       require.Equal(t, 2, countV)
+}
+
+func TestMap(t *testing.T) {
+       flags := Flags{"--flag1", "--flag2=foo", "--flag3=bar"}
+       m := flags.Map()
+       require.Len(t, m, 3)
+       require.Equal(t, "", m["--flag1"])
+       require.Equal(t, "foo", m["--flag2"])
+       require.Equal(t, "bar", m["--flag3"])
+}
+
+func TestEquals(t *testing.T) {
+       flags1 := Flags{"--flag1", "--flag2=foo", "--flag3=bar"}
+       flags2 := Flags{"--flag1", "--flag2=foo", "--flag3=bar"}
+       require.True(t, flags1.Equals(flags2))
+
+       flags2 = Flags{"--flag1", "--flag2=foo"}
+       require.False(t, flags1.Equals(flags2))
+
+       flags2 = Flags{"-f", "--flag2=foo", "--flag3=baz"}
+       require.False(t, flags1.Equals(flags2))
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go
--- old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go     
2024-10-25 12:43:06.000000000 +0200
+++ new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host.go     
2024-11-12 13:03:52.000000000 +0100
@@ -5,9 +5,6 @@
        "net/url"
        gos "os"
        gopath "path"
-       "regexp"
-       "sort"
-       "strconv"
        "strings"
        "time"
 
@@ -16,6 +13,7 @@
        "github.com/go-playground/validator/v10"
        "github.com/jellydator/validation"
        "github.com/jellydator/validation/is"
+       "github.com/k0sproject/k0sctl/internal/shell"
        "github.com/k0sproject/rig"
        "github.com/k0sproject/rig/exec"
        "github.com/k0sproject/rig/os"
@@ -184,7 +182,7 @@
        K0sNewConfig      string
        K0sJoinToken      string
        K0sJoinTokenID    string
-       K0sStatusArgs     []string
+       K0sStatusArgs     Flags
        Arch              string
        IsK0sLeader       bool
        Hostname          string
@@ -275,64 +273,63 @@
        return h.Configurer.K0sConfigPath()
 }
 
-// unquote + unescape a string
-func unQE(s string) string {
-       unq, err := strconv.Unquote(s)
-       if err != nil {
-               return s
+func (h *Host) K0sRole() string {
+       switch h.Role {
+       case "controller+worker", "single":
+               return "controller"
+       default:
+               return h.Role
        }
-
-       c := string(s[0])                                           // string 
was quoted, c now has the quote char
-       re := regexp.MustCompile(fmt.Sprintf(`(?:^|[^\\])\\%s`, c)) // replace 
\" with " (remove escaped quotes inside quoted string)
-       return string(re.ReplaceAllString(unq, c))
 }
 
-// K0sInstallCommand returns a full command that will install k0s service with 
necessary flags
-func (h *Host) K0sInstallCommand() (string, error) {
-       role := h.Role
-       flags := h.InstallFlags
+func (h *Host) K0sInstallFlags() (Flags, error) {
+       flags := Flags(h.InstallFlags)
 
-       flags.AddOrReplace(fmt.Sprintf("--data-dir=%s", h.K0sDataDir()))
+       flags.AddOrReplace(fmt.Sprintf("--data-dir=%s", 
shellescape.Quote(h.K0sDataDir())))
 
-       switch role {
+       switch h.Role {
        case "controller+worker":
-               role = "controller"
                flags.AddUnlessExist("--enable-worker")
                if h.NoTaints {
                        flags.AddUnlessExist("--no-taints")
                }
        case "single":
-               role = "controller"
-               flags.AddUnlessExist("--single")
+               flags.AddUnlessExist("--single=true")
        }
 
        if !h.Metadata.IsK0sLeader {
-               flags.AddUnlessExist(fmt.Sprintf(`--token-file "%s"`, 
h.K0sJoinTokenPath()))
+               flags.AddUnlessExist(fmt.Sprintf(`--token-file=%s`, 
shellescape.Quote(h.K0sJoinTokenPath())))
        }
 
        if h.IsController() {
-               flags.AddUnlessExist(fmt.Sprintf(`--config "%s"`, 
h.K0sConfigPath()))
+               flags.AddUnlessExist(fmt.Sprintf(`--config=%s`, 
shellescape.Quote(h.K0sConfigPath())))
        }
 
        if strings.HasSuffix(h.Role, "worker") {
                var extra Flags
                if old := flags.GetValue("--kubelet-extra-args"); old != "" {
-                       extra = Flags{unQE(old)}
+                       parts, err := shell.Split(old)
+                       if err != nil {
+                               return flags, fmt.Errorf("failed to split 
kubelet-extra-args: %w", err)
+                       }
+                       for _, part := range parts {
+                               extra.Add(part)
+                       }
                }
                // set worker's private address to --node-ip in 
--extra-kubelet-args if cloud ins't enabled
                enableCloudProvider, err := 
h.InstallFlags.GetBoolean("--enable-cloud-provider")
                if err != nil {
-                       return "", fmt.Errorf("--enable-cloud-provider flag is 
set to invalid value: %s. (%v)", 
h.InstallFlags.GetValue("--enable-cloud-provider"), err)
+                       return flags, fmt.Errorf("--enable-cloud-provider flag 
is set to invalid value: %s. (%v)", 
h.InstallFlags.GetValue("--enable-cloud-provider"), err)
                }
                if !enableCloudProvider && h.PrivateAddress != "" {
-                       extra.AddUnlessExist(fmt.Sprintf("--node-ip=%s", 
h.PrivateAddress))
+                       extra.AddUnlessExist("--node-ip=" + h.PrivateAddress)
                }
 
                if h.HostnameOverride != "" {
-                       
extra.AddOrReplace(fmt.Sprintf("--hostname-override=%s", h.HostnameOverride))
+                       extra.AddOrReplace("--hostname-override=" + 
h.HostnameOverride)
                }
                if extra != nil {
-                       
flags.AddOrReplace(fmt.Sprintf("--kubelet-extra-args=%s", 
strconv.Quote(extra.Join())))
+                       
flags.AddOrReplace(fmt.Sprintf("--kubelet-extra-args=%s", 
shellescape.Quote(extra.Join())))
                }
        }
 
@@ -341,7 +338,17 @@
                flags.Delete("--force")
        }
 
-       return h.Configurer.K0sCmdf("install %s %s", role, flags.Join()), nil
+       return flags, nil
+}
+
+// K0sInstallCommand returns a full command that will install k0s service with 
necessary flags
+func (h *Host) K0sInstallCommand() (string, error) {
+       flags, err := h.K0sInstallFlags()
+       if err != nil {
+               return "", err
+       }
+
+       return h.Configurer.K0sCmdf("install %s %s", h.K0sRole(), 
flags.Join()), nil
 }
 
 // K0sBackupCommand returns a full command to be used as run k0s backup
@@ -572,120 +579,19 @@
        return builder.String()
 }
 
-var flagParseRe = regexp.MustCompile(`--?([\w\-]+)(?:[=\s](\S+))?`)
-
 // FlagsChanged returns true when the flags have changed by comparing the 
host.Metadata.K0sStatusArgs to what host.InstallFlags would produce
 func (h *Host) FlagsChanged() bool {
-       var formattedFlags []string
-
-       cmd, err := h.K0sInstallCommand()
+       installFlags, err := h.K0sInstallFlags()
        if err != nil {
-               log.Warnf("%s: could not get install command: %s", h, err)
-               return false
-       }
-       flags, err := shellSplit(cmd)
-       if err != nil {
-               log.Warnf("%s: could not split install command: %s", h, err)
-               return false
+               log.Warnf("%s: could not get install flags: %s", h, err)
+               installFlags = Flags{}
        }
-       if len(flags) < 4 {
-               // ["k0s", "install", <role>, <flags..>]
-               log.Debugf("%s: no installFlags", h)
-               return false
-       }
-       flags = flags[3:] // discard the first 3 elements
 
-       // format the flags the same way as spf13/cobra does in k0s
-       for i := 0; i < len(flags); i++ {
-               flag := flags[i]
-               var key string
-               var value string
-               match := flagParseRe.FindStringSubmatch(flag)
-               if len(match) < 2 {
-                       log.Warnf("%s: could not parse flag: %s", h, flag)
-                       continue
-               }
-
-               key = match[1]
-
-               if len(match) > 2 && len(match[2]) > 0 {
-                       value = match[2]
-               } else if len(flags) > i+1 && !strings.HasPrefix(flags[i+1], 
"-") {
-                       value = flags[i+1]
-                       i++
-               } else {
-                       value = "true"
-               }
-               if s, err := strconv.Unquote(value); err == nil {
-                       value = s
-               }
-               formattedFlags = append(formattedFlags, fmt.Sprintf("--%s=%s", 
key, value))
-       }
-
-       k0sArgs := h.Metadata.K0sStatusArgs
-       if len(k0sArgs) != len(formattedFlags) {
-               log.Debugf("%s: installFlags seem to have changed because of 
different length: %d %v vs %d %v", h, len(k0sArgs), k0sArgs, 
len(formattedFlags), formattedFlags)
-               return true
-       }
-       sort.Strings(formattedFlags)
-       sort.Strings(k0sArgs)
-       for i := range formattedFlags {
-               if formattedFlags[i] != k0sArgs[i] {
-                       log.Debugf("%s: installFlags seem to have changed 
because of different flags: %v vs %v", h, formattedFlags, k0sArgs)
-                       return true
-               }
-       }
-
-       log.Debugf("%s: installFlags have not changed", h)
-
-       return false
-}
-
-// Split splits the input string respecting shell-like quoted segments -- 
borrowed from rig v2 until migration
-func shellSplit(input string) ([]string, error) { //nolint:cyclop
-       var segments []string
-
-       currentSegment := &strings.Builder{}
-
-       var inDoubleQuotes, inSingleQuotes, isEscaped bool
-
-       for i := range len(input) {
-               currentChar := input[i]
-
-               if isEscaped {
-                       currentSegment.WriteByte(currentChar)
-                       isEscaped = false
-                       continue
-               }
-
-               switch {
-               case currentChar == '\\' && !inSingleQuotes:
-                       isEscaped = true
-               case currentChar == '"' && !inSingleQuotes:
-                       inDoubleQuotes = !inDoubleQuotes
-               case currentChar == '\'' && !inDoubleQuotes:
-                       inSingleQuotes = !inSingleQuotes
-               case currentChar == ' ' && !inDoubleQuotes && !inSingleQuotes:
-                       // Space outside quotes; delimiter for a new segment
-                       segments = append(segments, currentSegment.String())
-                       currentSegment.Reset()
-               default:
-                       currentSegment.WriteByte(currentChar)
-               }
-       }
-
-       if inDoubleQuotes || inSingleQuotes {
-               return nil, fmt.Errorf("split `%q`: mismatched quotes", input)
-       }
-
-       if isEscaped {
-               return nil, fmt.Errorf("split `%q`: trailing backslash", input)
-       }
-
-       // Add the last segment if present
-       if currentSegment.Len() > 0 {
-               segments = append(segments, currentSegment.String())
+       if installFlags.Equals(h.Metadata.K0sStatusArgs) {
+               log.Debugf("%s: installFlags have not changed", h)
+               return false
        }
 
-       return segments, nil
+       log.Debugf("%s: installFlags seem to have changed. existing: %+v new: 
%+v", h, h.Metadata.K0sStatusArgs.Map(), installFlags.Map())
+       return true
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host_test.go 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host_test.go
--- 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host_test.go    
    2024-10-25 12:43:06.000000000 +0200
+++ 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/host_test.go    
    2024-11-12 13:03:52.000000000 +0100
@@ -64,12 +64,6 @@
        require.Equal(t, "from-install-short-flag", h.K0sConfigPath())
 }
 
-func TestUnQE(t *testing.T) {
-       require.Equal(t, `hello`, unQE(`hello`))
-       require.Equal(t, `hello`, unQE(`"hello"`))
-       require.Equal(t, `hello "world"`, unQE(`"hello \"world\""`))
-}
-
 func TestK0sInstallCommand(t *testing.T) {
        h := Host{Role: "worker", DataDir: "/tmp/k0s", Connection: 
rig.Connection{Localhost: &rig.Localhost{Enabled: true}}}
        _ = h.Connect()
@@ -79,45 +73,48 @@
 
        cmd, err := h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install worker --data-dir=/tmp/k0s --token-file 
"from-configurer"`, cmd)
+       require.Equal(t, `k0s install worker --data-dir=/tmp/k0s 
--token-file=from-configurer`, cmd)
 
        h.Role = "controller"
        h.Metadata.IsK0sLeader = true
        cmd, err = h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s --config 
"from-configurer"`, cmd)
+       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s 
--config=from-configurer`, cmd)
 
        h.Metadata.IsK0sLeader = false
        cmd, err = h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s 
--token-file "from-configurer" --config "from-configurer"`, cmd)
+       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s 
--token-file=from-configurer --config=from-configurer`, cmd)
 
        h.Role = "controller+worker"
        h.Metadata.IsK0sLeader = true
        cmd, err = h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s 
--enable-worker --config "from-configurer"`, cmd)
+       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s 
--enable-worker --config=from-configurer`, cmd)
 
        h.Metadata.IsK0sLeader = false
        cmd, err = h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s 
--enable-worker --token-file "from-configurer" --config "from-configurer"`, cmd)
+       require.Equal(t, `k0s install controller --data-dir=/tmp/k0s 
--enable-worker --token-file=from-configurer --config=from-configurer`, cmd)
 
        h.Role = "worker"
        h.PrivateAddress = "10.0.0.9"
        cmd, err = h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install worker --data-dir=/tmp/k0s --token-file 
"from-configurer" --kubelet-extra-args="--node-ip=10.0.0.9"`, cmd)
+       require.Equal(t, `k0s install worker --data-dir=/tmp/k0s 
--token-file=from-configurer --kubelet-extra-args=--node-ip=10.0.0.9`, cmd)
 
        h.InstallFlags = []string{`--kubelet-extra-args="--foo bar"`}
        cmd, err = h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install worker --kubelet-extra-args="--foo bar 
--node-ip=10.0.0.9" --data-dir=/tmp/k0s --token-file "from-configurer"`, cmd)
+       require.Equal(t, `k0s install worker --kubelet-extra-args='--foo bar 
--node-ip=10.0.0.9' --data-dir=/tmp/k0s --token-file=from-configurer`, cmd)
 
-       h.InstallFlags = []string{`--enable-cloud-provider`}
+       // Verify that K0sInstallCommand does not modify InstallFlags"
+       require.Equal(t, `--kubelet-extra-args='--foo bar'`, 
h.InstallFlags.Join())
+
+       h.InstallFlags = []string{`--enable-cloud-provider=true`}
        cmd, err = h.K0sInstallCommand()
        require.NoError(t, err)
-       require.Equal(t, `k0s install worker --enable-cloud-provider 
--data-dir=/tmp/k0s --token-file "from-configurer"`, cmd)
+       require.Equal(t, `k0s install worker --enable-cloud-provider=true 
--data-dir=/tmp/k0s --token-file=from-configurer`, cmd)
 }
 
 func TestValidation(t *testing.T) {
@@ -156,3 +153,62 @@
        require.NoError(t, err)
        require.Equal(t, "test%20expand/k0s-v1.0.0%2Bk0s.0-amd64", 
h.ExpandTokens("test%20expand/k0s-%v-%p%x", ver))
 }
+
+func TestFlagsChanged(t *testing.T) {
+       cfg := &mockconfigurer{}
+       cfg.SetPath("K0sConfigPath", "/tmp/foo.yaml")
+       cfg.SetPath("K0sJoinTokenPath", "/tmp/token")
+       t.Run("simple", func(t *testing.T) {
+               h := Host{
+                       Configurer:     cfg,
+                       DataDir:        "/tmp/data",
+                       Role:           "controller",
+                       PrivateAddress: "10.0.0.1",
+                       InstallFlags:   []string{"--foo"},
+                       Metadata: HostMetadata{
+                               K0sStatusArgs: []string{"--foo", 
"--data-dir=/tmp/data", "--token-file=/tmp/token", "--config=/tmp/foo.yaml"},
+                       },
+               }
+               require.False(t, h.FlagsChanged())
+               h.InstallFlags = []string{"--bar"}
+               require.True(t, h.FlagsChanged())
+       })
+       t.Run("quoted values", func(t *testing.T) {
+               h := Host{
+                       Configurer:     cfg,
+                       DataDir:        "/tmp/data",
+                       Role:           "controller",
+                       PrivateAddress: "10.0.0.1",
+                       InstallFlags:   []string{"--foo='bar'", "--bar=foo"},
+                       Metadata: HostMetadata{
+                               K0sStatusArgs: []string{"--foo=bar", 
`--bar="foo"`, "--data-dir=/tmp/data", "--token-file=/tmp/token", 
"--config=/tmp/foo.yaml"},
+                       },
+               }
+               require.False(t, h.FlagsChanged())
+               h.InstallFlags = []string{"--foo=bar", `--bar="foo"`}
+               require.False(t, h.FlagsChanged())
+               h.InstallFlags = []string{"--foo=baz", `--bar="foo"`}
+               require.True(t, h.FlagsChanged())
+       })
+       t.Run("kubelet-extra-args and single", func(t *testing.T) {
+               h := Host{
+                       Configurer:     cfg,
+                       DataDir:        "/tmp/data",
+                       Role:           "single",
+                       PrivateAddress: "10.0.0.1",
+                       InstallFlags:   []string{"--foo='bar'", 
`--kubelet-extra-args="--bar=foo --foo='bar'"`},
+                       Metadata: HostMetadata{
+                               K0sStatusArgs: []string{"--foo=bar", 
`--kubelet-extra-args="--bar=foo --foo='bar'"`, "--data-dir=/tmp/data", 
"--single=true", "--token-file=/tmp/token", "--config=/tmp/foo.yaml"},
+                       },
+               }
+               flags, err := h.K0sInstallFlags()
+               require.NoError(t, err)
+               require.Equal(t, `--foo=bar --kubelet-extra-args='--bar=foo 
--foo='"'"'bar'"'"'' --data-dir=/tmp/data --single=true --token-file=/tmp/token 
--config=/tmp/foo.yaml`, flags.Join())
+               require.False(t, h.FlagsChanged())
+               h.InstallFlags = []string{"--foo='baz'", 
`--kubelet-extra-args='--bar=baz --foo="bar"'`}
+               flags, err = h.K0sInstallFlags()
+               require.NoError(t, err)
+               require.Equal(t, `--foo=baz --kubelet-extra-args='--bar=baz 
--foo="bar"' --data-dir=/tmp/data --single=true --token-file=/tmp/token 
--config=/tmp/foo.yaml`, flags.Join())
+               require.True(t, h.FlagsChanged())
+       })
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go
--- old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go     
2024-10-25 12:43:06.000000000 +0200
+++ new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec.go     
2024-11-12 13:03:52.000000000 +0100
@@ -6,7 +6,7 @@
 
        "github.com/creasty/defaults"
        "github.com/jellydator/validation"
-       "github.com/k0sproject/dig"
+       "gopkg.in/yaml.v2"
 )
 
 // Spec defines cluster config spec section
@@ -82,22 +82,48 @@
        )
 }
 
+type k0sCPLBConfig struct {
+       Spec struct {
+               Network struct {
+                       ControlPlaneLoadBalancing struct {
+                               Enabled    bool   `yaml:"enabled"`
+                               Type       string `yaml:"type"`
+                               Keepalived struct {
+                                       VirtualServers []struct {
+                                               IPAddress string 
`yaml:"ipAddress"`
+                                       } `yaml:"virtualServers"`
+                               } `yaml:"keepalived"`
+                       } `yaml:"controlPlaneLoadBalancing"`
+               } `yaml:"network"`
+       } `yaml:"spec"`
+}
+
 func (s *Spec) clusterExternalAddress() string {
-       if a := s.K0s.Config.DigString("spec", "api", "externalAddress"); a != 
"" {
-               return a
-       }
+       if s.K0s != nil {
+               if a := s.K0s.Config.DigString("spec", "api", 
"externalAddress"); a != "" {
+                       return a
+               }
 
-       if cplb, ok := s.K0s.Config.Dig("spec", "network", 
"controlPlaneLoadBalancing").(dig.Mapping); ok {
-               if enabled, ok := cplb.Dig("enabled").(bool); ok && enabled && 
cplb.DigString("type") == "Keepalived" {
-                       if vrrpAddresses, ok := cplb.Dig("keepalived", 
"virtualServers").([]dig.Mapping); ok && len(vrrpAddresses) > 0 {
-                               if addr, ok := 
vrrpAddresses[0]["ipAddress"].(string); ok && addr != "" {
-                                       return addr
+               if cfg, err := yaml.Marshal(s.K0s.Config); err == nil {
+                       k0scfg := k0sCPLBConfig{}
+                       if err := yaml.Unmarshal(cfg, &k0scfg); err == nil {
+                               cplb := 
k0scfg.Spec.Network.ControlPlaneLoadBalancing
+                               if cplb.Enabled && cplb.Type == "Keepalived" {
+                                       for _, vs := range 
cplb.Keepalived.VirtualServers {
+                                               if addr := vs.IPAddress; addr 
!= "" {
+                                                       return addr
+                                               }
+                                       }
                                }
                        }
                }
        }
 
-       return s.K0sLeader().Address()
+       if leader := s.K0sLeader(); leader != nil {
+               return leader.Address()
+       }
+
+       return ""
 }
 
 func (s *Spec) clusterInternalAddress() string {
@@ -112,8 +138,10 @@
 const defaultAPIPort = 6443
 
 func (s *Spec) APIPort() int {
-       if p, ok := s.K0s.Config.Dig("spec", "api", "port").(int); ok {
-               return p
+       if s.K0s != nil {
+               if p, ok := s.K0s.Config.Dig("spec", "api", "port").(int); ok {
+                       return p
+               }
        }
        return defaultAPIPort
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec_test.go 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec_test.go
--- 
old/k0sctl-0.19.2/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec_test.go    
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/k0sctl-0.19.4/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/spec_test.go    
    2024-11-12 13:03:52.000000000 +0100
@@ -0,0 +1,82 @@
+package cluster
+
+import (
+       "testing"
+
+       "github.com/k0sproject/dig"
+       "github.com/k0sproject/rig"
+       "github.com/stretchr/testify/require"
+       "gopkg.in/yaml.v2"
+)
+
+func TestKubeAPIURL(t *testing.T) {
+       t.Run("with external address and port", func(t *testing.T) {
+               spec := &Spec{
+                       K0s: &K0s{
+                               Config: dig.Mapping(map[string]any{
+                                       "spec": dig.Mapping(map[string]any{
+                                               "api": 
dig.Mapping(map[string]any{
+                                                       "port":            6444,
+                                                       "externalAddress": 
"test.example.com",
+                                               }),
+                                       }),
+                               }),
+                       }, Hosts: Hosts{
+                               &Host{
+                                       Role: "controller",
+                                       Connection: rig.Connection{
+                                               SSH: &rig.SSH{
+                                                       Address: "10.0.0.1",
+                                               },
+                                       },
+                               },
+                       },
+               }
+               require.Equal(t, "https://test.example.com:6444";, 
spec.KubeAPIURL())
+       })
+
+       t.Run("without k0s config", func(t *testing.T) {
+               spec := &Spec{
+                       Hosts: Hosts{
+                               &Host{
+                                       Role:           "controller",
+                                       PrivateAddress: "10.0.0.1",
+                                       Connection: rig.Connection{
+                                               SSH: &rig.SSH{
+                                                       Address: "192.168.0.1",
+                                               },
+                                       },
+                               },
+                       },
+               }
+               require.Equal(t, "https://192.168.0.1:6443";, spec.KubeAPIURL())
+       })
+
+       t.Run("with CPLB", func(t *testing.T) {
+               specYaml := []byte(`
+hosts:
+  - role: controller
+    ssh:
+      address: 192.168.0.1
+    privateAddress: 10.0.0.1
+k0s:
+  config:
+    spec:
+      network:
+        controlPlaneLoadBalancing:
+          enabled: true
+          type: Keepalived
+          keepalived:
+            vrrpInstances:
+            - virtualIPs: ["192.168.0.10/24"]
+              authPass: CPLB
+            virtualServers:
+            - ipAddress: 192.168.0.10`)
+
+               spec := &Spec{}
+               err := yaml.Unmarshal(specYaml, spec)
+               require.NoError(t, err)
+
+               require.Equal(t, "https://192.168.0.10:6443";, spec.KubeAPIURL())
+       })
+}

++++++ k0sctl.obsinfo ++++++
--- /var/tmp/diff_new_pack.frJlpH/_old  2024-11-14 16:10:06.062315515 +0100
+++ /var/tmp/diff_new_pack.frJlpH/_new  2024-11-14 16:10:06.066315682 +0100
@@ -1,5 +1,5 @@
 name: k0sctl
-version: 0.19.2
-mtime: 1729852986
-commit: 081dfeb085418df4824e00778b092e77f84715ad
+version: 0.19.4
+mtime: 1731413032
+commit: a06d3f6c227d15c3c7f1b87205ee6b32a2000521
 

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

Reply via email to