feat: Validate swagger definitions (#5694)

* docs: audit, deploymentconfig, files, parameters

* Swagger comments in workspacebuilds.go

* structs in workspacebuilds.go

* workspaceagents: instance identity

* workspaceagents.go in progress

* workspaceagents.go in progress

* Agents

* workspacebuilds.go

* /workspaces

* templates.go, templateversions.go

* templateversion.go in progress

* cancel

* templateversions

* wip

* Merge

* x-apidocgen

* NullTime hack not needed anymore

* Fix: x-apidocgen

* Members

* Fixes

* Fix

* WIP

* WIP

* Users

* Logout

* User profile

* Status suspend activate

* User roles

* User tokens

* Keys

* SSH key

* All

* Typo

* Fix

* Entitlements

* Groups

* SCIM

* Fix

* Fix

* Clean templates

* Sort API pages

* Fix: HashedSecret

* WIP

* WIP

* WIP

* Fix: cover workspaceagents

* Assert: consistent ID and summary

* Assert: success or failure defined

* Fix: parallel

* Refactor

* Support enterprise

* Go comment goes to top

* Security

* assertPathParametersDefined

* assertUniqueRoutes

* assertRequestBody

* More fixes

* Fix: exceptions

* Fix field format

* Address PR comments

* Refactor
This commit is contained in:
Marcin Tojek 2023-01-13 12:27:21 +01:00 committed by GitHub
parent dcab87358e
commit deebfcbd53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2442 additions and 705 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,7 @@ import (
// @Summary Create token API key
// @ID create-token-api-key
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Users
// @Param user path string true "User ID, name, or me"
@ -209,9 +210,8 @@ func (api *API) tokens(rw http.ResponseWriter, r *http.Request) {
}
// @Summary Delete API key
// @ID delete-user-tokens
// @ID delete-api-key
// @Security CoderSessionToken
// @Produce json
// @Tags Users
// @Param user path string true "User ID, name, or me"
// @Param keyid path string true "Key ID" format(uuid)

View File

@ -89,7 +89,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
}
// @Summary Generate fake audit log
// @ID generate-fake-audit-logs
// @ID generate-fake-audit-log
// @Security CoderSessionToken
// @Accept json
// @Tags Audit

View File

@ -0,0 +1,66 @@
package coderdtest_test
import (
"go/ast"
"go/parser"
"go/token"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
)
func TestEndpointsDocumented(t *testing.T) {
t.Parallel()
swaggerComments, err := coderdtest.ParseSwaggerComments("..")
require.NoError(t, err, "can't parse swagger comments")
_, _, api := coderdtest.NewWithAPI(t, nil)
coderdtest.VerifySwaggerDefinitions(t, api.APIHandler, swaggerComments)
}
func TestSDKFieldsFormatted(t *testing.T) {
t.Parallel()
fileSet := token.NewFileSet()
nodes, err := parser.ParseDir(fileSet, "../../codersdk", nil, parser.ParseComments)
require.NoError(t, err, "parser.ParseDir failed")
for _, node := range nodes {
ast.Inspect(node, func(n ast.Node) bool {
typeSpec, ok := n.(*ast.TypeSpec)
if !ok {
return true
}
structureName := typeSpec.Name
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
return true // not a structure
}
for _, field := range structType.Fields.List {
selectorExpr, ok := field.Type.(*ast.SelectorExpr)
if !ok {
continue // rather a basic, or primitive
}
if field.Tag == nil || !strings.Contains(field.Tag.Value, `json:"`) {
continue // not a JSON property
}
switch selectorExpr.Sel.Name {
case "UUID":
assert.Contains(t, field.Tag.Value, `format:"uuid"`, `Swagger formatting requires to annotate the field with - format:"uuid". Location: %s/%s`, structureName, field.Names)
case "Time":
assert.Contains(t, field.Tag.Value, `format:"date-time"`, `Swagger formatting requires to annotate the field with - format:"date-time". Location: %s/%s`, structureName, field.Names)
}
}
return true
})
}
}

View File

