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)