X Tutup
Skip to content

Commit d5b03df

Browse files
committed
Merge branch 'main' of github.com:github/ghcs into jg/poll-on-async-creation
2 parents 323462c + 090e0c8 commit d5b03df

File tree

13 files changed

+144
-74
lines changed

13 files changed

+144
-74
lines changed

cmd/ghcs/code.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
package main
1+
package ghcs
22

33
import (
44
"context"
55
"fmt"
66
"net/url"
7-
"os"
87

98
"github.com/github/ghcs/internal/api"
109
"github.com/skratchdot/open-golang/open"
@@ -32,12 +31,8 @@ func newCodeCmd() *cobra.Command {
3231
return codeCmd
3332
}
3433

35-
func init() {
36-
rootCmd.AddCommand(newCodeCmd())
37-
}
38-
3934
func code(codespaceName string, useInsiders bool) error {
40-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
35+
apiClient := api.New(GithubToken)
4136
ctx := context.Background()
4237

4338
user, err := apiClient.GetUser(ctx)

cmd/ghcs/common.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
package main
1+
package ghcs
22

33
// This file defines functions common to the entire ghcs command set.
44

55
import (
66
"context"
77
"errors"
88
"fmt"
9+
"io"
910
"os"
1011
"sort"
1112

@@ -93,6 +94,12 @@ func getOrChooseCodespace(ctx context.Context, apiClient *api.API, user *api.Use
9394
return codespace, token, nil
9495
}
9596

97+
func safeClose(closer io.Closer, err *error) {
98+
if closeErr := closer.Close(); *err == nil {
99+
*err = closeErr
100+
}
101+
}
102+
96103
// hasTTY indicates whether the process connected to a terminal.
97104
// It is not portable to assume stdin/stdout are fds 0 and 1.
98105
var hasTTY = term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd()))
@@ -120,3 +127,17 @@ func ask(qs []*survey.Question, response interface{}) error {
120127
}
121128
return err
122129
}
130+
131+
// checkAuthorizedKeys reports an error if the user has not registered any SSH keys;
132+
// see https://github.com/github/ghcs/issues/166#issuecomment-921769703.
133+
// The check is not required for security but it improves the error message.
134+
func checkAuthorizedKeys(ctx context.Context, client *api.API, user string) error {
135+
keys, err := client.AuthorizedKeys(ctx, user)
136+
if err != nil {
137+
return fmt.Errorf("failed to read GitHub-authorized SSH keys for %s: %w", user, err)
138+
}
139+
if len(keys) == 0 {
140+
return fmt.Errorf("user %s has no GitHub-authorized SSH keys", user)
141+
}
142+
return nil // success
143+
}

cmd/ghcs/create.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package ghcs
22

