Skip to content

feat: add template setting to require active template version #10277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
pass atomic pointer to autobuild
  • Loading branch information
sreya committed Oct 18, 2023
commit 8a4e0bf9de0ccb6d2e833a24731e0ac65b75820f
2 changes: 1 addition & 1 deletion cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
autobuildTicker := time.NewTicker(vals.AutobuildPollInterval.Value())
defer autobuildTicker.Stop()
autobuildExecutor := autobuild.NewExecutor(
ctx, options.Database, options.Pubsub, coderAPI.TemplateScheduleStore, &coderAPI.Auditor, logger, autobuildTicker.C)
ctx, options.Database, options.Pubsub, coderAPI.TemplateScheduleStore, &coderAPI.Auditor, coderAPI.AccessControlStore, logger, autobuildTicker.C)
autobuildExecutor.Run()

hangDetectorTicker := time.NewTicker(vals.JobHangDetectorInterval.Value())
Expand Down
3 changes: 2 additions & 1 deletion cli/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package cli
import (
"time"

"github.com/coder/pretty"
"github.com/google/uuid"
"golang.org/x/xerrors"

"github.com/coder/pretty"

"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
Expand Down
3 changes: 2 additions & 1 deletion cli/templateversionarchive.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
"strings"
"time"

"github.com/coder/pretty"
"golang.org/x/xerrors"

"github.com/coder/pretty"

"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
Expand Down
3 changes: 2 additions & 1 deletion cli/templateversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import (
"strings"
"time"

"github.com/coder/pretty"
"github.com/google/uuid"
"golang.org/x/xerrors"

"github.com/coder/pretty"

"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
Expand Down
14 changes: 11 additions & 3 deletions coderd/autobuild/lifecycle_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Executor struct {
db database.Store
ps pubsub.Pubsub
templateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore]
accessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
auditor *atomic.Pointer[audit.Auditor]
log slog.Logger
tick <-chan time.Time
Expand All @@ -46,7 +47,7 @@ type Stats struct {
}

// New returns a new wsactions executor.
func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss *atomic.Pointer[schedule.TemplateScheduleStore], auditor *atomic.Pointer[audit.Auditor], log slog.Logger, tick <-chan time.Time) *Executor {
func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss *atomic.Pointer[schedule.TemplateScheduleStore], auditor *atomic.Pointer[audit.Auditor], acs *atomic.Pointer[dbauthz.AccessControlStore], log slog.Logger, tick <-chan time.Time) *Executor {
le := &Executor{
//nolint:gocritic // Autostart has a limited set of permissions.
ctx: dbauthz.AsAutostart(ctx),
Expand All @@ -56,6 +57,7 @@ func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss *
tick: tick,
log: log.Named("autobuild"),
auditor: auditor,
accessControlStore: acs,
}
return le
}
Expand Down Expand Up @@ -159,6 +161,12 @@ func (e *Executor) runOnce(t time.Time) Stats {
return nil
}

template, err := tx.GetTemplateByID(e.ctx, ws.TemplateID)
if err != nil {
log.Warn(e.ctx, "get template by id", slog.Error(err))
}
accessControl := (*(e.accessControlStore.Load())).GetTemplateAccessControl(template)

latestJob, err := tx.GetProvisionerJobByID(e.ctx, latestBuild.JobID)
if err != nil {
log.Warn(e.ctx, "get last provisioner job for workspace %q: %w", slog.Error(err))
Expand All @@ -179,7 +187,7 @@ func (e *Executor) runOnce(t time.Time) Stats {
Reason(reason)
log.Debug(e.ctx, "auto building workspace", slog.F("transition", nextTransition))
if nextTransition == database.WorkspaceTransitionStart &&
useActiveVersion(templateSchedule, ws) {
useActiveVersion(accessControl, ws) {
log.Debug(e.ctx, "autostarting with active version")
builder = builder.ActiveVersion()
}
Expand Down Expand Up @@ -470,6 +478,6 @@ func auditBuild(ctx context.Context, log slog.Logger, auditor audit.Auditor, par
})
}

func useActiveVersion(opts schedule.TemplateScheduleOptions, ws database.Workspace) bool {
func useActiveVersion(opts dbauthz.TemplateAccessControl, ws database.Workspace) bool {
return opts.RequireActiveVersion || ws.AutomaticUpdates == database.AutomaticUpdatesAlways
}
13 changes: 8 additions & 5 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ type Options struct {
SetUserSiteRoles func(ctx context.Context, logger slog.Logger, tx database.Store, userID uuid.UUID, roles []string) error
TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore]
UserQuietHoursScheduleStore *atomic.Pointer[schedule.UserQuietHoursScheduleStore]
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
// AppSecurityKey is the crypto key used to sign and encrypt tokens related to
// workspace applications. It consists of both a signing and encryption key.
AppSecurityKey workspaceapps.SecurityKey
Expand Down Expand Up @@ -209,15 +210,17 @@ func New(options *Options) *API {
options.Authorizer = rbac.NewCachingAuthorizer(options.PrometheusRegistry)
}

acs := &atomic.Pointer[dbauthz.AccessControlStore]{}
var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
acs.Store(&tacs)
if options.AccessControlStore == nil {
options.AccessControlStore = &atomic.Pointer[dbauthz.AccessControlStore]{}
var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
options.AccessControlStore.Store(&tacs)
}

options.Database = dbauthz.New(
options.Database,
options.Authorizer,
options.Logger.Named("authz_querier"),
acs,
options.AccessControlStore,
)

experiments := ReadExperiments(
Expand Down Expand Up @@ -376,7 +379,7 @@ func New(options *Options) *API {
Auditor: atomic.Pointer[audit.Auditor]{},
TemplateScheduleStore: options.TemplateScheduleStore,
UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore,
AccessControlStore: acs,
AccessControlStore: options.AccessControlStore,
Experiments: experiments,
healthCheckGroup: &singleflight.Group[string, *healthcheck.Report]{},
Acquirer: provisionerdserver.NewAcquirer(
Expand Down
6 changes: 6 additions & 0 deletions coderd/coderdtest/coderdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
t.Cleanup(closeBatcher)
}

var accessControlStore = &atomic.Pointer[dbauthz.AccessControlStore]{}
var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
accessControlStore.Store(&acs)

var templateScheduleStore atomic.Pointer[schedule.TemplateScheduleStore]
if options.TemplateScheduleStore == nil {
options.TemplateScheduleStore = schedule.NewAGPLTemplateScheduleStore()
Expand All @@ -278,6 +282,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
options.Pubsub,
&templateScheduleStore,
&auditor,
accessControlStore,
*options.Logger,
options.AutobuildTicker,
).WithStatsChannel(options.AutobuildStats)
Expand Down Expand Up @@ -415,6 +420,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
Authorizer: options.Authorizer,
Telemetry: telemetry.NewNoop(),
TemplateScheduleStore: &templateScheduleStore,
AccessControlStore: accessControlStore,
TLSCertificates: options.TLSCertificates,
TrialGenerator: options.TrialGenerator,
TailnetCoordinator: options.Coordinator,
Expand Down
6 changes: 3 additions & 3 deletions coderd/database/dbauthz/accesscontrol.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// that is used when determining whether an actor is authorized
// to interact with an RBAC object.
type AccessControlStore interface {
GetTemplateAccessControl(t database.Template) (TemplateAccessControl, error)
GetTemplateAccessControl(t database.Template) TemplateAccessControl
SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts TemplateAccessControl) error
}

Expand All @@ -26,10 +26,10 @@ type AGPLTemplateAccessControlStore struct{}

var _ AccessControlStore = AGPLTemplateAccessControlStore{}

func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(database.Template) (TemplateAccessControl, error) {
func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(database.Template) TemplateAccessControl {
return TemplateAccessControl{
RequireActiveVersion: false,
}, nil
}
}

func (AGPLTemplateAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, TemplateAccessControl) error {
Expand Down
5 changes: 1 addition & 4 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2222,10 +2222,7 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW
return xerrors.Errorf("get template by id: %w", err)
}

accessControl, err := (*q.acs.Load()).GetTemplateAccessControl(t)
if err != nil {
return xerrors.Errorf("get template access control by id: %w", err)
}
accessControl := (*q.acs.Load()).GetTemplateAccessControl(t)

// If the template requires the active version we need to check if
// the user is a template admin. If they aren't and are attempting
Expand Down
4 changes: 2 additions & 2 deletions coderd/database/dbauthz/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,10 @@ func accessControlStorePointer() *atomic.Pointer[dbauthz.AccessControlStore] {

type fakeAccessControlStore struct{}

func (fakeAccessControlStore) GetTemplateAccessControl(t database.Template) (dbauthz.TemplateAccessControl, error) {
func (fakeAccessControlStore) GetTemplateAccessControl(t database.Template) dbauthz.TemplateAccessControl {
return dbauthz.TemplateAccessControl{
RequireActiveVersion: t.RequireActiveVersion,
}, nil
}
}

func (fakeAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, dbauthz.TemplateAccessControl) error {
Expand Down
4 changes: 0 additions & 4 deletions coderd/schedule/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,6 @@ type TemplateScheduleOptions struct {
// workspaces whose dormant_at field violates the new template time_til_dormant_autodelete
// threshold.
UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"`
// RequireActiveVersion requires that a starting a workspace uses the active
// version for a template.
RequireActiveVersion bool `json:"require_active_version"`
}

// TemplateScheduleStore provides an interface for retrieving template
Expand Down Expand Up @@ -203,7 +200,6 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te
FailureTTL: 0,
TimeTilDormant: 0,
TimeTilDormantAutoDelete: 0,
RequireActiveVersion: false,
}, nil
}

Expand Down
13 changes: 6 additions & 7 deletions coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,11 +347,6 @@ 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)
}

if createTemplate.RequireActiveVersion {
err = (*api.AccessControlStore.Load()).SetTemplateAccessControl(ctx, tx, id, dbauthz.TemplateAccessControl{
RequireActiveVersion: createTemplate.RequireActiveVersion,
Expand All @@ -361,6 +356,11 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
}
}

dbTemplate, err = tx.GetTemplateByID(ctx, id)
if err != nil {
return xerrors.Errorf("get template by id: %s", err)
}

dbTemplate, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, dbTemplate, schedule.TemplateScheduleOptions{
UserAutostartEnabled: allowUserAutostart,
UserAutostopEnabled: allowUserAutostop,
Expand All @@ -379,7 +379,6 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
FailureTTL: failureTTL,
TimeTilDormant: dormantTTL,
TimeTilDormantAutoDelete: dormantAutoDeletionTTL,
RequireActiveVersion: createTemplate.RequireActiveVersion,
})
if err != nil {
return xerrors.Errorf("set template schedule options: %s", err)
Expand Down Expand Up @@ -661,6 +660,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
if err != nil {
return xerrors.Errorf("set template access control: %w", err)
}
updated.RequireActiveVersion = req.RequireActiveVersion
}

defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond
Expand Down Expand Up @@ -700,7 +700,6 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
TimeTilDormantAutoDelete: timeTilDormantAutoDelete,
UpdateWorkspaceLastUsedAt: req.UpdateWorkspaceLastUsedAt,
UpdateWorkspaceDormantAt: req.UpdateWorkspaceDormantAt,
RequireActiveVersion: req.RequireActiveVersion,
})
if err != nil {
return xerrors.Errorf("set template schedule options: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions enterprise/coderd/dbauthz/accesscontrol.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (

type EnterpriseTemplateAccessControlStore struct{}

func (EnterpriseTemplateAccessControlStore) GetTemplateAccessControl(t database.Template) (agpldbz.TemplateAccessControl, error) {
func (EnterpriseTemplateAccessControlStore) GetTemplateAccessControl(t database.Template) agpldbz.TemplateAccessControl {
return agpldbz.TemplateAccessControl{
RequireActiveVersion: t.RequireActiveVersion,
}, nil
}
}

func (EnterpriseTemplateAccessControlStore) SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts agpldbz.TemplateAccessControl) error {
Expand Down
4 changes: 1 addition & 3 deletions enterprise/coderd/schedule/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ func (s *EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.S
FailureTTL: time.Duration(tpl.FailureTTL),
TimeTilDormant: time.Duration(tpl.TimeTilDormant),
TimeTilDormantAutoDelete: time.Duration(tpl.TimeTilDormantAutoDelete),
RequireActiveVersion: tpl.RequireActiveVersion,
}, nil
}

Expand All @@ -117,8 +116,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
int64(opts.TimeTilDormant) == tpl.TimeTilDormant &&
int64(opts.TimeTilDormantAutoDelete) == tpl.TimeTilDormantAutoDelete &&
opts.UserAutostartEnabled == tpl.AllowUserAutostart &&
opts.UserAutostopEnabled == tpl.AllowUserAutostop &&
opts.RequireActiveVersion == tpl.RequireActiveVersion {
opts.UserAutostopEnabled == tpl.AllowUserAutostop {
// Avoid updating the UpdatedAt timestamp if nothing will be changed.
return tpl, nil
}
Expand Down