Skip to content

Commit 17006c2

Browse files
committed
Merge branch 'main' into code-server-templates
2 parents f781e50 + 3cf17d3 commit 17006c2

32 files changed

+860
-278
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ build: site/out/index.html $(shell find . -not -path './vendor/*' -type f -name
5656
.PHONY: build
5757

5858
# Runs migrations to output a dump of the database.
59-
coderd/database/dump.sql: coderd/database/dump/main.go $(wildcard coderd/database/migrations/*.sql)
60-
go run coderd/database/dump/main.go
59+
coderd/database/dump.sql: coderd/database/gen/dump/main.go $(wildcard coderd/database/migrations/*.sql)
60+
go run coderd/database/gen/dump/main.go
6161

6262
# Generates Go code for querying the database.
63-
coderd/database/querier.go: coderd/database/sqlc.yaml coderd/database/dump.sql $(wildcard coderd/database/queries/*.sql)
63+
coderd/database/querier.go: coderd/database/sqlc.yaml coderd/database/dump.sql $(wildcard coderd/database/queries/*.sql) coderd/database/gen/enum/main.go
6464
coderd/database/generate.sh
6565

6666
fmt/prettier:

cli/root.go

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,20 @@ var (
3535
)
3636

3737
const (
38-
varURL = "url"
39-
varToken = "token"
40-
varAgentToken = "agent-token"
41-
varAgentURL = "agent-url"
42-
varGlobalConfig = "global-config"
43-
varNoOpen = "no-open"
44-
varNoVersionCheck = "no-version-warning"
45-
varForceTty = "force-tty"
46-
varVerbose = "verbose"
47-
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
48-
49-
envNoVersionCheck = "CODER_NO_VERSION_WARNING"
38+
varURL = "url"
39+
varToken = "token"
40+
varAgentToken = "agent-token"
41+
varAgentURL = "agent-url"
42+
varGlobalConfig = "global-config"
43+
varNoOpen = "no-open"
44+
varNoVersionCheck = "no-version-warning"
45+
varNoFeatureWarning = "no-feature-warning"
46+
varForceTty = "force-tty"
47+
varVerbose = "verbose"
48+
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
49+
50+
envNoVersionCheck = "CODER_NO_VERSION_WARNING"
51+
envNoFeatureWarning = "CODER_NO_FEATURE_WARNING"
5052
)
5153

5254
var (
@@ -103,36 +105,33 @@ func Root(subcommands []*cobra.Command) *cobra.Command {
103105
Long: `Coder — A tool for provisioning self-hosted development environments.
104106
`,
105107
PersistentPreRun: func(cmd *cobra.Command, args []string) {
106-
err := func() error {
107-
if cliflag.IsSetBool(cmd, varNoVersionCheck) {
108-
return nil
109-
}
110-
111-
// Login handles checking the versions itself since it
112-
// has a handle to an unauthenticated client.
113-
// Server is skipped for obvious reasons.
114-
if cmd.Name() == "login" || cmd.Name() == "server" || cmd.Name() == "gitssh" {
115-
return nil
116-
}
117-
118-
client, err := CreateClient(cmd)
119-
// If the client is unauthenticated we can ignore the check.
120-
// The child commands should handle an unauthenticated client.
121-
if xerrors.Is(err, errUnauthenticated) {
122-
return nil
123-
}
124-
if err != nil {
125-
return xerrors.Errorf("create client: %w", err)
126-
}
127-
return checkVersions(cmd, client)
128-
}()
108+
if cliflag.IsSetBool(cmd, varNoVersionCheck) &&
109+
cliflag.IsSetBool(cmd, varNoFeatureWarning) {
110+
return
111+
}
112+
113+
// Login handles checking the versions itself since it
114+
// has a handle to an unauthenticated client.
115+
// Server is skipped for obvious reasons.
116+
if cmd.Name() == "login" || cmd.Name() == "server" || cmd.Name() == "gitssh" {
117+
return
118+
}
119+
120+
client, err := CreateClient(cmd)
121+
// If we are unable to create a client, presumably the subcommand will fail as well
122+
// so we can bail out here.
123+
if err != nil {
124+
return
125+
}
126+
err = checkVersions(cmd, client)
129127
if err != nil {
130128
// Just log the error here. We never want to fail a command
131129
// due to a pre-run.
132130
_, _ = fmt.Fprintf(cmd.ErrOrStderr(),
133131
cliui.Styles.Warn.Render("check versions error: %s"), err)
134132
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
135133
}
134+
checkWarnings(cmd, client)
136135
},
137136
Example: formatExamples(
138137
example{
@@ -152,6 +151,7 @@ func Root(subcommands []*cobra.Command) *cobra.Command {
152151

153152
cmd.PersistentFlags().String(varURL, "", "Specify the URL to your deployment.")
154153
cliflag.Bool(cmd.PersistentFlags(), varNoVersionCheck, "", envNoVersionCheck, false, "Suppress warning when client and server versions do not match.")
154+
cliflag.Bool(cmd.PersistentFlags(), varNoFeatureWarning, "", envNoFeatureWarning, false, "Suppress warnings about unlicensed features.")
155155
cliflag.String(cmd.PersistentFlags(), varToken, "", envSessionToken, "", fmt.Sprintf("Specify an authentication token. For security reasons setting %s is preferred.", envSessionToken))
156156
cliflag.String(cmd.PersistentFlags(), varAgentToken, "", "CODER_AGENT_TOKEN", "", "Specify an agent authentication token.")
157157
_ = cmd.PersistentFlags().MarkHidden(varAgentToken)
@@ -493,3 +493,16 @@ download the server version with: 'curl -L https://coder.com/install.sh | sh -s
493493

494494
return nil
495495
}
496+
497+
func checkWarnings(cmd *cobra.Command, client *codersdk.Client) {
498+
if cliflag.IsSetBool(cmd, varNoFeatureWarning) {
499+
return
500+
}
501+
entitlements, err := client.Entitlements(cmd.Context())
502+
if err != nil {
503+
return
504+
}
505+
for _, w := range entitlements.Warnings {
506+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Warn.Render(w))
507+
}
508+
}

cli/server.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -496,12 +496,16 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
496496

497497
shutdownConnsCtx, shutdownConns := context.WithCancel(ctx)
498498
defer shutdownConns()
499+
500+
// ReadHeaderTimeout is purposefully not enabled. It caused some issues with
501+
// websockets over the dev tunnel.
502+
// See: https://github.com/coder/coder/pull/3730
503+
//nolint:gosec
499504
server := &http.Server{
500505
// These errors are typically noise like "TLS: EOF". Vault does similar:
501506
// https://github.com/hashicorp/vault/blob/e2490059d0711635e529a4efcbaa1b26998d6e1c/command/server.go#L2714
502-
ErrorLog: log.New(io.Discard, "", 0),
503-
Handler: coderAPI.Handler,
504-
ReadHeaderTimeout: time.Minute,
507+
ErrorLog: log.New(io.Discard, "", 0),
508+
Handler: coderAPI.Handler,
505509
BaseContext: func(_ net.Listener) context.Context {
506510
return shutdownConnsCtx
507511
},
@@ -659,13 +663,13 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
659663
root.AddCommand(&cobra.Command{
660664
Use: "postgres-builtin-url",
661665
Short: "Output the connection URL for the built-in PostgreSQL deployment.",
662-
RunE: func(cmd *cobra.Command, args []string) error {
666+
RunE: func(cmd *cobra.Command, _ []string) error {
663667
cfg := createConfig(cmd)
664668
url, err := embeddedPostgresURL(cfg)
665669
if err != nil {
666670
return err
667671
}
668-
cmd.Println(cliui.Styles.Code.Render("psql \"" + url + "\""))
672+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "psql %q\n", url)
669673
return nil
670674
},
671675
})
@@ -1106,10 +1110,13 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
11061110
func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler, addr, name string) (closeFunc func()) {
11071111
logger.Debug(ctx, "http server listening", slog.F("addr", addr), slog.F("name", name))
11081112

1113+
// ReadHeaderTimeout is purposefully not enabled. It caused some issues with
1114+
// websockets over the dev tunnel.
1115+
// See: https://github.com/coder/coder/pull/3730
1116+
//nolint:gosec
11091117
srv := &http.Server{
1110-
Addr: addr,
1111-
Handler: handler,
1112-
ReadHeaderTimeout: time.Minute,
1118+
Addr: addr,
1119+
Handler: handler,
11131120
}
11141121
go func() {
11151122
err := srv.ListenAndServe()

coderd/coderd.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,19 @@ func New(options *Options) *API {
136136
apps := func(r chi.Router) {
137137
r.Use(
138138
httpmw.RateLimitPerMinute(options.APIRateLimit),
139+
tracing.HTTPMW(api.TracerProvider, "coderd.http"),
139140
httpmw.ExtractAPIKey(options.Database, oauthConfigs, true),
140141
httpmw.ExtractUserParam(api.Database),
141-
tracing.HTTPMW(api.TracerProvider, "coderd.http"),
142+
// Extracts the <workspace.agent> from the url
143+
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
142144
)
143145
r.HandleFunc("/*", api.workspaceAppsProxyPath)
144146
}
145147
// %40 is the encoded character of the @ symbol. VS Code Web does
146148
// not handle character encoding properly, so it's safe to assume
147149
// other applications might not as well.
148-
r.Route("/%40{user}/{workspacename}/apps/{workspaceapp}", apps)
149-
r.Route("/@{user}/{workspacename}/apps/{workspaceapp}", apps)
150+
r.Route("/%40{user}/{workspace_and_agent}/apps/{workspaceapp}", apps)
151+
r.Route("/@{user}/{workspace_and_agent}/apps/{workspaceapp}", apps)
150152

151153
r.Route("/api/v2", func(r chi.Router) {
152154
r.NotFound(func(rw http.ResponseWriter, r *http.Request) {

coderd/coderdtest/authtest.go

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func NewAuthTester(ctx context.Context, t *testing.T, options *Options) *AuthTes
8585
Name: "some",
8686
Type: "example",
8787
Agents: []*proto.Agent{{
88+
Name: "agent",
8889
Id: "something",
8990
Auth: &proto.Agent_Token{},
9091
Apps: []*proto.App{{
@@ -119,22 +120,23 @@ func NewAuthTester(ctx context.Context, t *testing.T, options *Options) *AuthTes
119120
require.NoError(t, err, "create template param")
120121

121122
urlParameters := map[string]string{
122-
"{organization}": admin.OrganizationID.String(),
123-
"{user}": admin.UserID.String(),
124-
"{organizationname}": organization.Name,
125-
"{workspace}": workspace.ID.String(),
126-
"{workspacebuild}": workspace.LatestBuild.ID.String(),
127-
"{workspacename}": workspace.Name,
128-
"{workspacebuildname}": workspace.LatestBuild.Name,
129-
"{workspaceagent}": workspaceResources[0].Agents[0].ID.String(),
130-
"{buildnumber}": strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10),
131-
"{template}": template.ID.String(),
132-
"{hash}": file.Hash,
133-
"{workspaceresource}": workspaceResources[0].ID.String(),
134-
"{workspaceapp}": workspaceResources[0].Agents[0].Apps[0].Name,
135-
"{templateversion}": version.ID.String(),
136-
"{jobID}": templateVersionDryRun.ID.String(),
137-
"{templatename}": template.Name,
123+
"{organization}": admin.OrganizationID.String(),
124+
"{user}": admin.UserID.String(),
125+
"{organizationname}": organization.Name,
126+
"{workspace}": workspace.ID.String(),
127+
"{workspacebuild}": workspace.LatestBuild.ID.String(),
128+
"{workspacename}": workspace.Name,
129+
"{workspacebuildname}": workspace.LatestBuild.Name,
130+
"{workspaceagent}": workspaceResources[0].Agents[0].ID.String(),
131+
"{buildnumber}": strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10),
132+
"{template}": template.ID.String(),
133+
"{hash}": file.Hash,
134+
"{workspaceresource}": workspaceResources[0].ID.String(),
135+
"{workspaceapp}": workspaceResources[0].Agents[0].Apps[0].Name,
136+
"{templateversion}": version.ID.String(),
137+
"{jobID}": templateVersionDryRun.ID.String(),
138+
"{templatename}": template.Name,
139+
"{workspace_and_agent}": workspace.Name + "." + workspaceResources[0].Agents[0].Name,
138140
// Only checking template scoped params here
139141
"parameters/{scope}/{id}": fmt.Sprintf("parameters/%s/%s",
140142
string(templateParam.Scope), templateParam.ScopeID.String()),
@@ -178,15 +180,6 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
178180
"POST:/api/v2/csp/reports": {NoAuthorize: true},
179181
"GET:/api/v2/entitlements": {NoAuthorize: true},
180182

181-
"GET:/%40{user}/{workspacename}/apps/{workspaceapp}/*": {
182-
AssertAction: rbac.ActionCreate,
183-
AssertObject: workspaceExecObj,
184-
},
185-
"GET:/@{user}/{workspacename}/apps/{workspaceapp}/*": {
186-
AssertAction: rbac.ActionCreate,
187-
AssertObject: workspaceExecObj,
188-
},
189-
190183
// Has it's own auth
191184
"GET:/api/v2/users/oauth2/github/callback": {NoAuthorize: true},
192185
"GET:/api/v2/users/oidc/callback": {NoAuthorize: true},
@@ -399,6 +392,29 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
399392
"POST:/api/v2/workspaces/{workspace}/builds": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
400393
"POST:/api/v2/organizations/{organization}/templateversions": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
401394
}
395+
396+
// Routes like proxy routes support all HTTP methods. A helper func to expand
397+
// 1 url to all http methods.
398+
assertAllHTTPMethods := func(url string, check RouteCheck) {
399+
methods := []string{http.MethodGet, http.MethodHead, http.MethodPost,
400+
http.MethodPut, http.MethodPatch, http.MethodDelete,
401+
http.MethodConnect, http.MethodOptions, http.MethodTrace}
402+
403+
for _, method := range methods {
404+
route := method + ":" + url
405+
assertRoute[route] = check
406+
}
407+
}
408+
409+
assertAllHTTPMethods("/%40{user}/{workspace_and_agent}/apps/{workspaceapp}/*", RouteCheck{
410+
AssertAction: rbac.ActionCreate,
411+
AssertObject: workspaceExecObj,
412+
})
413+
assertAllHTTPMethods("/@{user}/{workspace_and_agent}/apps/{workspaceapp}/*", RouteCheck{
414+
AssertAction: rbac.ActionCreate,
415+
AssertObject: workspaceExecObj,
416+
})
417+
402418
return skipRoutes, assertRoute
403419
}
404420

@@ -446,6 +462,7 @@ func (a *AuthTester) Test(ctx context.Context, assertRoute map[string]RouteCheck
446462
a.t.Run(name, func(t *testing.T) {
447463
a.authorizer.reset()
448464
routeKey := strings.TrimRight(name, "/")
465+
449466
routeAssertions, ok := assertRoute[routeKey]
450467
if !ok {
451468
// By default, all omitted routes check for just "authorize" called

coderd/database/databasefake/databasefake.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -592,14 +592,14 @@ func (q *fakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(_ context.Context, wo
592592
defer q.mutex.RUnlock()
593593

594594
var row database.WorkspaceBuild
595-
var buildNum int32
595+
var buildNum int32 = -1
596596
for _, workspaceBuild := range q.workspaceBuilds {
597597
if workspaceBuild.WorkspaceID.String() == workspaceID.String() && workspaceBuild.BuildNumber > buildNum {
598598
row = workspaceBuild
599599
buildNum = workspaceBuild.BuildNumber
600600
}
601601
}
602-
if buildNum == 0 {
602+
if buildNum == -1 {
603603
return database.WorkspaceBuild{}, sql.ErrNoRows
604604
}
605605
return row, nil
@@ -1263,9 +1263,6 @@ func (q *fakeQuerier) GetWorkspaceAgentsByResourceIDs(_ context.Context, resourc
12631263
workspaceAgents = append(workspaceAgents, agent)
12641264
}
12651265
}
1266-
if len(workspaceAgents) == 0 {
1267-
return nil, sql.ErrNoRows
1268-
}
12691266
return workspaceAgents, nil
12701267
}
12711268

@@ -1347,9 +1344,6 @@ func (q *fakeQuerier) GetWorkspaceResourcesByJobID(_ context.Context, jobID uuid
13471344
}
13481345
resources = append(resources, resource)
13491346
}
1350-
if len(resources) == 0 {
1351-
return nil, sql.ErrNoRows
1352-
}
13531347
return resources, nil
13541348
}
13551349

coderd/database/errors.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,6 @@ import (
66
"github.com/lib/pq"
77
)
88

9-
// UniqueConstraint represents a named unique constraint on a table.
10-
type UniqueConstraint string
11-
12-
// UniqueConstraint enums.
13-
// TODO(mafredri): Generate these from the database schema.
14-
const (
15-
UniqueWorkspacesOwnerIDLowerIdx UniqueConstraint = "workspaces_owner_id_lower_idx"
16-
)
17-
189
// IsUniqueViolation checks if the error is due to a unique violation.
1910
// If one or more specific unique constraints are given as arguments,
2011
// the error must be caused by one of them. If no constraints are given,

coderd/database/dump/main.go renamed to coderd/database/gen/dump/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func main() {
8888
if !ok {
8989
panic("couldn't get caller path")
9090
}
91-
err = os.WriteFile(filepath.Join(mainPath, "..", "..", "dump.sql"), []byte(dump), 0600)
91+
err = os.WriteFile(filepath.Join(mainPath, "..", "..", "..", "dump.sql"), []byte(dump), 0o600)
9292
if err != nil {
9393
panic(err)
9494
}

0 commit comments

Comments
 (0)