mirror of https://github.com/coder/coder.git
816 lines
24 KiB
Go
816 lines
24 KiB
Go
package provisionerdserver_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/url"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cdr.dev/slog/sloggers/slogtest"
|
|
"github.com/coder/coder/coderd/audit"
|
|
"github.com/coder/coder/coderd/database"
|
|
"github.com/coder/coder/coderd/database/databasefake"
|
|
"github.com/coder/coder/coderd/provisionerdserver"
|
|
"github.com/coder/coder/coderd/telemetry"
|
|
"github.com/coder/coder/codersdk"
|
|
"github.com/coder/coder/provisionerd/proto"
|
|
sdkproto "github.com/coder/coder/provisionersdk/proto"
|
|
)
|
|
|
|
func mockAuditor() *atomic.Pointer[audit.Auditor] {
|
|
ptr := &atomic.Pointer[audit.Auditor]{}
|
|
mock := audit.Auditor(audit.NewMock())
|
|
ptr.Store(&mock)
|
|
return ptr
|
|
}
|
|
|
|
func TestAcquireJob(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Debounce", func(t *testing.T) {
|
|
t.Parallel()
|
|
db := databasefake.New()
|
|
pubsub := database.NewPubsubInMemory()
|
|
srv := &provisionerdserver.Server{
|
|
ID: uuid.New(),
|
|
Logger: slogtest.Make(t, nil),
|
|
AccessURL: &url.URL{},
|
|
Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
Database: db,
|
|
Pubsub: pubsub,
|
|
Telemetry: telemetry.NewNoop(),
|
|
AcquireJobDebounce: time.Hour,
|
|
Auditor: mockAuditor(),
|
|
}
|
|
job, err := srv.AcquireJob(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, &proto.AcquiredJob{}, job)
|
|
_, err = srv.Database.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
InitiatorID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
job, err = srv.AcquireJob(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, &proto.AcquiredJob{}, job)
|
|
})
|
|
t.Run("NoJobs", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.AcquireJob(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, &proto.AcquiredJob{}, job)
|
|
})
|
|
t.Run("InitiatorNotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
_, err := srv.Database.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
InitiatorID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.AcquireJob(context.Background(), nil)
|
|
require.ErrorContains(t, err, "sql: no rows in result set")
|
|
})
|
|
t.Run("WorkspaceBuildJob", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
ctx := context.Background()
|
|
user, err := srv.Database.InsertUser(context.Background(), database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
Username: "testing",
|
|
})
|
|
require.NoError(t, err)
|
|
template, err := srv.Database.InsertTemplate(ctx, database.InsertTemplateParams{
|
|
ID: uuid.New(),
|
|
Name: "template",
|
|
})
|
|
require.NoError(t, err)
|
|
version, err := srv.Database.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
|
|
ID: uuid.New(),
|
|
TemplateID: uuid.NullUUID{
|
|
UUID: template.ID,
|
|
Valid: true,
|
|
},
|
|
JobID: uuid.New(),
|
|
})
|
|
require.NoError(t, err)
|
|
workspace, err := srv.Database.InsertWorkspace(ctx, database.InsertWorkspaceParams{
|
|
ID: uuid.New(),
|
|
OwnerID: user.ID,
|
|
TemplateID: template.ID,
|
|
Name: "workspace",
|
|
})
|
|
require.NoError(t, err)
|
|
build, err := srv.Database.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
|
|
ID: uuid.New(),
|
|
WorkspaceID: workspace.ID,
|
|
BuildNumber: 1,
|
|
JobID: uuid.New(),
|
|
TemplateVersionID: version.ID,
|
|
Transition: database.WorkspaceTransitionStart,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
data, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
|
WorkspaceBuildID: build.ID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
file, err := srv.Database.InsertFile(ctx, database.InsertFileParams{
|
|
ID: uuid.New(),
|
|
Hash: "something",
|
|
Data: []byte{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = srv.Database.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{
|
|
ID: build.JobID,
|
|
CreatedAt: database.Now(),
|
|
UpdatedAt: database.Now(),
|
|
OrganizationID: uuid.New(),
|
|
InitiatorID: user.ID,
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
FileID: file.ID,
|
|
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
|
Input: data,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
published := make(chan struct{})
|
|
closeSubscribe, err := srv.Pubsub.Subscribe(codersdk.WorkspaceNotifyChannel(workspace.ID), func(_ context.Context, _ []byte) {
|
|
close(published)
|
|
})
|
|
require.NoError(t, err)
|
|
defer closeSubscribe()
|
|
|
|
job, err := srv.AcquireJob(ctx, nil)
|
|
require.NoError(t, err)
|
|
|
|
<-published
|
|
|
|
got, err := json.Marshal(job.Type)
|
|
require.NoError(t, err)
|
|
|
|
want, err := json.Marshal(&proto.AcquiredJob_WorkspaceBuild_{
|
|
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
|
|
WorkspaceBuildId: build.ID.String(),
|
|
WorkspaceName: workspace.Name,
|
|
ParameterValues: []*sdkproto.ParameterValue{},
|
|
Metadata: &sdkproto.Provision_Metadata{
|
|
CoderUrl: srv.AccessURL.String(),
|
|
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
|
|
WorkspaceName: workspace.Name,
|
|
WorkspaceOwner: user.Username,
|
|
WorkspaceOwnerEmail: user.Email,
|
|
WorkspaceId: workspace.ID.String(),
|
|
WorkspaceOwnerId: user.ID.String(),
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.JSONEq(t, string(want), string(got))
|
|
})
|
|
t.Run("TemplateVersionDryRun", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
ctx := context.Background()
|
|
user, err := srv.Database.InsertUser(ctx, database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
Username: "testing",
|
|
})
|
|
require.NoError(t, err)
|
|
version, err := srv.Database.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
|
|
ID: uuid.New(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
data, err := json.Marshal(provisionerdserver.TemplateVersionDryRunJob{
|
|
TemplateVersionID: version.ID,
|
|
WorkspaceName: "testing",
|
|
ParameterValues: []database.ParameterValue{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
file, err := srv.Database.InsertFile(ctx, database.InsertFileParams{
|
|
ID: uuid.New(),
|
|
Hash: "something",
|
|
Data: []byte{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = srv.Database.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
UpdatedAt: database.Now(),
|
|
OrganizationID: uuid.New(),
|
|
InitiatorID: user.ID,
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
FileID: file.ID,
|
|
Type: database.ProvisionerJobTypeTemplateVersionDryRun,
|
|
Input: data,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
job, err := srv.AcquireJob(ctx, nil)
|
|
require.NoError(t, err)
|
|
|
|
got, err := json.Marshal(job.Type)
|
|
require.NoError(t, err)
|
|
|
|
want, err := json.Marshal(&proto.AcquiredJob_TemplateDryRun_{
|
|
TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{
|
|
ParameterValues: []*sdkproto.ParameterValue{},
|
|
Metadata: &sdkproto.Provision_Metadata{
|
|
CoderUrl: srv.AccessURL.String(),
|
|
WorkspaceName: "testing",
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.JSONEq(t, string(want), string(got))
|
|
})
|
|
t.Run("TemplateVersionImport", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
ctx := context.Background()
|
|
user, err := srv.Database.InsertUser(ctx, database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
Username: "testing",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
file, err := srv.Database.InsertFile(ctx, database.InsertFileParams{
|
|
ID: uuid.New(),
|
|
Hash: "something",
|
|
Data: []byte{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = srv.Database.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
UpdatedAt: database.Now(),
|
|
OrganizationID: uuid.New(),
|
|
InitiatorID: user.ID,
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
FileID: file.ID,
|
|
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
|
Input: json.RawMessage{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
job, err := srv.AcquireJob(ctx, nil)
|
|
require.NoError(t, err)
|
|
|
|
got, err := json.Marshal(job.Type)
|
|
require.NoError(t, err)
|
|
|
|
want, err := json.Marshal(&proto.AcquiredJob_TemplateImport_{
|
|
TemplateImport: &proto.AcquiredJob_TemplateImport{
|
|
Metadata: &sdkproto.Provision_Metadata{
|
|
CoderUrl: srv.AccessURL.String(),
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.JSONEq(t, string(want), string(got))
|
|
})
|
|
}
|
|
|
|
func TestUpdateJob(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
t.Run("NotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
_, err := srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: "hello",
|
|
})
|
|
require.ErrorContains(t, err, "invalid UUID")
|
|
|
|
_, err = srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: uuid.NewString(),
|
|
})
|
|
require.ErrorContains(t, err, "no rows in result set")
|
|
})
|
|
t.Run("NotRunning", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: job.ID.String(),
|
|
})
|
|
require.ErrorContains(t, err, "job isn't running yet")
|
|
})
|
|
// This test prevents runners from updating jobs they don't own!
|
|
t.Run("NotOwner", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: uuid.New(),
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: job.ID.String(),
|
|
})
|
|
require.ErrorContains(t, err, "you don't own this job")
|
|
})
|
|
|
|
setupJob := func(t *testing.T, srv *provisionerdserver.Server) uuid.UUID {
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: srv.ID,
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
return job.ID
|
|
}
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job := setupJob(t, srv)
|
|
_, err := srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: job.String(),
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("Logs", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job := setupJob(t, srv)
|
|
|
|
published := make(chan struct{})
|
|
|
|
closeListener, err := srv.Pubsub.Subscribe(provisionerdserver.ProvisionerJobLogsNotifyChannel(job), func(_ context.Context, _ []byte) {
|
|
close(published)
|
|
})
|
|
require.NoError(t, err)
|
|
defer closeListener()
|
|
|
|
_, err = srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: job.String(),
|
|
Logs: []*proto.Log{{
|
|
Source: proto.LogSource_PROVISIONER,
|
|
Level: sdkproto.LogLevel_INFO,
|
|
Output: "hi",
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
<-published
|
|
})
|
|
t.Run("Readme", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job := setupJob(t, srv)
|
|
version, err := srv.Database.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
|
|
ID: uuid.New(),
|
|
JobID: job,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: job.String(),
|
|
Readme: []byte("# hello world"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
version, err = srv.Database.GetTemplateVersionByID(ctx, version.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "# hello world", version.Readme)
|
|
})
|
|
}
|
|
|
|
func TestFailJob(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
t.Run("NotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
_, err := srv.FailJob(ctx, &proto.FailedJob{
|
|
JobId: "hello",
|
|
})
|
|
require.ErrorContains(t, err, "invalid UUID")
|
|
|
|
_, err = srv.UpdateJob(ctx, &proto.UpdateJobRequest{
|
|
JobId: uuid.NewString(),
|
|
})
|
|
require.ErrorContains(t, err, "no rows in result set")
|
|
})
|
|
// This test prevents runners from updating jobs they don't own!
|
|
t.Run("NotOwner", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: uuid.New(),
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.FailJob(ctx, &proto.FailedJob{
|
|
JobId: job.ID.String(),
|
|
})
|
|
require.ErrorContains(t, err, "you don't own this job")
|
|
})
|
|
t.Run("AlreadyCompleted", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: srv.ID,
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
err = srv.Database.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{
|
|
ID: job.ID,
|
|
CompletedAt: sql.NullTime{
|
|
Time: database.Now(),
|
|
Valid: true,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.FailJob(ctx, &proto.FailedJob{
|
|
JobId: job.ID.String(),
|
|
})
|
|
require.ErrorContains(t, err, "job already completed")
|
|
})
|
|
t.Run("WorkspaceBuild", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
build, err := srv.Database.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
|
|
ID: uuid.New(),
|
|
})
|
|
require.NoError(t, err)
|
|
input, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
|
WorkspaceBuildID: build.ID,
|
|
})
|
|
require.NoError(t, err)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
Input: input,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: srv.ID,
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
publishedWorkspace := make(chan struct{})
|
|
closeWorkspaceSubscribe, err := srv.Pubsub.Subscribe(codersdk.WorkspaceNotifyChannel(build.WorkspaceID), func(_ context.Context, _ []byte) {
|
|
close(publishedWorkspace)
|
|
})
|
|
require.NoError(t, err)
|
|
defer closeWorkspaceSubscribe()
|
|
publishedLogs := make(chan struct{})
|
|
closeLogsSubscribe, err := srv.Pubsub.Subscribe(provisionerdserver.ProvisionerJobLogsNotifyChannel(job.ID), func(_ context.Context, _ []byte) {
|
|
close(publishedLogs)
|
|
})
|
|
require.NoError(t, err)
|
|
defer closeLogsSubscribe()
|
|
|
|
_, err = srv.FailJob(ctx, &proto.FailedJob{
|
|
JobId: job.ID.String(),
|
|
Type: &proto.FailedJob_WorkspaceBuild_{
|
|
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
|
|
State: []byte("some state"),
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
<-publishedWorkspace
|
|
<-publishedLogs
|
|
build, err = srv.Database.GetWorkspaceBuildByID(ctx, build.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "some state", string(build.ProvisionerState))
|
|
})
|
|
}
|
|
|
|
func TestCompleteJob(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
t.Run("NotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
_, err := srv.CompleteJob(ctx, &proto.CompletedJob{
|
|
JobId: "hello",
|
|
})
|
|
require.ErrorContains(t, err, "invalid UUID")
|
|
|
|
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
|
|
JobId: uuid.NewString(),
|
|
})
|
|
require.ErrorContains(t, err, "no rows in result set")
|
|
})
|
|
// This test prevents runners from updating jobs they don't own!
|
|
t.Run("NotOwner", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: uuid.New(),
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
|
|
JobId: job.ID.String(),
|
|
})
|
|
require.ErrorContains(t, err, "you don't own this job")
|
|
})
|
|
t.Run("TemplateImport", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
Input: []byte(`{"template_version_id": "` + uuid.NewString() + `"}`),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: srv.ID,
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
|
|
JobId: job.ID.String(),
|
|
Type: &proto.CompletedJob_TemplateImport_{
|
|
TemplateImport: &proto.CompletedJob_TemplateImport{
|
|
StartResources: []*sdkproto.Resource{{
|
|
Name: "hello",
|
|
Type: "aws_instance",
|
|
}},
|
|
StopResources: []*sdkproto.Resource{},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("WorkspaceBuild", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
workspace, err := srv.Database.InsertWorkspace(ctx, database.InsertWorkspaceParams{
|
|
ID: uuid.New(),
|
|
})
|
|
require.NoError(t, err)
|
|
build, err := srv.Database.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
|
|
ID: uuid.New(),
|
|
WorkspaceID: workspace.ID,
|
|
Transition: database.WorkspaceTransitionDelete,
|
|
})
|
|
require.NoError(t, err)
|
|
input, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
|
WorkspaceBuildID: build.ID,
|
|
})
|
|
require.NoError(t, err)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
Input: input,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: srv.ID,
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
publishedWorkspace := make(chan struct{})
|
|
closeWorkspaceSubscribe, err := srv.Pubsub.Subscribe(codersdk.WorkspaceNotifyChannel(build.WorkspaceID), func(_ context.Context, _ []byte) {
|
|
close(publishedWorkspace)
|
|
})
|
|
require.NoError(t, err)
|
|
defer closeWorkspaceSubscribe()
|
|
publishedLogs := make(chan struct{})
|
|
closeLogsSubscribe, err := srv.Pubsub.Subscribe(provisionerdserver.ProvisionerJobLogsNotifyChannel(job.ID), func(_ context.Context, _ []byte) {
|
|
close(publishedLogs)
|
|
})
|
|
require.NoError(t, err)
|
|
defer closeLogsSubscribe()
|
|
|
|
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
|
|
JobId: job.ID.String(),
|
|
Type: &proto.CompletedJob_WorkspaceBuild_{
|
|
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
|
|
State: []byte{},
|
|
Resources: []*sdkproto.Resource{{
|
|
Name: "example",
|
|
Type: "aws_instance",
|
|
}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
<-publishedWorkspace
|
|
<-publishedLogs
|
|
|
|
workspace, err = srv.Database.GetWorkspaceByID(ctx, workspace.ID)
|
|
require.NoError(t, err)
|
|
require.True(t, workspace.Deleted)
|
|
})
|
|
|
|
t.Run("TemplateDryRun", func(t *testing.T) {
|
|
t.Parallel()
|
|
srv := setup(t)
|
|
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
Provisioner: database.ProvisionerTypeEcho,
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
|
WorkerID: uuid.NullUUID{
|
|
UUID: srv.ID,
|
|
Valid: true,
|
|
},
|
|
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, 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",
|
|
}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestInsertWorkspaceResource(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
insert := func(db database.Store, jobID uuid.UUID, resource *sdkproto.Resource) error {
|
|
return provisionerdserver.InsertWorkspaceResource(ctx, db, jobID, database.WorkspaceTransitionStart, resource, &telemetry.Snapshot{})
|
|
}
|
|
t.Run("NoAgents", func(t *testing.T) {
|
|
t.Parallel()
|
|
db := databasefake.New()
|
|
job := uuid.New()
|
|
err := insert(db, job, &sdkproto.Resource{
|
|
Name: "something",
|
|
Type: "aws_instance",
|
|
})
|
|
require.NoError(t, err)
|
|
resources, err := db.GetWorkspaceResourcesByJobID(ctx, job)
|
|
require.NoError(t, err)
|
|
require.Len(t, resources, 1)
|
|
})
|
|
t.Run("InvalidAgentToken", func(t *testing.T) {
|
|
t.Parallel()
|
|
err := insert(databasefake.New(), uuid.New(), &sdkproto.Resource{
|
|
Name: "something",
|
|
Type: "aws_instance",
|
|
Agents: []*sdkproto.Agent{{
|
|
Auth: &sdkproto.Agent_Token{
|
|
Token: "bananas",
|
|
},
|
|
}},
|
|
})
|
|
require.ErrorContains(t, err, "invalid UUID length")
|
|
})
|
|
t.Run("DuplicateApps", func(t *testing.T) {
|
|
t.Parallel()
|
|
err := insert(databasefake.New(), uuid.New(), &sdkproto.Resource{
|
|
Name: "something",
|
|
Type: "aws_instance",
|
|
Agents: []*sdkproto.Agent{{
|
|
Apps: []*sdkproto.App{{
|
|
Slug: "a",
|
|
}, {
|
|
Slug: "a",
|
|
}},
|
|
}},
|
|
})
|
|
require.ErrorContains(t, err, "duplicate app slug")
|
|
})
|
|
t.Run("Success", func(t *testing.T) {
|
|
t.Parallel()
|
|
db := databasefake.New()
|
|
job := uuid.New()
|
|
err := insert(db, job, &sdkproto.Resource{
|
|
Name: "something",
|
|
Type: "aws_instance",
|
|
DailyCost: 10,
|
|
Agents: []*sdkproto.Agent{{
|
|
Name: "dev",
|
|
Env: map[string]string{
|
|
"something": "test",
|
|
},
|
|
StartupScript: "value",
|
|
OperatingSystem: "linux",
|
|
Architecture: "amd64",
|
|
Auth: &sdkproto.Agent_Token{
|
|
Token: uuid.NewString(),
|
|
},
|
|
Apps: []*sdkproto.App{{
|
|
Slug: "a",
|
|
}},
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
resources, err := db.GetWorkspaceResourcesByJobID(ctx, job)
|
|
require.NoError(t, err)
|
|
require.Len(t, resources, 1)
|
|
require.EqualValues(t, 10, resources[0].DailyCost)
|
|
agents, err := db.GetWorkspaceAgentsByResourceIDs(ctx, []uuid.UUID{resources[0].ID})
|
|
require.NoError(t, err)
|
|
require.Len(t, agents, 1)
|
|
agent := agents[0]
|
|
require.Equal(t, "amd64", agent.Architecture)
|
|
require.Equal(t, "linux", agent.OperatingSystem)
|
|
require.Equal(t, "value", agent.StartupScript.String)
|
|
want, err := json.Marshal(map[string]string{
|
|
"something": "test",
|
|
})
|
|
require.NoError(t, err)
|
|
got, err := agent.EnvironmentVariables.RawMessage.MarshalJSON()
|
|
require.NoError(t, err)
|
|
require.Equal(t, want, got)
|
|
})
|
|
}
|
|
|
|
func setup(t *testing.T) *provisionerdserver.Server {
|
|
t.Helper()
|
|
db := databasefake.New()
|
|
pubsub := database.NewPubsubInMemory()
|
|
|
|
return &provisionerdserver.Server{
|
|
ID: uuid.New(),
|
|
Logger: slogtest.Make(t, nil),
|
|
AccessURL: &url.URL{},
|
|
Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
|
Database: db,
|
|
Pubsub: pubsub,
|
|
Telemetry: telemetry.NewNoop(),
|
|
Auditor: mockAuditor(),
|
|
}
|
|
}
|