Merge pull request #91 from github/edgonmsft/codespaces-ssh-rpc

Implement RPC interface so that the codespaces agent starts and configures ssh in the codespace
This commit is contained in:
Edmundo Gonzalez 2021-08-31 13:05:56 -07:00 committed by GitHub
commit d6f52ae557
4 changed files with 58 additions and 32 deletions

View file

@ -57,7 +57,12 @@ func logs(tail bool, codespaceName string) error {
return fmt.Errorf("connecting to liveshare: %v", err)
}
tunnelPort, connClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, 0)
remoteSSHServerPort, sshUser, err := codespaces.StartSSHServer(ctx, lsclient, log)
if err != nil {
return fmt.Errorf("error getting ssh server details: %v", err)
}
tunnelPort, connClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, 0, remoteSSHServerPort)
if err != nil {
return fmt.Errorf("make ssh tunnel: %v", err)
}
@ -67,7 +72,7 @@ func logs(tail bool, codespaceName string) error {
cmdType = "tail -f"
}
dst := fmt.Sprintf("%s@localhost", getSSHUser(codespace))
dst := fmt.Sprintf("%s@localhost", sshUser)
stdout, err := codespaces.RunCommand(
ctx, tunnelPort, dst, fmt.Sprintf("%v /workspaces/.codespaces/.persistedshare/creation.log", cmdType),
)

View file

