Skip to content

Commit 4cbf11b

Browse files
committed
fix findings
Signed-off-by: Kimmo Lehto <[email protected]>
1 parent b7db273 commit 4cbf11b

File tree

8 files changed

+163
-73
lines changed

8 files changed

+163
-73
lines changed

action/apply.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ type Apply struct {
4949
// gatherK0sFacts := &phase.GatherK0sFacts{} // advisable to get the title from the phase itself instead of hardcoding the title.
5050
// apply.Phases.InsertBefore(gatherK0sFacts.Title(), &myCustomPhase{}) // insert a custom phase before the GatherK0sFacts phase
5151
func NewApply(opts ApplyOptions) *Apply {
52-
lockPhase := &phase.Lock{}
53-
unlockPhase := lockPhase.UnlockPhase()
52+
// lockPhase := &phase.Lock{}
53+
// unlockPhase := lockPhase.UnlockPhase()
5454
apply := &Apply{
5555
ApplyOptions: opts,
5656
Phases: phase.Phases{
5757
&phase.DefaultK0sVersion{},
58-
&phase.Connect{},
59-
&phase.DetectOS{},
60-
lockPhase,
58+
&phase.Connect{},
59+
&phase.DetectOS{},
60+
// lockPhase,
6161
&phase.PrepareHosts{},
6262
&phase.GatherFacts{},
6363
&phase.ValidateHosts{},
@@ -90,13 +90,13 @@ func NewApply(opts ApplyOptions) *Apply {
9090
&phase.ResetControllers{NoDrain: opts.NoDrain},
9191
&phase.RunHooks{Stage: "after", Action: "apply"},
9292
&phase.ApplyManifests{},
93-
unlockPhase,
94-
&phase.Disconnect{},
93+
// unlockPhase,
9594
},
9695
}
9796
if opts.KubeconfigOut != nil {
98-
apply.Phases.InsertBefore(unlockPhase.Title(), &phase.GetKubeconfig{APIAddress: opts.KubeconfigAPIAddress, User: opts.KubeconfigUser, Cluster: opts.KubeconfigCluster})
97+
apply.Phases = append(apply.Phases, &phase.GetKubeconfig{APIAddress: opts.KubeconfigAPIAddress, User: opts.KubeconfigUser, Cluster: opts.KubeconfigCluster})
9998
}
99+
apply.Phases = append(apply.Phases, &phase.Disconnect{})
100100

101101
return apply
102102
}

configurer/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,6 @@ type Configurer interface {
5858
SetPath(string, string)
5959
SystemTime(os.Host) (time.Time, error)
6060
Touch(os.Host, string, time.Time, ...exec.Option) error
61+
Dir(string) string
62+
Base(string) string
6163
}

configurer/linux.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,13 @@ func (l *Linux) SystemTime(h os.Host) (time.Time, error) {
296296
}
297297
return time.Unix(unixTime, 0), nil
298298
}
299+
300+
// Dir returns the directory part of a path
301+
func (l *Linux) Dir(p string) string {
302+
return path.Dir(p)
303+
}
304+
305+
// Base returns the base part of a path
306+
func (l *Linux) Base(p string) string {
307+
return path.Base(p)
308+
}

configurer/windows.go

Lines changed: 108 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package configurer
22

33
import (
4-
"bufio"
54
"fmt"
6-
"path/filepath"
5+
"path"
76
"strconv"
87
"strings"
98
"sync"
109
"time"
1110

11+
"github.com/k0sproject/k0sctl/internal/shell"
1212
"github.com/k0sproject/rig/exec"
1313
"github.com/k0sproject/rig/os"
1414
ps "github.com/k0sproject/rig/pkg/powershell"
@@ -29,10 +29,10 @@ func (w *BaseWindows) OSKind() string {
2929
func (w *BaseWindows) initPaths() {
3030
w.pathOnce.Do(func() {
3131
w.paths = map[string]string{
32-
"K0sBinaryPath": `C:\\Program Files\\k0s\\k0s.exe`,
33-
"K0sConfigPath": `C:\\ProgramData\\k0s\\k0s.yaml`,
34-
"K0sJoinTokenPath": `C:\\ProgramData\\k0s\\k0stoken`,
35-
"DataDirDefaultPath": `C:\\ProgramData\\k0s`,
32+
"K0sBinaryPath": `C:/Program Files/k0s/k0s.exe`,
33+
"K0sConfigPath": `C:/etc/k0s/k0s.yaml`,
34+
"K0sJoinTokenPath": `C:/etc/k0s/k0stoken`,
35+
"DataDirDefaultPath": `C:/var/lib/k0s`,
3636
}
3737
})
3838
}
@@ -93,7 +93,7 @@ func (w *BaseWindows) Arch(h os.Host) (string, error) {
9393

9494
// K0sCmdf can be used to construct k0s commands in sprintf style.
9595
func (w *BaseWindows) K0sCmdf(template string, args ...interface{}) string {
96-
return fmt.Sprintf(`& %s %s`, ps.DoubleQuotePath(w.K0sBinaryPath()), fmt.Sprintf(template, args...))
96+
return fmt.Sprintf(`%s %s`, ps.DoubleQuotePath(ps.ToWindowsPath(w.K0sBinaryPath())), fmt.Sprintf(template, args...))
9797
}
9898

9999
// K0sctlLockFilePath returns a path to a lock file
@@ -104,20 +104,41 @@ func (w *BaseWindows) K0sctlLockFilePath(h os.Host) string {
104104

105105
// TempFile returns a temp file path
106106
func (w *BaseWindows) TempFile(h os.Host) (string, error) {
107-
// Use .NET to generate a temp file path
108-
return h.ExecOutput(ps.Cmd(`[System.IO.Path]::GetTempFileName()`))
107+
output, err := h.ExecOutput(ps.Cmd(`[System.IO.Path]::GetTempFileName()`))
108+
if err != nil {
109+
return "", fmt.Errorf("failed to create temp file: %w", err)
110+
}
111+
output = strings.TrimSpace(output)
112+
// Normalize to use forward slashes
113+
output, err = shell.Unquote(output)
114+
if err != nil {
115+
return "", fmt.Errorf("failed to parse temp file path: %w", err)
116+
}
117+
output = strings.ReplaceAll(output, "\\", "/")
118+
return output, nil
109119
}
110120

111121
// TempDir returns a temp dir path
112122
func (w *BaseWindows) TempDir(h os.Host) (string, error) {
113123
// Create a unique temp directory and output its path
114124
script := `$p = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString()); New-Item -ItemType Directory -Path $p | Out-Null; Write-Output $p`
115-
return h.ExecOutput(ps.Cmd(script))
125+
output, err := h.ExecOutput(ps.Cmd(script))
126+
if err != nil {
127+
return "", fmt.Errorf("failed to create temp dir: %w", err)
128+
}
129+
output = strings.TrimSpace(output)
130+
// Normalize to use forward slashes
131+
output, err = shell.Unquote(output)
132+
if err != nil {
133+
return "", fmt.Errorf("failed to parse temp dir path: %w", err)
134+
}
135+
output = strings.ReplaceAll(output, "\\", "/")
136+
return output, nil
116137
}
117138

118139
// DownloadURL performs a download from a URL on the host
119140
func (w *BaseWindows) DownloadURL(h os.Host, url, destination string, opts ...exec.Option) error {
120-
cmd := ps.Cmd(fmt.Sprintf(`Invoke-WebRequest -UseBasicParsing -Uri %s -OutFile %s`, ps.SingleQuote(url), ps.DoubleQuotePath(destination)))
141+
cmd := ps.Cmd(fmt.Sprintf(`Invoke-WebRequest -UseBasicParsing -Uri %s -OutFile %s`, ps.SingleQuote(url), ps.DoubleQuotePath(ps.ToWindowsPath(destination))))
121142
if err := h.Exec(cmd, opts...); err != nil {
122143
return fmt.Errorf("download failed: %w", err)
123144
}
@@ -127,19 +148,19 @@ func (w *BaseWindows) DownloadURL(h os.Host, url, destination string, opts ...ex
127148
// ReplaceK0sTokenPath replaces the config path in the service stub
128149
func (w *BaseWindows) ReplaceK0sTokenPath(h os.Host, spath string) error {
129150
// Replace literal REPLACEME with actual token path
130-
cmd := ps.Cmd(fmt.Sprintf(`(Get-Content -Path %s) -replace 'REPLACEME', %s | Set-Content -Path %s -Encoding ascii`, ps.DoubleQuotePath(spath), ps.SingleQuote(w.K0sJoinTokenPath()), ps.DoubleQuotePath(spath)))
151+
cmd := ps.Cmd(fmt.Sprintf(`(Get-Content -Path %s) -replace 'REPLACEME', %s | Set-Content -Path %s -Encoding ascii`, ps.DoubleQuotePath(ps.ToWindowsPath(spath)), ps.SingleQuote(ps.ToWindowsPath(w.K0sJoinTokenPath())), ps.DoubleQuotePath(ps.ToWindowsPath(spath))))
131152
return h.Exec(cmd)
132153
}
133154

134155
// FileContains returns true if a file contains the substring
135156
func (w *BaseWindows) FileContains(h os.Host, path, s string) bool {
136-
cmd := ps.Cmd(fmt.Sprintf(`if (Select-String -Path %s -Pattern %s -SimpleMatch -Quiet) { exit 0 } else { exit 1 }`, ps.DoubleQuotePath(path), ps.SingleQuote(s)))
157+
cmd := ps.Cmd(fmt.Sprintf(`if (Select-String -Path %s -Pattern %s -SimpleMatch -Quiet) { exit 0 } else { exit 1 }`, ps.DoubleQuotePath(ps.ToWindowsPath(path)), ps.SingleQuote(ps.ToWindowsPath(s))))
137158
return h.Exec(cmd) == nil
138159
}
139160

140161
// MoveFile moves a file on the host
141162
func (w *BaseWindows) MoveFile(h os.Host, src, dst string) error {
142-
return h.Exec(ps.Cmd(fmt.Sprintf(`Move-Item -Force -Path %s -Destination %s`, ps.DoubleQuotePath(src), ps.DoubleQuotePath(dst))))
163+
return h.Exec(ps.Cmd(fmt.Sprintf(`Move-Item -Force -Path %s -Destination %s`, ps.DoubleQuotePath(ps.ToWindowsPath(src)), ps.DoubleQuotePath(ps.ToWindowsPath(dst)))))
143164
}
144165

145166
// Chown is a no-op on Windows; ownership semantics differ and are not managed here
@@ -149,17 +170,18 @@ func (w *BaseWindows) Chown(h os.Host, path, owner string, _ ...exec.Option) err
149170

150171
// KubeconfigPath returns the path to a kubeconfig on the host
151172
func (w *BaseWindows) KubeconfigPath(h os.Host, dataDir string) string {
152-
win := &os.Windows{}
153-
adminConfPath := filepath.Join(dataDir, "pki", "admin.conf")
154-
if win.FileExist(h, adminConfPath) {
173+
adminConfPath := path.Join(dataDir, "pki", "admin.conf")
174+
adminConfPath = strings.ReplaceAll(adminConfPath, "\\", "/")
175+
adminConfPath = ps.ToWindowsPath(adminConfPath)
176+
if err := h.Exec(ps.Cmd(fmt.Sprintf(`Test-Path -Path %s`, ps.DoubleQuotePath(adminConfPath))), exec.Sudo(h)); err == nil {
155177
return adminConfPath
156178
}
157-
return filepath.Join(dataDir, "kubelet.conf")
179+
return path.Join(dataDir, "kubelet.conf")
158180
}
159181

160182
// KubectlCmdf returns a command line in sprintf manner for running kubectl on the host using the kubeconfig from KubeconfigPath
161183
func (w *BaseWindows) KubectlCmdf(h os.Host, dataDir, s string, args ...interface{}) string {
162-
return fmt.Sprintf(`$env:KUBECONFIG=%s; %s`, ps.DoubleQuotePath(w.KubeconfigPath(h, dataDir)), w.K0sCmdf(`kubectl %s`, fmt.Sprintf(s, args...)))
184+
return ps.Cmd(fmt.Sprintf(`$env:KUBECONFIG=%s; %s`, ps.DoubleQuotePath(ps.ToWindowsPath(w.KubeconfigPath(h, dataDir))), w.K0sCmdf(`kubectl %s`, fmt.Sprintf(s, args...))))
163185
}
164186

165187
// HTTPStatus makes a HTTP GET request to the url and returns the status code or an error
@@ -177,37 +199,60 @@ func (w *BaseWindows) HTTPStatus(h os.Host, url string) (int, error) {
177199

178200
// PrivateInterface tries to find a private network interface (not implemented for Windows yet)
179201
func (w *BaseWindows) PrivateInterface(h os.Host) (string, error) {
180-
out, err := h.ExecOutput(ps.Cmd(`(Get-NetConnectionProfile -NetworkCategory Private | Select-Object -First 1).InterfaceAlias`))
181-
if err != nil || strings.TrimSpace(out) == "" {
182-
out, err = h.ExecOutput(ps.Cmd(`(Get-NetConnectionProfile | Select-Object -First 1).InterfaceAlias`))
183-
}
184-
if err != nil || strings.TrimSpace(out) == "" {
185-
return "", fmt.Errorf("failed to detect a private network interface, define the host privateInterface manually: %w", err)
202+
cmd := ps.Cmd(`
203+
$if = Get-NetIPAddress -AddressFamily IPv4 |
204+
Where-Object { $_.IPAddress -match '^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)' } |
205+
Sort-Object InterfaceMetric |
206+
Select-Object -First 1 -ExpandProperty InterfaceAlias;
207+
if (-not $if) {
208+
$if = Get-NetRoute -DestinationPrefix '0.0.0.0/0' |
209+
Sort-Object RouteMetric, InterfaceMetric |
210+
Select-Object -First 1 -ExpandProperty InterfaceAlias
211+
};
212+
$if`)
213+
cmd = strings.ReplaceAll(cmd, "\n", " ")
214+
cmd = strings.ReplaceAll(cmd, "\t", " ")
215+
output, err := h.ExecOutput(cmd, exec.Sudo(h))
216+
if err != nil {
217+
return "", fmt.Errorf("failed to detect private network interface: %s", err)
186218
}
187-
sc := bufio.NewScanner(strings.NewReader(out))
188-
if sc.Scan() {
189-
return strings.TrimSpace(sc.Text()), nil
219+
220+
iface := strings.TrimSpace(output)
221+
if iface == "" {
222+
return "", fmt.Errorf("no private interface found, define host privateInterface manually")
190223
}
191-
return "", fmt.Errorf("failed to detect a private network interface")
224+
225+
return iface, nil
192226
}
193227

194228
// PrivateAddress resolves internal ip from private interface (not implemented for Windows yet)
195229
func (w *BaseWindows) PrivateAddress(h os.Host, iface, publicip string) (string, error) {
196-
ip, err := h.ExecOutput(ps.Cmd(fmt.Sprintf(`(Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias %s).IPAddress`, ps.SingleQuote(iface))))
197-
if err != nil || strings.TrimSpace(ip) == "" {
198-
if !strings.HasPrefix(iface, "vEthernet") {
199-
ve := fmt.Sprintf("vEthernet (%s)", iface)
200-
ip, err = h.ExecOutput(ps.Cmd(fmt.Sprintf(`(Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias %s).IPAddress`, ps.SingleQuote(ve))))
201-
}
202-
}
230+
cmd := ps.Cmd(fmt.Sprintf(`
231+
(Get-NetIPAddress -InterfaceAlias %s -AddressFamily IPv4 |
232+
Where-Object {
233+
$_.AddressState -eq 'Preferred' -and
234+
$_.IPAddress -match '^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)' -and
235+
$_.IPAddress -ne '%s'
236+
} |
237+
Select-Object -First 1 -ExpandProperty IPAddress)
238+
`, ps.SingleQuote(iface), publicip))
239+
cmd = strings.ReplaceAll(cmd, "\n", " ")
240+
cmd = strings.ReplaceAll(cmd, "\t", " ")
241+
output, err := h.ExecOutput(cmd)
203242
if err != nil {
204-
return "", fmt.Errorf("failed to get IP address for interface %s: %w", iface, err)
243+
return "", fmt.Errorf("failed to get IP for interface %s: %s", iface, err)
244+
}
245+
246+
ip := strings.TrimSpace(output)
247+
if ip == "" {
248+
return "", fmt.Errorf("no IPv4 address found for interface %s", iface)
205249
}
206-
addr := strings.TrimSpace(ip)
207-
if addr != "" && addr != publicip {
208-
return addr, nil
250+
251+
if ip == publicip {
252+
return "", fmt.Errorf("resolved IP equals public IP, no private address found")
209253
}
210-
return "", fmt.Errorf("not found")
254+
255+
return ip, nil
211256
}
212257

213258
// UpsertFile creates a file in path with content only if the file does not exist already
@@ -219,25 +264,25 @@ func (w *BaseWindows) UpsertFile(h os.Host, path, content string) error {
219264
// Write content to temp file
220265
if err := h.Exec(ps.Cmd(fmt.Sprintf(`Set-Content -Path %s -Value @'
221266
%s
222-
'@ -Encoding ascii`, ps.DoubleQuotePath(tmpf), content))); err != nil {
267+
'@ -Encoding ascii`, ps.DoubleQuotePath(ps.ToWindowsPath(tmpf)), content))); err != nil {
223268
return err
224269
}
225270

226271
// Atomically move if destination does not exist
227-
script := ps.Cmd(fmt.Sprintf(`if (!(Test-Path -Path %s)) { Move-Item -Path %s -Destination %s } else { Remove-Item -Path %s -Force }`, ps.DoubleQuotePath(path), ps.DoubleQuotePath(tmpf), ps.DoubleQuotePath(path), ps.DoubleQuotePath(tmpf)))
272+
script := ps.Cmd(fmt.Sprintf(`if (!(Test-Path -Path %s)) { Move-Item -Path %s -Destination %s } else { Remove-Item -Path %s -Force }`, ps.DoubleQuotePath(ps.ToWindowsPath(path)), ps.DoubleQuotePath(ps.ToWindowsPath(tmpf)), ps.DoubleQuotePath(ps.ToWindowsPath(path)), ps.DoubleQuotePath(ps.ToWindowsPath(tmpf))))
228273
if err := h.Exec(script); err != nil {
229274
return fmt.Errorf("upsert failed: %w", err)
230275
}
231276

232277
// Ensure temp file is gone
233-
if h.Exec(ps.Cmd(fmt.Sprintf(`Test-Path -Path %s`, ps.DoubleQuotePath(tmpf)))) == nil {
278+
if h.Exec(ps.Cmd(fmt.Sprintf(`Test-Path -Path %s`, ps.DoubleQuotePath(ps.ToWindowsPath(tmpf))))) == nil {
234279
return fmt.Errorf("upsert failed")
235280
}
236281
return nil
237282
}
238283

239284
func (w *BaseWindows) DeleteDir(h os.Host, path string, opts ...exec.Option) error {
240-
return h.Exec(ps.Cmd(fmt.Sprintf(`Remove-Item -Recurse -Force -Path %s`, ps.DoubleQuotePath(path))), opts...)
285+
return h.Exec(ps.Cmd(fmt.Sprintf(`Remove-Item -Recurse -Force -Path %s`, ps.DoubleQuotePath(ps.ToWindowsPath(path)))), opts...)
241286
}
242287

243288
func (w *BaseWindows) MachineID(h os.Host) (string, error) {
@@ -257,3 +302,21 @@ func (w *BaseWindows) SystemTime(h os.Host) (time.Time, error) {
257302
}
258303
return time.Unix(unixTime, 0), nil
259304
}
305+
306+
// Dir returns the directory part of a path
307+
func (w *BaseWindows) Dir(path string) string {
308+
index := strings.LastIndexAny(path, `\/`)
309+
if index == -1 {
310+
return "."
311+
}
312+
return path[:index]
313+
}
314+
315+
// Base returns the last element of a path
316+
func (w *BaseWindows) Base(path string) string {
317+
index := strings.LastIndexAny(path, `\/`)
318+
if index == -1 {
319+
return path
320+
}
321+
return path[index+1:]
322+
}

phase/download_k0s.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ func (p *DownloadK0s) downloadK0s(_ context.Context, h *cluster.Host) error {
7777
if err != nil {
7878
return fmt.Errorf("failed to determine k0s download url: %w", err)
7979
}
80+
// Esnure directory exists
81+
log.Debugf("%s: ensuring k0s install directory exists %s", h, h.Configurer.Dir(tmp))
82+
if err := h.SudoFsys().MkDirAll(h.Configurer.Dir(tmp), fs.FileMode(0o755)); err != nil {
83+
return fmt.Errorf("failed to create k0s install directory: %w", err)
84+
}
8085
if err := h.Configurer.DownloadURL(h, url, tmp, exec.Sudo(h)); err != nil {
8186
return fmt.Errorf("failed to download k0s binary: %w", err)
8287
}

phase/install_binaries.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (p *InstallBinaries) Run(ctx context.Context) error {
6767
}
6868

6969
func (p *InstallBinaries) installBinary(_ context.Context, h *cluster.Host) error {
70+
logrus.Debugf("%s: installing k0s binary from tempfile %s to %s", h, h.Metadata.K0sBinaryTempFile, h.K0sInstallLocation())
7071
if err := h.UpdateK0sBinary(h.Metadata.K0sBinaryTempFile, p.Config.Spec.K0s.Version); err != nil {
7172
return fmt.Errorf("failed to install k0s binary: %w", err)
7273
}

0 commit comments

Comments
 (0)