Skip to content

Commit 7505b01

Browse files
committed
Merge branch 'main' into mvworkspaces
2 parents cb6fa53 + 185d97a commit 7505b01

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+2126
-733
lines changed

.github/workflows/coder.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ jobs:
172172
key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
173173

174174
- name: Install goreleaser
175-
uses: jaxxstorm/action-install-gh-release@v1.4.0
175+
uses: jaxxstorm/action-install-gh-release@v1.5.0
176176
env:
177177
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
178178
with:
@@ -241,7 +241,7 @@ jobs:
241241
key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
242242

243243
- name: Install goreleaser
244-
uses: jaxxstorm/action-install-gh-release@v1.4.0
244+
uses: jaxxstorm/action-install-gh-release@v1.5.0
245245
env:
246246
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
247247
with:

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"nolint",
3636
"nosec",
3737
"ntqry",
38+
"OIDC",
3839
"oneof",
3940
"parameterscopeid",
4041
"pqtype",
@@ -46,6 +47,7 @@
4647
"ptytest",
4748
"retrier",
4849
"sdkproto",
50+
"Signup",
4951
"stretchr",
5052
"TCGETS",
5153
"tcpip",

agent/agent_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ func TestAgent(t *testing.T) {
7676
session.Stdin = ptty.Input()
7777
err = session.Start(command)
7878
require.NoError(t, err)
79+
caret := "$"
80+
if runtime.GOOS == "windows" {
81+
caret = ">"
82+
}
83+
ptty.ExpectMatch(caret)
7984
ptty.WriteLine("echo test")
8085
ptty.ExpectMatch("test")
8186
ptty.WriteLine("exit")

cli/agent.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ func workspaceAgent() *cobra.Command {
120120
if err != nil {
121121
return xerrors.Errorf("writing agent session token to config: %w", err)
122122
}
123+
err = cfg.URL().Write(client.URL.String())
124+
if err != nil {
125+
return xerrors.Errorf("writing agent url to config: %w", err)
126+
}
123127

124128
closer := agent.New(client.ListenWorkspaceAgent, logger)
125129
<-cmd.Context().Done()

cli/cliflag/cliflag.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"fmt"
1515
"os"
1616
"strconv"
17+
"strings"
1718

1819
"github.com/spf13/pflag"
1920
)
@@ -27,6 +28,14 @@ func StringVarP(flagset *pflag.FlagSet, p *string, name string, shorthand string
2728
flagset.StringVarP(p, name, shorthand, v, fmtUsage(usage, env))
2829
}
2930

31+
func StringArrayVarP(flagset *pflag.FlagSet, ptr *[]string, name string, shorthand string, env string, def []string, usage string) {
32+
val, ok := os.LookupEnv(env)
33+
if ok {
34+
def = strings.Split(val, ",")
35+
}
36+
flagset.StringArrayVarP(ptr, name, shorthand, def, usage)
37+
}
38+
3039
// Uint8VarP sets a uint8 flag on the given flag set.
3140
func Uint8VarP(flagset *pflag.FlagSet, ptr *uint8, name string, shorthand string, env string, def uint8, usage string) {
3241
val, ok := os.LookupEnv(env)

cli/cliflag/cliflag_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ func TestCliflag(t *testing.T) {
5454
require.NotContains(t, flagset.FlagUsages(), " - consumes")
5555
})
5656

57+
t.Run("StringArrayDefault", func(t *testing.T) {
58+
var ptr []string
59+
flagset, name, shorthand, env, usage := randomFlag()
60+
def := []string{"hello"}
61+
cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, def, usage)
62+
got, err := flagset.GetStringArray(name)
63+
require.NoError(t, err)
64+
require.Equal(t, def, got)
65+
})
66+
67+
t.Run("StringArrayEnvVar", func(t *testing.T) {
68+
var ptr []string
69+
flagset, name, shorthand, env, usage := randomFlag()
70+
t.Setenv(env, "wow,test")
71+
cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, nil, usage)
72+
got, err := flagset.GetStringArray(name)
73+
require.NoError(t, err)
74+
require.Equal(t, []string{"wow", "test"}, got)
75+
})
76+
5777
t.Run("IntDefault", func(t *testing.T) {
5878
var ptr uint8
5979
flagset, name, shorthand, env, usage := randomFlag()

cli/gitssh.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package cli
22

33
import (
4+
"fmt"
5+
"net/url"
46
"os"
57
"os/exec"
8+
"strings"
69

10+
"github.com/coder/coder/cli/cliui"
11+
"github.com/coder/coder/codersdk"
712
"github.com/spf13/cobra"
813
"golang.org/x/xerrors"
914
)
@@ -14,15 +19,20 @@ func gitssh() *cobra.Command {
1419
Hidden: true,
1520
Short: `Wraps the "ssh" command and uses the coder gitssh key for authentication`,
1621
RunE: func(cmd *cobra.Command, args []string) error {
17-
client, err := createClient(cmd)
22+
cfg := createConfig(cmd)
23+
rawURL, err := cfg.URL().Read()
1824
if err != nil {
19-
return xerrors.Errorf("create codersdk client: %w", err)
25+
return xerrors.Errorf("read agent url from config: %w", err)
26+
}
27+
parsedURL, err := url.Parse(rawURL)
28+
if err != nil {
29+
return xerrors.Errorf("parse agent url from config: %w", err)
2030
}
21-
cfg := createConfig(cmd)
2231
session, err := cfg.AgentSession().Read()
2332
if err != nil {
2433
return xerrors.Errorf("read agent session from config: %w", err)
2534
}
35+
client := codersdk.New(parsedURL)
2636
client.SessionToken = session
2737

2838
key, err := client.AgentGitSSHKey(cmd.Context())
@@ -47,12 +57,25 @@ func gitssh() *cobra.Command {
4757
return xerrors.Errorf("close temp gitsshkey file: %w", err)
4858
}
4959

50-
a := append([]string{"-i", privateKeyFile.Name()}, args...)
51-
c := exec.CommandContext(cmd.Context(), "ssh", a...)
60+
args = append([]string{"-i", privateKeyFile.Name()}, args...)
61+
c := exec.CommandContext(cmd.Context(), "ssh", args...)
62+
c.Stderr = cmd.ErrOrStderr()
5263
c.Stdout = cmd.OutOrStdout()
5364
c.Stdin = cmd.InOrStdin()
5465
err = c.Run()
5566
if err != nil {
67+
exitErr := &exec.ExitError{}
68+
if xerrors.As(err, &exitErr) && exitErr.ExitCode() == 255 {
69+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(),
70+
"\n"+cliui.Styles.Wrap.Render("Coder authenticates with "+cliui.Styles.Field.Render("git")+
71+
" using the public key below. All clones with SSH are authenticated automatically 🪄.")+"\n")
72+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Code.Render(strings.TrimSpace(key.PublicKey))+"\n")
73+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Add to GitHub and GitLab:")
74+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Prompt.String()+"https://github.com/settings/ssh/new")
75+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Prompt.String()+"https://gitlab.com/-/profile/keys")
76+
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
77+
return err
78+
}
5679
return xerrors.Errorf("run ssh command: %w", err)
5780
}
5881