@ -58,6 +58,11 @@ func ssh(sshProfile, codespaceName string, sshServerPort int) error {
return fmt.Errorf("error connecting to liveshare: %v", err)
}
remoteSSHServerPort, sshUser, err := codespaces.StartSSHServer(ctx, lsclient, log)
if err != nil {
return fmt.Errorf("error getting ssh server details: %v", err)
}
terminal, err := liveshare.NewTerminal(lsclient)
if err != nil {
return fmt.Errorf("error creating liveshare terminal: %v", err)
@ -70,20 +75,20 @@ func ssh(sshProfile, codespaceName string, sshServerPort int) error {
return fmt.Errorf("error getting container id: %v", err)
}
if err := setupSSH(ctx, log, terminal, containerID, codespace.RepositoryName); err != nil {
if err := setupEnv(ctx, log, terminal, containerID, codespace.RepositoryName, sshUser); err != nil {
return fmt.Errorf("error creating ssh server: %v", err)
}
}
log.Print("\n")
tunnelPort, tunnelClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, sshServerPort)
tunnelPort, tunnelClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, sshServerPort, remoteSSHServerPort)
if err != nil {
return fmt.Errorf("make ssh tunnel: %v", err)
}
connectDestination := sshProfile
if connectDestination == "" {
connectDestination = fmt.Sprintf("%s@localhost", getSSHUser(codespace))
connectDestination = fmt.Sprintf("%s@localhost", sshUser)
}
usingCustomPort := tunnelPort == sshServerPort
@ -135,8 +140,8 @@ func getContainerID(ctx context.Context, logger *output.Logger, terminal *livesh
return containerID, nil
}
func setupSSH(ctx context.Context, logger *output.Logger, terminal *liveshare.Terminal, containerID, repositoryName string) error {
setupBashProfileCmd := fmt.Sprintf(`echo "cd /workspaces/%v; export $(cat /workspaces/.codespaces/shared/.env | xargs); exec /bin/zsh;" > /home/codespace/.bash_profile`, repositoryName)
func setupEnv(ctx context.Context, logger *output.Logger, terminal *liveshare.Terminal, containerID, repositoryName, containerUser string) error {
setupBashProfileCmd := fmt.Sprintf(`echo "cd /workspaces/%v; export $(cat /workspaces/.codespaces/shared/.env | xargs); exec /bin/zsh;" > /home/%v/.bash_profile`, repositoryName, containerUser)
logger.Print(".")
compositeCommand := []string{setupBashProfileCmd}
@ -156,10 +161,3 @@ func setupSSH(ctx context.Context, logger *output.Logger, terminal *liveshare.Te
return nil
}
func getSSHUser(codespace *api.Codespace) string {
if codespace.RepositoryNWO == "github/github" {
return "root"
}
return "codespace"
}

View file

@ -2,6 +2,7 @@ package codespaces
import (
"context"
"errors"
"fmt"
"io"
"math/rand"
@ -14,7 +15,7 @@ import (
"github.com/github/go-liveshare"
)
func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort int) (int, <-chan error, error) {
func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, localSSHPort int, remoteSSHPort int) (int, <-chan error, error) {
tunnelClosed := make(chan error)
server, err := liveshare.NewServer(lsclient)
@ -24,12 +25,11 @@ func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort i
rand.Seed(time.Now().Unix())
port := rand.Intn(9999-2000) + 2000 // improve this obviously
if serverPort != 0 {
port = serverPort
if localSSHPort != 0 {
port = localSSHPort
}
// TODO(josebalius): This port won't always be 2222
if err := server.StartSharing(ctx, "sshd", 2222); err != nil {
if err := server.StartSharing(ctx, "sshd", remoteSSHPort); err != nil {
return 0, nil, fmt.Errorf("sharing sshd port: %v", err)
}
@ -45,6 +45,33 @@ func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort i
return port, tunnelClosed, nil
}
// StartSSHServer installs (if necessary) and starts the SSH in the codespace.
// It returns the remote port where it is running, the user to log in with, or an error if something failed.
func StartSSHServer(ctx context.Context, client *liveshare.Client, log logger) (serverPort int, user string, err error) {
log.Println("Fetching SSH details...")
sshServer, err := liveshare.NewSSHServer(client)
if err != nil {
return 0, "", fmt.Errorf("error creating live share: %v", err)
}
sshServerStartResult, err := sshServer.StartRemoteServer(ctx)
if err != nil {
return 0, "", fmt.Errorf("error starting live share: %v", err)
}
if !sshServerStartResult.Result {
return 0, "", errors.New(sshServerStartResult.Message)
}
portInt, err := strconv.Atoi(sshServerStartResult.ServerPort)
if err != nil {
return 0, "", fmt.Errorf("error parsing port: %v", err)
}
return portInt, sshServerStartResult.User, nil
}
func makeSSHArgs(port int, dst, cmd string) ([]string, []string) {
connArgs := []string{"-p", strconv.Itoa(port), "-o", "NoHostAuthenticationForLocalhost=yes"}
cmdArgs := append([]string{dst, "-X", "-Y", "-C"}, connArgs...) // X11, X11Trust, Compression

View file

@ -45,7 +45,12 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u
return fmt.Errorf("connect to liveshare: %v", err)
}
tunnelPort, connClosed, err := MakeSSHTunnel(ctx, lsclient, 0)
remoteSSHServerPort, sshUser, err := StartSSHServer(ctx, lsclient, log)
if err != nil {
return fmt.Errorf("error getting ssh server details: %v", err)
}
tunnelPort, connClosed, err := MakeSSHTunnel(ctx, lsclient, 0, remoteSSHServerPort)
if err != nil {
return fmt.Errorf("make ssh tunnel: %v", err)
}
@ -60,7 +65,7 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u
case err := <-connClosed:
return fmt.Errorf("connection closed: %v", err)
case <-t.C:
states, err := getPostCreateOutput(ctx, tunnelPort, codespace)
states, err := getPostCreateOutput(ctx, tunnelPort, codespace, sshUser)
if err != nil {
return fmt.Errorf("get post create output: %v", err)
}
@ -70,9 +75,9 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u
}
}
func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Codespace) ([]PostCreateState, error) {
func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Codespace, user string) ([]PostCreateState, error) {
stdout, err := RunCommand(
ctx, tunnelPort, sshDestination(codespace),
ctx, tunnelPort, fmt.Sprintf("%s@localhost", user),
"cat /workspaces/.codespaces/shared/postCreateOutput.json",
)
if err != nil {
@ -94,12 +99,3 @@ func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Cod
return output.Steps, nil
}
// TODO(josebalius): this won't be needed soon
func sshDestination(codespace *api.Codespace) string {
user := "codespace"
if codespace.RepositoryNWO == "github/github" {
user = "root"
}
return user + "@localhost"
}