33
import (
44
"context"
@@ -43,13 +43,9 @@ func newCreateCmd() *cobra.Command {
4343
return createCmd
4444
}
4545

46-
func init() {
47-
rootCmd.AddCommand(newCreateCmd())
48-
}
49-
5046
func create(opts *createOptions) error {
5147
ctx := context.Background()
52-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
48+
apiClient := api.New(GithubToken)
5349
locationCh := getLocation(ctx, apiClient)
5450
userCh := getUser(ctx, apiClient)
5551
log := output.NewLogger(os.Stdout, os.Stderr, false)

cmd/ghcs/delete.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package ghcs
22

33
import (
44
"context"
@@ -27,6 +27,9 @@ func newDeleteCmd() *cobra.Command {
2727
Use: "delete",
2828
Short: "Delete a codespace",
2929
RunE: func(cmd *cobra.Command, args []string) error {
30+
if len(args) > 0 {
31+
return fmt.Errorf("delete: unexpected positional arguments")
32+
}
3033
switch {
3134
case allCodespaces && repo != "":
3235
return errors.New("both --all and --repo is not supported")
@@ -48,12 +51,8 @@ func newDeleteCmd() *cobra.Command {
4851
return deleteCmd
4952
}
5053

51-
func init() {
52-
rootCmd.AddCommand(newDeleteCmd())
53-
}
54-
5554
func delete_(log *output.Logger, codespaceName string, force bool) error {
56-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
55+
apiClient := api.New(GithubToken)
5756
ctx := context.Background()
5857

5958
user, err := apiClient.GetUser(ctx)
@@ -85,7 +84,7 @@ func delete_(log *output.Logger, codespaceName string, force bool) error {
8584
}
8685

8786
func deleteAll(log *output.Logger, force bool) error {
88-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
87+
apiClient := api.New(GithubToken)
8988
ctx := context.Background()
9089

9190
user, err := apiClient.GetUser(ctx)
@@ -124,7 +123,7 @@ func deleteAll(log *output.Logger, force bool) error {
124123
}
125124

126125
func deleteByRepo(log *output.Logger, repo string, force bool) error {
127-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
126+
apiClient := api.New(GithubToken)
128127
ctx := context.Background()
129128

130129
user, err := apiClient.GetUser(ctx)

cmd/ghcs/list.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package ghcs
22

33
import (
44
"context"
@@ -31,12 +31,8 @@ func newListCmd() *cobra.Command {
3131
return listCmd
3232
}
3333

34-
func init() {
35-
rootCmd.AddCommand(newListCmd())
36-
}
37-
3834
func list(opts *listOptions) error {
39-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
35+
apiClient := api.New(GithubToken)
4036
ctx := context.Background()
4137

4238
user, err := apiClient.GetUser(ctx)

cmd/ghcs/logs.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package ghcs
22

33
import (
44
"context"
@@ -36,22 +36,23 @@ func newLogsCmd() *cobra.Command {
3636
return logsCmd
3737
}
3838

39-
func init() {
40-
rootCmd.AddCommand(newLogsCmd())
41-
}
42-
43-
func logs(ctx context.Context, log *output.Logger, codespaceName string, follow bool) error {
39+
func logs(ctx context.Context, log *output.Logger, codespaceName string, follow bool) (err error) {
4440
// Ensure all child tasks (port forwarding, remote exec) terminate before return.
4541
ctx, cancel := context.WithCancel(ctx)
4642
defer cancel()
4743

48-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
44+
apiClient := api.New(GithubToken)
4945

5046
user, err := apiClient.GetUser(ctx)
5147
if err != nil {
5248
return fmt.Errorf("getting user: %w", err)
5349
}
5450

51+
authkeys := make(chan error, 1)
52+
go func() {
53+
authkeys <- checkAuthorizedKeys(ctx, apiClient, user.Login)
54+
}()
55+
5556
codespace, token, err := getOrChooseCodespace(ctx, apiClient, user, codespaceName)
5657
if err != nil {
5758
return fmt.Errorf("get or choose codespace: %w", err)
@@ -61,6 +62,11 @@ func logs(ctx context.Context, log *output.Logger, codespaceName string, follow
6162
if err != nil {
6263
return fmt.Errorf("connecting to Live Share: %w", err)
6364
}
65+
defer safeClose(session, &err)
66+
67+
if err := <-authkeys; err != nil {
68+
return err
69+
}
6470

6571
// Ensure local port is listening before client (getPostCreateOutput) connects.
6672
listen, err := net.Listen("tcp", ":0") // arbitrary port

cmd/ghcs/main/main.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
9+
"github.com/github/ghcs/cmd/ghcs"
10+
)
11+
12+
func main() {
13+
rootCmd := ghcs.NewRootCmd()
14+
if err := rootCmd.Execute(); err != nil {
15+
explainError(os.Stderr, err)
16+
os.Exit(1)
17+
}
18+
}
19+
20+
func explainError(w io.Writer, err error) {
21+
if errors.Is(err, ghcs.ErrTokenMissing) {
22+
fmt.Fprintln(w, "The GITHUB_TOKEN environment variable is required. Create a Personal Access Token at https://github.com/settings/tokens/new?scopes=repo")
23+
fmt.Fprintln(w, "Make sure to enable SSO for your organizations after creating the token.")
24+
return
25+
}
26+
}

cmd/ghcs/ports.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package ghcs
22

33
import (
44
"bytes"
@@ -47,11 +47,7 @@ func newPortsCmd() *cobra.Command {
4747
return portsCmd
4848
}
4949

50-
func init() {
51-
rootCmd.AddCommand(newPortsCmd())
52-
}
53-
54-
func ports(codespaceName string, asJSON bool) error {
50+
func ports(codespaceName string, asJSON bool) (err error) {
5551
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
5652
ctx := context.Background()
5753
log := output.NewLogger(os.Stdout, os.Stderr, asJSON)
@@ -76,6 +72,7 @@ func ports(codespaceName string, asJSON bool) error {
7672
if err != nil {
7773
return fmt.Errorf("error connecting to Live Share: %w", err)
7874
}
75+
defer safeClose(session, &err)
7976

8077
log.Println("Loading ports...")
8178
ports, err := session.GetSharedServers(ctx)
@@ -198,9 +195,9 @@ func newPortsPrivateCmd() *cobra.Command {
198195
}
199196
}
200197

201-
func updatePortVisibility(log *output.Logger, codespaceName, sourcePort string, public bool) error {
198+
func updatePortVisibility(log *output.Logger, codespaceName, sourcePort string, public bool) (err error) {
202199
ctx := context.Background()
203-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
200+
apiClient := api.New(GithubToken)
204201

205202
user, err := apiClient.GetUser(ctx)
206203
if err != nil {
@@ -219,6 +216,7 @@ func updatePortVisibility(log *output.Logger, codespaceName, sourcePort string,
219216
if err != nil {
220217
return fmt.Errorf("error connecting to Live Share: %w", err)
221218
}
219+
defer safeClose(session, &err)
222220

223221
port, err := strconv.Atoi(sourcePort)
224222
if err != nil {
@@ -260,9 +258,9 @@ func newPortsForwardCmd() *cobra.Command {
260258
}
261259
}
262260

263-
func forwardPorts(log *output.Logger, codespaceName string, ports []string) error {
261+
func forwardPorts(log *output.Logger, codespaceName string, ports []string) (err error) {
264262
ctx := context.Background()
265-
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
263+
apiClient := api.New(GithubToken)
266264

267265
portPairs, err := getPortPairs(ports)
268266
if err != nil {
@@ -286,6 +284,7 @@ func forwardPorts(log *output.Logger, codespaceName string, ports []string) erro
286284
if err != nil {
287285
return fmt.Errorf("error connecting to Live Share: %w", err)
288286
}
287+
defer safeClose(session, &err)
289288

290289
// Run forwarding of all ports concurrently, aborting all of
291290
// them at the first failure, including cancellation of the context.

cmd/ghcs/main.go renamed to cmd/ghcs/root.go

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
package main
1+
package ghcs
22

33
import (
44
"errors"
55
"fmt"
6-
"io"
76
"log"
87
"os"
98
"strconv"
@@ -14,18 +13,12 @@ import (
1413
"github.com/spf13/cobra"
1514
)
1615

17-
func main() {
18-
if err := rootCmd.Execute(); err != nil {
19-
explainError(os.Stderr, err)
20-
os.Exit(1)
21-
}
22-
}
23-
2416
var version = "DEV" // Replaced in the release build process (by GoReleaser or Homebrew) by the git tag version number.
2517

26-
var rootCmd = newRootCmd()
18+
// GithubToken is a temporary stopgap to make the token configurable by apps that import this package
19+
var GithubToken = os.Getenv("GITHUB_TOKEN")
2720

28-
func newRootCmd() *cobra.Command {
21+
func NewRootCmd() *cobra.Command {
2922
var lightstep string
3023

3124
root := &cobra.Command{
@@ -40,26 +33,26 @@ token to access the GitHub API with.`,
4033

4134
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
4235
if os.Getenv("GITHUB_TOKEN") == "" {
43-
return errTokenMissing
36+
return ErrTokenMissing
4437
}
4538
return initLightstep(lightstep)
4639
},
4740
}
4841

4942
root.PersistentFlags().StringVar(&lightstep, "lightstep", "", "Lightstep tracing endpoint (service:token@host:port)")
5043

44+
root.AddCommand(newCodeCmd())
45+
root.AddCommand(newCreateCmd())
46+
root.AddCommand(newDeleteCmd())
47+
root.AddCommand(newListCmd())
48+
root.AddCommand(newLogsCmd())
49+
root.AddCommand(newPortsCmd())
50+
root.AddCommand(newSSHCmd())
51+
5152
return root
5253
}
5354

54-
var errTokenMissing = errors.New("GITHUB_TOKEN is missing")
55-
56-
func explainError(w io.Writer, err error) {
57-
if errors.Is(err, errTokenMissing) {
58-
fmt.Fprintln(w, "The GITHUB_TOKEN environment variable is required. Create a Personal Access Token at https://github.com/settings/tokens/new?scopes=repo")
59-
fmt.Fprintln(w, "Make sure to enable SSO for your organizations after creating the token.")
60-
return
61-
}
62-
}
55+
var ErrTokenMissing = errors.New("GITHUB_TOKEN is missing")
6356

6457
// initLightstep parses the --lightstep=service:token@host:port flag and
6558
// enables tracing if non-empty.

0 commit comments

Comments
 (0)
X Tutup