Skip to content

Commit ebc5110

Browse files
committed
Merge remote-tracking branch 'origin/main' into stevenmasley/template_edit_unset
2 parents dd05c60 + ace188b commit ebc5110

Some content is hidden

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

49 files changed

+674
-115
lines changed

cli/ssh_test.go

+22-16
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,8 @@ func TestSSH(t *testing.T) {
636636
defer httpServer.Close()
637637

638638
client, workspace, agentToken := setupWorkspaceForAgent(t, nil)
639+
_ = agenttest.New(t, client.URL, agentToken)
640+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
639641

640642
inv, root := clitest.New(t,
641643
"ssh",
@@ -644,32 +646,36 @@ func TestSSH(t *testing.T) {
644646
"8222:"+httpServer.Listener.Addr().String(),
645647
)
646648
clitest.SetupConfig(t, client, root)
647-
pty := ptytest.New(t).Attach(inv)
648-
inv.Stderr = pty.Output()
649649

650650
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
651651
defer cancel()
652652

653653
cmdDone := tGo(t, func() {
654654
err := inv.WithContext(ctx).Run()
655-
assert.NoError(t, err, "ssh command failed")
655+
// fails because we cancel context to close
656+
assert.Error(t, err, "ssh command should fail")
656657
})
657658

658-
// Agent is still starting
659-
pty.ExpectMatch("Waiting")
660-
661-
_ = agenttest.New(t, client.URL, agentToken)
662-
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
663-
664-
// Startup script has just finished
665-
pty.ExpectMatch(startupScriptPattern)
666-
667-
// Download the test page
668-
pty.WriteLine("curl localhost:8222")
669-
pty.ExpectMatch("hello world")
659+
require.Eventually(t, func() bool {
660+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8222/", nil)
661+
if !assert.NoError(t, err) {
662+
// true exits the loop.
663+
return true
664+
}
665+
resp, err := http.DefaultClient.Do(req)
666+
if err != nil {
667+
t.Logf("HTTP GET http://localhost:8222/ %s", err)
668+
return false
669+
}
670+
defer resp.Body.Close()
671+
body, err := io.ReadAll(resp.Body)
672+
assert.NoError(t, err)
673+
assert.EqualValues(t, "hello world", body)
674+
return true
675+
}, testutil.WaitLong, testutil.IntervalFast)
670676

671677
// And we're done.
672-
pty.WriteLine("exit")
678+
cancel()
673679
<-cmdDone
674680
})
675681

cli/templateedit.go

+14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
)
1717

1818
func (r *RootCmd) templateEdit() *clibase.Cmd {
19+
const deprecatedFlagName = "deprecated"
1920
var (
2021
name string
2122
displayName string
@@ -32,6 +33,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
3233
allowUserAutostart bool
3334
allowUserAutostop bool
3435
requireActiveVersion bool
36+
deprecationMessage string
3537
)
3638
client := new(codersdk.Client)
3739

@@ -131,6 +133,11 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
131133
displayName = template.DisplayName
132134
}
133135

136+
var deprecated *string
137+
if !userSetOption(inv, "deprecated") {
138+
deprecated = &deprecationMessage
139+
}
140+
134141
req := codersdk.UpdateTemplateMeta{
135142
Name: name,
136143
DisplayName: displayName,
@@ -151,6 +158,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
151158
AllowUserAutostart: allowUserAutostart,
152159
AllowUserAutostop: allowUserAutostop,
153160
RequireActiveVersion: requireActiveVersion,
161+
DeprecationMessage: deprecated,
154162
}
155163

