Skip to content

Commit fe70708

Browse files
committed
Merge branch 'main' of https://github.com/coder/coder into bq/verify-external-auth-first
2 parents 7cf3361 + e828dab commit fe70708

File tree

19 files changed

+317
-59
lines changed

19 files changed

+317
-59
lines changed

agent/agentssh/agentssh.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,11 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
681681

682682
// This adds the ports dialog to code-server that enables
683683
// proxying a port dynamically.
684-
cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", manifest.VSCodePortProxyURI))
684+
// If this is empty string, do not set anything. Code-server auto defaults
685+
// using its basepath to construct a path based port proxy.
686+
if manifest.VSCodePortProxyURI != "" {
687+
cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", manifest.VSCodePortProxyURI))
688+
}
685689

686690
// Hide Coder message on code-server's "Getting Started" page
687691
cmd.Env = append(cmd.Env, "CS_DISABLE_GETTING_STARTED_OVERRIDE=true")

cli/restart.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (r *RootCmd) restart() *clibase.Cmd {
6464
// It's possible for a workspace build to fail due to the template requiring starting
6565
// workspaces with the active version.
6666
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusForbidden {
67-
_, _ = fmt.Fprintln(inv.Stdout, "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
67+
_, _ = fmt.Fprintln(inv.Stdout, "Unable to restart the workspace with the template version from the last build. Policy may require you to restart with the current active template version.")
6868
build, err = startWorkspace(inv, client, workspace, parameterFlags, WorkspaceUpdate)
6969
if err != nil {
7070
return xerrors.Errorf("start workspace with active template version: %w", err)

cli/ssh.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"io"
9+
"net/http"
910
"net/url"
1011
"os"
1112
"os/exec"
@@ -575,13 +576,27 @@ func getWorkspaceAndAgent(ctx context.Context, inv *clibase.Invocation, client *
575576
codersdk.WorkspaceStatusStopped,
576577
)
577578
}
578-
// startWorkspace based on the last build parameters.
579+
580+
// Start workspace based on the last build parameters.
581+
// It's possible for a workspace build to fail due to the template requiring starting
582+
// workspaces with the active version.
579583
_, _ = fmt.Fprintf(inv.Stderr, "Workspace was stopped, starting workspace to allow connecting to %q...\n", workspace.Name)
580-
build, err := startWorkspace(inv, client, workspace, workspaceParameterFlags{}, WorkspaceStart)
584+
_, err = startWorkspace(inv, client, workspace, workspaceParameterFlags{}, WorkspaceStart)
585+
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusForbidden {
586+
_, _ = fmt.Fprintln(inv.Stdout, "Unable to start the workspace with template version from last build. The auto-update policy may require you to restart with the current active template version.")
587+
_, err = startWorkspace(inv, client, workspace, workspaceParameterFlags{}, WorkspaceUpdate)
588+
if err != nil {
589+
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("start workspace with active template version: %w", err)
590+
}
591+
} else if err != nil {
592+
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("start workspace with current template version: %w", err)
593+
}
594+
595+
// Refresh workspace state so that `outdated`, `build`,`template_*` fields are up-to-date.
596+
workspace, err = namedWorkspace(ctx, client, workspaceParts[0])
581597
if err != nil {
582-
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("unable to start workspace: %w", err)
598+
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
583599
}
584-
workspace.LatestBuild = build
585600
}
586601
if workspace.LatestBuild.Job.CompletedAt == nil {
587602
err := cliui.WorkspaceBuild(ctx, inv.Stderr, client, workspace.LatestBuild.ID)

cli/ssh_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,71 @@ func TestSSH(t *testing.T) {
133133
pty.WriteLine("exit")
134134
<-cmdDone
135135
})
136+
t.Run("RequireActiveVersion", func(t *testing.T) {
137+
t.Parallel()
138+
139+
authToken := uuid.NewString()
140+
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
141+
owner := coderdtest.CreateFirstUser(t, ownerClient)
142+
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleMember())
143+
144+
echoResponses := &echo.Responses{
145+
Parse: echo.ParseComplete,
146+
ProvisionPlan: echo.PlanComplete,
147+
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
148+
}
149+
150+
version := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses)
151+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version.ID)
152+
template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version.ID)
153+
154+
workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
155+
cwr.AutomaticUpdates = codersdk.AutomaticUpdatesAlways
156+
})
157+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
158+
159+
// Stop the workspace
160+
workspaceBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
161+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceBuild.ID)
162+
163+
// Update template version
164+
version = coderdtest.UpdateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses, template.ID)
165+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version.ID)
166+
err := ownerClient.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
167+
ID: version.ID,
168+
})
169+
require.NoError(t, err)
170+
171+
// SSH to the workspace which should auto-update and autostart it
172+
inv, root := clitest.New(t, "ssh", workspace.Name)
173+
clitest.SetupConfig(t, client, root)
174+
pty := ptytest.New(t).Attach(inv)
175+
176+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
177+
defer cancel()
178+
179+
cmdDone := tGo(t, func() {
180+
err := inv.WithContext(ctx).Run()
181+
assert.NoError(t, err)
182+
})
183+
184+
// When the agent connects, the workspace was started, and we should
185+
// have access to the shell.
186+
_ = agenttest.New(t, client.URL, authToken)
187+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
188+
189+
// Shells on Mac, Windows, and Linux all exit shells with the "exit" command.
190+
pty.WriteLine("exit")
191+
<-cmdDone
192+
193+
// Double-check if workspace's template version is up-to-date
194+
workspace, err = client.Workspace(context.Background(), workspace.ID)
195+
require.NoError(t, err)
196+
assert.Equal(t, version.ID, workspace.TemplateActiveVersionID)
197+
assert.Equal(t, workspace.TemplateActiveVersionID, workspace.LatestBuild.TemplateVersionID)
198+
assert.False(t, workspace.Outdated)
199+
})
200+
136201
t.Run("ShowTroubleshootingURLAfterTimeout", func(t *testing.T) {
137202
t.Parallel()
138203

cli/start.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (r *RootCmd) start() *clibase.Cmd {
4949
// It's possible for a workspace build to fail due to the template requiring starting
5050
// workspaces with the active version.
5151
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusForbidden {
52-
_, _ = fmt.Fprintln(inv.Stdout, "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
52+
_, _ = fmt.Fprintln(inv.Stdout, "Unable to start the workspace with the template version from the last build. Policy may require you to restart with the current active template version.")
5353
build, err = startWorkspace(inv, client, workspace, parameterFlags, WorkspaceUpdate)
5454
if err != nil {
5555
return xerrors.Errorf("start workspace with active template version: %w", err)
@@ -79,6 +79,7 @@ func (r *RootCmd) start() *clibase.Cmd {
7979

8080
func buildWorkspaceStartRequest(inv *clibase.Invocation, client *codersdk.Client, workspace codersdk.Workspace, parameterFlags workspaceParameterFlags, action WorkspaceCLIAction) (codersdk.CreateWorkspaceBuildRequest, error) {
8181
version := workspace.LatestBuild.TemplateVersionID
82+
8283
if workspace.AutomaticUpdates == codersdk.AutomaticUpdatesAlways || action == WorkspaceUpdate {
8384
version = workspace.TemplateActiveVersionID
8485
if version != workspace.LatestBuild.TemplateVersionID {

coderd/agentapi/manifest.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,14 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
150150
}
151151

152152
func vscodeProxyURI(app appurl.ApplicationURL, accessURL *url.URL, appHost string) string {
153-
// This will handle the ports from the accessURL or appHost.
154-
appHost = appurl.SubdomainAppHost(appHost, accessURL)
155-
// If there is no appHost, then we want to use the access url as the proxy uri.
153+
// Proxying by port only works for subdomains. If subdomain support is not
154+
// available, return an empty string.
156155
if appHost == "" {
157-
appHost = accessURL.Host
156+
return ""
158157
}
158+
159+
// This will handle the ports from the accessURL or appHost.
160+
appHost = appurl.SubdomainAppHost(appHost, accessURL)
159161
// Return the url with a scheme and any wildcards replaced with the app slug.
160162
return accessURL.Scheme + "://" + strings.ReplaceAll(appHost, "*", app.String())
161163
}

coderd/agentapi/manifest_internal_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,18 @@ func Test_vscodeProxyURI(t *testing.T) {
3535
Expected string
3636
}{
3737
{
38-
// No hostname proxies through the access url.
3938
Name: "NoHostname",
4039
AccessURL: coderAccessURL,
4140
AppHostname: "",
4241
App: basicApp,
43-
Expected: coderAccessURL.String(),
42+
Expected: "",
4443
},
4544
{
4645
Name: "NoHostnameAccessURLPort",
4746
AccessURL: accessURLWithPort,
4847
AppHostname: "",
4948
App: basicApp,
50-
Expected: accessURLWithPort.String(),
49+
Expected: "",
5150
},
5251
{
5352
Name: "Hostname",

coderd/database/dbmem/dbmem.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7534,6 +7534,23 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
75347534
}
75357535
}
75367536

7537+
if arg.UsingActive.Valid {
7538+
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
7539+
if err != nil {
7540+
return nil, xerrors.Errorf("get latest build: %w", err)
7541+
}
7542+
7543+
template, err := q.getTemplateByIDNoLock(ctx, workspace.TemplateID)
7544+
if err != nil {
7545+
return nil, xerrors.Errorf("get template: %w", err)
7546+
}
7547+
7548+
updated := build.TemplateVersionID == template.ActiveVersionID
7549+
if arg.UsingActive.Bool != updated {
7550+
continue
7551+
}
7552+
}
7553+
75377554
if !arg.Deleted && workspace.Deleted {
75387555
continue
75397556
}

coderd/database/modelqueries.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ type workspaceQuerier interface {
198198
// This code is copied from `GetWorkspaces` and adds the authorized filter WHERE
199199
// clause.
200200
func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, prepared rbac.PreparedAuthorized) ([]GetWorkspacesRow, error) {
201-
authorizedFilter, err := prepared.CompileToSQL(ctx, rbac.ConfigWithoutACL())
201+
authorizedFilter, err := prepared.CompileToSQL(ctx, rbac.ConfigWorkspaces())
202202
if err != nil {
203203
return nil, xerrors.Errorf("compile authorized filter: %w", err)
204204
}
@@ -225,6 +225,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
225225
arg.Dormant,
226226
arg.LastUsedBefore,
227227
arg.LastUsedAfter,
228+
arg.UsingActive,
228229
arg.Offset,
229230
arg.Limit,
230231
)

coderd/database/queries.sql.go

Lines changed: 27 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaces.sql

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ WHERE
7979
-- name: GetWorkspaces :many
8080
SELECT
8181
workspaces.*,
82-
COALESCE(template_name.template_name, 'unknown') as template_name,
82+
COALESCE(template.name, 'unknown') as template_name,
8383
latest_build.template_version_id,
8484
latest_build.template_version_name,
8585
COUNT(*) OVER () as count
@@ -120,12 +120,12 @@ LEFT JOIN LATERAL (
120120
) latest_build ON TRUE
121121
LEFT JOIN LATERAL (
122122
SELECT
123-
templates.name AS template_name
123+
*
124124
FROM
125125
templates
126126
WHERE
127127
templates.id = workspaces.template_id
128-
) template_name ON true
128+
) template ON true
129129
WHERE
130130
-- Optionally include deleted workspaces
131131
workspaces.deleted = @deleted
@@ -259,6 +259,11 @@ WHERE
259259
workspaces.last_used_at >= @last_used_after
260260
ELSE true
261261
END
262+
AND CASE
263+
WHEN sqlc.narg('using_active') :: boolean IS NOT NULL THEN
264+
(latest_build.template_version_id = template.active_version_id) = sqlc.narg('using_active') :: boolean
265+
ELSE true
266+
END
262267
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces
263268
-- @authorize_filter
264269
ORDER BY

coderd/rbac/authz.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,12 @@ func ConfigWithoutACL() regosql.ConvertConfig {
611611
}
612612
}
613613

614+
func ConfigWorkspaces() regosql.ConvertConfig {
615+
return regosql.ConvertConfig{
616+
VariableConverter: regosql.WorkspaceConverter(),
617+
}
618+
}
619+
614620
func Compile(cfg regosql.ConvertConfig, pa *PartialAuthorizer) (AuthorizeFilter, error) {
615621
root, err := regosql.ConvertRegoAst(cfg, pa.partialQueries)
616622
if err != nil {

coderd/rbac/regosql/configs.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ func UserConverter() *sqltypes.VariableConverter {
5353
return matcher
5454
}
5555

56+
func WorkspaceConverter() *sqltypes.VariableConverter {
57+
matcher := sqltypes.NewVariableConverter().RegisterMatcher(
58+
resourceIDMatcher(),
59+
sqltypes.StringVarMatcher("workspaces.organization_id :: text", []string{"input", "object", "org_owner"}),
60+
userOwnerMatcher(),
61+
)
62+
matcher.RegisterMatcher(
63+
sqltypes.AlwaysFalse(groupACLMatcher(matcher)),
64+
sqltypes.AlwaysFalse(userACLMatcher(matcher)),
65+
)
66+
67+
return matcher
68+
}
69+
5670
// NoACLConverter should be used when the target SQL table does not contain
5771
// group or user ACL columns.
5872
func NoACLConverter() *sqltypes.VariableConverter {

0 commit comments

Comments
 (0)