feat(coderd): add matched provisioner daemons information to more places (#15688)

- Refactors `checkProvisioners` into `db2sdk.MatchedProvisioners`
- Adds a separate RBAC subject just for reading provisioner daemons
- Adds matched provisioners information to additional endpoints relating to
  workspace builds and templates
-Updates existing unit tests for above endpoints
-Adds API endpoint for matched provisioners of template dry-run job
-Updates CLI to show warning when creating/starting/stopping/deleting
 workspaces for which no provisoners are available

---------

Co-authored-by: Danny Kopping <danny@coder.com>
This commit is contained in:
Cian Johnston
2024-12-02 20:54:32 +00:00
committed by GitHub
co-authored by Danny Kopping
parent 7e1ac2e22b
commit 2b57dcc68c
30 changed files with 1058 additions and 166 deletions
+38 -21
View File
@@ -24,6 +24,7 @@ import (
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/provisionerdserver"
@@ -213,12 +214,12 @@ func (b *Builder) Build(
authFunc func(action policy.Action, object rbac.Objecter) bool,
auditBaggage audit.WorkspaceBuildBaggage,
) (
*database.WorkspaceBuild, *database.ProvisionerJob, error,
*database.WorkspaceBuild, *database.ProvisionerJob, []database.ProvisionerDaemon, error,
) {
var err error
b.ctx, err = audit.BaggageToContext(ctx, auditBaggage)
if err != nil {
return nil, nil, xerrors.Errorf("create audit baggage: %w", err)
return nil, nil, nil, xerrors.Errorf("create audit baggage: %w", err)
}
// Run the build in a transaction with RepeatableRead isolation, and retries.
@@ -227,16 +228,17 @@ func (b *Builder) Build(
// later reads are consistent with earlier ones.
var workspaceBuild *database.WorkspaceBuild
var provisionerJob *database.ProvisionerJob
var provisionerDaemons []database.ProvisionerDaemon
err = database.ReadModifyUpdate(store, func(tx database.Store) error {
var err error
b.store = tx
workspaceBuild, provisionerJob, err = b.buildTx(authFunc)
workspaceBuild, provisionerJob, provisionerDaemons, err = b.buildTx(authFunc)
return err
})
if err != nil {
return nil, nil, xerrors.Errorf("build tx: %w", err)
return nil, nil, nil, xerrors.Errorf("build tx: %w", err)
}
return workspaceBuild, provisionerJob, nil
return workspaceBuild, provisionerJob, provisionerDaemons, nil
}
// buildTx contains the business logic of computing a new build. Attributes of the new database objects are computed
@@ -246,35 +248,35 @@ func (b *Builder) Build(
//
// In order to utilize this cache, the functions that compute build attributes use a pointer receiver type.
func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Objecter) bool) (
*database.WorkspaceBuild, *database.ProvisionerJob, error,
*database.WorkspaceBuild, *database.ProvisionerJob, []database.ProvisionerDaemon, error,
) {
if authFunc != nil {
err := b.authorize(authFunc)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
}
err := b.checkTemplateVersionMatchesTemplate()
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
err = b.checkTemplateJobStatus()
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
err = b.checkRunningBuild()
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
template, err := b.getTemplate()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to fetch template", err}
return nil, nil, nil, BuildError{http.StatusInternalServerError, "failed to fetch template", err}
}
templateVersionJob, err := b.getTemplateVersionJob()
if err != nil {
return nil, nil, BuildError{
return nil, nil, nil, BuildError{
http.StatusInternalServerError, "failed to fetch template version job", err,
}
}
@@ -294,7 +296,7 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
LogLevel: b.logLevel,
})
if err != nil {
return nil, nil, BuildError{
return nil, nil, nil, BuildError{
http.StatusInternalServerError,
"marshal provision job",
err,
@@ -302,12 +304,12 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
}
traceMetadataRaw, err := json.Marshal(tracing.MetadataFromContext(b.ctx))
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "marshal metadata", err}
return nil, nil, nil, BuildError{http.StatusInternalServerError, "marshal metadata", err}
}
tags, err := b.getProvisionerTags()
if err != nil {
return nil, nil, err // already wrapped BuildError
return nil, nil, nil, err // already wrapped BuildError
}
now := dbtime.Now()
@@ -329,20 +331,35 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
},
})
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "insert provisioner job", err}
return nil, nil, nil, BuildError{http.StatusInternalServerError, "insert provisioner job", err}
}
// nolint:gocritic // The user performing this request may not have permission
// to read all provisioner daemons. We need to retrieve the eligible
// provisioner daemons for this job to show in the UI if there is no
// matching provisioner daemon.
provisionerDaemons, err := b.store.GetProvisionerDaemonsByOrganization(dbauthz.AsSystemReadProvisionerDaemons(b.ctx), database.GetProvisionerDaemonsByOrganizationParams{
OrganizationID: template.OrganizationID,
WantTags: provisionerJob.Tags,
})
if err != nil {
// NOTE: we do **not** want to fail a workspace build if we fail to
// retrieve provisioner daemons. This is just to show in the UI if there
// is no matching provisioner daemon for the job.
provisionerDaemons = []database.ProvisionerDaemon{}
}
templateVersionID, err := b.getTemplateVersionID()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "compute template version ID", err}
return nil, nil, nil, BuildError{http.StatusInternalServerError, "compute template version ID", err}
}
buildNum, err := b.getBuildNumber()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "compute build number", err}
return nil, nil, nil, BuildError{http.StatusInternalServerError, "compute build number", err}
}
state, err := b.getState()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "compute build state", err}
return nil, nil, nil, BuildError{http.StatusInternalServerError, "compute build state", err}
}
var workspaceBuild database.WorkspaceBuild
@@ -393,10 +410,10 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
return nil
}, nil)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
return &workspaceBuild, &provisionerJob, nil
return &workspaceBuild, &provisionerJob, provisionerDaemons, nil
}
func (b *Builder) getTemplate() (*database.Template, error) {
+40 -13
View File
@@ -61,6 +61,7 @@ func TestBuilder_NoOptions(t *testing.T) {
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
@@ -94,7 +95,8 @@ func TestBuilder_NoOptions(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -114,6 +116,7 @@ func TestBuilder_Initiator(t *testing.T) {
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
@@ -130,7 +133,8 @@ func TestBuilder_Initiator(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).Initiator(otherUserID)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -157,6 +161,7 @@ func TestBuilder_Baggage(t *testing.T) {
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
@@ -172,7 +177,8 @@ func TestBuilder_Baggage(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).Initiator(otherUserID)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
req.NoError(err)
}
@@ -192,6 +198,7 @@ func TestBuilder_Reason(t *testing.T) {
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(_ database.InsertProvisionerJobParams) {
@@ -207,7 +214,8 @@ func TestBuilder_Reason(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).Reason(database.BuildReasonAutostart)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -226,6 +234,7 @@ func TestBuilder_ActiveVersion(t *testing.T) {
withLastBuildNotFound,
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// previous rich parameters are not queried because there is no previous build.
// Outputs
@@ -247,7 +256,8 @@ func TestBuilder_ActiveVersion(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).ActiveVersion()
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -314,6 +324,7 @@ func TestWorkspaceBuildWithTags(t *testing.T) {
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, workspaceTags),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
@@ -343,7 +354,8 @@ func TestWorkspaceBuildWithTags(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(buildParameters)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -404,6 +416,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
withRichParameters(initialBuildParameters),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
@@ -422,7 +435,8 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(nextBuildParameters)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
t.Run("UsePreviousParameterValues", func(t *testing.T) {
@@ -448,6 +462,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
withRichParameters(initialBuildParameters),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
@@ -466,7 +481,8 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(nextBuildParameters)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
@@ -502,7 +518,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
bldErr := wsbuilder.BuildError{}
req.ErrorAs(err, &bldErr)
asrt.Equal(http.StatusBadRequest, bldErr.Status)
@@ -536,7 +552,8 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(nextBuildParameters)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
bldErr := wsbuilder.BuildError{}
req.ErrorAs(err, &bldErr)
asrt.Equal(http.StatusBadRequest, bldErr.Status)
@@ -579,6 +596,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
withRichParameters(initialBuildParameters),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
@@ -599,7 +617,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
RichParameterValues(nextBuildParameters).
VersionID(activeVersionID)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
@@ -640,6 +658,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
withRichParameters(initialBuildParameters),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
@@ -660,7 +679,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
RichParameterValues(nextBuildParameters).
VersionID(activeVersionID)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
@@ -699,6 +718,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
withRichParameters(initialBuildParameters),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.ProvisionerDaemon{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
@@ -719,7 +739,8 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
RichParameterValues(nextBuildParameters).
VersionID(activeVersionID)
_, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
}
@@ -987,3 +1008,9 @@ func expectBuildParameters(
)
}
}
func withProvisionerDaemons(provisionerDaemons []database.ProvisionerDaemon) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetProvisionerDaemonsByOrganization(gomock.Any(), gomock.Any()).Return(provisionerDaemons, nil)
}
}