cli/publickey.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
package cli
22

33
import (
4+
"strings"
5+
46
"github.com/spf13/cobra"
57
"golang.org/x/xerrors"
68

9+
"github.com/coder/coder/cli/cliui"
710
"github.com/coder/coder/codersdk"
811
)
912

1013
func publickey() *cobra.Command {
1114
return &cobra.Command{
12-
Use: "publickey",
15+
Use: "publickey",
16+
Aliases: []string{"pubkey"},
1317
RunE: func(cmd *cobra.Command, args []string) error {
1418
client, err := createClient(cmd)
1519
if err != nil {
@@ -21,7 +25,16 @@ func publickey() *cobra.Command {
2125
return xerrors.Errorf("create codersdk client: %w", err)
2226
}
2327

24-
cmd.Println(key.PublicKey)
28+
cmd.Println(cliui.Styles.Wrap.Render(
29+
"This is your public key for using " + cliui.Styles.Field.Render("git") + " in " +
30+
"Coder. All clones with SSH will be authenticated automatically 🪄.",
31+
))
32+
cmd.Println()
33+
cmd.Println(cliui.Styles.Code.Render(strings.TrimSpace(key.PublicKey)))
34+
cmd.Println()
35+
cmd.Println("Add to GitHub and GitLab:")
36+
cmd.Println(cliui.Styles.Prompt.String() + "https://github.com/settings/ssh/new")
37+
cmd.Println(cliui.Styles.Prompt.String() + "https://gitlab.com/-/profile/keys")
2538

2639
return nil
2740
},

cli/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func Root() *cobra.Command {
4343
`,
4444
Example: cliui.Styles.Paragraph.Render(`Start Coder in "dev" mode. This dev-mode requires no further setup, and your local `+cliui.Styles.Code.Render("coder")+` CLI will be authenticated to talk to it. This makes it easy to experiment with Coder.`) + `
4545
46-
` + cliui.Styles.Code.Render("$ coder start --dev") + `
46+
` + cliui.Styles.Code.Render("$ coder server --dev") + `
4747
` + cliui.Styles.Paragraph.Render("Get started by creating a template from an example.") + `
4848
4949
` + cliui.Styles.Code.Render("$ coder templates init"),
@@ -63,7 +63,7 @@ func Root() *cobra.Command {
6363

6464
cmd.AddCommand(
6565
configSSH(),
66-
start(),
66+
server(),
6767
login(),
6868
parameters(),
6969
templates(),

cli/start.go renamed to cli/server.go

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ import (
1818

1919
"github.com/briandowns/spinner"
2020
"github.com/coreos/go-systemd/daemon"
21+
"github.com/google/go-github/v43/github"
2122
"github.com/pion/turn/v2"
23+
"github.com/pion/webrtc/v3"
2224
"github.com/spf13/cobra"
25+
"golang.org/x/oauth2"
26+
xgithub "golang.org/x/oauth2/github"
2327
"golang.org/x/xerrors"
2428
"google.golang.org/api/idtoken"
2529
"google.golang.org/api/option"
@@ -43,31 +47,37 @@ import (
4347
"github.com/coder/coder/provisionersdk/proto"
4448
)
4549

46-
func start() *cobra.Command {
50+
// nolint:gocyclo
51+
func server() *cobra.Command {
4752
var (
4853
accessURL string
4954
address string
5055
cacheDir string
5156
dev bool
5257
postgresURL string
5358
// provisionerDaemonCount is a uint8 to ensure a number > 0.
54-
provisionerDaemonCount uint8
55-
tlsCertFile string
56-
tlsClientCAFile string
57-
tlsClientAuth string
58-
tlsEnable bool
59-
tlsKeyFile string
60-
tlsMinVersion string
61-
turnRelayAddress string
62-
skipTunnel bool
63-
traceDatadog bool
64-
secureAuthCookie bool
65-
sshKeygenAlgorithmRaw string
66-
spooky bool
59+
provisionerDaemonCount uint8
60+
oauth2GithubClientID string
61+
oauth2GithubClientSecret string
62+
oauth2GithubAllowedOrganizations []string
63+
oauth2GithubAllowSignups bool
64+
tlsCertFile string
65+
tlsClientCAFile string
66+
tlsClientAuth string
67+
tlsEnable bool
68+
tlsKeyFile string
69+
tlsMinVersion string
70+
turnRelayAddress string
71+
skipTunnel bool
72+
stunServers []string
73+
traceDatadog bool
74+
secureAuthCookie bool
75+
sshKeygenAlgorithmRaw string
76+
spooky bool
6777
)
6878

6979
root := &cobra.Command{
70-
Use: "start",
80+
Use: "server",
7181
RunE: func(cmd *cobra.Command, args []string) error {
7282
logger := slog.Make(sloghuman.Sink(os.Stderr))
7383
if traceDatadog {
@@ -169,8 +179,15 @@ func start() *cobra.Command {
169179
return xerrors.Errorf("create turn server: %w", err)
170180
}
171181

182+
iceServers := make([]webrtc.ICEServer, 0)
183+
for _, stunServer := range stunServers {
184+
iceServers = append(iceServers, webrtc.ICEServer{
185+
URLs: []string{stunServer},
186+
})
187+
}
172188
options := &coderd.Options{
173189
AccessURL: accessURLParsed,
190+
ICEServers: iceServers,
174191
Logger: logger.Named("coderd"),
175192
Database: databasefake.New(),
176193
Pubsub: database.NewPubsubInMemory(),
@@ -180,6 +197,13 @@ func start() *cobra.Command {
180197
TURNServer: turnServer,
181198
}
182199

200+
if oauth2GithubClientSecret != "" {
201+
options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, oauth2GithubClientID, oauth2GithubClientSecret, oauth2GithubAllowSignups, oauth2GithubAllowedOrganizations)
202+
if err != nil {
203+
return xerrors.Errorf("configure github oauth2: %w", err)
204+
}
205+
}
206+
183207
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "access-url: %s\n", accessURL)
184208
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "provisioner-daemons: %d\n", provisionerDaemonCount)
185209
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
@@ -377,6 +401,14 @@ func start() *cobra.Command {
377401
cliflag.BoolVarP(root.Flags(), &dev, "dev", "", "CODER_DEV_MODE", false, "Serve Coder in dev mode for tinkering")
378402
cliflag.StringVarP(root.Flags(), &postgresURL, "postgres-url", "", "CODER_PG_CONNECTION_URL", "", "URL of a PostgreSQL database to connect to")
379403
cliflag.Uint8VarP(root.Flags(), &provisionerDaemonCount, "provisioner-daemons", "", "CODER_PROVISIONER_DAEMONS", 1, "The amount of provisioner daemons to create on start.")
404+
cliflag.StringVarP(root.Flags(), &oauth2GithubClientID, "oauth2-github-client-id", "", "CODER_OAUTH2_GITHUB_CLIENT_ID", "",
405+
"Specifies a client ID to use for oauth2 with GitHub.")
406+
cliflag.StringVarP(root.Flags(), &oauth2GithubClientSecret, "oauth2-github-client-secret", "", "CODER_OAUTH2_GITHUB_CLIENT_SECRET", "",
407+
"Specifies a client secret to use for oauth2 with GitHub.")
408+
cliflag.StringArrayVarP(root.Flags(), &oauth2GithubAllowedOrganizations, "oauth2-github-allowed-orgs", "", "CODER_OAUTH2_GITHUB_ALLOWED_ORGS", nil,
409+
"Specifies organizations the user must be a member of to authenticate with GitHub.")
410+
cliflag.BoolVarP(root.Flags(), &oauth2GithubAllowSignups, "oauth2-github-allow-signups", "", "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", false,
411+
"Specifies whether new users can sign up with GitHub.")
380412
cliflag.BoolVarP(root.Flags(), &tlsEnable, "tls-enable", "", "CODER_TLS_ENABLE", false, "Specifies if TLS will be enabled")
381413
cliflag.StringVarP(root.Flags(), &tlsCertFile, "tls-cert-file", "", "CODER_TLS_CERT_FILE", "",
382414
"Specifies the path to the certificate for TLS. It requires a PEM-encoded file. "+
@@ -393,6 +425,9 @@ func start() *cobra.Command {
393425
`Specifies the minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13"`)
394426
cliflag.BoolVarP(root.Flags(), &skipTunnel, "skip-tunnel", "", "CODER_DEV_SKIP_TUNNEL", false, "Skip serving dev mode through an exposed tunnel for simple setup.")
395427
_ = root.Flags().MarkHidden("skip-tunnel")
428+
cliflag.StringArrayVarP(root.Flags(), &stunServers, "stun-server", "", "CODER_STUN_SERVERS", []string{
429+
"stun:stun.l.google.com:19302",
430+
}, "Specify URLs for STUN servers to enable P2P connections.")
396431
cliflag.BoolVarP(root.Flags(), &traceDatadog, "trace-datadog", "", "CODER_TRACE_DATADOG", false, "Send tracing data to a datadog agent")
397432
cliflag.StringVarP(root.Flags(), &turnRelayAddress, "turn-relay-address", "", "CODER_TURN_RELAY_ADDRESS", "127.0.0.1",
398433
"Specifies the address to bind TURN connections.")
@@ -576,6 +611,42 @@ func configureTLS(listener net.Listener, tlsMinVersion, tlsClientAuth, tlsCertFi
576611
return tls.NewListener(listener, tlsConfig), nil
577612
}
578613

614+
func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, allowSignups bool, allowOrgs []string) (*coderd.GithubOAuth2Config, error) {
615+
redirectURL, err := accessURL.Parse("/api/v2/users/oauth2/github/callback")
616+
if err != nil {
617+
return nil, xerrors.Errorf("parse github oauth callback url: %w", err)
618+
}
619+
return &coderd.GithubOAuth2Config{
620+
OAuth2Config: &oauth2.Config{
621+
ClientID: clientID,
622+
ClientSecret: clientSecret,
623+
Endpoint: xgithub.Endpoint,
624+
RedirectURL: redirectURL.String(),
625+
Scopes: []string{
626+
"read:user",
627+
"read:org",
628+
"user:email",
629+
},
630+
},
631+
AllowSignups: allowSignups,
632+
AllowOrganizations: allowOrgs,
633+
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
634+
user, _, err := github.NewClient(client).Users.Get(ctx, "")
635+
return user, err
636+
},
637+
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
638+
emails, _, err := github.NewClient(client).Users.ListEmails(ctx, &github.ListOptions{})
639+
return emails, err
640+
},
641+
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
642+
memberships, _, err := github.NewClient(client).Organizations.ListOrgMemberships(ctx, &github.ListOrgMembershipsOptions{
643+
State: "active",
644+
})
645+
return memberships, err
646+
},
647+
}, nil
648+
}
649+
579650
type datadogLogger struct {
580651
logger slog.Logger
581652
}

0 commit comments

Comments
 (0)