Skip to content

Commit 61a5132

Browse files
committed
Add external support for provision scripts
The provision scripts are run with ssh, instead of being started by cloud-init like other drivers. The ExecuteScript was duplicated from sshocker, since it needs support for running with "sudo". Signed-off-by: Anders F Björklund <[email protected]>
1 parent 80a1e4e commit 61a5132

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

pkg/hostagent/hostagent.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,12 @@ sudo chown -R "${USER}" /run/host-services`
564564
})
565565
}
566566

567+
if *a.instConfig.VMType == limatype.EXT {
568+
if err := a.runProvisionScripts(); err != nil {
569+
return err
570+
}
571+
}
572+
567573
staticPortForwards := a.separateStaticPortForwards()
568574
a.addStaticPortForwardsFromList(ctx, staticPortForwards)
569575

pkg/hostagent/provision.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package hostagent
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
10+
"github.com/lima-vm/sshocker/pkg/ssh"
11+
"github.com/sirupsen/logrus"
12+
13+
"github.com/lima-vm/lima/v2/pkg/limatype"
14+
)
15+
16+
func (a *HostAgent) runProvisionScripts() error {
17+
var errs []error
18+
19+
for i, f := range a.instConfig.Provision {
20+
switch f.Mode {
21+
case limatype.ProvisionModeSystem, limatype.ProvisionModeUser:
22+
logrus.Infof("Running %s provision %d of %d", f.Mode, i+1, len(a.instConfig.Provision))
23+
err := a.waitForProvision(
24+
provision{
25+
description: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
26+
sudo: f.Mode == limatype.ProvisionModeSystem,
27+
script: f.Script,
28+
})
29+
if err != nil {
30+
errs = append(errs, err)
31+
}
32+
case limatype.ProvisionModeDependency, limatype.ProvisionModeBoot:
33+
logrus.Infof("Skipping %s provision %d of %d", f.Mode, i+1, len(a.instConfig.Provision))
34+
continue
35+
default:
36+
return fmt.Errorf("unknown provision mode %q", f.Mode)
37+
}
38+
}
39+
return errors.Join(errs...)
40+
}
41+
42+
func (a *HostAgent) waitForProvision(p provision) error {
43+
if p.sudo {
44+
return a.waitForSystemProvision(p)
45+
}
46+
return a.waitForUserProvision(p)
47+
}
48+
49+
func (a *HostAgent) waitForSystemProvision(p provision) error {
50+
logrus.Debugf("executing script %q", p.description)
51+
stdout, stderr, err := sudoExecuteScript(a.instSSHAddress, a.sshLocalPort, a.sshConfig, p.script, p.description)
52+
logrus.Debugf("stdout=%q, stderr=%q, err=%v", stdout, stderr, err)
53+
if err != nil {
54+
return fmt.Errorf("stdout=%q, stderr=%q: %w", stdout, stderr, err)
55+
}
56+
return nil
57+
}
58+
59+
func (a *HostAgent) waitForUserProvision(p provision) error {
60+
logrus.Debugf("executing script %q", p.description)
61+
stdout, stderr, err := ssh.ExecuteScript(a.instSSHAddress, a.sshLocalPort, a.sshConfig, p.script, p.description)
62+
logrus.Debugf("stdout=%q, stderr=%q, err=%v", stdout, stderr, err)
63+
if err != nil {
64+
return fmt.Errorf("stdout=%q, stderr=%q: %w", stdout, stderr, err)
65+
}
66+
return nil
67+
}
68+
69+
type provision struct {
70+
description string
71+
script string
72+
sudo bool
73+
}

pkg/hostagent/sudo.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package hostagent
5+
6+
import (
7+
"bytes"
8+
"context"
9+
"errors"
10+
"fmt"
11+
"os/exec"
12+
"strconv"
13+
"strings"
14+
15+
"github.com/lima-vm/sshocker/pkg/ssh"
16+
"github.com/sirupsen/logrus"
17+
)
18+
19+
// sudoExecuteScript executes the given script (as root) on the remote host via stdin.
20+
// Returns stdout and stderr.
21+
//
22+
// scriptName is used only for readability of error strings.
23+
func sudoExecuteScript(host string, port int, c *ssh.SSHConfig, script, scriptName string) (stdout, stderr string, err error) {
24+
if c == nil {
25+
return "", "", errors.New("got nil SSHConfig")
26+
}
27+
interpreter, err := ssh.ParseScriptInterpreter(script)
28+
if err != nil {
29+
return "", "", err
30+
}
31+
sshBinary := c.Binary()
32+
sshArgs := c.Args()
33+
if port != 0 {
34+
sshArgs = append(sshArgs, "-p", strconv.Itoa(port))
35+
}
36+
sshArgs = append(sshArgs, host, "--", "sudo", interpreter)
37+
sshCmd := exec.CommandContext(context.TODO(), sshBinary, sshArgs...)
38+
sshCmd.Stdin = strings.NewReader(script)
39+
var buf bytes.Buffer
40+
sshCmd.Stderr = &buf
41+
logrus.Debugf("executing ssh for script %q: %s %v", scriptName, sshCmd.Path, sshCmd.Args)
42+
out, err := sshCmd.Output()
43+
if err != nil {
44+
return string(out), buf.String(), fmt.Errorf("failed to execute script %q: stdout=%q, stderr=%q: %w", scriptName, string(out), buf.String(), err)
45+
}
46+
return string(out), buf.String(), nil
47+
}

0 commit comments

Comments
 (0)