@ -0,0 +1,318 @@
package coderdtest
import (
"go/ast"
"go/parser"
"go/token"
"net/http"
"regexp"
"strings"
"testing"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
)
type SwaggerComment struct {
summary string
id string
security string
tags string
accept string
produce string
method string
router string
successes []response
failures []response
parameters []parameter
raw []*ast.Comment
}
type parameter struct {
name string
kind string
}
type response struct {
status string
kind string // {object} or {array}
model string
}
func ParseSwaggerComments(dirs ...string) ([]SwaggerComment, error) {
fileSet := token.NewFileSet()
var swaggerComments []SwaggerComment
for _, dir := range dirs {
nodes, err := parser.ParseDir(fileSet, dir, nil, parser.ParseComments)
if err != nil {
return nil, xerrors.Errorf(`parser.ParseDir failed for "%s": %w`, dir, err)
}
for _, node := range nodes {
ast.Inspect(node, func(n ast.Node) bool {
commentGroup, ok := n.(*ast.CommentGroup)
if !ok {
return true
}
var isSwaggerComment bool
for _, line := range commentGroup.List {
text := strings.TrimSpace(line.Text)
if strings.HasPrefix(text, "//") && strings.Contains(text, "@Router") {
isSwaggerComment = true
break
}
}
if isSwaggerComment {
swaggerComments = append(swaggerComments, parseSwaggerComment(commentGroup))
}
return true
})
}
}
return swaggerComments, nil
}
func parseSwaggerComment(commentGroup *ast.CommentGroup) SwaggerComment {
c := SwaggerComment{
raw: commentGroup.List,
parameters: []parameter{},
successes: []response{},
failures: []response{},
}
for _, line := range commentGroup.List {
// @<annotationName> [args...]
splitN := strings.SplitN(strings.TrimSpace(line.Text), " ", 3)
if len(splitN) < 2 {
continue // comment prefix without any content
}
if !strings.HasPrefix(splitN[1], "@") {
continue // not a swagger annotation
}
annotationName := splitN[1]
annotationArgs := splitN[2]
args := strings.Split(splitN[2], " ")
switch annotationName {
case "@Router":
c.router = args[0]
c.method = args[1][1 : len(args[1])-1]
case "@Success", "@Failure":
var r response
if len(args) > 0 {
r.status = args[0]
}
if len(args) > 1 {
r.kind = args[1]
}
if len(args) > 2 {
r.model = args[2]
}
if annotationName == "@Success" {
c.successes = append(c.successes, r)
} else if annotationName == "@Failure" {
c.failures = append(c.failures, r)
}
case "@Param":
p := parameter{
name: args[0],
kind: args[1],
}
c.parameters = append(c.parameters, p)
case "@Summary":
c.summary = annotationArgs
case "@ID":
c.id = annotationArgs
case "@Tags":
c.tags = annotationArgs
case "@Security":
c.security = annotationArgs
case "@Accept":
c.accept = annotationArgs
case "@Produce":
c.produce = annotationArgs
}
}
return c
}
func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments []SwaggerComment) {
assertUniqueRoutes(t, swaggerComments)
err := chi.Walk(router, func(method, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
method = strings.ToLower(method)
if route != "/" && strings.HasSuffix(route, "/") {
route = route[:len(route)-1]
}
t.Run(method+" "+route, func(t *testing.T) {
t.Parallel()
c := findSwaggerCommentByMethodAndRoute(swaggerComments, method, route)
assert.NotNil(t, c, "Missing @Router annotation")
if c == nil {
return // do not fail next assertion for this route
}
assertConsistencyBetweenRouteIDAndSummary(t, *c)
assertSuccessOrFailureDefined(t, *c)
assertRequiredAnnotations(t, *c)
assertGoCommentFirst(t, *c)
assertPathParametersDefined(t, *c)
assertSecurityDefined(t, *c)
assertAccept(t, *c)
assertProduce(t, *c)
})
return nil
})
require.NoError(t, err, "chi.Walk should not fail")
}
func assertUniqueRoutes(t *testing.T, comments []SwaggerComment) {
m := map[string]struct{}{}
for _, c := range comments {
key := c.method + " " + c.router
_, alreadyDefined := m[key]
assert.False(t, alreadyDefined, "defined route must be unique (method: %s, route: %s)", c.method, c.router)
if !alreadyDefined {
m[key] = struct{}{}
}
}
}
func findSwaggerCommentByMethodAndRoute(comments []SwaggerComment, method, route string) *SwaggerComment {
for _, c := range comments {
if c.method == method && c.router == route {
return &c
}
}
return nil
}
var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9-]+`)
func assertConsistencyBetweenRouteIDAndSummary(t *testing.T, comment SwaggerComment) {
exp := strings.ToLower(comment.summary)
exp = strings.ReplaceAll(exp, " ", "-")
exp = nonAlphanumericRegex.ReplaceAllString(exp, "")
assert.Equal(t, exp, comment.id, "Router ID must match summary")
}
func assertSuccessOrFailureDefined(t *testing.T, comment SwaggerComment) {
assert.True(t, len(comment.successes) > 0 || len(comment.failures) > 0, "At least one @Success or @Failure annotation must be defined")
}
func assertRequiredAnnotations(t *testing.T, comment SwaggerComment) {
assert.NotEmpty(t, comment.id, "@ID must be defined")
assert.NotEmpty(t, comment.summary, "@Summary must be defined")
assert.NotEmpty(t, comment.tags, "@Tags must be defined")
}
func assertGoCommentFirst(t *testing.T, comment SwaggerComment) {
var inSwaggerBlock bool
for _, line := range comment.raw {
text := strings.TrimSpace(line.Text)
if inSwaggerBlock {
if !strings.HasPrefix(text, "// @") {
assert.Fail(t, "Go function comment must be placed before swagger comments")
return
}
}
if strings.HasPrefix(text, "// @Summary") {
inSwaggerBlock = true
}
}
}
var urlParameterRegexp = regexp.MustCompile(`{[^{}]*}`)
func assertPathParametersDefined(t *testing.T, comment SwaggerComment) {
matches := urlParameterRegexp.FindAllString(comment.router, -1)
if matches == nil {
return // router does not require any parameters
}
for _, m := range matches {
var matched bool
for _, p := range comment.parameters {
if p.kind == "path" && "{"+p.name+"}" == m {
matched = true
break
}
}
if !matched {
assert.Failf(t, "Missing @Param annotation", "Path parameter: %s", m)
}
}
}
func assertSecurityDefined(t *testing.T, comment SwaggerComment) {
if comment.router == "/updatecheck" ||
comment.router == "/buildinfo" ||
comment.router == "/" {
return // endpoints do not require authorization
}
assert.Equal(t, "CoderSessionToken", comment.security, "@Security must be equal CoderSessionToken")
}
func assertAccept(t *testing.T, comment SwaggerComment) {
var hasRequestBody bool
for _, c := range comment.parameters {
if c.name == "request" && c.kind == "body" ||
c.name == "file" && c.kind == "formData" {
hasRequestBody = true
break
}
}
var hasAccept bool
if comment.accept != "" {
hasAccept = true
}
if comment.method == "get" {
assert.Empty(t, comment.accept, "GET route does not require the @Accept annotation")
assert.False(t, hasRequestBody, "GET route does not require the request body")
} else {
assert.False(t, hasRequestBody && !hasAccept, "Route with the request body requires the @Accept annotation")
assert.False(t, !hasRequestBody && hasAccept, "Route with @Accept annotation requires the request body or file formData parameter")
}
}
func assertProduce(t *testing.T, comment SwaggerComment) {
var hasResponseModel bool
for _, r := range comment.successes {
if r.model != "" {
hasResponseModel = true
break
}
}
if hasResponseModel {
assert.True(t, comment.produce != "", "Route must have @Produce annotation as it responds with a model structure")
} else {
if (comment.router == "/workspaceagents/me/app-health" && comment.method == "post") ||
(comment.router == "/workspaceagents/me/version" && comment.method == "post") ||
(comment.router == "/licenses/{id}" && comment.method == "delete") {
return // Exception: HTTP 200 is returned without response entity
}
assert.True(t, comment.produce == "", "Response model is undefined, so we can't predict the content type", comment)
}
}

View File

@ -20,7 +20,6 @@ type cspViolation struct {
// @ID report-csp-violations
// @Security CoderSessionToken
// @Accept json
// @Produce text/plain
// @Tags General
// @Param request body cspViolation true "Violation report"
// @Success 200

View File

@ -25,7 +25,7 @@ const (
// @Summary Upload file
// @Description Swagger notice: Swagger 2.0 doesn't support file upload with a `content-type` different than `application/x-www-form-urlencoded`.
// @ID update-file
// @ID upload-file
// @Security CoderSessionToken
// @Produce json
// @Accept application/x-tar

View File

@ -119,7 +119,6 @@ func (api *API) gitSSHKey(rw http.ResponseWriter, r *http.Request) {
// @Summary Get workspace agent Git SSH key
// @ID get-workspace-agent-git-ssh-key
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Agents
// @Success 200 {object} codersdk.AgentGitSSHKey

View File

@ -40,6 +40,7 @@ func (api *API) organization(rw http.ResponseWriter, r *http.Request) {
// @Summary Create organization
// @ID create-organization
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Organizations
// @Param request body codersdk.CreateOrganizationRequest true "Create organization request"

View File

@ -23,15 +23,16 @@ import (
"github.com/coder/coder/examples"
)
// Returns a single template.
//
// @Summary Get template metadata by ID
// @ID get-template-metadata-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Param template path string true "Template ID" format(uuid)
// @Success 200 {object} codersdk.Template
// @Router /templates/{id} [get]
// Returns a single template.
// @Router /templates/{template} [get]
func (api *API) template(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
template := httpmw.TemplateParam(r)
@ -75,9 +76,9 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) {
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Param template path string true "Template ID" format(uuid)
// @Success 200 {object} codersdk.Response
// @Router /templates/{id} [delete]
// @Router /templates/{template} [delete]
func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -131,6 +132,9 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
})
}
// Create a new template in an organization.
// Returns a single template.
//
// @Summary Create template by organization
// @ID create-template-by-organization
// @Security CoderSessionToken
@ -141,8 +145,6 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
// @Param organization path string true "Organization ID"
// @Success 200 {object} codersdk.Template
// @Router /organizations/{organization}/templates [post]
// Returns a single template.
// Create a new template in an organization.
func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -461,13 +463,13 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re
}
// @Summary Update template metadata by ID
// @ID update-template-metadata
// @ID update-template-metadata-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Param template path string true "Template ID" format(uuid)
// @Success 200 {object} codersdk.Template
// @Router /templates/{id} [patch]
// @Router /templates/{template} [patch]
func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -593,9 +595,9 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Param template path string true "Template ID" format(uuid)
// @Success 200 {object} codersdk.TemplateDAUsResponse
// @Router /templates/{id}/daus [get]
// @Router /templates/{template}/daus [get]
func (api *API) templateDAUs(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
template := httpmw.TemplateParam(r)

View File

@ -353,13 +353,12 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques
// @Summary Get template version dry-run by job ID
// @ID get-template-version-dry-run-by-job-id
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Templates
// @Param templateversion path string true "Template version ID" format(uuid)
// @Param jobid path string true "Job ID" format(uuid)
// @Param jobID path string true "Job ID" format(uuid)
// @Success 200 {object} codersdk.ProvisionerJob
// @Router /templateversions/{templateversion}/dry-run/{jobid} [get]
// @Router /templateversions/{templateversion}/dry-run/{jobID} [get]
func (api *API) templateVersionDryRun(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
job, ok := api.fetchTemplateVersionDryRunJob(rw, r)
@ -376,9 +375,9 @@ func (api *API) templateVersionDryRun(rw http.ResponseWriter, r *http.Request) {
// @Produce json
// @Tags Templates
// @Param templateversion path string true "Template version ID" format(uuid)
// @Param jobid path string true "Job ID" format(uuid)
// @Param jobID path string true "Job ID" format(uuid)
// @Success 200 {array} codersdk.WorkspaceResource
// @Router /templateversions/{templateversion}/dry-run/{jobid}/resources [get]
// @Router /templateversions/{templateversion}/dry-run/{jobID}/resources [get]
func (api *API) templateVersionDryRunResources(rw http.ResponseWriter, r *http.Request) {
job, ok := api.fetchTemplateVersionDryRunJob(rw, r)
if !ok {
@ -394,12 +393,12 @@ func (api *API) templateVersionDryRunResources(rw http.ResponseWriter, r *http.R
// @Produce json
// @Tags Templates
// @Param templateversion path string true "Template version ID" format(uuid)
// @Param jobid path string true "Job ID" format(uuid)
// @Param jobID path string true "Job ID" format(uuid)
// @Param before query int false "Before Unix timestamp"
// @Param after query int false "After Unix timestamp"
// @Param follow query bool false "Follow log stream"
// @Success 200 {array} codersdk.ProvisionerJobLog
// @Router /templateversions/{templateversion}/dry-run/{jobid}/logs [get]
// @Router /templateversions/{templateversion}/dry-run/{jobID}/logs [get]
func (api *API) templateVersionDryRunLogs(rw http.ResponseWriter, r *http.Request) {
job, ok := api.fetchTemplateVersionDryRunJob(rw, r)
if !ok {
@ -414,9 +413,10 @@ func (api *API) templateVersionDryRunLogs(rw http.ResponseWriter, r *http.Reques
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param jobID path string true "Job ID" format(uuid)
// @Param templateversion path string true "Template version ID" format(uuid)
// @Success 200 {object} codersdk.Response
// @Router /templateversions/{templateversion}/dry-run/{jobid}/cancel [patch]
// @Router /templateversions/{templateversion}/dry-run/{jobID}/cancel [patch]
func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
templateVersion := httpmw.TemplateVersionParam(r)
@ -536,16 +536,16 @@ func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Re
}
// @Summary List template versions by template ID
// @ID list-template-versions-by-template-ID
// @ID list-template-versions-by-template-id
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Param template path string true "Template ID" format(uuid)
// @Param after_id query string false "After ID" format(uuid)
// @Param limit query int false "Page limit"
// @Param offset query int false "Page offset"
// @Success 200 {array} codersdk.TemplateVersion
// @Router /templates/{id}/versions [get]
// @Router /templates/{template}/versions [get]
func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
template := httpmw.TemplateParam(r)
@ -648,10 +648,10 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Param template path string true "Template ID" format(uuid)
// @Param templateversionname path string true "Template version name"
// @Success 200 {array} codersdk.TemplateVersion
// @Router /templates/{id}/versions/{templateversionname} [get]
// @Router /templates/{template}/versions/{templateversionname} [get]
func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
template := httpmw.TemplateParam(r)
@ -828,15 +828,15 @@ func (api *API) previousTemplateVersionByOrganizationAndName(rw http.ResponseWri
}
// @Summary Update active template version by template ID
// @ID update-active-template-version-by-template-ID
// @ID update-active-template-version-by-template-id
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Templates
// @Param request body codersdk.UpdateActiveTemplateVersion true "Modified template version"
// @Param id path string true "Template ID" format(uuid)
// @Param template path string true "Template ID" format(uuid)
// @Success 200 {object} codersdk.Response
// @Router /templates/{id}/versions [patch]
// @Router /templates/{template}/versions [patch]
func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -911,6 +911,8 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque
})
}
// postTemplateVersionsByOrganization creates a new version of a template. An import job is queued to parse the storage method provided.
//
// @Summary Create template version by organization
// @ID create-template-version-by-organization
// @Security CoderSessionToken
@ -921,8 +923,6 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque
// @Param request body codersdk.CreateTemplateVersionDryRunRequest true "Create template version request"
// @Success 201 {object} codersdk.TemplateVersion
// @Router /organizations/{organization}/templateversions [post]
//
// postTemplateVersionsByOrganization creates a new version of a template. An import job is queued to parse the storage method provided.
func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -1207,6 +1207,12 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
httpapi.Write(ctx, rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), user))
}
// templateVersionResources returns the workspace agent resources associated
// with a template version. A template can specify more than one resource to be
// provisioned, each resource can have an agent that dials back to coderd. The
// agents returned are informative of the template version, and do not return
// agents associated with any particular workspace.
//
// @Summary Get resources by template version
// @ID get-resources-by-template-version
// @Security CoderSessionToken
@ -1215,12 +1221,6 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
// @Param templateversion path string true "Template version ID" format(uuid)
// @Success 200 {array} codersdk.WorkspaceResource
// @Router /templateversions/{templateversion}/resources [get]
//
// templateVersionResources returns the workspace agent resources associated
// with a template version. A template can specify more than one resource to be
// provisioned, each resource can have an agent that dials back to coderd. The
// agents returned are informative of the template version, and do not return
// agents associated with any particular workspace.
func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -1244,6 +1244,11 @@ func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request
api.provisionerJobResources(rw, r, job)
}
// templateVersionLogs returns the logs returned by the provisioner for the given
// template version. These logs are only associated with the template version,
// and not any build logs for a workspace.
// Eg: Logs returned from 'terraform plan' when uploading a new terraform file.
//
// @Summary Get logs by template version
// @ID get-logs-by-template-version
// @Security CoderSessionToken
@ -1255,11 +1260,6 @@ func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request
// @Param follow query bool false "Follow log stream"
// @Success 200 {array} codersdk.ProvisionerJobLog
// @Router /templateversions/{templateversion}/logs [get]
//
// templateVersionLogs returns the logs returned by the provisioner for the given
// template version. These logs are only associated with the template version,
// and not any build logs for a workspace.
// Eg: Logs returned from 'terraform plan' when uploading a new terraform file.
func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()

View File

@ -59,9 +59,8 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) {
}
// @Summary OAuth 2.0 GitHub Callback
// @ID oauth2-github-callback
// @ID oauth-20-github-callback
// @Security CoderSessionToken
// @Produce json
// @Tags Users
// @Success 307
// @Router /users/oauth2/github/callback [get]
@ -218,9 +217,8 @@ type OIDCConfig struct {
}
// @Summary OpenID Connect Callback
// @ID oidc-callback
// @ID openid-connect-callback
// @Security CoderSessionToken
// @Produce json
// @Tags Users
// @Success 307
// @Router /users/oidc/callback [get]

View File

@ -435,6 +435,7 @@ func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
// @Summary Update user profile
// @ID update-user-profile
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Users
// @Param user path string true "User ID, name, or me"
@ -617,7 +618,7 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
// @Summary Update user password
// @ID update-user-password
// @Security CoderSessionToken
// @Produce json
// @Accept json
// @Tags Users
// @Param user path string true "User ID, name, or me"
// @Param request body codersdk.UpdateUserPasswordRequest true "Update password request"
@ -908,7 +909,7 @@ func (api *API) updateSiteUserRoles(ctx context.Context, args database.UpdateUse
// Returns organizations the parameterized user has access to.
//
// @Summary Get organizations by user
// @ID get-organizations-by-users
// @ID get-organizations-by-user
// @Security CoderSessionToken
// @Produce json
// @Tags Users
@ -990,6 +991,7 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques
// @Summary Log in user
// @ID log-in-user
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Authorization
// @Param request body codersdk.LoginWithPasswordRequest true "Login request"

View File

@ -35,6 +35,14 @@ import (
"github.com/coder/coder/tailnet"
)
// @Summary Get workspace agent by ID
// @ID get-workspace-agent-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Agents
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
// @Success 200 {object} codersdk.WorkspaceAgent
// @Router /workspaceagents/{workspaceagent} [get]
func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspaceAgent := httpmw.WorkspaceAgentParam(r)
@ -66,7 +74,6 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) {
// @Summary Get authorized workspace agent metadata
// @ID get-authorized-workspace-agent-metadata
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Agents
// @Success 200 {object} codersdk.WorkspaceAgentMetadata
@ -147,9 +154,10 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
}
// @Summary Submit workspace agent version
// @ID submit-workspace-workspace-agent-version
// @ID submit-workspace-agent-version
// @Security CoderSessionToken
// @Produce application/json
// @Accept json
// @Produce json
// @Tags Agents
// @Param request body codersdk.PostWorkspaceAgentVersionRequest true "Version request"
// @Success 200
@ -198,6 +206,14 @@ func (api *API) postWorkspaceAgentVersion(rw http.ResponseWriter, r *http.Reques
// workspaceAgentPTY spawns a PTY and pipes it over a WebSocket.
// This is used for the web terminal.
//
// @Summary Open PTY to workspace agent
// @ID open-pty-to-workspace-agent
// @Security CoderSessionToken
// @Tags Agents
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
// @Success 101
// @Router /workspaceagents/{workspaceagent}/pty [get]
func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -276,6 +292,14 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
agent.Bicopy(ctx, wsNetConn, ptNetConn)
}
// @Summary Get listening ports for workspace agent
// @ID get-listening-ports-for-workspace-agent
// @Security CoderSessionToken
// @Produce json
// @Tags Agents
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
// @Success 200 {object} codersdk.ListeningPortsResponse
// @Router /workspaceagents/{workspaceagent}/listening-ports [get]
func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
@ -443,6 +467,14 @@ func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (*
}, nil
}
// @Summary Get connection info for workspace agent
// @ID get-connection-info-for-workspace-agent
// @Security CoderSessionToken
// @Produce json
// @Tags Agents
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
// @Success 200 {object} codersdk.WorkspaceAgentConnectionInfo
// @Router /workspaceagents/{workspaceagent}/connection [get]
func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
@ -458,9 +490,8 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request
// @Summary Coordinate workspace agent via Tailnet
// @Description It accepts a WebSocket connection to an agent that listens to
// @Description incoming connections and publishes node updates.
// @ID get-workspace-agent-git-ssh-key-via-tailnet
// @ID coordinate-workspace-agent-via-tailnet
// @Security CoderSessionToken
// @Produce json
// @Tags Agents
// @Success 101
// @Router /workspaceagents/me/coordinate [get]
@ -622,6 +653,14 @@ func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request
// workspaceAgentClientCoordinate accepts a WebSocket that reads node network updates.
// After accept a PubSub starts listening for new connection node updates
// which are written to the WebSocket.
//
// @Summary Coordinate workspace agent
// @ID coordinate-workspace-agent
// @Security CoderSessionToken
// @Tags Agents
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
// @Success 101
// @Router /workspaceagents/{workspaceagent}/coordinate [get]
func (api *API) workspaceAgentClientCoordinate(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -784,8 +823,9 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin
}
// @Summary Submit workspace agent stats
// @ID submit-workspace-workspace-agent-stats
// @ID submit-workspace-agent-stats
// @Security CoderSessionToken
// @Accept json
// @Produce application/json
// @Tags Agents
// @Param request body codersdk.AgentStats true "Stats request"
@ -860,9 +900,10 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques
})
}
// @Summary Submit workspace application health
// @ID submit-workspace-workspace-agent-health
// @Summary Submit workspace agent application health
// @ID submit-workspace-agent-application-health
// @Security CoderSessionToken
// @Accept json
// @Produce application/json
// @Tags Agents
// @Param request body codersdk.PostWorkspaceAppHealthsRequest true "Application health request"
@ -989,7 +1030,6 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request)
// @Summary Get workspace agent Git auth
// @ID get-workspace-agent-git-auth
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Agents
// @Param url query string true "Git URL" format(uri)

View File

@ -66,7 +66,7 @@ var nonCanonicalHeaders = map[string]string{
}
// @Summary Get applications host
// @ID get-app-host
// @ID get-applications-host
// @Security CoderSessionToken
// @Produce json
// @Tags Applications
@ -614,6 +614,13 @@ func (api *API) setWorkspaceAppCookie(rw http.ResponseWriter, r *http.Request, t
return true
}
// workspaceApplicationAuth is an endpoint on the main router that handles
// redirects from the subdomain handler.
//
// This endpoint is under /api so we don't return the friendly error page here.
// Any errors on this endpoint should be errors that are unlikely to happen
// in production unless the user messes with the URL.
//
// @Summary Redirect to URI with encrypted API key
// @ID redirect-to-uri-with-encrypted-api-key
// @Security CoderSessionToken
@ -621,13 +628,6 @@ func (api *API) setWorkspaceAppCookie(rw http.ResponseWriter, r *http.Request, t
// @Param redirect_uri query string false "Redirect destination"
// @Success 307
// @Router /applications/auth-redirect [get]
//
// workspaceApplicationAuth is an endpoint on the main router that handles
// redirects from the subdomain handler.
//
// This endpoint is under /api so we don't return the friendly error page here.
// Any errors on this endpoint should be errors that are unlikely to happen
// in production unless the user messes with the URL.
func (api *API) workspaceApplicationAuth(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if api.AppHostname == "" {

View File

@ -77,13 +77,13 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
// @Security CoderSessionToken
// @Produce json
// @Tags Builds
// @Param id path string true "Workspace ID" format(uuid)
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param after_id query string false "After ID" format(uuid)
// @Param limit query int false "Page limit"
// @Param offset query int false "Page offset"
// @Param since query string false "Since timestamp" format(date-time)
// @Success 200 {array} codersdk.WorkspaceBuild
// @Router /workspaces/{id}/builds [get]
// @Router /workspaces/{workspace}/builds [get]
func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
@ -290,13 +290,13 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
// @Summary Create workspace build
// @ID create-workspace-build
// @Security CoderSessionToken
// @Accepts json
// @Accept json
// @Produce json
// @Tags Builds
// @Param id path string true "Workspace ID" format(uuid)
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.CreateWorkspaceBuildRequest true "Create workspace build request"
// @Success 200 {object} codersdk.WorkspaceBuild
// @Router /workspaces/{id}/builds [post]
// @Router /workspaces/{workspace}/builds [post]
func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)

View File

@ -23,6 +23,7 @@ import (
// @Summary Authenticate agent on Azure instance
// @ID authenticate-agent-on-azure-instance
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Agents
// @Param request body codersdk.AzureInstanceIdentityToken true "Instance identity token"
@ -45,18 +46,19 @@ func (api *API) postWorkspaceAuthAzureInstanceIdentity(rw http.ResponseWriter, r
api.handleAuthInstanceID(rw, r, instanceID)
}
// AWS supports instance identity verification:
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
// Using this, we can exchange a signed instance payload for an agent token.
//
// @Summary Authenticate agent on AWS instance
// @ID authenticate-agent-on-aws-instance
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Agents
// @Param request body codersdk.AWSInstanceIdentityToken true "Instance identity token"
// @Success 200 {object} codersdk.WorkspaceAgentAuthenticateResponse
// @Router /workspaceagents/aws-instance-identity [post]
//
// AWS supports instance identity verification:
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
// Using this, we can exchange a signed instance payload for an agent token.
func (api *API) postWorkspaceAuthAWSInstanceIdentity(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var req codersdk.AWSInstanceIdentityToken
@ -74,18 +76,19 @@ func (api *API) postWorkspaceAuthAWSInstanceIdentity(rw http.ResponseWriter, r *
api.handleAuthInstanceID(rw, r, identity.InstanceID)
}
// Google Compute Engine supports instance identity verification:
// https://cloud.google.com/compute/docs/instances/verifying-instance-identity
// Using this, we can exchange a signed instance payload for an agent token.
//
// @Summary Authenticate agent on Google Cloud instance
// @ID authenticate-agent-on-google-cloud-instance
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Agents
// @Param request body codersdk.GoogleInstanceIdentityToken true "Instance identity token"
// @Success 200 {object} codersdk.WorkspaceAgentAuthenticateResponse
// @Router /workspaceagents/google-instance-identity [post]
//
// Google Compute Engine supports instance identity verification:
// https://cloud.google.com/compute/docs/instances/verifying-instance-identity
// Using this, we can exchange a signed instance payload for an agent token.
func (api *API) postWorkspaceAuthGoogleInstanceIdentity(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var req codersdk.GoogleInstanceIdentityToken

View File

@ -48,10 +48,10 @@ var (
// @Security CoderSessionToken
// @Produce json
// @Tags Workspaces
// @Param id path string true "Workspace ID" format(uuid)
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param include_deleted query bool false "Return data instead of HTTP 404 if the workspace is deleted"
// @Success 200 {object} codersdk.Workspace
// @Router /workspaces/{id} [get]
// @Router /workspaces/{workspace} [get]
func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
@ -101,8 +101,11 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
))
}
// workspaces returns all workspaces a user can read.
// Optional filters with query params
//
// @Summary List workspaces
// @ID get-workspaces
// @ID list-workspaces
// @Security CoderSessionToken
// @Produce json
// @Tags Workspaces
@ -113,9 +116,6 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
// @Param has_agent query string false "Filter by agent status" Enums(connected,connecting,disconnected,timeout)
// @Success 200 {object} codersdk.WorkspacesResponse
// @Router /workspaces [get]
//
// workspaces returns all workspaces a user can read.
// Optional filters with query params
func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@ -266,6 +266,8 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
))
}
// Create a new workspace for the currently authenticated user.
//
// @Summary Create user workspace by organization
// @ID create-user-workspace-by-organization
// @Security CoderSessionToken
@ -275,8 +277,6 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
// @Param user path string true "Username, UUID, or me"
// @Success 200 {object} codersdk.Workspace
// @Router /organizations/{organization}/members/{user}/workspaces [post]
//
// Create a new workspace for the currently authenticated user.
func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -558,7 +558,6 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
// @ID update-workspace-metadata-by-id
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.UpdateWorkspaceRequest true "Metadata update request"
@ -648,7 +647,6 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
// @ID update-workspace-autostart-schedule-by-id
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.UpdateWorkspaceAutostartRequest true "Schedule update request"
@ -711,7 +709,6 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
// @ID update-workspace-ttl-by-id
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.UpdateWorkspaceTTLRequest true "Workspace TTL update request"
@ -875,7 +872,7 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
}
// @Summary Watch workspace by ID
// @ID watch-workspace-id
// @ID watch-workspace-by-id
// @Security CoderSessionToken
// @Produce text/event-stream
// @Tags Workspaces

View File

@ -88,14 +88,14 @@ type AuditDiffField struct {
}
type AuditLog struct {
ID uuid.UUID `json:"id"`
RequestID uuid.UUID `json:"request_id"`
Time time.Time `json:"time"`
OrganizationID uuid.UUID `json:"organization_id"`
ID uuid.UUID `json:"id" format:"uuid"`
RequestID uuid.UUID `json:"request_id" format:"uuid"`
Time time.Time `json:"time" format:"date-time"`
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
IP netip.Addr `json:"ip"`
UserAgent string `json:"user_agent"`
ResourceType ResourceType `json:"resource_type"`
ResourceID uuid.UUID `json:"resource_id"`
ResourceID uuid.UUID `json:"resource_id" format:"uuid"`
// ResourceTarget is the name of the resource.
ResourceTarget string `json:"resource_target"`
ResourceIcon string `json:"resource_icon"`
@ -123,8 +123,8 @@ type AuditLogResponse struct {
type CreateTestAuditLogRequest struct {
Action AuditAction `json:"action,omitempty" enums:"create,write,delete,start,stop"`
ResourceType ResourceType `json:"resource_type,omitempty" enums:"organization,template,template_version,user,workspace,workspace_build,git_ssh_key,api_key,group"`
ResourceID uuid.UUID `json:"resource_id,omitempty"`
Time time.Time `json:"time,omitempty"`
ResourceID uuid.UUID `json:"resource_id,omitempty" format:"uuid"`
Time time.Time `json:"time,omitempty" format:"date-time"`
}
// AuditLogs retrieves audit logs from the given page.

View File

@ -67,7 +67,7 @@ type CreateTemplateRequest struct {
// This is required on creation to enable a user-flow of validating a
// template works. There is no reason the data-model cannot support empty
// templates, but it doesn't make sense for users.
VersionID uuid.UUID `json:"template_version_id" validate:"required"`
VersionID uuid.UUID `json:"template_version_id" validate:"required" format:"uuid"`
ParameterValues []CreateParameterRequest `json:"parameter_values,omitempty"`
// DefaultTTLMillis allows optionally specifying the default TTL
@ -81,7 +81,7 @@ type CreateTemplateRequest struct {
// CreateWorkspaceRequest provides options for creating a new workspace.
type CreateWorkspaceRequest struct {
TemplateID uuid.UUID `json:"template_id" validate:"required"`
TemplateID uuid.UUID `json:"template_id" validate:"required" format:"uuid"`
Name string `json:"name" validate:"workspace_name,required"`
AutostartSchedule *string `json:"autostart_schedule"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`

View File

@ -14,7 +14,7 @@ type Pagination struct {
// Offset for better performance. To use it as an alternative,
// set AfterID to the last UUID returned by the previous
// request.
AfterID uuid.UUID `json:"after_id,omitempty"`
AfterID uuid.UUID `json:"after_id,omitempty" format:"uuid"`
// Limit sets the maximum number of users to be returned
// in a single page. If the limit is <= 0, there is no limit
// and all users are returned.

View File

@ -176,7 +176,7 @@ func (c *Client) UpdateActiveTemplateVersion(ctx context.Context, template uuid.
// TemplateVersionsByTemplateRequest defines the request parameters for
// TemplateVersionsByTemplate.
type TemplateVersionsByTemplateRequest struct {
TemplateID uuid.UUID `json:"template_id" validate:"required"`
TemplateID uuid.UUID `json:"template_id" validate:"required" format:"uuid"`
Pagination
}
@ -210,7 +210,7 @@ func (c *Client) TemplateVersionByName(ctx context.Context, template uuid.UUID,
}
type DAUEntry struct {
Date time.Time `json:"date"`
Date time.Time `json:"date" format:"date-time"`
Amount int `json:"amount"`
}

View File

@ -47,7 +47,7 @@ type WorkspacesResponse struct {
// CreateWorkspaceBuildRequest provides options to update the latest workspace build.
type CreateWorkspaceBuildRequest struct {
TemplateVersionID uuid.UUID `json:"template_version_id,omitempty"`
TemplateVersionID uuid.UUID `json:"template_version_id,omitempty" format:"uuid"`
Transition WorkspaceTransition `json:"transition" validate:"oneof=create start stop delete,required"`
DryRun bool `json:"dry_run,omitempty"`
ProvisionerState []byte `json:"state,omitempty"`
@ -245,7 +245,7 @@ func (c *Client) UpdateWorkspaceTTL(ctx context.Context, id uuid.UUID, req Updat
// PutExtendWorkspaceRequest is a request to extend the deadline of
// the active workspace build.
type PutExtendWorkspaceRequest struct {
Deadline time.Time `json:"deadline" validate:"required"`
Deadline time.Time `json:"deadline" validate:"required" format:"date-time"`
}
// PutExtendWorkspace updates the deadline for resources of the latest workspace build.

View File

@ -142,7 +142,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/google-instance-ide
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Submit workspace application health
## Submit workspace agent application health
### Code samples
@ -437,3 +437,271 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/report-stats \
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.AgentStatsResponse](schemas.md#codersdkagentstatsresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get workspace agent by ID
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaceagents/{workspaceagent}`
### Parameters
| Name | In | Type | Required | Description |
| ---------------- | ---- | ------------ | -------- | ------------------ |
| `workspaceagent` | path | string(uuid) | true | Workspace agent ID |
### Example responses
> 200 Response
```json
{
"apps": [
{
"command": "string",
"display_name": "string",
"external": true,
"health": "disabled",
"healthcheck": {
"interval": 0,
"threshold": 0,
"url": "string"
},
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"sharing_level": "owner",
"slug": "string",
"subdomain": true,
"url": "string"
}
],
"architecture": "string",
"connection_timeout_seconds": 0,
"created_at": "2019-08-24T14:15:22Z",
"directory": "string",
"disconnected_at": "2019-08-24T14:15:22Z",
"environment_variables": {
"property1": "string",
"property2": "string"
},
"first_connected_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"instance_id": "string",
"last_connected_at": "2019-08-24T14:15:22Z",
"latency": {
"property1": {
"latency_ms": 0,
"preferred": true
},
"property2": {
"latency_ms": 0,
"preferred": true
}
},
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"startup_script": "string",
"status": "connecting",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceAgent](schemas.md#codersdkworkspaceagent) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get connection info for workspace agent
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/connection \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaceagents/{workspaceagent}/connection`
### Parameters
| Name | In | Type | Required | Description |
| ---------------- | ---- | ------------ | -------- | ------------------ |
| `workspaceagent` | path | string(uuid) | true | Workspace agent ID |
### Example responses
> 200 Response
```json
{
"derp_map": {
"omitDefaultRegions": true,
"regions": {
"property1": {
"avoid": true,
"embeddedRelay": true,
"nodes": [
{
"certName": "string",
"derpport": 0,
"forceHTTP": true,
"hostName": "string",
"insecureForTests": true,
"ipv4": "string",
"ipv6": "string",
"name": "string",
"regionID": 0,
"stunonly": true,
"stunport": 0,
"stuntestIP": "string"
}
],
"regionCode": "string",
"regionID": 0,
"regionName": "string"
},
"property2": {
"avoid": true,
"embeddedRelay": true,
"nodes": [
{
"certName": "string",
"derpport": 0,
"forceHTTP": true,
"hostName": "string",
"insecureForTests": true,
"ipv4": "string",
"ipv6": "string",
"name": "string",
"regionID": 0,
"stunonly": true,
"stunport": 0,
"stuntestIP": "string"
}
],
"regionCode": "string",
"regionID": 0,
"regionName": "string"
}
}
}
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceAgentConnectionInfo](schemas.md#codersdkworkspaceagentconnectioninfo) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Coordinate workspace agent
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/coordinate \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaceagents/{workspaceagent}/coordinate`
### Parameters
| Name | In | Type | Required | Description |
| ---------------- | ---- | ------------ | -------- | ------------------ |
| `workspaceagent` | path | string(uuid) | true | Workspace agent ID |
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------------------------ | ------------------- | ------ |
| 101 | [Switching Protocols](https://tools.ietf.org/html/rfc7231#section-6.2.2) | Switching Protocols | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get listening ports for workspace agent
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/listening-ports \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaceagents/{workspaceagent}/listening-ports`
### Parameters
| Name | In | Type | Required | Description |
| ---------------- | ---- | ------------ | -------- | ------------------ |
| `workspaceagent` | path | string(uuid) | true | Workspace agent ID |
### Example responses
> 200 Response
```json
{
"ports": [
{
"network": "tcp",
"port": 0,
"process_name": "string"
}
]
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.ListeningPortsResponse](schemas.md#codersdklisteningportsresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Open PTY to workspace agent
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/pty \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaceagents/{workspaceagent}/pty`
### Parameters
| Name | In | Type | Required | Description |
| ---------------- | ---- | ------------ | -------- | ------------------ |
| `workspaceagent` | path | string(uuid) | true | Workspace agent ID |
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------------------------ | ------------------- | ------ |
| 101 | [Switching Protocols](https://tools.ietf.org/html/rfc7231#section-6.2.2) | Switching Protocols | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).

View File

@ -47,18 +47,18 @@ curl -X GET http://coder-server:8080/api/v2/audit?q=string \
"secret": true
}
},
"id": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"ip": "string",
"is_deleted": true,
"organization_id": "string",
"request_id": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
"resource_icon": "string",
"resource_id": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"resource_link": "string",
"resource_target": "string",
"resource_type": "organization",
"status_code": 0,
"time": "string",
"time": "2019-08-24T14:15:22Z",
"user": {
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
@ -108,9 +108,9 @@ curl -X POST http://coder-server:8080/api/v2/audit/testgenerate \
```json
{
"action": "create",
"resource_id": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"resource_type": "organization",
"time": "string"
"time": "2019-08-24T14:15:22Z"
}
```

View File

@ -740,22 +740,22 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaces/{id}/builds \
curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaces/{id}/builds`
`GET /workspaces/{workspace}/builds`
### Parameters
| Name | In | Type | Required | Description |
| ---------- | ----- | ----------------- | -------- | --------------- |
| `id` | path | string(uuid) | true | Workspace ID |
| `after_id` | query | string(uuid) | false | After ID |
| `limit` | query | integer | false | Page limit |
| `offset` | query | integer | false | Page offset |
| `since` | query | string(date-time) | false | Since timestamp |
| Name | In | Type | Required | Description |
| ----------- | ----- | ----------------- | -------- | --------------- |
| `workspace` | path | string(uuid) | true | Workspace ID |
| `after_id` | query | string(uuid) | false | After ID |
| `limit` | query | integer | false | Page limit |
| `offset` | query | integer | false | Page offset |
| `since` | query | string(date-time) | false | Since timestamp |
### Example responses
@ -1019,13 +1019,13 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/workspaces/{id}/builds \
curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /workspaces/{id}/builds`
`POST /workspaces/{workspace}/builds`
> Body parameter
@ -1043,17 +1043,17 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{id}/builds \
}
],
"state": [0],
"template_version_id": "string",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"transition": "create"
}
```
### Parameters
| Name | In | Type | Required | Description |
| ------ | ---- | -------------------------------------------------------------------------------------- | -------- | ------------------------------ |
| `id` | path | string(uuid) | true | Workspace ID |
| `body` | body | [codersdk.CreateWorkspaceBuildRequest](schemas.md#codersdkcreateworkspacebuildrequest) | true | Create workspace build request |
| Name | In | Type | Required | Description |
| ----------- | ---- | -------------------------------------------------------------------------------------- | -------- | ------------------------------ |
| `workspace` | path | string(uuid) | true | Workspace ID |
| `body` | body | [codersdk.CreateWorkspaceBuildRequest](schemas.md#codersdkcreateworkspacebuildrequest) | true | Create workspace build request |
### Example responses

View File

@ -242,18 +242,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/groups/{groupName} \
curl -X GET http://coder-server:8080/api/v2/groups/{group} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /groups/{groupName}`
`GET /groups/{group}`
### Parameters
| Name | In | Type | Required | Description |
| ----------- | ---- | ------ | -------- | ----------- |
| `groupName` | path | string | true | Group name |
| Name | In | Type | Required | Description |
| ------- | ---- | ------ | -------- | ----------- |
| `group` | path | string | true | Group name |
### Example responses
@ -295,29 +295,121 @@ curl -X GET http://coder-server:8080/api/v2/groups/{groupName} \
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Delete license
## Delete group by name
### Code samples
```shell
# Example request using curl
curl -X DELETE http://coder-server:8080/api/v2/license/{id} \
curl -X DELETE http://coder-server:8080/api/v2/groups/{group} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`DELETE /license/{id}`
`DELETE /groups/{group}`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | -------------- | -------- | ----------- |
| `id` | path | string(number) | true | License ID |
| Name | In | Type | Required | Description |
| ------- | ---- | ------ | -------- | ----------- |
| `group` | path | string | true | Group name |
### Example responses
> 200 Response
```json
{
"avatar_url": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"members": [
{
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_seen_at": "2019-08-24T14:15:22Z",
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
"roles": [
{
"display_name": "string",
"name": "string"
}
],
"status": "active",
"username": "string"
}
],
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"quota_allowance": 0
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | |
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Group](schemas.md#codersdkgroup) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Update group by name
### Code samples
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/groups/{group} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /groups/{group}`
### Parameters
| Name | In | Type | Required | Description |
| ------- | ---- | ------ | -------- | ----------- |
| `group` | path | string | true | Group name |
### Example responses
> 200 Response
```json
{
"avatar_url": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"members": [
{
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_seen_at": "2019-08-24T14:15:22Z",
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
"roles": [
{
"display_name": "string",
"name": "string"
}
],
"status": "active",
"username": "string"
}
],
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"quota_allowance": 0
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Group](schemas.md#codersdkgroup) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
@ -369,6 +461,32 @@ Status Code **200**
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Delete license
### Code samples
```shell
# Example request using curl
curl -X DELETE http://coder-server:8080/api/v2/licenses/{id} \
-H 'Coder-Session-Token: API_KEY'
```
`DELETE /licenses/{id}`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | -------------- | -------- | ----------- |
| `id` | path | string(number) | true | License ID |
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get groups by organization
### Code samples
@ -462,6 +580,66 @@ Status Code **200**
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get group by organization and group name
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups/{groupName} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /organizations/{organization}/groups/{groupName}`
### Parameters
| Name | In | Type | Required | Description |
| -------------- | ---- | ------------ | -------- | --------------- |
| `organization` | path | string(uuid) | true | Organization ID |
| `groupName` | path | string | true | Group name |
### Example responses
> 200 Response
```json
{
"avatar_url": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"members": [
{
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_seen_at": "2019-08-24T14:15:22Z",
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
"roles": [
{
"display_name": "string",
"name": "string"
}
],
"status": "active",
"username": "string"
}
],
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"quota_allowance": 0
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Group](schemas.md#codersdkgroup) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get provisioner daemons
### Code samples
@ -609,6 +787,26 @@ Status Code **200**
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## SCIM 2.0: Get users
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/scim/v2/Users \
-H 'Coder-Session-Token: API_KEY'
```
`GET /scim/v2/Users`
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## SCIM 2.0: Create new user
### Code samples

View File

@ -323,18 +323,18 @@
"secret": true
}
},
"id": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"ip": "string",
"is_deleted": true,
"organization_id": "string",
"request_id": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
"resource_icon": "string",
"resource_id": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"resource_link": "string",
"resource_target": "string",
"resource_type": "organization",
"status_code": 0,
"time": "string",
"time": "2019-08-24T14:15:22Z",
"user": {
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
@ -399,18 +399,18 @@
"secret": true
}
},
"id": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"ip": "string",
"is_deleted": true,
"organization_id": "string",
"request_id": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
"resource_icon": "string",
"resource_id": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"resource_link": "string",
"resource_target": "string",
"resource_type": "organization",
"status_code": 0,
"time": "string",
"time": "2019-08-24T14:15:22Z",
"user": {
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
@ -731,7 +731,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
"source_value": "string"
}
],
"template_version_id": "string"
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1"
}
```
@ -778,9 +778,9 @@ CreateParameterRequest is a structure used to create a new parameter value for a
```json
{
"action": "create",
"resource_id": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"resource_type": "organization",
"time": "string"
"time": "2019-08-24T14:15:22Z"
}
```
@ -871,7 +871,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
}
],
"state": [0],
"template_version_id": "string",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"transition": "create"
}
```
@ -901,7 +901,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
```json
{
"amount": 0,
"date": "string"
"date": "2019-08-24T14:15:22Z"
}
```
@ -2561,6 +2561,58 @@ CreateParameterRequest is a structure used to create a new parameter value for a
| `uploaded_at` | string | false | | |
| `uuid` | string | false | | |
## codersdk.ListeningPort
```json
{
"network": "tcp",
"port": 0,
"process_name": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| -------------- | -------------------------------------------------------------- | -------- | ------------ | ------------------------ |
| `network` | [codersdk.ListeningPortNetwork](#codersdklisteningportnetwork) | false | | only "tcp" at the moment |
| `port` | integer | false | | |
| `process_name` | string | false | | may be empty |
## codersdk.ListeningPortNetwork
```json
"tcp"
```
### Properties
#### Enumerated Values
| Value |
| ----- |
| `tcp` |
## codersdk.ListeningPortsResponse
```json
{
"ports": [
{
"network": "tcp",
"port": 0,
"process_name": "string"
}
]
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ------- | --------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ports` | array of [codersdk.ListeningPort](#codersdklisteningport) | false | | If there are no ports in the list, nothing should be displayed in the UI. There must not be a "no ports available" message or anything similar, as there will always be no ports displayed on platforms where our port detection logic is unsupported. |
## codersdk.LogLevel
```json
@ -3463,7 +3515,7 @@ Parameter represents a set value for the scope.
```json
{
"deadline": "string"
"deadline": "2019-08-24T14:15:22Z"
}
```
@ -3907,7 +3959,7 @@ Parameter represents a set value for the scope.
"entries": [
{
"amount": 0,
"date": "string"
"date": "2019-08-24T14:15:22Z"
}
]
}
@ -4617,6 +4669,70 @@ Parameter represents a set value for the scope.
| --------------- | ------ | -------- | ------------ | ----------- |
| `session_token` | string | false | | |
## codersdk.WorkspaceAgentConnectionInfo
```json
{
"derp_map": {
"omitDefaultRegions": true,
"regions": {
"property1": {
"avoid": true,
"embeddedRelay": true,
"nodes": [
{
"certName": "string",
"derpport": 0,
"forceHTTP": true,
"hostName": "string",
"insecureForTests": true,
"ipv4": "string",
"ipv6": "string",
"name": "string",
"regionID": 0,
"stunonly": true,
"stunport": 0,
"stuntestIP": "string"
}
],
"regionCode": "string",
"regionID": 0,
"regionName": "string"
},
"property2": {
"avoid": true,
"embeddedRelay": true,
"nodes": [
{
"certName": "string",
"derpport": 0,
"forceHTTP": true,
"hostName": "string",
"insecureForTests": true,
"ipv4": "string",
"ipv6": "string",
"name": "string",
"regionID": 0,
"stunonly": true,
"stunport": 0,
"stuntestIP": "string"
}
],
"regionCode": "string",
"regionID": 0,
"regionName": "string"
}
}
}
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ---------- | ---------------------------------- | -------- | ------------ | ----------- |
| `derp_map` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | |
## codersdk.WorkspaceAgentGitAuthResponse
```json

View File

@ -204,7 +204,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
"source_value": "string"
}
],
"template_version_id": "string"
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1"
}
```
@ -630,18 +630,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templates/{id} \
curl -X GET http://coder-server:8080/api/v2/templates/{template} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templates/{id}`
`GET /templates/{template}`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | ------------ | -------- | ----------- |
| `id` | path | string(uuid) | true | Template ID |
| Name | In | Type | Required | Description |
| ---------- | ---- | ------------ | -------- | ----------- |
| `template` | path | string(uuid) | true | Template ID |
### Example responses
@ -692,18 +692,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X DELETE http://coder-server:8080/api/v2/templates/{id} \
curl -X DELETE http://coder-server:8080/api/v2/templates/{template} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`DELETE /templates/{id}`
`DELETE /templates/{template}`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | ------------ | -------- | ----------- |
| `id` | path | string(uuid) | true | Template ID |
| Name | In | Type | Required | Description |
| ---------- | ---- | ------------ | -------- | ----------- |
| `template` | path | string(uuid) | true | Template ID |
### Example responses
@ -736,18 +736,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/templates/{id} \
curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /templates/{id}`
`PATCH /templates/{template}`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | ------------ | -------- | ----------- |
| `id` | path | string(uuid) | true | Template ID |
| Name | In | Type | Required | Description |
| ---------- | ---- | ------------ | -------- | ----------- |
| `template` | path | string(uuid) | true | Template ID |
### Example responses
@ -798,18 +798,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templates/{id}/daus \
curl -X GET http://coder-server:8080/api/v2/templates/{template}/daus \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templates/{id}/daus`
`GET /templates/{template}/daus`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | ------------ | -------- | ----------- |
| `id` | path | string(uuid) | true | Template ID |
| Name | In | Type | Required | Description |
| ---------- | ---- | ------------ | -------- | ----------- |
| `template` | path | string(uuid) | true | Template ID |
### Example responses
@ -820,7 +820,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{id}/daus \
"entries": [
{
"amount": 0,
"date": "string"
"date": "2019-08-24T14:15:22Z"
}
]
}
@ -840,18 +840,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templates/{id}/versions \
curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templates/{id}/versions`
`GET /templates/{template}/versions`
### Parameters
| Name | In | Type | Required | Description |
| ---------- | ----- | ------------ | -------- | ----------- |
| `id` | path | string(uuid) | true | Template ID |
| `template` | path | string(uuid) | true | Template ID |
| `after_id` | query | string(uuid) | false | After ID |
| `limit` | query | integer | false | Page limit |
| `offset` | query | integer | false | Page offset |
@ -971,13 +971,13 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/templates/{id}/versions \
curl -X PATCH http://coder-server:8080/api/v2/templates/{template}/versions \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /templates/{id}/versions`
`PATCH /templates/{template}/versions`
> Body parameter
@ -989,10 +989,10 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{id}/versions \
### Parameters
| Name | In | Type | Required | Description |
| ------ | ---- | -------------------------------------------------------------------------------------- | -------- | ------------------------- |
| `id` | path | string(uuid) | true | Template ID |
| `body` | body | [codersdk.UpdateActiveTemplateVersion](schemas.md#codersdkupdateactivetemplateversion) | true | Modified template version |
| Name | In | Type | Required | Description |
| ---------- | ---- | -------------------------------------------------------------------------------------- | -------- | ------------------------- |
| `template` | path | string(uuid) | true | Template ID |
| `body` | body | [codersdk.UpdateActiveTemplateVersion](schemas.md#codersdkupdateactivetemplateversion) | true | Modified template version |
### Example responses
@ -1025,18 +1025,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templates/{id}/versions/{templateversionname} \
curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions/{templateversionname} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templates/{id}/versions/{templateversionname}`
`GET /templates/{template}/versions/{templateversionname}`
### Parameters
| Name | In | Type | Required | Description |
| --------------------- | ---- | ------------ | -------- | --------------------- |
| `id` | path | string(uuid) | true | Template ID |
| `template` | path | string(uuid) | true | Template ID |
| `templateversionname` | path | string | true | Template version name |
### Example responses
@ -1340,19 +1340,19 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobid} \
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobID} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templateversions/{templateversion}/dry-run/{jobid}`
`GET /templateversions/{templateversion}/dry-run/{jobID}`
### Parameters
| Name | In | Type | Required | Description |
| ----------------- | ---- | ------------ | -------- | ------------------- |
| `templateversion` | path | string(uuid) | true | Template version ID |
| `jobid` | path | string(uuid) | true | Job ID |
| `jobID` | path | string(uuid) | true | Job ID |
### Example responses
@ -1390,17 +1390,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobid}/cancel \
curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobID}/cancel \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /templateversions/{templateversion}/dry-run/{jobid}/cancel`
`PATCH /templateversions/{templateversion}/dry-run/{jobID}/cancel`
### Parameters
| Name | In | Type | Required | Description |
| ----------------- | ---- | ------------ | -------- | ------------------- |
| `jobID` | path | string(uuid) | true | Job ID |
| `templateversion` | path | string(uuid) | true | Template version ID |
### Example responses
@ -1434,19 +1435,19 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobid}/logs \
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobID}/logs \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templateversions/{templateversion}/dry-run/{jobid}/logs`
`GET /templateversions/{templateversion}/dry-run/{jobID}/logs`
### Parameters
| Name | In | Type | Required | Description |
| ----------------- | ----- | ------------ | -------- | --------------------- |
| `templateversion` | path | string(uuid) | true | Template version ID |
| `jobid` | path | string(uuid) | true | Job ID |
| `jobID` | path | string(uuid) | true | Job ID |
| `before` | query | integer | false | Before Unix timestamp |
| `after` | query | integer | false | After Unix timestamp |
| `follow` | query | boolean | false | Follow log stream |
@ -1508,19 +1509,19 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobid}/resources \
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/dry-run/{jobID}/resources \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templateversions/{templateversion}/dry-run/{jobid}/resources`
`GET /templateversions/{templateversion}/dry-run/{jobID}/resources`
### Parameters
| Name | In | Type | Required | Description |
| ----------------- | ---- | ------------ | -------- | ------------------- |
| `templateversion` | path | string(uuid) | true | Template version ID |
| `jobid` | path | string(uuid) | true | Job ID |
| `jobID` | path | string(uuid) | true | Job ID |
### Example responses

View File

@ -511,18 +511,18 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaces/{id} \
curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaces/{id}`
`GET /workspaces/{workspace}`
### Parameters
| Name | In | Type | Required | Description |
| ----------------- | ----- | ------------ | -------- | ----------------------------------------------------------- |
| `id` | path | string(uuid) | true | Workspace ID |
| `workspace` | path | string(uuid) | true | Workspace ID |
| `include_deleted` | query | boolean | false | Return data instead of HTTP 404 if the workspace is deleted |
### Example responses
@ -755,7 +755,7 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/extend \
```json
{
"deadline": "string"
"deadline": "2019-08-24T14:15:22Z"
}
```

View File

@ -84,6 +84,7 @@ func validateHexColor(color string) error {
// @Summary Update appearance
// @ID update-appearance
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Enterprise
// @Param request body codersdk.AppearanceConfig true "Update appearance request"

View File

@ -87,7 +87,7 @@ func New(ctx context.Context, options *Options) (*API, error) {
httpmw.ExtractGroupByNameParam(api.Database),
)
r.Get("/", api.group)
r.Get("/", api.groupByOrganization)
})
})
r.Route("/organizations/{organization}/provisionerdaemons", func(r chi.Router) {

View File

@ -0,0 +1,20 @@
package coderdenttest_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/enterprise/coderd/coderdenttest"
)
func TestEnterpriseEndpointsDocumented(t *testing.T) {
t.Parallel()
swaggerComments, err := coderdtest.ParseSwaggerComments("..", "../../../coderd")
require.NoError(t, err, "can't parse swagger comments")
_, _, api := coderdenttest.NewWithAPI(t, nil)
coderdtest.VerifySwaggerDefinitions(t, api.AGPL.APIHandler, swaggerComments)
}

View File

@ -80,6 +80,14 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request)
httpapi.Write(ctx, rw, http.StatusCreated, convertGroup(group, nil))
}
// @Summary Update group by name
// @ID update-group-by-name
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param group path string true "Group name"
// @Success 200 {object} codersdk.Group
// @Router /groups/{group} [patch]
func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -236,6 +244,14 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, convertGroup(group, members))
}
// @Summary Delete group by name
// @ID delete-group-by-name
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param group path string true "Group name"
// @Success 200 {object} codersdk.Group
// @Router /groups/{group} [delete]
func (api *API) deleteGroup(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -274,14 +290,27 @@ func (api *API) deleteGroup(rw http.ResponseWriter, r *http.Request) {
})
}
// @Summary Get group by organization and group name
// @ID get-group-by-organization-and-group-name
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param organization path string true "Organization ID" format(uuid)
// @Param groupName path string true "Group name"
// @Success 200 {object} codersdk.Group
// @Router /organizations/{organization}/groups/{groupName} [get]
func (api *API) groupByOrganization(rw http.ResponseWriter, r *http.Request) {
api.group(rw, r)
}
// @Summary Get group by name
// @ID get-group-by-name
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param groupName path string true "Group name"
// @Param group path string true "Group name"
// @Success 200 {object} codersdk.Group
// @Router /groups/{groupName} [get]
// @Router /groups/{group} [get]
func (api *API) group(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()

View File

@ -52,6 +52,7 @@ var Keys = map[string]ed25519.PublicKey{"2022-08-12": ed25519.PublicKey(key20220
// @Summary Add new license
// @ID add-new-license
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Organizations
// @Param request body codersdk.AddLicenseRequest true "Add license request"
@ -178,7 +179,7 @@ func (api *API) licenses(rw http.ResponseWriter, r *http.Request) {
// @Tags Enterprise
// @Param id path string true "License ID" format(number)
// @Success 200
// @Router /license/{id} [delete]
// @Router /licenses/{id} [delete]
func (api *API) deleteLicense(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if !api.AGPL.Authorize(r, rbac.ActionDelete, rbac.ResourceLicense) {

View File

@ -94,7 +94,6 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
// @Summary Serve provisioner daemon
// @ID serve-provisioner-daemon
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param organization path string true "Organization ID" format(uuid)
// @Success 101

View File

@ -49,7 +49,7 @@ func (api *API) scimVerifyAuthHeader(r *http.Request) bool {
// @Produce application/scim+json
// @Tags Enterprise
// @Success 200
// @Router /scim/v2/Users [post]
// @Router /scim/v2/Users [get]
//
//nolint:revive
func (api *API) scimGetUsers(rw http.ResponseWriter, r *http.Request) {

View File

@ -103,6 +103,7 @@ func (api *API) templateACL(rw http.ResponseWriter, r *http.Request) {
// @Summary Update template ACL
// @ID update-template-acl
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Enterprise
// @Param template path string true "Template ID" format(uuid)