chore: add template_with_user view to include user contextual data (#8568)

* chore: Refactor template sql queries to use new view
* TemplateWithUser -> Template
* Add unit test to enforce good view
This commit is contained in:
Steven Masley
2023-07-19 16:07:33 -04:00
committed by GitHub
parent cdbae29a83
commit aceedefce3
25 changed files with 453 additions and 358 deletions

View File

@ -1808,9 +1808,12 @@ func (q *querier) InsertReplica(ctx context.Context, arg database.InsertReplicaP
return q.db.InsertReplica(ctx, arg)
}
func (q *querier) InsertTemplate(ctx context.Context, arg database.InsertTemplateParams) (database.Template, error) {
func (q *querier) InsertTemplate(ctx context.Context, arg database.InsertTemplateParams) error {
obj := rbac.ResourceTemplate.InOrg(arg.OrganizationID)
return insert(q.log, q.auth, obj, q.db.InsertTemplate)(ctx, arg)
if err := q.authorizeContext(ctx, rbac.ActionCreate, obj); err != nil {
return err
}
return q.db.InsertTemplate(ctx, arg)
}
func (q *querier) InsertTemplateVersion(ctx context.Context, arg database.InsertTemplateVersionParams) (database.TemplateVersion, error) {
@ -2134,13 +2137,13 @@ func (q *querier) UpdateReplica(ctx context.Context, arg database.UpdateReplicaP
return q.db.UpdateReplica(ctx, arg)
}
func (q *querier) UpdateTemplateACLByID(ctx context.Context, arg database.UpdateTemplateACLByIDParams) (database.Template, error) {
// UpdateTemplateACL uses the ActionCreate action. Only users that can create the template
// may update the ACL.
func (q *querier) UpdateTemplateACLByID(ctx context.Context, arg database.UpdateTemplateACLByIDParams) error {
fetch := func(ctx context.Context, arg database.UpdateTemplateACLByIDParams) (database.Template, error) {
return q.db.GetTemplateByID(ctx, arg.ID)
}
return fetchAndQuery(q.log, q.auth, rbac.ActionCreate, fetch, q.db.UpdateTemplateACLByID)(ctx, arg)
// UpdateTemplateACL uses the ActionCreate action. Only users that can create the template
// may update the ACL.
return fetchAndExec(q.log, q.auth, rbac.ActionCreate, fetch, q.db.UpdateTemplateACLByID)(ctx, arg)
}
func (q *querier) UpdateTemplateActiveVersionByID(ctx context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error {
@ -2155,18 +2158,18 @@ func (q *querier) UpdateTemplateDeletedByID(ctx context.Context, arg database.Up
return q.SoftDeleteTemplateByID(ctx, arg.ID)
}
func (q *querier) UpdateTemplateMetaByID(ctx context.Context, arg database.UpdateTemplateMetaByIDParams) (database.Template, error) {
func (q *querier) UpdateTemplateMetaByID(ctx context.Context, arg database.UpdateTemplateMetaByIDParams) error {
fetch := func(ctx context.Context, arg database.UpdateTemplateMetaByIDParams) (database.Template, error) {
return q.db.GetTemplateByID(ctx, arg.ID)
}
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateTemplateMetaByID)(ctx, arg)
return update(q.log, q.auth, fetch, q.db.UpdateTemplateMetaByID)(ctx, arg)
}
func (q *querier) UpdateTemplateScheduleByID(ctx context.Context, arg database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
func (q *querier) UpdateTemplateScheduleByID(ctx context.Context, arg database.UpdateTemplateScheduleByIDParams) error {
fetch := func(ctx context.Context, arg database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
return q.db.GetTemplateByID(ctx, arg.ID)
}
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateTemplateScheduleByID)(ctx, arg)
return update(q.log, q.auth, fetch, q.db.UpdateTemplateScheduleByID)(ctx, arg)
}
func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) (database.TemplateVersion, error) {

View File

@ -772,7 +772,7 @@ func (s *MethodTestSuite) TestTemplate() {
t1 := dbgen.Template(s.T(), db, database.Template{})
check.Args(database.UpdateTemplateACLByIDParams{
ID: t1.ID,
}).Asserts(t1, rbac.ActionCreate).Returns(t1)
}).Asserts(t1, rbac.ActionCreate)
}))
s.Run("UpdateTemplateActiveVersionByID", s.Subtest(func(db database.Store, check *expects) {
t1 := dbgen.Template(s.T(), db, database.Template{

View File

@ -58,7 +58,7 @@ func New() database.Store {
workspaceResourceMetadata: make([]database.WorkspaceResourceMetadatum, 0),
provisionerJobs: make([]database.ProvisionerJob, 0),
templateVersions: make([]database.TemplateVersion, 0),
templates: make([]database.Template, 0),
templates: make([]database.TemplateTable, 0),
workspaceAgentStats: make([]database.WorkspaceAgentStat, 0),
workspaceAgentLogs: make([]database.WorkspaceAgentStartupLog, 0),
workspaceBuilds: make([]database.WorkspaceBuild, 0),
@ -130,7 +130,7 @@ type data struct {
templateVersions []database.TemplateVersion
templateVersionParameters []database.TemplateVersionParameter
templateVersionVariables []database.TemplateVersionVariable
templates []database.Template
templates []database.TemplateTable
workspaceAgents []database.WorkspaceAgent
workspaceAgentMetadata []database.WorkspaceAgentMetadatum
workspaceAgentLogs []database.WorkspaceAgentStartupLog
@ -446,12 +446,37 @@ func (q *FakeQuerier) getLatestWorkspaceBuildByWorkspaceIDNoLock(_ context.Conte
func (q *FakeQuerier) getTemplateByIDNoLock(_ context.Context, id uuid.UUID) (database.Template, error) {
for _, template := range q.templates {
if template.ID == id {
return template.DeepCopy(), nil
return q.templateWithUserNoLock(template), nil
}
}
return database.Template{}, sql.ErrNoRows
}
func (q *FakeQuerier) templatesWithUserNoLock(tpl []database.TemplateTable) []database.Template {
cpy := make([]database.Template, 0, len(tpl))
for _, t := range tpl {
cpy = append(cpy, q.templateWithUserNoLock(t))
}
return cpy
}
func (q *FakeQuerier) templateWithUserNoLock(tpl database.TemplateTable) database.Template {
var user database.User
for _, _user := range q.users {
if _user.ID == tpl.CreatedBy {
user = _user
break
}
}
var withUser database.Template
// This is a cheeky way to copy the fields over without explicitly listing them all.
d, _ := json.Marshal(tpl)
_ = json.Unmarshal(d, &withUser)
withUser.CreatedByUsername = user.Username
withUser.CreatedByAvatarURL = user.AvatarURL.String
return withUser
}
func (q *FakeQuerier) getTemplateVersionByIDNoLock(_ context.Context, templateVersionID uuid.UUID) (database.TemplateVersion, error) {
for _, templateVersion := range q.templateVersions {
if templateVersion.ID != templateVersionID {
@ -1869,7 +1894,7 @@ func (q *FakeQuerier) GetTemplateByOrganizationAndName(_ context.Context, arg da
if template.Deleted != arg.Deleted {
continue
}
return template.DeepCopy(), nil
return q.templateWithUserNoLock(template), nil
}
return database.Template{}, sql.ErrNoRows
}
@ -2092,17 +2117,14 @@ func (q *FakeQuerier) GetTemplates(_ context.Context) ([]database.Template, erro
defer q.mutex.RUnlock()
templates := slices.Clone(q.templates)
for i := range templates {
templates[i] = templates[i].DeepCopy()
}
slices.SortFunc(templates, func(i, j database.Template) bool {
slices.SortFunc(templates, func(i, j database.TemplateTable) bool {
if i.Name != j.Name {
return i.Name < j.Name
}
return i.ID.String() < j.ID.String()
})
return templates, nil
return q.templatesWithUserNoLock(templates), nil
}
func (q *FakeQuerier) GetTemplatesWithFilter(ctx context.Context, arg database.GetTemplatesWithFilterParams) ([]database.Template, error) {
@ -3436,16 +3458,16 @@ func (q *FakeQuerier) InsertReplica(_ context.Context, arg database.InsertReplic
return replica, nil
}
func (q *FakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTemplateParams) (database.Template, error) {
func (q *FakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTemplateParams) error {
if err := validateDatabaseType(arg); err != nil {
return database.Template{}, err
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
//nolint:gosimple
template := database.Template{
template := database.TemplateTable{
ID: arg.ID,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
@ -3464,7 +3486,7 @@ func (q *FakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
AllowUserAutostop: true,
}
q.templates = append(q.templates, template)
return template.DeepCopy(), nil
return nil
}
func (q *FakeQuerier) InsertTemplateVersion(_ context.Context, arg database.InsertTemplateVersionParams) (database.TemplateVersion, error) {
@ -4172,9 +4194,9 @@ func (q *FakeQuerier) UpdateReplica(_ context.Context, arg database.UpdateReplic
return database.Replica{}, sql.ErrNoRows
}
func (q *FakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.UpdateTemplateACLByIDParams) (database.Template, error) {
func (q *FakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.UpdateTemplateACLByIDParams) error {
if err := validateDatabaseType(arg); err != nil {
return database.Template{}, err
return err
}
q.mutex.Lock()
@ -4186,11 +4208,11 @@ func (q *FakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.Upda
template.UserACL = arg.UserACL
q.templates[i] = template
return template.DeepCopy(), nil
return nil
}
}
return database.Template{}, sql.ErrNoRows
return sql.ErrNoRows
}
func (q *FakeQuerier) UpdateTemplateActiveVersionByID(_ context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error {
@ -4233,9 +4255,9 @@ func (q *FakeQuerier) UpdateTemplateDeletedByID(_ context.Context, arg database.
return sql.ErrNoRows
}
func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.UpdateTemplateMetaByIDParams) (database.Template, error) {
func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.UpdateTemplateMetaByIDParams) error {
if err := validateDatabaseType(arg); err != nil {
return database.Template{}, err
return err
}
q.mutex.Lock()
@ -4251,15 +4273,15 @@ func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
tpl.Description = arg.Description
tpl.Icon = arg.Icon
q.templates[idx] = tpl
return tpl.DeepCopy(), nil
return nil
}
return database.Template{}, sql.ErrNoRows
return sql.ErrNoRows
}
func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database.UpdateTemplateScheduleByIDParams) error {
if err := validateDatabaseType(arg); err != nil {
return database.Template{}, err
return err
}
q.mutex.Lock()
@ -4278,10 +4300,10 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database
tpl.InactivityTTL = arg.InactivityTTL
tpl.LockedTTL = arg.LockedTTL
q.templates[idx] = tpl
return tpl.DeepCopy(), nil
return nil
}
return database.Template{}, sql.ErrNoRows
return sql.ErrNoRows
}
func (q *FakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) (database.TemplateVersion, error) {
@ -4984,7 +5006,8 @@ func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.G
}
var templates []database.Template
for _, template := range q.templates {
for _, templateTable := range q.templates {
template := q.templateWithUserNoLock(templateTable)
if prepared != nil && prepared.Authorize(ctx, template.RBACObject()) != nil {
continue
}
@ -5012,7 +5035,7 @@ func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.G
continue
}
}
templates = append(templates, template.DeepCopy())
templates = append(templates, template)
}
if len(templates) > 0 {
slices.SortFunc(templates, func(i, j database.Template) bool {
@ -5031,7 +5054,7 @@ func (q *FakeQuerier) GetTemplateGroupRoles(_ context.Context, id uuid.UUID) ([]
q.mutex.RLock()
defer q.mutex.RUnlock()
var template database.Template
var template database.TemplateTable
for _, t := range q.templates {
if t.ID == id {
template = t
@ -5068,7 +5091,7 @@ func (q *FakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]d
q.mutex.RLock()
defer q.mutex.RUnlock()
var template database.Template
var template database.TemplateTable
for _, t := range q.templates {
if t.ID == id {
template = t

View File

@ -62,8 +62,9 @@ func AuditLog(t testing.TB, db database.Store, seed database.AuditLog) database.
}
func Template(t testing.TB, db database.Store, seed database.Template) database.Template {
template, err := db.InsertTemplate(genCtx, database.InsertTemplateParams{
ID: takeFirst(seed.ID, uuid.New()),
id := takeFirst(seed.ID, uuid.New())
err := db.InsertTemplate(genCtx, database.InsertTemplateParams{
ID: id,
CreatedAt: takeFirst(seed.CreatedAt, database.Now()),
UpdatedAt: takeFirst(seed.UpdatedAt, database.Now()),
OrganizationID: takeFirst(seed.OrganizationID, uuid.New()),
@ -79,6 +80,9 @@ func Template(t testing.TB, db database.Store, seed database.Template) database.
AllowUserCancelWorkspaceJobs: seed.AllowUserCancelWorkspaceJobs,
})
require.NoError(t, err, "insert template")
template, err := db.GetTemplateByID(context.Background(), id)
require.NoError(t, err, "get template")
return template
}

View File

@ -1103,11 +1103,11 @@ func (m metricsStore) InsertReplica(ctx context.Context, arg database.InsertRepl
return replica, err
}
func (m metricsStore) InsertTemplate(ctx context.Context, arg database.InsertTemplateParams) (database.Template, error) {
func (m metricsStore) InsertTemplate(ctx context.Context, arg database.InsertTemplateParams) error {
start := time.Now()
template, err := m.s.InsertTemplate(ctx, arg)
err := m.s.InsertTemplate(ctx, arg)
m.queryLatencies.WithLabelValues("InsertTemplate").Observe(time.Since(start).Seconds())
return template, err
return err
}
func (m metricsStore) InsertTemplateVersion(ctx context.Context, arg database.InsertTemplateVersionParams) (database.TemplateVersion, error) {
@ -1306,11 +1306,11 @@ func (m metricsStore) UpdateReplica(ctx context.Context, arg database.UpdateRepl
return replica, err
}
func (m metricsStore) UpdateTemplateACLByID(ctx context.Context, arg database.UpdateTemplateACLByIDParams) (database.Template, error) {
func (m metricsStore) UpdateTemplateACLByID(ctx context.Context, arg database.UpdateTemplateACLByIDParams) error {
start := time.Now()
template, err := m.s.UpdateTemplateACLByID(ctx, arg)
err := m.s.UpdateTemplateACLByID(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateTemplateACLByID").Observe(time.Since(start).Seconds())
return template, err
return err
}
func (m metricsStore) UpdateTemplateActiveVersionByID(ctx context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error {
@ -1327,18 +1327,18 @@ func (m metricsStore) UpdateTemplateDeletedByID(ctx context.Context, arg databas
return err
}
func (m metricsStore) UpdateTemplateMetaByID(ctx context.Context, arg database.UpdateTemplateMetaByIDParams) (database.Template, error) {
func (m metricsStore) UpdateTemplateMetaByID(ctx context.Context, arg database.UpdateTemplateMetaByIDParams) error {
start := time.Now()
template, err := m.s.UpdateTemplateMetaByID(ctx, arg)
err := m.s.UpdateTemplateMetaByID(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateTemplateMetaByID").Observe(time.Since(start).Seconds())
return template, err
return err
}
func (m metricsStore) UpdateTemplateScheduleByID(ctx context.Context, arg database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
func (m metricsStore) UpdateTemplateScheduleByID(ctx context.Context, arg database.UpdateTemplateScheduleByIDParams) error {
start := time.Now()
template, err := m.s.UpdateTemplateScheduleByID(ctx, arg)
err := m.s.UpdateTemplateScheduleByID(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateTemplateScheduleByID").Observe(time.Since(start).Seconds())
return template, err
return err
}
func (m metricsStore) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) (database.TemplateVersion, error) {

View File

@ -2318,12 +2318,11 @@ func (mr *MockStoreMockRecorder) InsertReplica(arg0, arg1 interface{}) *gomock.C
}
// InsertTemplate mocks base method.
func (m *MockStore) InsertTemplate(arg0 context.Context, arg1 database.InsertTemplateParams) (database.Template, error) {
func (m *MockStore) InsertTemplate(arg0 context.Context, arg1 database.InsertTemplateParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertTemplate", arg0, arg1)
ret0, _ := ret[0].(database.Template)
ret1, _ := ret[1].(error)
return ret0, ret1
ret0, _ := ret[0].(error)
return ret0
}
// InsertTemplate indicates an expected call of InsertTemplate.
@ -2761,12 +2760,11 @@ func (mr *MockStoreMockRecorder) UpdateReplica(arg0, arg1 interface{}) *gomock.C
}
// UpdateTemplateACLByID mocks base method.
func (m *MockStore) UpdateTemplateACLByID(arg0 context.Context, arg1 database.UpdateTemplateACLByIDParams) (database.Template, error) {
func (m *MockStore) UpdateTemplateACLByID(arg0 context.Context, arg1 database.UpdateTemplateACLByIDParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateTemplateACLByID", arg0, arg1)
ret0, _ := ret[0].(database.Template)
ret1, _ := ret[1].(error)
return ret0, ret1
ret0, _ := ret[0].(error)
return ret0
}
// UpdateTemplateACLByID indicates an expected call of UpdateTemplateACLByID.
@ -2804,12 +2802,11 @@ func (mr *MockStoreMockRecorder) UpdateTemplateDeletedByID(arg0, arg1 interface{
}
// UpdateTemplateMetaByID mocks base method.
func (m *MockStore) UpdateTemplateMetaByID(arg0 context.Context, arg1 database.UpdateTemplateMetaByIDParams) (database.Template, error) {
func (m *MockStore) UpdateTemplateMetaByID(arg0 context.Context, arg1 database.UpdateTemplateMetaByIDParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateTemplateMetaByID", arg0, arg1)
ret0, _ := ret[0].(database.Template)
ret1, _ := ret[1].(error)
return ret0, ret1
ret0, _ := ret[0].(error)
return ret0
}
// UpdateTemplateMetaByID indicates an expected call of UpdateTemplateMetaByID.
@ -2819,12 +2816,11 @@ func (mr *MockStoreMockRecorder) UpdateTemplateMetaByID(arg0, arg1 interface{})
}
// UpdateTemplateScheduleByID mocks base method.
func (m *MockStore) UpdateTemplateScheduleByID(arg0 context.Context, arg1 database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
func (m *MockStore) UpdateTemplateScheduleByID(arg0 context.Context, arg1 database.UpdateTemplateScheduleByIDParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateTemplateScheduleByID", arg0, arg1)
ret0, _ := ret[0].(database.Template)
ret1, _ := ret[1].(error)
return ret0, ret1
ret0, _ := ret[0].(error)
return ret0
}
// UpdateTemplateScheduleByID indicates an expected call of UpdateTemplateScheduleByID.

View File

@ -579,15 +579,6 @@ COMMENT ON COLUMN templates.allow_user_autostart IS 'Allow users to specify an a
COMMENT ON COLUMN templates.allow_user_autostop IS 'Allow users to specify custom autostop values for workspaces (enterprise).';
CREATE TABLE user_links (
user_id uuid NOT NULL,
login_type login_type NOT NULL,
linked_id text DEFAULT ''::text NOT NULL,
oauth_access_token text DEFAULT ''::text NOT NULL,
oauth_refresh_token text DEFAULT ''::text NOT NULL,
oauth_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL
);
CREATE TABLE users (
id uuid NOT NULL,
email text NOT NULL,
@ -603,6 +594,53 @@ CREATE TABLE users (
last_seen_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL
);
CREATE VIEW visible_users AS
SELECT users.id,
users.username,
users.avatar_url
FROM users;
COMMENT ON VIEW visible_users IS 'Visible fields of users are allowed to be joined with other tables for including context of other resources.';
CREATE VIEW template_with_users AS
SELECT templates.id,
templates.created_at,
templates.updated_at,
templates.organization_id,
templates.deleted,
templates.name,
templates.provisioner,
templates.active_version_id,
templates.description,
templates.default_ttl,
templates.created_by,
templates.icon,
templates.user_acl,
templates.group_acl,
templates.display_name,
templates.allow_user_cancel_workspace_jobs,
templates.max_ttl,
templates.allow_user_autostart,
templates.allow_user_autostop,
templates.failure_ttl,
templates.inactivity_ttl,
templates.locked_ttl,
COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url,
COALESCE(visible_users.username, ''::text) AS created_by_username
FROM (public.templates
LEFT JOIN visible_users ON ((templates.created_by = visible_users.id)));
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
CREATE TABLE user_links (
user_id uuid NOT NULL,
login_type login_type NOT NULL,
linked_id text DEFAULT ''::text NOT NULL,
oauth_access_token text DEFAULT ''::text NOT NULL,
oauth_refresh_token text DEFAULT ''::text NOT NULL,
oauth_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL
);
CREATE UNLOGGED TABLE workspace_agent_metadata (
workspace_agent_id uuid NOT NULL,
display_name character varying(127) NOT NULL,

View File

@ -57,4 +57,6 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
go run golang.org/x/tools/cmd/goimports@latest -w queries.sql.go
go run ../../scripts/dbgen/main.go
# This will error if a view is broken.
go test -run=TestViewSubset
)

View File

@ -0,0 +1,6 @@
BEGIN;
DROP VIEW template_with_users;
DROP VIEW visible_users;
COMMIT;

View File

@ -0,0 +1,30 @@
BEGIN;
CREATE VIEW
visible_users
AS
SELECT
id, username, avatar_url
FROM
users;
COMMENT ON VIEW visible_users IS 'Visible fields of users are allowed to be joined with other tables for including context of other resources.';
-- If you need to update this view, put 'DROP VIEW template_with_users;' before this.
CREATE VIEW
template_with_users
AS
SELECT
templates.*,
coalesce(visible_users.avatar_url, '') AS created_by_avatar_url,
coalesce(visible_users.username, '') AS created_by_username
FROM
templates
LEFT JOIN
visible_users
ON
templates.created_by = visible_users.id;
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
COMMIT;

View File

@ -54,7 +54,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
pq.Array(arg.IDs),
)
if err != nil {
return nil, xerrors.Errorf("query context: %w", err)
return nil, err
}
defer rows.Close()
var items []Template
@ -83,16 +83,18 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
); err != nil {
return nil, xerrors.Errorf("scan: %w", err)
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, xerrors.Errorf("close: %w", err)
return nil, err
}
if err := rows.Err(); err != nil {
return nil, xerrors.Errorf("rows err: %w", err)
return nil, err
}
return items, nil
}

View File

@ -1567,7 +1567,35 @@ type TailnetCoordinator struct {
HeartbeatAt time.Time `db:"heartbeat_at" json:"heartbeat_at"`
}
// Joins in the username + avatar url of the created by user.
type Template struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
Provisioner ProvisionerType `db:"provisioner" json:"provisioner"`
ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"`
Description string `db:"description" json:"description"`
DefaultTTL int64 `db:"default_ttl" json:"default_ttl"`
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
Icon string `db:"icon" json:"icon"`
UserACL TemplateACL `db:"user_acl" json:"user_acl"`
GroupACL TemplateACL `db:"group_acl" json:"group_acl"`
DisplayName string `db:"display_name" json:"display_name"`
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
MaxTTL int64 `db:"max_ttl" json:"max_ttl"`
AllowUserAutostart bool `db:"allow_user_autostart" json:"allow_user_autostart"`
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
InactivityTTL int64 `db:"inactivity_ttl" json:"inactivity_ttl"`
LockedTTL int64 `db:"locked_ttl" json:"locked_ttl"`
CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"`
CreatedByUsername string `db:"created_by_username" json:"created_by_username"`
}
type TemplateTable struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
@ -1691,6 +1719,13 @@ type UserLink struct {
OAuthExpiry time.Time `db:"oauth_expiry" json:"oauth_expiry"`
}
// Visible fields of users are allowed to be joined with other tables for including context of other resources.
type VisibleUser struct {
ID uuid.UUID `db:"id" json:"id"`
Username string `db:"username" json:"username"`
AvatarURL sql.NullString `db:"avatar_url" json:"avatar_url"`
}
type Workspace struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`

View File

@ -0,0 +1,46 @@
package database_test
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/coder/coder/coderd/database"
)
// TestViewSubsetTemplate ensures TemplateTable is a subset of Template
func TestViewSubsetTemplate(t *testing.T) {
t.Parallel()
table := reflect.TypeOf(database.TemplateTable{})
joined := reflect.TypeOf(database.Template{})
tableFields := allFields(table)
joinedFields := allFields(joined)
if !assert.Subset(t, fieldNames(joinedFields), fieldNames(tableFields), "table is not subset") {
t.Log("Some fields were added to the Template Table without updating the 'template_with_users' view.")
t.Log("See migration 000138_join_users_up.sql to create the view.")
}
}
func fieldNames(fields []reflect.StructField) []string {
names := make([]string, len(fields))
for i, field := range fields {
names[i] = field.Name
}
return names
}
func allFields(rt reflect.Type) []reflect.StructField {
fields := make([]reflect.StructField, 0, rt.NumField())
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
if field.Anonymous && field.Type.Kind() == reflect.Struct {
// Recurse into anonymous struct fields.
fields = append(fields, allFields(field.Type)...)
continue
}
fields = append(fields, rt.Field(i))
}
return fields
}

View File

@ -191,7 +191,7 @@ type sqlcQuerier interface {
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error)
InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error)
InsertTemplate(ctx context.Context, arg InsertTemplateParams) (Template, error)
InsertTemplate(ctx context.Context, arg InsertTemplateParams) error
InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) (TemplateVersion, error)
InsertTemplateVersionParameter(ctx context.Context, arg InsertTemplateVersionParameterParams) (TemplateVersionParameter, error)
InsertTemplateVersionVariable(ctx context.Context, arg InsertTemplateVersionVariableParams) (TemplateVersionVariable, error)
@ -225,11 +225,11 @@ type sqlcQuerier interface {
UpdateProvisionerJobWithCancelByID(ctx context.Context, arg UpdateProvisionerJobWithCancelByIDParams) error
UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteByIDParams) error
UpdateReplica(ctx context.Context, arg UpdateReplicaParams) (Replica, error)
UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) (Template, error)
UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) error
UpdateTemplateActiveVersionByID(ctx context.Context, arg UpdateTemplateActiveVersionByIDParams) error
UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error
UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) (Template, error)
UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) (Template, error)
UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error
UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error
UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) (TemplateVersion, error)
UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg UpdateTemplateVersionDescriptionByJobIDParams) error
UpdateTemplateVersionGitAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionGitAuthProvidersByJobIDParams) error

View File

@ -3637,9 +3637,9 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem
const getTemplateByID = `-- name: GetTemplateByID :one
SELECT
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, created_by_avatar_url, created_by_username
FROM
templates
template_with_users
WHERE
id = $1
LIMIT
@ -3672,15 +3672,17 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
)
return i, err
}
const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one
SELECT
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, created_by_avatar_url, created_by_username
FROM
templates
template_with_users AS templates
WHERE
organization_id = $1
AND deleted = $2
@ -3721,12 +3723,14 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
)
return i, err
}
const getTemplates = `-- name: GetTemplates :many
SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl FROM templates
SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, created_by_avatar_url, created_by_username FROM template_with_users AS templates
ORDER BY (name, id) ASC
`
@ -3762,6 +3766,8 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
); err != nil {
return nil, err
}
@ -3778,9 +3784,9 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many
SELECT
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl, created_by_avatar_url, created_by_username
FROM
templates
template_with_users AS templates
WHERE
-- Optionally include deleted templates
templates.deleted = $1
@ -3851,6 +3857,8 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
); err != nil {
return nil, err
}
@ -3865,7 +3873,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
return items, nil
}
const insertTemplate = `-- name: InsertTemplate :one
const insertTemplate = `-- name: InsertTemplate :exec
INSERT INTO
templates (
id,
@ -3884,7 +3892,7 @@ INSERT INTO
allow_user_cancel_workspace_jobs
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
`
type InsertTemplateParams struct {
@ -3904,8 +3912,8 @@ type InsertTemplateParams struct {
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
}
func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) (Template, error) {
row := q.db.QueryRowContext(ctx, insertTemplate,
func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) error {
_, err := q.db.ExecContext(ctx, insertTemplate,
arg.ID,
arg.CreatedAt,
arg.UpdatedAt,
@ -3921,35 +3929,10 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
arg.DisplayName,
arg.AllowUserCancelWorkspaceJobs,
)
var i Template
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.UpdatedAt,
&i.OrganizationID,
&i.Deleted,
&i.Name,
&i.Provisioner,
&i.ActiveVersionID,
&i.Description,
&i.DefaultTTL,
&i.CreatedBy,
&i.Icon,
&i.UserACL,
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
)
return i, err
return err
}
const updateTemplateACLByID = `-- name: UpdateTemplateACLByID :one
const updateTemplateACLByID = `-- name: UpdateTemplateACLByID :exec
UPDATE
templates
SET
@ -3957,8 +3940,6 @@ SET
user_acl = $2
WHERE
id = $3
RETURNING
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl
`
type UpdateTemplateACLByIDParams struct {
@ -3967,34 +3948,9 @@ type UpdateTemplateACLByIDParams struct {
ID uuid.UUID `db:"id" json:"id"`
}
func (q *sqlQuerier) UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) (Template, error) {
row := q.db.QueryRowContext(ctx, updateTemplateACLByID, arg.GroupACL, arg.UserACL, arg.ID)
var i Template
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.UpdatedAt,
&i.OrganizationID,
&i.Deleted,
&i.Name,
&i.Provisioner,
&i.ActiveVersionID,
&i.Description,
&i.DefaultTTL,
&i.CreatedBy,
&i.Icon,
&i.UserACL,
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
)
return i, err
func (q *sqlQuerier) UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) error {
_, err := q.db.ExecContext(ctx, updateTemplateACLByID, arg.GroupACL, arg.UserACL, arg.ID)
return err
}
const updateTemplateActiveVersionByID = `-- name: UpdateTemplateActiveVersionByID :exec
@ -4039,7 +3995,7 @@ func (q *sqlQuerier) UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTe
return err
}
const updateTemplateMetaByID = `-- name: UpdateTemplateMetaByID :one
const updateTemplateMetaByID = `-- name: UpdateTemplateMetaByID :exec
UPDATE
templates
SET
@ -4051,8 +4007,6 @@ SET
allow_user_cancel_workspace_jobs = $7
WHERE
id = $1
RETURNING
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl
`
type UpdateTemplateMetaByIDParams struct {
@ -4065,8 +4019,8 @@ type UpdateTemplateMetaByIDParams struct {
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
}
func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) (Template, error) {
row := q.db.QueryRowContext(ctx, updateTemplateMetaByID,
func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error {
_, err := q.db.ExecContext(ctx, updateTemplateMetaByID,
arg.ID,
arg.UpdatedAt,
arg.Description,
@ -4075,35 +4029,10 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
arg.DisplayName,
arg.AllowUserCancelWorkspaceJobs,
)
var i Template
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.UpdatedAt,
&i.OrganizationID,
&i.Deleted,
&i.Name,
&i.Provisioner,
&i.ActiveVersionID,
&i.Description,
&i.DefaultTTL,
&i.CreatedBy,
&i.Icon,
&i.UserACL,
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
)
return i, err
return err
}
const updateTemplateScheduleByID = `-- name: UpdateTemplateScheduleByID :one
const updateTemplateScheduleByID = `-- name: UpdateTemplateScheduleByID :exec
UPDATE
templates
SET
@ -4117,8 +4046,6 @@ SET
locked_ttl = $9
WHERE
id = $1
RETURNING
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, inactivity_ttl, locked_ttl
`
type UpdateTemplateScheduleByIDParams struct {
@ -4133,8 +4060,8 @@ type UpdateTemplateScheduleByIDParams struct {
LockedTTL int64 `db:"locked_ttl" json:"locked_ttl"`
}
func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) (Template, error) {
row := q.db.QueryRowContext(ctx, updateTemplateScheduleByID,
func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error {
_, err := q.db.ExecContext(ctx, updateTemplateScheduleByID,
arg.ID,
arg.UpdatedAt,
arg.AllowUserAutostart,
@ -4145,32 +4072,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
arg.InactivityTTL,
arg.LockedTTL,
)
var i Template
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.UpdatedAt,
&i.OrganizationID,
&i.Deleted,
&i.Name,
&i.Provisioner,
&i.ActiveVersionID,
&i.Description,
&i.DefaultTTL,
&i.CreatedBy,
&i.Icon,
&i.UserACL,
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
&i.InactivityTTL,
&i.LockedTTL,
)
return i, err
return err
}
const getTemplateVersionParameters = `-- name: GetTemplateVersionParameters :many

View File

@ -2,7 +2,7 @@
SELECT
*
FROM
templates
template_with_users
WHERE
id = $1
LIMIT
@ -12,7 +12,7 @@ LIMIT
SELECT
*
FROM
templates
template_with_users AS templates
WHERE
-- Optionally include deleted templates
templates.deleted = @deleted
@ -43,7 +43,7 @@ ORDER BY (name, id) ASC
SELECT
*
FROM
templates
template_with_users AS templates
WHERE
organization_id = @organization_id
AND deleted = @deleted
@ -52,11 +52,11 @@ LIMIT
1;
-- name: GetTemplates :many
SELECT * FROM templates
SELECT * FROM template_with_users AS templates
ORDER BY (name, id) ASC
;
-- name: InsertTemplate :one
-- name: InsertTemplate :exec
INSERT INTO
templates (
id,
@ -75,7 +75,7 @@ INSERT INTO
allow_user_cancel_workspace_jobs
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *;
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);
-- name: UpdateTemplateActiveVersionByID :exec
UPDATE
@ -95,7 +95,7 @@ SET
WHERE
id = $1;
-- name: UpdateTemplateMetaByID :one
-- name: UpdateTemplateMetaByID :exec
UPDATE
templates
SET
@ -107,10 +107,9 @@ SET
allow_user_cancel_workspace_jobs = $7
WHERE
id = $1
RETURNING
*;
;
-- name: UpdateTemplateScheduleByID :one
-- name: UpdateTemplateScheduleByID :exec
UPDATE
templates
SET
@ -124,10 +123,9 @@ SET
locked_ttl = $9
WHERE
id = $1
RETURNING
*;
;
-- name: UpdateTemplateACLByID :one
-- name: UpdateTemplateACLByID :exec
UPDATE
templates
SET
@ -135,8 +133,7 @@ SET
user_acl = $2
WHERE
id = $3
RETURNING
*;
;
-- name: GetTemplateAverageBuildTime :one
WITH build_times AS (

View File

@ -21,12 +21,24 @@ overrides:
- column: "templates.group_acl"
go_type:
type: "TemplateACL"
- column: "template_with_users.user_acl"
go_type:
type: "TemplateACL"
- column: "template_with_users.group_acl"
go_type:
type: "TemplateACL"
- column: "template_with_users.created_by_avatar_url"
go_type:
type: "string"
rename:
template: TemplateTable
template_with_user: Template
api_key: APIKey
api_key_scope: APIKeyScope
api_key_scope_all: APIKeyScopeAll
api_key_scope_application_connect: APIKeyScopeApplicationConnect
avatar_url: AvatarURL
created_by_avatar_url: CreatedByAvatarURL
session_count_vscode: SessionCountVSCode
session_count_jetbrains: SessionCountJetBrains
session_count_reconnecting_pty: SessionCountReconnectingPTY

View File

@ -349,11 +349,14 @@ func TestCache_BuildTime(t *testing.T) {
defer cache.Close()
template, err := db.InsertTemplate(ctx, database.InsertTemplateParams{
ID: uuid.New(),
id := uuid.New()
err := db.InsertTemplate(ctx, database.InsertTemplateParams{
ID: id,
Provisioner: database.ProvisionerTypeEcho,
})
require.NoError(t, err)
template, err := db.GetTemplateByID(ctx, id)
require.NoError(t, err)
templateVersion, err := db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
ID: uuid.New(),

View File

@ -1025,7 +1025,7 @@ func TestCompleteJob(t *testing.T) {
Name: "template",
Provisioner: database.ProvisionerTypeEcho,
})
template, err := srv.Database.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
err := srv.Database.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
ID: template.ID,
UpdatedAt: database.Now(),
AllowUserAutostart: c.templateAllowAutostop,

View File

@ -4,6 +4,8 @@ import (
"context"
"time"
"golang.org/x/xerrors"
"github.com/google/uuid"
"github.com/coder/coder/coderd/database"
@ -68,17 +70,35 @@ func (*agplTemplateScheduleStore) SetTemplateScheduleOptions(ctx context.Context
return tpl, nil
}
return db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
ID: tpl.ID,
UpdatedAt: database.Now(),
DefaultTTL: int64(opts.DefaultTTL),
// Don't allow changing it, but keep the value in the DB (to avoid
// clearing settings if the license has an issue).
AllowUserAutostart: tpl.AllowUserAutostart,
AllowUserAutostop: tpl.AllowUserAutostop,
MaxTTL: tpl.MaxTTL,
FailureTTL: tpl.FailureTTL,
InactivityTTL: tpl.InactivityTTL,
LockedTTL: tpl.LockedTTL,
})
var template database.Template
err := db.InTx(func(db database.Store) error {
err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
ID: tpl.ID,
UpdatedAt: database.Now(),
DefaultTTL: int64(opts.DefaultTTL),
// Don't allow changing it, but keep the value in the DB (to avoid
// clearing settings if the license has an issue).
AllowUserAutostart: tpl.AllowUserAutostart,
AllowUserAutostop: tpl.AllowUserAutostop,
MaxTTL: tpl.MaxTTL,
FailureTTL: tpl.FailureTTL,
InactivityTTL: tpl.InactivityTTL,
LockedTTL: tpl.LockedTTL,
})
if err != nil {
return xerrors.Errorf("update template schedule: %w", err)
}
template, err = db.GetTemplateByID(ctx, tpl.ID)
if err != nil {
return xerrors.Errorf("fetch updated template: %w", err)
}
return nil
}, nil)
if err != nil {
return database.Template{}, err
}
return template, err
}

View File

@ -1,7 +1,6 @@
package coderd
import (
"context"
"database/sql"
"errors"
"fmt"
@ -40,16 +39,7 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
template := httpmw.TemplateParam(r)
createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, []database.Template{template})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching creator name.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template, createdByNameMap[template.ID.String()]))
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template))
}
// @Summary Delete template by ID
@ -290,8 +280,9 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
}
err = api.Database.InTx(func(tx database.Store) error {
now := database.Now()
dbTemplate, err = tx.InsertTemplate(ctx, database.InsertTemplateParams{
ID: uuid.New(),
id := uuid.New()
err = tx.InsertTemplate(ctx, database.InsertTemplateParams{
ID: id,
CreatedAt: now,
UpdatedAt: now,
OrganizationID: organization.ID,
@ -310,6 +301,11 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
return xerrors.Errorf("insert template: %s", err)
}
dbTemplate, err = tx.GetTemplateByID(ctx, id)
if err != nil {
return xerrors.Errorf("get template by id: %s", err)
}
dbTemplate, err = (*api.TemplateScheduleStore.Load()).SetTemplateScheduleOptions(ctx, tx, dbTemplate, schedule.TemplateScheduleOptions{
UserAutostartEnabled: allowUserAutostart,
UserAutostopEnabled: allowUserAutostop,
@ -348,12 +344,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
}
templateVersionAudit.New = newTemplateVersion
createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, tx, []database.Template{dbTemplate})
if err != nil {
return xerrors.Errorf("get creator name: %w", err)
}
template = api.convertTemplate(dbTemplate, createdByNameMap[dbTemplate.ID.String()])
template = api.convertTemplate(dbTemplate)
return nil
}, nil)
if err != nil {
@ -409,16 +400,7 @@ func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request)
return
}
createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, templates)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching creator names.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplates(templates, createdByNameMap))
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplates(templates))
}
// @Summary Get templates by organization and template name
@ -451,16 +433,7 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re
return
}
createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, []database.Template{template})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching creator name.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template, createdByNameMap[template.ID.String()]))
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template))
}
// @Summary Update template metadata by ID
@ -546,7 +519,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
}
var err error
updated, err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{
err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{
ID: template.ID,
UpdatedAt: database.Now(),
Name: name,
@ -559,6 +532,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
return xerrors.Errorf("update template metadata: %w", err)
}
updated, err = tx.GetTemplateByID(ctx, template.ID)
if err != nil {
return xerrors.Errorf("fetch updated template metadata: %w", err)
}
defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond
maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond
failureTTL := time.Duration(req.FailureTTLMillis) * time.Millisecond
@ -603,16 +581,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
}
aReq.New = updated
createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, []database.Template{updated})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching creator name.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(updated, createdByNameMap[updated.ID.String()]))
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(updated))
}
// @Summary Get template DAUs by ID
@ -680,23 +649,11 @@ func (api *API) templateExamples(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, ex)
}
func getCreatedByNamesByTemplateIDs(ctx context.Context, db database.Store, templates []database.Template) (map[string]string, error) {
creators := make(map[string]string, len(templates))
for _, template := range templates {
creator, err := db.GetUserByID(ctx, template.CreatedBy)
if err != nil {
return map[string]string{}, err
}
creators[template.ID.String()] = creator.Username
}
return creators, nil
}
func (api *API) convertTemplates(templates []database.Template, createdByNameMap map[string]string) []codersdk.Template {
func (api *API) convertTemplates(templates []database.Template) []codersdk.Template {
apiTemplates := make([]codersdk.Template, 0, len(templates))
for _, template := range templates {
apiTemplates = append(apiTemplates, api.convertTemplate(template, createdByNameMap[template.ID.String()]))
apiTemplates = append(apiTemplates, api.convertTemplate(template))
}
// Sort templates by ActiveUserCount DESC
@ -708,7 +665,7 @@ func (api *API) convertTemplates(templates []database.Template, createdByNameMap
}
func (api *API) convertTemplate(
template database.Template, createdByName string,
template database.Template,
) codersdk.Template {
activeCount, _ := api.metricsCache.TemplateUniqueUsers(template.ID)
@ -730,7 +687,7 @@ func (api *API) convertTemplate(
DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(),
MaxTTLMillis: time.Duration(template.MaxTTL).Milliseconds(),
CreatedByID: template.CreatedBy,
CreatedByName: createdByName,
CreatedByName: template.CreatedByUsername,
AllowUserAutostart: template.AllowUserAutostart,
AllowUserAutostop: template.AllowUserAutostop,
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,