Merge remote-tracking branch 'origin/main' into jjs/presets

This commit is contained in:
Sas Swart
2025-02-14 08:34:48 +00:00
29 changed files with 331 additions and 252 deletions

16
coderd/apidoc/docs.go generated
View File

@ -3055,6 +3055,16 @@ const docTemplate = `{
"name": "limit",
"in": "query"
},
{
"type": "array",
"format": "uuid",
"items": {
"type": "string"
},
"description": "Filter results by job IDs",
"name": "ids",
"in": "query"
},
{
"enum": [
"pending",
@ -3075,6 +3085,12 @@ const docTemplate = `{
"description": "Filter results by status",
"name": "status",
"in": "query"
},
{
"type": "object",
"description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})",
"name": "tags",
"in": "query"
}
],
"responses": {

View File

@ -2683,6 +2683,16 @@
"name": "limit",
"in": "query"
},
{
"type": "array",
"format": "uuid",
"items": {
"type": "string"
},
"description": "Filter results by job IDs",
"name": "ids",
"in": "query"
},
{
"enum": [
"pending",
@ -2703,6 +2713,12 @@
"description": "Filter results by status",
"name": "status",
"in": "query"
},
{
"type": "object",
"description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})",
"name": "tags",
"in": "query"
}
],
"responses": {

View File

@ -1304,7 +1304,7 @@ func New(options *Options) *API {
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if !api.Authorize(r, policy.ActionRead, rbac.ResourceDebugInfo) {
httpapi.ResourceNotFound(rw)
httpapi.Forbidden(rw)
return
}

View File

@ -4170,6 +4170,9 @@ func (q *FakeQuerier) GetProvisionerJobsByOrganizationAndStatusWithQueuePosition
if len(arg.IDs) > 0 && !slices.Contains(arg.IDs, job.ID) {
continue
}
if len(arg.Tags) > 0 && !tagsSubset(job.Tags, arg.Tags) {
continue
}
row := database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow{
ProvisionerJob: rowQP.ProvisionerJob,

View File

@ -6472,6 +6472,7 @@ WHERE
($1::uuid IS NULL OR pj.organization_id = $1)
AND (COALESCE(array_length($2::uuid[], 1), 0) = 0 OR pj.id = ANY($2::uuid[]))
AND (COALESCE(array_length($3::provisioner_job_status[], 1), 0) = 0 OR pj.job_status = ANY($3::provisioner_job_status[]))
AND ($4::tagset = 'null'::tagset OR provisioner_tagset_contains(pj.tags::tagset, $4::tagset))
GROUP BY
pj.id,
qp.queue_position,
@ -6486,13 +6487,14 @@ GROUP BY
ORDER BY
pj.created_at DESC
LIMIT
$4::int
$5::int
`
type GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams struct {
OrganizationID uuid.NullUUID `db:"organization_id" json:"organization_id"`
IDs []uuid.UUID `db:"ids" json:"ids"`
Status []ProvisionerJobStatus `db:"status" json:"status"`
Tags StringMap `db:"tags" json:"tags"`
Limit sql.NullInt32 `db:"limit" json:"limit"`
}
@ -6515,6 +6517,7 @@ func (q *sqlQuerier) GetProvisionerJobsByOrganizationAndStatusWithQueuePositionA
arg.OrganizationID,
pq.Array(arg.IDs),
pq.Array(arg.Status),
arg.Tags,
arg.Limit,
)
if err != nil {

View File

@ -158,6 +158,7 @@ WHERE
(sqlc.narg('organization_id')::uuid IS NULL OR pj.organization_id = @organization_id)
AND (COALESCE(array_length(@ids::uuid[], 1), 0) = 0 OR pj.id = ANY(@ids::uuid[]))
AND (COALESCE(array_length(@status::provisioner_job_status[], 1), 0) = 0 OR pj.job_status = ANY(@status::provisioner_job_status[]))
AND (@tags::tagset = 'null'::tagset OR provisioner_tagset_contains(pj.tags::tagset, @tags::tagset))
GROUP BY
pj.id,
qp.queue_position,

View File

@ -72,7 +72,9 @@ func (api *API) provisionerJob(rw http.ResponseWriter, r *http.Request) {
// @Tags Organizations
// @Param organization path string true "Organization ID" format(uuid)
// @Param limit query int false "Page limit"
// @Param ids query []string false "Filter results by job IDs" format(uuid)
// @Param status query codersdk.ProvisionerJobStatus false "Filter results by status" enums(pending,running,succeeded,canceling,canceled,failed)
// @Param tags query object false "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})"
// @Success 200 {array} codersdk.ProvisionerJob
// @Router /organizations/{organization}/provisionerjobs [get]
func (api *API) provisionerJobs(rw http.ResponseWriter, r *http.Request) {
@ -103,6 +105,10 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt
p := httpapi.NewQueryParamParser()
limit := p.PositiveInt32(qp, 50, "limit")
status := p.Strings(qp, nil, "status")
if ids == nil {
ids = p.UUIDs(qp, nil, "ids")
}
tagsRaw := p.String(qp, "", "tags")
p.ErrorExcessParams(qp)
if len(p.Errors) > 0 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@ -112,11 +118,23 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt
return nil, false
}
tags := database.StringMap{}
if tagsRaw != "" {
if err := tags.Scan([]byte(tagsRaw)); err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid tags query parameter",
Detail: err.Error(),
})
return nil, false
}
}
jobs, err := api.Database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(ctx, database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams{
OrganizationID: uuid.NullUUID{UUID: org.ID, Valid: true},
Status: slice.StringEnums[database.ProvisionerJobStatus](status),
Limit: sql.NullInt32{Int32: limit, Valid: limit > 0},
IDs: ids,
Tags: tags,
})
if err != nil {
if httpapi.Is404Error(err) {

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"encoding/json"
"strconv"
"testing"
"time"
@ -65,9 +66,10 @@ func TestProvisionerJobs(t *testing.T) {
})
// Add more jobs than the default limit.
for range 60 {
for i := range 60 {
dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
OrganizationID: owner.OrganizationID,
Tags: database.StringMap{"count": strconv.Itoa(i)},
})
}
@ -132,6 +134,16 @@ func TestProvisionerJobs(t *testing.T) {
require.Len(t, jobs, 50)
})
t.Run("IDs", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{
IDs: []uuid.UUID{workspace.LatestBuild.Job.ID, version.Job.ID},
})
require.NoError(t, err)
require.Len(t, jobs, 2)
})
t.Run("Status", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
@ -142,6 +154,16 @@ func TestProvisionerJobs(t *testing.T) {
require.Len(t, jobs, 1)
})
t.Run("Tags", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
jobs, err := templateAdminClient.OrganizationProvisionerJobs(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerJobsOptions{
Tags: map[string]string{"count": "1"},
})
require.NoError(t, err)
require.Len(t, jobs, 1)
})
t.Run("Limit", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)