156164
_, err = client.UpdateTemplateMeta(inv.Context(), template.ID, req)
@@ -178,6 +186,12 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
178186
Description: "Edit the template description.",
179187
Value: clibase.StringOf(&description),
180188
},
189+
{
190+
Name: deprecatedFlagName,
191+
Flag: "deprecated",
192+
Description: "Sets the template as deprecated. Must be a message explaining why the template is deprecated.",
193+
Value: clibase.StringOf(&deprecationMessage),
194+
},
181195
{
182196
Flag: "icon",
183197
Description: "Edit the template icon path.",

cli/templateedit_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -1085,5 +1085,6 @@ func TestTemplateEdit(t *testing.T) {
10851085
assert.Equal(t, template.Icon, updated.Icon)
10861086
assert.Equal(t, template.DisplayName, updated.DisplayName)
10871087
assert.Equal(t, template.Description, updated.Description)
1088+
assert.Equal(t, template.DeprecationMessage, updated.DeprecationMessage)
10881089
})
10891090
}

cli/testdata/coder_templates_edit_--help.golden

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ OPTIONS:
2828
from this template default to this value. Maps to "Default autostop"
2929
in the UI.
3030

31+
--deprecated string
32+
Sets the template as deprecated. Must be a message explaining why the
33+
template is deprecated.
34+
3135
--description string
3236
Edit the template description.
3337

coderd/apidoc/docs.go

+6-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+6-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderdtest/coderdtest.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,21 @@ func CreateAnotherUserMutators(t testing.TB, client *codersdk.Client, organizati
614614
return createAnotherUserRetry(t, client, organizationID, 5, roles, mutators...)
615615
}
616616

617+
// AuthzUserSubject does not include the user's groups.
618+
func AuthzUserSubject(user codersdk.User) rbac.Subject {
619+
roles := make(rbac.RoleNames, 0, len(user.Roles))
620+
for _, r := range user.Roles {
621+
roles = append(roles, r.Name)
622+
}
623+
624+
return rbac.Subject{
625+
ID: user.ID.String(),
626+
Roles: roles,
627+
Groups: []string{},
628+
Scope: rbac.ScopeAll,
629+
}
630+
}
631+
617632
func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, retries int, roles []string, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
618633
req := codersdk.CreateUserRequest{
619634
Email: namesgenerator.GetRandomName(10) + "@coder.com",
@@ -689,7 +704,7 @@ func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationI
689704
siteRoles = append(siteRoles, r.Name)
690705
}
691706

692-
_, err := client.UpdateUserRoles(context.Background(), user.ID.String(), codersdk.UpdateRoles{Roles: siteRoles})
707+
user, err = client.UpdateUserRoles(context.Background(), user.ID.String(), codersdk.UpdateRoles{Roles: siteRoles})
693708
require.NoError(t, err, "update site roles")
694709

695710
// Update org roles

coderd/database/dbauthz/accesscontrol.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55

66
"github.com/google/uuid"
7+
"golang.org/x/xerrors"
78

