package installer import ( "crypto/ed25519" "crypto/rand" "fmt" "net" "strings" "sync" "testing" "time" "golang.org/x/crypto/ssh" "rocketgit.ru/rsmon/worker/internal/sshinstall" ) const ( fakeSSHPassword = "fake-ssh-password" fakeCommitHex = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" fakeOsRelease = "ID=ubuntu\nNAME=\"Ubuntu\"\nVERSION_ID=24.04\n" ) // fakeSSHServer is a minimal in-process SSH server that simulates a // remote Linux host for the SourceInstall orchestration tests. It uses // real golang.org/x/crypto/ssh transport (no mocked SSH library), so the // executor's dial, session, exec, and stdout/stderr plumbing is // exercised end to end, and it records every command it ran. type fakeSSHServer struct { addr string onExec func(command string) (stdout, stderr string, code int) mu sync.Mutex commands []string } func startFakeSSHServer(t *testing.T, onExec func(command string) (string, string, int)) *fakeSSHServer { t.Helper() _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } signer, err := ssh.NewSignerFromKey(priv) if err != nil { t.Fatal(err) } config := &ssh.ServerConfig{ PasswordCallback: func(_ ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { if string(pass) == fakeSSHPassword { return nil, nil } return nil, fmt.Errorf("password rejected") }, } config.AddHostKey(signer) ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = ln.Close() }) srv := &fakeSSHServer{addr: ln.Addr().String(), onExec: onExec} go func() { for { conn, err := ln.Accept() if err != nil { return } go srv.handleConn(conn, config) } }() return srv } func (s *fakeSSHServer) Port() int { _, port, err := net.SplitHostPort(s.addr) if err != nil { return 0 } p := 0 fmt.Sscanf(port, "%d", &p) return p } func (s *fakeSSHServer) Commands() []string { s.mu.Lock() defer s.mu.Unlock() return append([]string(nil), s.commands...) } func (s *fakeSSHServer) handleConn(conn net.Conn, config *ssh.ServerConfig) { sconn, chans, reqs, err := ssh.NewServerConn(conn, config) if err != nil { return } defer sconn.Close() //nolint:errcheck go ssh.DiscardRequests(reqs) for newChannel := range chans { if newChannel.ChannelType() != "session" { _ = newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") continue } channel, requests, err := newChannel.Accept() if err != nil { continue } go func() { defer channel.Close() s.handleSession(channel, requests) }() } } func (s *fakeSSHServer) handleSession(channel ssh.Channel, requests <-chan *ssh.Request) { for req := range requests { if req.Type != "exec" { if req.WantReply { _ = req.Reply(false, nil) } continue } var payload struct{ Command string } if err := ssh.Unmarshal(req.Payload, &payload); err != nil { if req.WantReply { _ = req.Reply(false, nil) } continue } if req.WantReply { _ = req.Reply(true, nil) } s.mu.Lock() s.commands = append(s.commands, payload.Command) s.mu.Unlock() s.execCommand(channel, payload.Command) return } } func (s *fakeSSHServer) execCommand(channel ssh.Channel, command string) { handler := s.onExec if handler == nil { handler = defaultFakeExec } stdout, stderr, code := handler(command) _, _ = channel.Write([]byte(stdout)) _, _ = channel.Stderr().Write([]byte(stderr)) _, _ = channel.SendRequest("exit-status", false, ssh.Marshal(struct{ Status uint32 }{uint32(code)})) _ = channel.CloseWrite() } // defaultFakeExec simulates a minimal Linux host: it answers os-release, // uname, the init-marker file probe, origin URL, branch resolution, and // rev-parse, and accepts every install step. A pinned-branch existence // check (show-ref) fails by default so the missing-branch path is // exercised without extra setup. func defaultFakeExec(command string) (string, string, int) { switch { case strings.Contains(command, "cat /etc/os-release"): return fakeOsRelease, "", 0 case strings.Contains(command, "uname -m"): return "x86_64\n", "", 0 case strings.Contains(command, "[ -e"): return "/usr/lib/systemd/system\n", "", 0 case strings.Contains(command, "remote.origin.url"): return sshinstall.DefaultRepo + "\n", "", 0 case strings.Contains(command, "symbolic-ref"): return "origin/master\n", "", 0 case strings.Contains(command, "rev-parse HEAD"): return fakeCommitHex + "\n", "", 0 case strings.Contains(command, "show-ref"): return "", "branch not found", 1 default: return "", "", 0 } } func testSSHOptions(port int) SourceInstallOptions { return SourceInstallOptions{ SSHOptions: SSHOptions{ Host: "127.0.0.1", Port: port, User: "root", Password: fakeSSHPassword, InsecureHostKey: true, }, } } func TestSourceInstallSSHFlow(t *testing.T) { srv := startFakeSSHServer(t, nil) res, err := SourceInstall(testSSHOptions(srv.Port())) if err != nil { t.Fatal(err) } if res.Detection.Distro != sshinstall.DistroUbuntu || res.Detection.PackageManager != sshinstall.PkgApt { t.Fatalf("detection = %+v", res.Detection) } if res.Detection.InitSystem != sshinstall.InitSystemd { t.Fatalf("init detection = %q, want systemd", res.Detection.InitSystem) } if res.Plan.Toolchain.Arch != "linux-amd64" || res.Plan.Toolchain.Version != "1.26.0" { t.Fatalf("toolchain = %+v", res.Plan.Toolchain) } if len(res.Plan.Packages) == 0 || res.Plan.Repo == "" { t.Fatalf("plan = %+v", res.Plan) } if res.ResolvedBranch != "master" || res.ResolvedCommit != fakeCommitHex { t.Fatalf("resolved = %s @ %s", res.ResolvedBranch, res.ResolvedCommit) } if res.ToolchainDir != "/usr/local/go" || res.StageBinary != "/opt/rsmon-worker-src/rsmon-worker" || res.RecordFile != "/opt/rsmon-worker-src/rsmon-worker.commit" { t.Fatalf("paths = %+v", res) } commands := srv.Commands() var joined strings.Builder for _, c := range commands { joined.WriteString(c) joined.WriteString("\n") } // Marker substrings that survive the nested `sh -c '