mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(coderd): prevent cross-tenant workspace app rebinding (#26103)
This commit is contained in:
@@ -1704,6 +1704,7 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro
|
||||
slog.F("transition", transition))
|
||||
|
||||
if err := InsertWorkspaceResource(ctx, db, jobID, transition, resource, telemetrySnapshot); err != nil {
|
||||
s.warnWorkspaceAppRebindRejected(ctx, jobID, err)
|
||||
return xerrors.Errorf("insert resource: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -2122,6 +2123,7 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
|
||||
InsertWorkspaceResourceWithAgentIDsFromProto(),
|
||||
)
|
||||
if err != nil {
|
||||
s.warnWorkspaceAppRebindRejected(ctx, jobID, err)
|
||||
return xerrors.Errorf("insert provisioner job: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -2590,6 +2592,7 @@ func (s *server) completeTemplateDryRunJob(ctx context.Context, job database.Pro
|
||||
|
||||
err := InsertWorkspaceResource(ctx, db, jobID, database.WorkspaceTransitionStart, resource, telemetrySnapshot)
|
||||
if err != nil {
|
||||
s.warnWorkspaceAppRebindRejected(ctx, jobID, err)
|
||||
return xerrors.Errorf("insert resource: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -3614,6 +3617,32 @@ func insertAgentScriptsAndLogSources(ctx context.Context, db database.Store, age
|
||||
return nil
|
||||
}
|
||||
|
||||
type workspaceAppRebindError struct {
|
||||
slug string
|
||||
appID uuid.UUID
|
||||
agentID uuid.UUID
|
||||
}
|
||||
|
||||
func (e *workspaceAppRebindError) Error() string {
|
||||
return fmt.Sprintf("workspace app slug %q with ID %q is already bound to a workspace-owned agent and cannot be rebound to an agent in another workspace or to an agent without a workspace; refusing to rebind to agent ID %q", e.slug, e.appID, e.agentID)
|
||||
}
|
||||
|
||||
func (s *server) warnWorkspaceAppRebindRejected(ctx context.Context, jobID uuid.UUID, err error) {
|
||||
slog.Helper()
|
||||
|
||||
var rebindErr *workspaceAppRebindError
|
||||
if !errors.As(err, &rebindErr) {
|
||||
return
|
||||
}
|
||||
|
||||
s.Logger.Warn(ctx, "workspace app rebind rejected by SQL guard",
|
||||
slog.F("job_id", jobID.String()),
|
||||
slog.F("app_id", rebindErr.appID.String()),
|
||||
slog.F("agent_id", rebindErr.agentID.String()),
|
||||
slog.F("app_slug", rebindErr.slug),
|
||||
)
|
||||
}
|
||||
|
||||
func insertAgentApp(ctx context.Context, db database.Store, agentID uuid.UUID, app *sdkproto.App, appSlugs map[string]struct{}, snapshot *telemetry.Snapshot) error {
|
||||
// Similar logic is duplicated in terraform/resources.go.
|
||||
slug := app.Slug
|
||||
@@ -3702,6 +3731,17 @@ func insertAgentApp(ctx context.Context, db database.Store, agentID uuid.UUID, a
|
||||
Tooltip: app.Tooltip,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// The upsert's ON CONFLICT guard refused to rebind an app
|
||||
// owned by a workspace to an agent outside that workspace,
|
||||
// including agents from import or dry-run jobs that resolve
|
||||
// to no workspace (SEC-91).
|
||||
return &workspaceAppRebindError{
|
||||
slug: slug,
|
||||
appID: id,
|
||||
agentID: agentID,
|
||||
}
|
||||
}
|
||||
return xerrors.Errorf("upsert app: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"storj.io/drpc"
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"cdr.dev/slog/v3/sloggers/slogtest"
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/coderd"
|
||||
@@ -2348,6 +2349,109 @@ func TestCompleteJob(t *testing.T) {
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("WorkspaceBuild_CrossWorkspaceAppRebindRejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logSink := &recordingSlogSink{}
|
||||
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).AppendSinks(logSink)
|
||||
srv, db, _, pd := setup(t, false, &overrides{provisionerdLogger: &logger})
|
||||
|
||||
// Given: a victim workspace whose agent owns an app with a known UUID.
|
||||
victimAppID, victimAgentID, victimSlug := setupWorkspaceAppRebindVictim(
|
||||
t, db, pd.OrganizationID,
|
||||
)
|
||||
|
||||
// Given: an attacker workspace with a running build job acquired by the
|
||||
// provisioner daemon.
|
||||
attackerUser := dbgen.User(t, db, database.User{})
|
||||
attackerTemplate := dbgen.Template(t, db, database.Template{
|
||||
CreatedBy: attackerUser.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
attackerVersion := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
CreatedBy: attackerUser.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
TemplateID: uuid.NullUUID{UUID: attackerTemplate.ID, Valid: true},
|
||||
JobID: uuid.New(),
|
||||
})
|
||||
attackerWorkspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
TemplateID: attackerTemplate.ID,
|
||||
OwnerID: attackerUser.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
attackerBuildID := uuid.New()
|
||||
attackerJob := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
InitiatorID: attackerUser.ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: attackerBuildID,
|
||||
})),
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
ID: attackerBuildID,
|
||||
JobID: attackerJob.ID,
|
||||
WorkspaceID: attackerWorkspace.ID,
|
||||
TemplateVersionID: attackerVersion.ID,
|
||||
InitiatorID: attackerUser.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
})
|
||||
_, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
WorkerID: uuid.NullUUID{UUID: pd.ID, Valid: true},
|
||||
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
StartedAt: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
ProvisionerTags: must(json.Marshal(attackerJob.Tags)),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// When: the attacker's build completes with an app that reuses the
|
||||
// victim's app UUID but points at the attacker's (new) agent.
|
||||
attackerAgent := &sdkproto.Agent{
|
||||
Id: uuid.NewString(),
|
||||
Name: "dev",
|
||||
Auth: &sdkproto.Agent_Token{Token: uuid.NewString()},
|
||||
Apps: []*sdkproto.App{{
|
||||
Id: victimAppID.String(),
|
||||
Slug: "attacker-app",
|
||||
}},
|
||||
}
|
||||
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
|
||||
JobId: attackerJob.ID.String(),
|
||||
Type: &proto.CompletedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
|
||||
State: []byte{},
|
||||
Resources: []*sdkproto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
Agents: []*sdkproto.Agent{attackerAgent},
|
||||
}},
|
||||
},
|
||||
},
|
||||
})
|
||||
// Then: the build is rejected with the cross-tenant rebind error.
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "already bound to a workspace-owned agent")
|
||||
assertWorkspaceAppRebindWarning(
|
||||
t,
|
||||
logSink,
|
||||
workspaceAppRebindWarning{
|
||||
jobID: attackerJob.ID,
|
||||
appID: victimAppID,
|
||||
slug: "attacker-app",
|
||||
agentID: attackerAgent.Id,
|
||||
},
|
||||
)
|
||||
|
||||
// And: the victim's app remains bound to the victim agent, unchanged.
|
||||
victimApps, err := db.GetWorkspaceAppsByAgentID(ctx, victimAgentID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, victimApps, 1)
|
||||
require.Equal(t, victimAppID, victimApps[0].ID)
|
||||
require.Equal(t, victimAgentID, victimApps[0].AgentID)
|
||||
require.Equal(t, victimSlug, victimApps[0].Slug)
|
||||
})
|
||||
t.Run("TemplateDryRun", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, db, _, pd := setup(t, false, &overrides{})
|
||||
@@ -2398,6 +2502,161 @@ func TestCompleteJob(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("TemplateDryRun_CrossWorkspaceAppRebindRejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logSink := &recordingSlogSink{}
|
||||
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).AppendSinks(logSink)
|
||||
srv, db, _, pd := setup(t, false, &overrides{provisionerdLogger: &logger})
|
||||
|
||||
victimAppID, victimAgentID, victimSlug := setupWorkspaceAppRebindVictim(
|
||||
t, db, pd.OrganizationID,
|
||||
)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
CreatedBy: user.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
JobID: uuid.New(),
|
||||
})
|
||||
job, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
||||
ID: version.JobID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
Type: database.ProvisionerJobTypeTemplateVersionDryRun,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionDryRunJob{
|
||||
TemplateVersionID: version.ID,
|
||||
})),
|
||||
OrganizationID: pd.OrganizationID,
|
||||
Tags: pd.Tags,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||
WorkerID: uuid.NullUUID{UUID: pd.ID, Valid: true},
|
||||
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
StartedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
|
||||
OrganizationID: pd.OrganizationID,
|
||||
ProvisionerTags: must(json.Marshal(job.Tags)),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
dryRunAgent := &sdkproto.Agent{
|
||||
Name: "dev",
|
||||
Auth: &sdkproto.Agent_Token{Token: uuid.NewString()},
|
||||
Apps: []*sdkproto.App{{
|
||||
Id: victimAppID.String(),
|
||||
Slug: "dry-run-app",
|
||||
}},
|
||||
}
|
||||
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
|
||||
JobId: job.ID.String(),
|
||||
Type: &proto.CompletedJob_TemplateDryRun_{
|
||||
TemplateDryRun: &proto.CompletedJob_TemplateDryRun{
|
||||
Resources: []*sdkproto.Resource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
Agents: []*sdkproto.Agent{dryRunAgent},
|
||||
}},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "already bound to a workspace-owned agent")
|
||||
assertWorkspaceAppRebindWarning(
|
||||
t,
|
||||
logSink,
|
||||
workspaceAppRebindWarning{
|
||||
jobID: job.ID,
|
||||
appID: victimAppID,
|
||||
slug: "dry-run-app",
|
||||
},
|
||||
)
|
||||
|
||||
victimApps, err := db.GetWorkspaceAppsByAgentID(ctx, victimAgentID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, victimApps, 1)
|
||||
require.Equal(t, victimAppID, victimApps[0].ID)
|
||||
require.Equal(t, victimAgentID, victimApps[0].AgentID)
|
||||
require.Equal(t, victimSlug, victimApps[0].Slug)
|
||||
})
|
||||
|
||||
t.Run("TemplateImport_CrossWorkspaceAppRebindRejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logSink := &recordingSlogSink{}
|
||||
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).AppendSinks(logSink)
|
||||
srv, db, _, pd := setup(t, false, &overrides{provisionerdLogger: &logger})
|
||||
|
||||
victimAppID, victimAgentID, victimSlug := setupWorkspaceAppRebindVictim(
|
||||
t, db, pd.OrganizationID,
|
||||
)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
CreatedBy: user.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
JobID: uuid.New(),
|
||||
})
|
||||
job, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
||||
ID: version.JobID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
||||
TemplateVersionID: version.ID,
|
||||
})),
|
||||
OrganizationID: pd.OrganizationID,
|
||||
Tags: pd.Tags,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||
WorkerID: uuid.NullUUID{UUID: pd.ID, Valid: true},
|
||||
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
StartedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
|
||||
OrganizationID: pd.OrganizationID,
|
||||
ProvisionerTags: must(json.Marshal(job.Tags)),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
importAgent := &sdkproto.Agent{
|
||||
Name: "dev",
|
||||
Auth: &sdkproto.Agent_Token{Token: uuid.NewString()},
|
||||
Apps: []*sdkproto.App{{
|
||||
Id: victimAppID.String(),
|
||||
Slug: "import-app",
|
||||
}},
|
||||
}
|
||||
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
|
||||
JobId: job.ID.String(),
|
||||
Type: &proto.CompletedJob_TemplateImport_{
|
||||
TemplateImport: &proto.CompletedJob_TemplateImport{
|
||||
StartResources: []*sdkproto.Resource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
Agents: []*sdkproto.Agent{importAgent},
|
||||
}},
|
||||
Plan: []byte("{}"),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "already bound to a workspace-owned agent")
|
||||
assertWorkspaceAppRebindWarning(
|
||||
t,
|
||||
logSink,
|
||||
workspaceAppRebindWarning{
|
||||
jobID: job.ID,
|
||||
appID: victimAppID,
|
||||
slug: "import-app",
|
||||
},
|
||||
)
|
||||
|
||||
victimApps, err := db.GetWorkspaceAppsByAgentID(ctx, victimAgentID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, victimApps, 1)
|
||||
require.Equal(t, victimAppID, victimApps[0].ID)
|
||||
require.Equal(t, victimAgentID, victimApps[0].AgentID)
|
||||
require.Equal(t, victimSlug, victimApps[0].Slug)
|
||||
})
|
||||
|
||||
t.Run("Modules", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -3383,6 +3642,59 @@ func TestCompleteJob(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func setupWorkspaceAppRebindVictim(
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
organizationID uuid.UUID,
|
||||
) (appID uuid.UUID, agentID uuid.UUID, slug string) {
|
||||
t.Helper()
|
||||
|
||||
victimUser := dbgen.User(t, db, database.User{})
|
||||
victimTemplate := dbgen.Template(t, db, database.Template{
|
||||
CreatedBy: victimUser.ID,
|
||||
OrganizationID: organizationID,
|
||||
})
|
||||
victimVersion := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
CreatedBy: victimUser.ID,
|
||||
OrganizationID: organizationID,
|
||||
TemplateID: uuid.NullUUID{UUID: victimTemplate.ID, Valid: true},
|
||||
})
|
||||
victimWorkspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
TemplateID: victimTemplate.ID,
|
||||
OwnerID: victimUser.ID,
|
||||
OrganizationID: organizationID,
|
||||
})
|
||||
victimJob := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
OrganizationID: organizationID,
|
||||
StartedAt: sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true},
|
||||
CompletedAt: sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true},
|
||||
})
|
||||
dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
JobID: victimJob.ID,
|
||||
WorkspaceID: victimWorkspace.ID,
|
||||
TemplateVersionID: victimVersion.ID,
|
||||
InitiatorID: victimUser.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
})
|
||||
victimResource := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{
|
||||
JobID: victimJob.ID,
|
||||
})
|
||||
victimAgent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
||||
ResourceID: victimResource.ID,
|
||||
})
|
||||
victimAppID := uuid.New()
|
||||
const victimSlug = "code-server"
|
||||
dbgen.WorkspaceApp(t, db, database.WorkspaceApp{
|
||||
ID: victimAppID,
|
||||
AgentID: victimAgent.ID,
|
||||
Slug: victimSlug,
|
||||
})
|
||||
|
||||
return victimAppID, victimAgent.ID, victimSlug
|
||||
}
|
||||
|
||||
type mockPrebuildsOrchestrator struct {
|
||||
agplprebuilds.ReconciliationOrchestrator
|
||||
|
||||
@@ -4781,6 +5093,70 @@ func TestServer_ExpirePrebuildsSessionToken(t *testing.T) {
|
||||
require.ErrorIs(t, err, sql.ErrNoRows, "api key for prebuilds user should be deleted")
|
||||
}
|
||||
|
||||
type workspaceAppRebindWarning struct {
|
||||
jobID uuid.UUID
|
||||
appID uuid.UUID
|
||||
slug string
|
||||
agentID string
|
||||
}
|
||||
|
||||
func assertWorkspaceAppRebindWarning(t *testing.T, logSink *recordingSlogSink, want workspaceAppRebindWarning) {
|
||||
t.Helper()
|
||||
|
||||
for _, entry := range logSink.Entries() {
|
||||
if entry.Message != "workspace app rebind rejected by SQL guard" {
|
||||
continue
|
||||
}
|
||||
|
||||
require.Equal(t, slog.LevelWarn, entry.Level)
|
||||
require.Contains(t, entry.File, "coderd/provisionerdserver/provisionerdserver.go")
|
||||
require.NotContains(t, entry.Func, "warnWorkspaceAppRebindRejected")
|
||||
fields := slogFieldsByName(entry.Fields)
|
||||
require.Equal(t, want.jobID.String(), fields["job_id"])
|
||||
require.Equal(t, want.appID.String(), fields["app_id"])
|
||||
require.Equal(t, want.slug, fields["app_slug"])
|
||||
agentID, ok := fields["agent_id"].(string)
|
||||
require.True(t, ok)
|
||||
require.NotEqual(t, uuid.Nil.String(), agentID)
|
||||
if want.agentID != "" {
|
||||
require.Equal(t, want.agentID, agentID)
|
||||
} else {
|
||||
_, err := uuid.Parse(agentID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
require.Fail(t, "expected workspace app rebind warning")
|
||||
}
|
||||
|
||||
type recordingSlogSink struct {
|
||||
mu sync.Mutex
|
||||
entries []slog.SinkEntry
|
||||
}
|
||||
|
||||
func (s *recordingSlogSink) LogEntry(_ context.Context, entry slog.SinkEntry) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.entries = append(s.entries, entry)
|
||||
}
|
||||
|
||||
func (*recordingSlogSink) Sync() {}
|
||||
|
||||
func (s *recordingSlogSink) Entries() []slog.SinkEntry {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return append([]slog.SinkEntry(nil), s.entries...)
|
||||
}
|
||||
|
||||
func slogFieldsByName(fields []slog.Field) map[string]any {
|
||||
byName := make(map[string]any, len(fields))
|
||||
for _, field := range fields {
|
||||
byName[field.Name] = field.Value
|
||||
}
|
||||
return byName
|
||||
}
|
||||
|
||||
type overrides struct {
|
||||
ctx context.Context
|
||||
deploymentValues *codersdk.DeploymentValues
|
||||
@@ -4795,6 +5171,7 @@ type overrides struct {
|
||||
auditor audit.Auditor
|
||||
notificationEnqueuer notifications.Enqueuer
|
||||
prebuildsOrchestrator agplprebuilds.ReconciliationOrchestrator
|
||||
provisionerdLogger *slog.Logger
|
||||
}
|
||||
|
||||
func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisionerDaemonServer, database.Store, pubsub.Pubsub, database.ProvisionerDaemon) {
|
||||
@@ -4871,6 +5248,10 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi
|
||||
} else {
|
||||
notifEnq = notifications.NewNoopEnqueuer()
|
||||
}
|
||||
provisionerdLogger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: ignoreLogErrors})
|
||||
if ov.provisionerdLogger != nil {
|
||||
provisionerdLogger = *ov.provisionerdLogger
|
||||
}
|
||||
|
||||
daemon, err := db.UpsertProvisionerDaemon(ov.ctx, database.UpsertProvisionerDaemonParams{
|
||||
Name: "test",
|
||||
@@ -4902,7 +5283,7 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi
|
||||
&url.URL{},
|
||||
daemon.ID,
|
||||
defOrg.ID,
|
||||
slogtest.Make(t, &slogtest.Options{IgnoreErrors: ignoreLogErrors}),
|
||||
provisionerdLogger,
|
||||
[]database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
provisionerdserver.Tags(daemon.Tags),
|
||||
serverDB,
|
||||
|
||||
Reference in New Issue
Block a user