mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
co-authored by
Danny Kopping
parent
7e1ac2e22b
commit
2b57dcc68c
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user