mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat(cli): add provisioner list and provisioner jobs list (#16030)
This commit is contained in:
committed by
GitHub
parent
91204c2e3c
commit
c0db364f3f
218
cli/provisioners_test.go
Normal file
218
cli/provisioners_test.go
Normal file
@ -0,0 +1,218 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/coderd"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestProvisioners_Golden(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Replace UUIDs with predictable values for golden files.
|
||||
replace := make(map[string]string)
|
||||
updateReplaceUUIDs := func(coderdAPI *coderd.API) {
|
||||
//nolint:gocritic // This is a test.
|
||||
systemCtx := dbauthz.AsSystemRestricted(context.Background())
|
||||
provisioners, err := coderdAPI.Database.GetProvisionerDaemons(systemCtx)
|
||||
require.NoError(t, err)
|
||||
slices.SortFunc(provisioners, func(a, b database.ProvisionerDaemon) int {
|
||||
return a.CreatedAt.Compare(b.CreatedAt)
|
||||
})
|
||||
pIdx := 0
|
||||
for _, p := range provisioners {
|
||||
if _, ok := replace[p.ID.String()]; !ok {
|
||||
replace[p.ID.String()] = fmt.Sprintf("00000000-0000-0000-aaaa-%012d", pIdx)
|
||||
pIdx++
|
||||
}
|
||||
}
|
||||
jobs, err := coderdAPI.Database.GetProvisionerJobsCreatedAfter(systemCtx, time.Time{})
|
||||
require.NoError(t, err)
|
||||
slices.SortFunc(jobs, func(a, b database.ProvisionerJob) int {
|
||||
return a.CreatedAt.Compare(b.CreatedAt)
|
||||
})
|
||||
jIdx := 0
|
||||
for _, j := range jobs {
|
||||
if _, ok := replace[j.ID.String()]; !ok {
|
||||
replace[j.ID.String()] = fmt.Sprintf("00000000-0000-0000-bbbb-%012d", jIdx)
|
||||
jIdx++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db, ps := dbtestutil.NewDB(t,
|
||||
dbtestutil.WithDumpOnFailure(),
|
||||
//nolint:gocritic // Use UTC for consistent timestamp length in golden files.
|
||||
dbtestutil.WithTimezone("UTC"),
|
||||
)
|
||||
client, _, coderdAPI := coderdtest.NewWithAPI(t, &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: false,
|
||||
Database: db,
|
||||
Pubsub: ps,
|
||||
})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
templateAdminClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgTemplateAdmin(owner.OrganizationID))
|
||||
memberClient, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
|
||||
// Create initial resources with a running provisioner.
|
||||
firstProvisioner := coderdtest.NewProvisionerDaemon(t, coderdAPI)
|
||||
t.Cleanup(func() { _ = firstProvisioner.Close() })
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent())
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
|
||||
workspace := coderdtest.CreateWorkspace(t, client, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
// Stop the provisioner so it doesn't grab any more jobs.
|
||||
firstProvisioner.Close()
|
||||
|
||||
// Sanitize the UUIDs for the initial resources.
|
||||
replace[version.ID.String()] = "00000000-0000-0000-cccc-000000000000"
|
||||
replace[workspace.LatestBuild.ID.String()] = "00000000-0000-0000-dddd-000000000000"
|
||||
|
||||
// Create a provisioner that's working on a job.
|
||||
pd1 := dbgen.ProvisionerDaemon(t, coderdAPI.Database, database.ProvisionerDaemon{
|
||||
Name: "provisioner-1",
|
||||
CreatedAt: dbtime.Now().Add(1 * time.Second),
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
Tags: database.StringMap{"owner": "", "scope": "organization", "foo": "bar"},
|
||||
})
|
||||
w1 := dbgen.Workspace(t, coderdAPI.Database, database.WorkspaceTable{
|
||||
OwnerID: member.ID,
|
||||
TemplateID: template.ID,
|
||||
})
|
||||
wb1ID := uuid.MustParse("00000000-0000-0000-dddd-000000000001")
|
||||
job1 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{
|
||||
WorkerID: uuid.NullUUID{UUID: pd1.ID, Valid: true},
|
||||
Input: json.RawMessage(`{"workspace_build_id":"` + wb1ID.String() + `"}`),
|
||||
CreatedAt: dbtime.Now().Add(2 * time.Second),
|
||||
StartedAt: sql.NullTime{Time: coderdAPI.Clock.Now(), Valid: true},
|
||||
Tags: database.StringMap{"owner": "", "scope": "organization", "foo": "bar"},
|
||||
})
|
||||
dbgen.WorkspaceBuild(t, coderdAPI.Database, database.WorkspaceBuild{
|
||||
ID: wb1ID,
|
||||
JobID: job1.ID,
|
||||
WorkspaceID: w1.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
})
|
||||
|
||||
// Create a provisioner that completed a job previously and is offline.
|
||||
pd2 := dbgen.ProvisionerDaemon(t, coderdAPI.Database, database.ProvisionerDaemon{
|
||||
Name: "provisioner-2",
|
||||
CreatedAt: dbtime.Now().Add(2 * time.Second),
|
||||
LastSeenAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(-time.Hour), Valid: true},
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
Tags: database.StringMap{"owner": "", "scope": "organization"},
|
||||
})
|
||||
w2 := dbgen.Workspace(t, coderdAPI.Database, database.WorkspaceTable{
|
||||
OwnerID: member.ID,
|
||||
TemplateID: template.ID,
|
||||
})
|
||||
wb2ID := uuid.MustParse("00000000-0000-0000-dddd-000000000002")
|
||||
job2 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{
|
||||
WorkerID: uuid.NullUUID{UUID: pd2.ID, Valid: true},
|
||||
Input: json.RawMessage(`{"workspace_build_id":"` + wb2ID.String() + `"}`),
|
||||
CreatedAt: dbtime.Now().Add(3 * time.Second),
|
||||
StartedAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(-2 * time.Hour), Valid: true},
|
||||
CompletedAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(-time.Hour), Valid: true},
|
||||
Tags: database.StringMap{"owner": "", "scope": "organization"},
|
||||
})
|
||||
dbgen.WorkspaceBuild(t, coderdAPI.Database, database.WorkspaceBuild{
|
||||
ID: wb2ID,
|
||||
JobID: job2.ID,
|
||||
WorkspaceID: w2.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
})
|
||||
|
||||
// Create a pending job.
|
||||
w3 := dbgen.Workspace(t, coderdAPI.Database, database.WorkspaceTable{
|
||||
OwnerID: member.ID,
|
||||
TemplateID: template.ID,
|
||||
})
|
||||
wb3ID := uuid.MustParse("00000000-0000-0000-dddd-000000000003")
|
||||
job3 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{
|
||||
Input: json.RawMessage(`{"workspace_build_id":"` + wb3ID.String() + `"}`),
|
||||
CreatedAt: dbtime.Now().Add(4 * time.Second),
|
||||
Tags: database.StringMap{"owner": "", "scope": "organization"},
|
||||
})
|
||||
dbgen.WorkspaceBuild(t, coderdAPI.Database, database.WorkspaceBuild{
|
||||
ID: wb3ID,
|
||||
JobID: job3.ID,
|
||||
WorkspaceID: w3.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
})
|
||||
|
||||
// Create a provisioner that is idle.
|
||||
_ = dbgen.ProvisionerDaemon(t, coderdAPI.Database, database.ProvisionerDaemon{
|
||||
Name: "provisioner-3",
|
||||
CreatedAt: dbtime.Now().Add(3 * time.Second),
|
||||
KeyID: uuid.MustParse(codersdk.ProvisionerKeyIDBuiltIn),
|
||||
Tags: database.StringMap{"owner": "", "scope": "organization"},
|
||||
})
|
||||
|
||||
updateReplaceUUIDs(coderdAPI)
|
||||
|
||||
for id, replaceID := range replace {
|
||||
t.Logf("replace[%q] = %q", id, replaceID)
|
||||
}
|
||||
|
||||
// Test provisioners list with member as members can access
|
||||
// provisioner daemons.
|
||||
t.Run("list", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got bytes.Buffer
|
||||
inv, root := clitest.New(t,
|
||||
"provisioners",
|
||||
"list",
|
||||
"--column", "id,created at,last seen at,name,version,tags,key name,status,current job id,current job status,previous job id,previous job status,organization",
|
||||
)
|
||||
inv.Stdout = &got
|
||||
clitest.SetupConfig(t, memberClient, root)
|
||||
err := inv.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
clitest.TestGoldenFile(t, t.Name(), got.Bytes(), replace)
|
||||
})
|
||||
|
||||
// Test jobs list with template admin as members are currently
|
||||
// unable to access provisioner jobs. In the future (with RBAC
|
||||
// changes), we may allow them to view _their_ jobs.
|
||||
t.Run("jobs list", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got bytes.Buffer
|
||||
inv, root := clitest.New(t,
|
||||
"provisioners",
|
||||
"jobs",
|
||||
"list",
|
||||
"--column", "id,created at,status,worker id,tags,template version id,workspace build id,type,available workers,organization,queue",
|
||||
)
|
||||
inv.Stdout = &got
|
||||
clitest.SetupConfig(t, templateAdminClient, root)
|
||||
err := inv.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
clitest.TestGoldenFile(t, t.Name(), got.Bytes(), replace)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user