feat: add harness runner (scenario executor, alert receiver, action executor, judge, revert)

This commit is contained in:
seon committed 2026-09-24 02:06:08 +09:00
1 parent 8f5378b6ea
commit 5daf13b669
16 files changed
+2550

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
package main
import (
"bytes"
"context"
"fmt"
"net"
"os"
"time"
"golang.org/x/crypto/ssh"
)
// sshRun executes one command on a lab VM as the harness user (key from Secret Manager, design 6.11).
// Host resolution: IP as-is, "legacy:N" or pet VM short name via the compute API.
func (a *App) sshRun(ctx context.Context, host, command string) (string, error) {
ip := host
if net.ParseIP(host) == nil {
in, err := a.resolveInstance(ctx, host)
if err != nil {
return "", err
}
ip = in.IP
}
key, err := os.ReadFile(a.cfg.SSHKeyFile)
if err != nil {
return "", fmt.Errorf("ssh key: %w", err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return "", fmt.Errorf("ssh key parse: %w", err)
}
cfg := &ssh.ClientConfig{
User: a.cfg.SSHUser,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // lab VMs are recreated by autohealing; host keys are not pinned
Timeout: 15 * time.Second,
}
d := net.Dialer{Timeout: 15 * time.Second}
conn, err := d.DialContext(ctx, "tcp", ip+":22")
if err != nil {
return "", fmt.Errorf("dial %s: %w", ip, err)
}
c, chans, reqs, err := ssh.NewClientConn(conn, ip+":22", cfg)
if err != nil {
conn.Close()
return "", fmt.Errorf("ssh %s: %w", ip, err)
}
client := ssh.NewClient(c, chans, reqs)
defer client.Close()
sess, err := client.NewSession()
if err != nil {
return "", err
}
defer sess.Close()
var out bytes.Buffer
sess.Stdout, sess.Stderr = &out, &out
done := make(chan error, 1)
go func() { done <- sess.Run(command) }()
select {
case err := <-done:
if err != nil {
return out.String(), fmt.Errorf("%s: %w: %s", command, err, out.String())
}
return out.String(), nil
case <-ctx.Done():
return out.String(), ctx.Err()
}
}