Skip to content

Commit d6f52ae

Browse files
authored
Merge pull request cli#91 from github/edgonmsft/codespaces-ssh-rpc
Implement RPC interface so that the codespaces agent starts and configures ssh in the codespace
2 parents fee4b48 + ebb04d1 commit d6f52ae

File tree

4 files changed

+58
-32
lines changed

4 files changed

+58
-32
lines changed

cmd/ghcs/logs.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ func logs(tail bool, codespaceName string) error {
5757
return fmt.Errorf("connecting to liveshare: %v", err)
5858
}
5959

60-
tunnelPort, connClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, 0)
60+
remoteSSHServerPort, sshUser, err := codespaces.StartSSHServer(ctx, lsclient, log)
61+
if err != nil {
62+
return fmt.Errorf("error getting ssh server details: %v", err)
63+
}
64+
65+
tunnelPort, connClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, 0, remoteSSHServerPort)
6166
if err != nil {
6267
return fmt.Errorf("make ssh tunnel: %v", err)
6368
}
@@ -67,7 +72,7 @@ func logs(tail bool, codespaceName string) error {
6772
cmdType = "tail -f"
6873
}
6974

70-
dst := fmt.Sprintf("%s@localhost", getSSHUser(codespace))
75+
dst := fmt.Sprintf("%s@localhost", sshUser)
7176
stdout, err := codespaces.RunCommand(
7277
ctx, tunnelPort, dst, fmt.Sprintf("%v /workspaces/.codespaces/.persistedshare/creation.log", cmdType),
7378
)

cmd/ghcs/ssh.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ func ssh(sshProfile, codespaceName string, sshServerPort int) error {
5858
return fmt.Errorf("error connecting to liveshare: %v", err)
5959
}
6060

61+
remoteSSHServerPort, sshUser, err := codespaces.StartSSHServer(ctx, lsclient, log)
62+
if err != nil {
63+
return fmt.Errorf("error getting ssh server details: %v", err)
64+
}
65+
6166
terminal, err := liveshare.NewTerminal(lsclient)
6267
if err != nil {
6368
return fmt.Errorf("error creating liveshare terminal: %v", err)
@@ -70,20 +75,20 @@ func ssh(sshProfile, codespaceName string, sshServerPort int) error {
7075
return fmt.Errorf("error getting container id: %v", err)
7176
}
7277

73-
if err := setupSSH(ctx, log, terminal, containerID, codespace.RepositoryName); err != nil {
78+
if err := setupEnv(ctx, log, terminal, containerID, codespace.RepositoryName, sshUser); err != nil {
7479
return fmt.Errorf("error creating ssh server: %v", err)
7580
}
7681
}
7782
log.Print("\n")
7883

79-
tunnelPort, tunnelClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, sshServerPort)
84+
tunnelPort, tunnelClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, sshServerPort, remoteSSHServerPort)
8085
if err != nil {
8186
return fmt.Errorf("make ssh tunnel: %v", err)
8287
}
8388

8489
connectDestination := sshProfile
8590
if connectDestination == "" {
86-
connectDestination = fmt.Sprintf("%s@localhost", getSSHUser(codespace))
91+
connectDestination = fmt.Sprintf("%s@localhost", sshUser)
8792
}
8893

8994
usingCustomPort := tunnelPort == sshServerPort
@@ -135,8 +140,8 @@ func getContainerID(ctx context.Context, logger *output.Logger, terminal *livesh
135140
return containerID, nil
136141
}
137142

