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:
commit
d6f52ae557
4 changed files with 58 additions and 32 deletions
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue