Skip to content

Commit e6a6b74

Browse files
authored
Merge branch 'main' into mafredri/enable-cli-goleak
2 parents 7597057 + 95f26f7 commit e6a6b74

File tree

140 files changed

+3885
-1312
lines changed

Some content is hidden

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

140 files changed

+3885
-1312
lines changed

.github/codecov.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ github_checks:
99
annotations: false
1010

1111
coverage:
12+
range: 50..75
13+
round: down
14+
precision: 2
1215
status:
1316
patch:
1417
default:
1518
informational: yes
1619
project:
1720
default:
1821
target: 65%
19-
informational: yes
22+
informational: true
2023

2124
ignore:
2225
# This is generated code.

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"coderdtest",
1212
"codersdk",
1313
"cronstrue",
14+
"databasefake",
1415
"devel",
1516
"drpc",
1617
"drpcconn",
@@ -52,6 +53,7 @@
5253
"oneof",
5354
"parameterscopeid",
5455
"pqtype",
56+
"prometheusmetrics",
5557
"promptui",
5658
"protobuf",
5759
"provisionerd",
@@ -72,6 +74,7 @@
7274
"templateversions",
7375
"testdata",
7476
"testid",
77+
"testutil",
7578
"tfexec",
7679
"tfjson",
7780
"tfplan",

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
[!["Join us on
44
Discord"](https://img.shields.io/badge/join-us%20on%20Discord-gray.svg?longCache=true&logo=discord&colorB=green)](https://discord.gg/coder)
55
[![codecov](https://codecov.io/gh/coder/coder/branch/main/graph/badge.svg?token=TNLW3OAP6G)](https://codecov.io/gh/coder/coder)
6+
[![Go Reference](https://pkg.go.dev/badge/github.com/coder/coder.svg)](https://pkg.go.dev/github.com/coder/coder)
67
[![Twitter
7-
Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq)
8+
Follow](https://img.shields.io/twitter/follow/coderhq?label=%40coderhq&style=social)](https://twitter.com/coderhq)
89

910
Coder creates remote development machines so your team can develop from anywhere.
1011

cli/configssh.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ func configSSH() *cobra.Command {
156156
Command: "coder config-ssh --dry-run",
157157
},
158158
),
159-
RunE: func(cmd *cobra.Command, args []string) error {
159+
Args: cobra.ExactArgs(0),
160+
RunE: func(cmd *cobra.Command, _ []string) error {
160161
client, err := createClient(cmd)
161162
if err != nil {
162163
return err

cli/server.go

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/google/uuid"
3131
"github.com/pion/turn/v2"
3232
"github.com/pion/webrtc/v3"
33+
"github.com/prometheus/client_golang/prometheus"
3334
"github.com/prometheus/client_golang/prometheus/promhttp"
3435
"github.com/spf13/afero"
3536
"github.com/spf13/cobra"
@@ -53,6 +54,7 @@ import (
5354
"github.com/coder/coder/coderd/database/databasefake"
5455
"github.com/coder/coder/coderd/devtunnel"
5556
"github.com/coder/coder/coderd/gitsshkey"
57+
"github.com/coder/coder/coderd/prometheusmetrics"
5658
"github.com/coder/coder/coderd/telemetry"
5759
"github.com/coder/coder/coderd/tracing"
5860
"github.com/coder/coder/coderd/turnconn"
@@ -85,6 +87,7 @@ func server() *cobra.Command {
8587
oauth2GithubAllowedOrganizations []string
8688
oauth2GithubAllowedTeams []string
8789
oauth2GithubAllowSignups bool
90+
oauth2GithubEnterpriseBaseURL string
8891
oidcAllowSignups bool
8992
oidcClientID string
9093
oidcClientSecret string
@@ -284,7 +287,7 @@ func server() *cobra.Command {
284287
}
285288

286289
if oauth2GithubClientSecret != "" {
287-
options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, oauth2GithubClientID, oauth2GithubClientSecret, oauth2GithubAllowSignups, oauth2GithubAllowedOrganizations, oauth2GithubAllowedTeams)
290+
options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, oauth2GithubClientID, oauth2GithubClientSecret, oauth2GithubAllowSignups, oauth2GithubAllowedOrganizations, oauth2GithubAllowedTeams, oauth2GithubEnterpriseBaseURL)
288291
if err != nil {
289292
return xerrors.Errorf("configure github oauth2: %w", err)
290293
}
@@ -392,6 +395,32 @@ func server() *cobra.Command {
392395
defer options.Telemetry.Close()
393396
}
394397

398+
// This prevents the pprof import from being accidentally deleted.
399+
_ = pprof.Handler
400+
if pprofEnabled {
401+
//nolint:revive
402+
defer serveHandler(ctx, logger, nil, pprofAddress, "pprof")()
403+
}
404+
if promEnabled {
405+
options.PrometheusRegistry = prometheus.NewRegistry()
406+
closeUsersFunc, err := prometheusmetrics.ActiveUsers(ctx, options.PrometheusRegistry, options.Database, 0)
407+
if err != nil {
408+
return xerrors.Errorf("register active users prometheus metric: %w", err)
409+
}
410+
defer closeUsersFunc()
411+
412+
closeWorkspacesFunc, err := prometheusmetrics.Workspaces(ctx, options.PrometheusRegistry, options.Database, 0)
413+
if err != nil {
414+
return xerrors.Errorf("register workspaces prometheus metric: %w", err)
415+
}
416+
defer closeWorkspacesFunc()
417+
418+
//nolint:revive
419+
defer serveHandler(ctx, logger, promhttp.InstrumentMetricHandler(
420+
options.PrometheusRegistry, promhttp.HandlerFor(options.PrometheusRegistry, promhttp.HandlerOpts{}),
421+
), promAddress, "prometheus")()
422+
}
423+
395424
coderAPI := coderd.New(options)
396425
defer coderAPI.Close()
397426

@@ -406,17 +435,6 @@ func server() *cobra.Command {
406435
}
407436
}
408437

409-
// This prevents the pprof import from being accidentally deleted.
410-
_ = pprof.Handler
411-
if pprofEnabled {
412-
//nolint:revive
413-
defer serveHandler(ctx, logger, nil, pprofAddress, "pprof")()
414-
}
415-
if promEnabled {
416-
//nolint:revive
417-
defer serveHandler(ctx, logger, promhttp.Handler(), promAddress, "prometheus")()
418-
}
419-
420438
// Since errCh only has one buffered slot, all routines
421439
// sending on it must be wrapped in a select/default to
422440
// avoid leaving dangling goroutines waiting for the
@@ -678,6 +696,8 @@ func server() *cobra.Command {
678696
"Specifies teams inside organizations the user must be a member of to authenticate with GitHub. Formatted as: <organization-name>/<team-slug>.")
679697
cliflag.BoolVarP(root.Flags(), &oauth2GithubAllowSignups, "oauth2-github-allow-signups", "", "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", false,
680698
"Specifies whether new users can sign up with GitHub.")
699+
cliflag.StringVarP(root.Flags(), &oauth2GithubEnterpriseBaseURL, "oauth2-github-enterprise-base-url", "", "CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL", "",
700+
"Specifies the base URL of a GitHub Enterprise instance to use for oauth2.")
681701
cliflag.BoolVarP(root.Flags(), &oidcAllowSignups, "oidc-allow-signups", "", "CODER_OIDC_ALLOW_SIGNUPS", true,
682702
"Specifies whether new users can sign up with OIDC.")
683703
cliflag.StringVarP(root.Flags(), &oidcClientID, "oidc-client-id", "", "CODER_OIDC_CLIENT_ID", "",
@@ -955,7 +975,7 @@ func configureTLS(listener net.Listener, tlsMinVersion, tlsClientAuth, tlsCertFi
955975
return tls.NewListener(listener, tlsConfig), nil
956976
}
957977

958-
func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, allowSignups bool, allowOrgs []string, rawTeams []string) (*coderd.GithubOAuth2Config, error) {
978+
func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, allowSignups bool, allowOrgs []string, rawTeams []string, enterpriseBaseURL string) (*coderd.GithubOAuth2Config, error) {
959979
redirectURL, err := accessURL.Parse("/api/v2/users/oauth2/github/callback")
960980
if err != nil {
961981
return nil, xerrors.Errorf("parse github oauth callback url: %w", err)
@@ -971,11 +991,38 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
971991
Slug: parts[1],
972992
})
973993
}
994+
createClient := func(client *http.Client) (*github.Client, error) {
995+
if enterpriseBaseURL != "" {
996+
return github.NewEnterpriseClient(enterpriseBaseURL, "", client)
997+
}
998+
return github.NewClient(client), nil
999+
}
1000+
1001+
endpoint := xgithub.Endpoint
1002+
if enterpriseBaseURL != "" {
1003+
enterpriseURL, err := url.Parse(enterpriseBaseURL)
1004+
if err != nil {
1005+
return nil, xerrors.Errorf("parse enterprise base url: %w", err)
1006+
}
1007+
authURL, err := enterpriseURL.Parse("/login/oauth/authorize")
1008+
if err != nil {
1009+
return nil, xerrors.Errorf("parse enterprise auth url: %w", err)
1010+
}
1011+
tokenURL, err := enterpriseURL.Parse("/login/oauth/access_token")
1012+
if err != nil {
1013+
return nil, xerrors.Errorf("parse enterprise token url: %w", err)
1014+
}
1015+
endpoint = oauth2.Endpoint{
1016+
AuthURL: authURL.String(),
1017+
TokenURL: tokenURL.String(),
1018+
}
1019+
}
1020+
9741021
return &coderd.GithubOAuth2Config{
9751022
OAuth2Config: &oauth2.Config{
9761023
ClientID: clientID,
9771024
ClientSecret: clientSecret,
978-
Endpoint: xgithub.Endpoint,
1025+
Endpoint: endpoint,
9791026
RedirectURL: redirectURL.String(),
9801027
Scopes: []string{
9811028
"read:user",
@@ -987,15 +1034,27 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
9871034
AllowOrganizations: allowOrgs,
9881035
AllowTeams: allowTeams,
9891036
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
990-
user, _, err := github.NewClient(client).Users.Get(ctx, "")
1037+
api, err := createClient(client)
1038+
if err != nil {
1039+
return nil, err
1040+
}
1041+
user, _, err := api.Users.Get(ctx, "")
9911042
return user, err
9921043
},
9931044
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
994-
emails, _, err := github.NewClient(client).Users.ListEmails(ctx, &github.ListOptions{})
1045+
api, err := createClient(client)
1046+
if err != nil {
1047+
return nil, err
1048+
}
1049+
emails, _, err := api.Users.ListEmails(ctx, &github.ListOptions{})
9951050
return emails, err
9961051
},
9971052
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
998-
memberships, _, err := github.NewClient(client).Organizations.ListOrgMemberships(ctx, &github.ListOrgMembershipsOptions{
1053+
api, err := createClient(client)
1054+
if err != nil {
1055+
return nil, err
1056+
}
1057+
memberships, _, err := api.Organizations.ListOrgMemberships(ctx, &github.ListOrgMembershipsOptions{
9991058
State: "active",
10001059
ListOptions: github.ListOptions{
10011060
PerPage: 100,
@@ -1004,7 +1063,11 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
10041063
return memberships, err
10051064
},
10061065
TeamMembership: func(ctx context.Context, client *http.Client, org, teamSlug, username string) (*github.Membership, error) {
1007-
team, _, err := github.NewClient(client).Teams.GetTeamMembershipBySlug(ctx, org, teamSlug, username)
1066+
api, err := createClient(client)
1067+
if err != nil {
1068+
return nil, err
1069+
}
1070+
team, _, err := api.Teams.GetTeamMembershipBySlug(ctx, org, teamSlug, username)
10081071
return team, err
10091072
},
10101073
}, nil

cli/server_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli_test
22

33
import (
4+
"bufio"
45
"context"
56
"crypto/ecdsa"
67
"crypto/elliptic"
@@ -10,17 +11,21 @@ import (
1011
"crypto/x509/pkix"
1112
"encoding/json"
1213
"encoding/pem"
14+
"fmt"
1315
"math/big"
1416
"net"
1517
"net/http"
1618
"net/http/httptest"
1719
"net/url"
1820
"os"
1921
"runtime"
22+
"strconv"
23+
"strings"
2024
"testing"
2125
"time"
2226

2327
"github.com/go-chi/chi"
28+
"github.com/stretchr/testify/assert"
2429
"github.com/stretchr/testify/require"
2530
"go.uber.org/goleak"
2631

@@ -374,6 +379,97 @@ func TestServer(t *testing.T) {
374379
cancelFunc()
375380
<-errC
376381
})
382+
t.Run("Prometheus", func(t *testing.T) {
383+
t.Parallel()
384+
ctx, cancelFunc := context.WithCancel(context.Background())
385+
defer cancelFunc()
386+
387+
random, err := net.Listen("tcp", "127.0.0.1:0")
388+
require.NoError(t, err)
389+
_ = random.Close()
390+
tcpAddr, valid := random.Addr().(*net.TCPAddr)
391+
require.True(t, valid)
392+
randomPort := tcpAddr.Port
393+
394+
root, cfg := clitest.New(t,
395+
"server",
396+
"--in-memory",
397+
"--address", ":0",
398+
"--provisioner-daemons", "1",
399+
"--prometheus-enable",
400+
"--prometheus-address", ":"+strconv.Itoa(randomPort),
401+
"--cache-dir", t.TempDir(),
402+
)
403+
serverErr := make(chan error, 1)
404+
go func() {
405+
serverErr <- root.ExecuteContext(ctx)
406+
}()
407+
_ = waitAccessURL(t, cfg)
408+
409+
var res *http.Response
410+
require.Eventually(t, func() bool {
411+
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://127.0.0.1:%d", randomPort), nil)
412+
assert.NoError(t, err)
413+
res, err = http.DefaultClient.Do(req)
414+
return err == nil
415+
}, testutil.WaitShort, testutil.IntervalFast)
416+
417+
scanner := bufio.NewScanner(res.Body)
418+
hasActiveUsers := false
419+
hasWorkspaces := false
420+
for scanner.Scan() {
421+
// This metric is manually registered to be tracked in the server. That's
422+
// why we test it's tracked here.
423+
if strings.HasPrefix(scanner.Text(), "coderd_api_active_users_duration_hour") {
424+
hasActiveUsers = true
425+
continue
426+
}
427+
if strings.HasPrefix(scanner.Text(), "coderd_api_workspace_latest_build_total") {
428+
hasWorkspaces = true
429+
continue
430+
}
431+
t.Logf("scanned %s", scanner.Text())
432+
}
433+
require.NoError(t, scanner.Err())
434+
require.True(t, hasActiveUsers)
435+
require.True(t, hasWorkspaces)
436+
cancelFunc()
437+
<-serverErr
438+
})
439+
t.Run("GitHubOAuth", func(t *testing.T) {
440+
t.Parallel()
441+
ctx, cancelFunc := context.WithCancel(context.Background())
442+
defer cancelFunc()
443+
444+
fakeRedirect := "https://fake-url.com"
445+
root, cfg := clitest.New(t,
446+
"server",
447+
"--in-memory",
448+
"--address", ":0",
449+
"--oauth2-github-client-id", "fake",
450+
"--oauth2-github-client-secret", "fake",
451+
"--oauth2-github-enterprise-base-url", fakeRedirect,
452+
)
453+
serverErr := make(chan error, 1)
454+
go func() {
455+
serverErr <- root.ExecuteContext(ctx)
456+
}()
457+
accessURL := waitAccessURL(t, cfg)
458+
client := codersdk.New(accessURL)
459+
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
460+
return http.ErrUseLastResponse
461+
}
462+
githubURL, err := accessURL.Parse("/api/v2/users/oauth2/github")
463+
require.NoError(t, err)
464+
res, err := client.HTTPClient.Get(githubURL.String())
465+
require.NoError(t, err)
466+
defer res.Body.Close()
467+
fakeURL, err := res.Location()
468+
require.NoError(t, err)
469+
require.True(t, strings.HasPrefix(fakeURL.String(), fakeRedirect), fakeURL.String())
470+
cancelFunc()
471+
<-serverErr
472+
})
377473
}
378474

379475
func generateTLSCertificate(t testing.TB) (certPath, keyPath string) {

0 commit comments

Comments
 (0)