138-
func setupSSH(ctx context.Context, logger *output.Logger, terminal *liveshare.Terminal, containerID, repositoryName string) error {
139-
setupBashProfileCmd := fmt.Sprintf(`echo "cd /workspaces/%v; export $(cat /workspaces/.codespaces/shared/.env | xargs); exec /bin/zsh;" > /home/codespace/.bash_profile`, repositoryName)
143+
func setupEnv(ctx context.Context, logger *output.Logger, terminal *liveshare.Terminal, containerID, repositoryName, containerUser string) error {
144+
setupBashProfileCmd := fmt.Sprintf(`echo "cd /workspaces/%v; export $(cat /workspaces/.codespaces/shared/.env | xargs); exec /bin/zsh;" > /home/%v/.bash_profile`, repositoryName, containerUser)
140145

141146
logger.Print(".")
142147
compositeCommand := []string{setupBashProfileCmd}
@@ -156,10 +161,3 @@ func setupSSH(ctx context.Context, logger *output.Logger, terminal *liveshare.Te
156161

157162
return nil
158163
}
159-
160-
func getSSHUser(codespace *api.Codespace) string {
161-
if codespace.RepositoryNWO == "github/github" {
162-
return "root"
163-
}
164-
return "codespace"
165-
}

internal/codespaces/ssh.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package codespaces
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78
"math/rand"
@@ -14,7 +15,7 @@ import (
1415
"github.com/github/go-liveshare"
1516
)
1617

17-
func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort int) (int, <-chan error, error) {
18+
func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, localSSHPort int, remoteSSHPort int) (int, <-chan error, error) {
1819
tunnelClosed := make(chan error)
1920

2021
server, err := liveshare.NewServer(lsclient)
@@ -24,12 +25,11 @@ func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort i
2425

2526
rand.Seed(time.Now().Unix())
2627
port := rand.Intn(9999-2000) + 2000 // improve this obviously
27-
if serverPort != 0 {
28-
port = serverPort
28+
if localSSHPort != 0 {
29+
port = localSSHPort
2930
}
3031

31-
// TODO(josebalius): This port won't always be 2222
32-
if err := server.StartSharing(ctx, "sshd", 2222); err != nil {
32+
if err := server.StartSharing(ctx, "sshd", remoteSSHPort); err != nil {
3333
return 0, nil, fmt.Errorf("sharing sshd port: %v", err)
3434
}
3535

@@ -45,6 +45,33 @@ func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort i
4545
return port, tunnelClosed, nil
4646
}
4747

48+
// StartSSHServer installs (if necessary) and starts the SSH in the codespace.
49+
// It returns the remote port where it is running, the user to log in with, or an error if something failed.
50+
func StartSSHServer(ctx context.Context, client *liveshare.Client, log logger) (serverPort int, user string, err error) {
51+
log.Println("Fetching SSH details...")
52+
53+
sshServer, err := liveshare.NewSSHServer(client)
54+
if err != nil {
55+
return 0, "", fmt.Errorf("error creating live share: %v", err)
56+
}
57+
58+
sshServerStartResult, err := sshServer.StartRemoteServer(ctx)
59+
if err != nil {
60+
return 0, "", fmt.Errorf("error starting live share: %v", err)
61+
}
62+
63+
if !sshServerStartResult.Result {
64+
return 0, "", errors.New(sshServerStartResult.Message)
65+
}
66+
67+
portInt, err := strconv.Atoi(sshServerStartResult.ServerPort)
68+
if err != nil {
69+
return 0, "", fmt.Errorf("error parsing port: %v", err)
70+
}
71+
72+
return portInt, sshServerStartResult.User, nil
73+
}
74+
4875
func makeSSHArgs(port int, dst, cmd string) ([]string, []string) {
4976
connArgs := []string{"-p", strconv.Itoa(port), "-o", "NoHostAuthenticationForLocalhost=yes"}
5077
cmdArgs := append([]string{dst, "-X", "-Y", "-C"}, connArgs...) // X11, X11Trust, Compression

internal/codespaces/states.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u
4545
return fmt.Errorf("connect to liveshare: %v", err)
4646
}
4747

48-
tunnelPort, connClosed, err := MakeSSHTunnel(ctx, lsclient, 0)
48+
remoteSSHServerPort, sshUser, err := StartSSHServer(ctx, lsclient, log)
49+
if err != nil {
50+
return fmt.Errorf("error getting ssh server details: %v", err)
51+
}
52+
53+
tunnelPort, connClosed, err := MakeSSHTunnel(ctx, lsclient, 0, remoteSSHServerPort)
4954
if err != nil {
5055
return fmt.Errorf("make ssh tunnel: %v", err)
5156
}
@@ -60,7 +65,7 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u
6065
case err := <-connClosed:
6166
return fmt.Errorf("connection closed: %v", err)
6267
case <-t.C:
63-
states, err := getPostCreateOutput(ctx, tunnelPort, codespace)
68+
states, err := getPostCreateOutput(ctx, tunnelPort, codespace, sshUser)
6469
if err != nil {
6570
return fmt.Errorf("get post create output: %v", err)
6671
}
@@ -70,9 +75,9 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u
7075
}
7176
}
7277

73-
func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Codespace) ([]PostCreateState, error) {
78+
func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Codespace, user string) ([]PostCreateState, error) {
7479
stdout, err := RunCommand(
75-
ctx, tunnelPort, sshDestination(codespace),
80+
ctx, tunnelPort, fmt.Sprintf("%s@localhost", user),
7681
"cat /workspaces/.codespaces/shared/postCreateOutput.json",
7782
)
7883
if err != nil {
@@ -94,12 +99,3 @@ func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Cod
9499

95100
return output.Steps, nil
96101
}
97-
98-
// TODO(josebalius): this won't be needed soon
99-
func sshDestination(codespace *api.Codespace) string {
100-
user := "codespace"
101-
if codespace.RepositoryNWO == "github/github" {
102-
user = "root"
103-
}
104-
return user + "@localhost"
105-
}

0 commit comments

Comments
 (0)