89
"github.com/coder/coder/v2/coderd/database"
910
)
@@ -18,6 +19,11 @@ type AccessControlStore interface {
1819

1920
type TemplateAccessControl struct {
2021
RequireActiveVersion bool
22+
Deprecated string
23+
}
24+
25+
func (t TemplateAccessControl) IsDeprecated() bool {
26+
return t.Deprecated != ""
2127
}
2228

2329
// AGPLTemplateAccessControlStore always returns the defaults for access control
@@ -26,12 +32,38 @@ type AGPLTemplateAccessControlStore struct{}
2632

2733
var _ AccessControlStore = AGPLTemplateAccessControlStore{}
2834

29-
func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(database.Template) TemplateAccessControl {
35+
func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(t database.Template) TemplateAccessControl {
3036
return TemplateAccessControl{
3137
RequireActiveVersion: false,
38+
// AGPL cannot set deprecated templates, but it should return
39+
// existing deprecated templates. This is erroring on the safe side
40+
// if a license expires, we should not allow deprecated templates
41+
// to be used for new workspaces.
42+
Deprecated: t.Deprecated,
3243
}
3344
}
3445

35-
func (AGPLTemplateAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, TemplateAccessControl) error {
46+
func (AGPLTemplateAccessControlStore) SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts TemplateAccessControl) error {
47+
// AGPL is allowed to unset deprecated templates.
48+
if opts.Deprecated == "" {
49+
// This does require fetching again to ensure other fields are not
50+
// changed.
51+
tpl, err := store.GetTemplateByID(ctx, id)
52+
if err != nil {
53+
return xerrors.Errorf("get template: %w", err)
54+
}
55+
56+
if tpl.Deprecated != "" {
57+
err := store.UpdateTemplateAccessControlByID(ctx, database.UpdateTemplateAccessControlByIDParams{
58+
ID: id,
59+
RequireActiveVersion: tpl.RequireActiveVersion,
60+
Deprecated: opts.Deprecated,
61+
})
62+
if err != nil {
63+
return xerrors.Errorf("update template access control: %w", err)
64+
}
65+
}
66+
}
67+
3668
return nil
3769
}

coderd/database/dbmem/dbmem.go

+4
Original file line numberDiff line numberDiff line change
@@ -5905,6 +5905,7 @@ func (q *FakeQuerier) UpdateTemplateAccessControlByID(_ context.Context, arg dat
59055905
continue
59065906
}
59075907
q.templates[idx].RequireActiveVersion = arg.RequireActiveVersion
5908+
q.templates[idx].Deprecated = arg.Deprecated
59085909
return nil
59095910
}
59105911

@@ -6887,6 +6888,9 @@ func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.G
68876888
if arg.ExactName != "" && !strings.EqualFold(template.Name, arg.ExactName) {
68886889
continue
68896890
}
6891+
if arg.Deprecated.Valid && arg.Deprecated.Bool == (template.Deprecated != "") {
6892+
continue
6893+
}
68906894

68916895
if len(arg.IDs) > 0 {
68926896
match := false

coderd/database/dump.sql

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
BEGIN;
2+
3+
DROP VIEW template_with_users;
4+
5+
ALTER TABLE templates
6+
DROP COLUMN deprecated;
7+
8+
CREATE VIEW
9+
template_with_users
10+
AS
11+
SELECT
12+
templates.*,
13+
coalesce(visible_users.avatar_url, '') AS created_by_avatar_url,
14+
coalesce(visible_users.username, '') AS created_by_username
15+
FROM
16+
templates
17+
LEFT JOIN
18+
visible_users
19+
ON
20+
templates.created_by = visible_users.id;
21+
22+
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
23+
24+
COMMIT;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
BEGIN;
2+
3+
-- The view will be rebuilt with the new column
4+
DROP VIEW template_with_users;
5+
6+
ALTER TABLE templates
7+
ADD COLUMN deprecated TEXT NOT NULL DEFAULT '';
8+
9+
COMMENT ON COLUMN templates.deprecated IS 'If set to a non empty string, the template will no longer be able to be used. The message will be displayed to the user.';
10+
11+
-- Restore the old version of the template_with_users view.
12+
CREATE VIEW
13+
template_with_users
14+
AS
15+
SELECT
16+
templates.*,
17+
coalesce(visible_users.avatar_url, '') AS created_by_avatar_url,
18+
coalesce(visible_users.username, '') AS created_by_username
19+
FROM
20+
templates
21+
LEFT JOIN
22+
visible_users
23+
ON
24+
templates.created_by = visible_users.id;
25+
26+
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
27+
28+
COMMIT;

coderd/database/modelqueries.go

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
5252
arg.OrganizationID,
5353
arg.ExactName,
5454
pq.Array(arg.IDs),
55+
arg.Deprecated,
5556
)
5657
if err != nil {
5758
return nil, err
@@ -87,6 +88,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
8788
&i.AutostopRequirementWeeks,
8889
&i.AutostartBlockDaysOfWeek,
8990
&i.RequireActiveVersion,
91+
&i.Deprecated,
9092
&i.CreatedByAvatarURL,
9193
&i.CreatedByUsername,
9294
); err != nil {

0 commit comments

Comments
 (0)