feat: Build framework for generating API docs (#5383)

* WIP

* Gen

* WIP

* chi swagger

* WIP

* WIP

* WIP

* GetWorkspaces

* GetWorkspaces

* Markdown

* Use widdershins

* WIP

* WIP

* WIP

* Markdown template

* Fix: makefile

* fmt

* Fix: comment

* Enable swagger conditionally

* fix: site

* Default false

* Flag tests

* fix

* fix

* template fixes

* Fix

* Fix

* Fix

* WIP

* Formatted

* Cleanup

* Templates

* BEGIN END SECTION

* subshell exit code

* Fix

* Fix merge

* WIP

* Fix

* Fix fmt

* Fix

* Generic api.md page

* Fix merge

* Link pages

* Fix

* Fix

* Fix: links

* Add icon

* Write manifest file

* Fix fmt

* Fix: enterprise

* Fix: Swagger.Enable

* Fix: rename apidocs to apidoc

* Fix: find -not -prune

* Fix: json not available

* Fix: rename Coderd API to Coder API

* Fix: npm exec

* Fix: api dir

* Fix: by ID

* Fix: string uuid

* Fix: include deleted

* Fix: indirect go.mod

* Fix: source lib.sh

* Fix: shellcheck

* Fix: pushd popd

* Fix: fmt

* Fix: improve workspaces

* Fix: swagger-enable

* Fix

* Fix: mention only HTTP 200

* Fix: IDs

* Fix: https

* Fix: icon

* More APis

* Fix: format swagger.json

* Fix: SwaggerEndpoint

* Fix: SCRIPT_DIR

* Fix: PROJECT_ROOT

* Fix: use code tags in schemas.md

* Fix: examples

* Fix: examples

* Fix: improve format

* Fix: date-time,enums

* Fix: include_deleted

* Fix: array of

* Fix: parameter, response

* Fix: string time or null

* Workspaces: more docs

* Workspaces: more docs

* Fix: renderDisplayName

* Fix: ActiveUserCount

* Fix

* Fix: typo

* Templates: docs

* Notice: incomplete
This commit is contained in:
Marcin Tojek 2022-12-19 18:43:46 +01:00 committed by GitHub
parent f239ca7ee3
commit dc6d271293
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 9272 additions and 76 deletions

View File

@ -410,13 +410,14 @@ gen: \
provisionersdk/proto/provisioner.pb.go \
provisionerd/proto/provisionerd.pb.go \
site/src/api/typesGenerated.ts \
docs/admin/prometheus.md
docs/admin/prometheus.md \
coderd/apidoc/swagger.json
.PHONY: gen
# Mark all generated files as fresh so make thinks they're up-to-date. This is
# used during releases so we don't run generation scripts.
gen/mark-fresh:
files="coderd/database/dump.sql coderd/database/querier.go provisionersdk/proto/provisioner.pb.go provisionerd/proto/provisionerd.pb.go site/src/api/typesGenerated.ts docs/admin/prometheus.md"
files="coderd/database/dump.sql coderd/database/querier.go provisionersdk/proto/provisioner.pb.go provisionerd/proto/provisionerd.pb.go site/src/api/typesGenerated.ts docs/admin/prometheus.md coderd/apidoc/swagger.json"
for file in $$files; do
echo "$$file"
if [ ! -f "$$file" ]; then
@ -464,6 +465,11 @@ docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/me
cd site
yarn run format:write ../docs/admin/prometheus.md
coderd/apidoc/swagger.json: $(shell find ./scripts/apidocgen -not \( -path './scripts/apidocgen/node_modules' -prune \) -type f) $(wildcard coderd/*.go) $(wildcard codersdk/*.go)
./scripts/apidocgen/generate.sh
cd site
yarn run format:write ../docs/api ../docs/manifest.json ../coderd/apidoc/swagger.json
update-golden-files: cli/testdata/.gen-golden
.PHONY: update-golden-files

View File

@ -452,6 +452,14 @@ func newConfig() *codersdk.DeploymentConfig {
Flag: "max-token-lifetime",
Default: 24 * 30 * time.Hour,
},
Swagger: &codersdk.SwaggerConfig{
Enable: &codersdk.DeploymentConfigField[bool]{
Name: "Enable swagger endpoint",
Usage: "Expose the swagger endpoint via /swagger.",
Flag: "swagger-enable",
Default: false,
},
},
}
}

View File

@ -678,6 +678,10 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
), cfg.Prometheus.Address.Value, "prometheus")()
}
if cfg.Swagger.Enable.Value {
options.SwaggerEndpoint = cfg.Swagger.Enable.Value
}
// We use a separate coderAPICloser so the Enterprise API
// can have it's own close functions. This is cleaner
// than abstracting the Coder API itself.

View File

@ -169,6 +169,8 @@ Flags:
"ecdsa", or "rsa4096".
Consumes $CODER_SSH_KEYGEN_ALGORITHM
(default "ed25519")
--swagger-enable Expose the swagger endpoint via /swagger.
Consumes $CODER_SWAGGER_ENABLE
--telemetry Whether telemetry is enabled or not.
Coder collects anonymized usage data to
help improve our product.

1305
coderd/apidoc/docs.go Normal file

File diff suppressed because it is too large Load Diff

1199
coderd/apidoc/swagger.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@ import (
"github.com/klauspost/compress/zstd"
"github.com/moby/moby/pkg/namesgenerator"
"github.com/prometheus/client_golang/prometheus"
httpSwagger "github.com/swaggo/http-swagger"
"go.opentelemetry.io/otel/trace"
"golang.org/x/xerrors"
"google.golang.org/api/idtoken"
@ -34,6 +35,9 @@ import (
"cdr.dev/slog"
"github.com/coder/coder/buildinfo"
// Used to serve the Swagger endpoint
_ "github.com/coder/coder/coderd/apidoc"
"github.com/coder/coder/coderd/audit"
"github.com/coder/coder/coderd/awsidentity"
"github.com/coder/coder/coderd/database"
@ -102,15 +106,34 @@ type Options struct {
TailnetCoordinator tailnet.Coordinator
DERPServer *derp.Server
DERPMap *tailcfg.DERPMap
SwaggerEndpoint bool
MetricsCacheRefreshInterval time.Duration
AgentStatsRefreshInterval time.Duration
Experimental bool
DeploymentConfig *codersdk.DeploymentConfig
UpdateCheckOptions *updatecheck.Options // Set non-nil to enable update checking.
HTTPClient *http.Client
HTTPClient *http.Client
}
// @title Coder API
// @version 2.0
// @description Coderd is the service created by running coder server. It is a thin API that connects workspaces, provisioners and users. coderd stores its state in Postgres and is the only service that communicates with Postgres.
// @termsOfService https://coder.com/legal/terms-of-service
// @contact.name API Support
// @contact.url https://coder.com
// @contact.email support@coder.com
// @license.name AGPL-3.0
// @license.url https://github.com/coder/coder/blob/main/LICENSE
// @BasePath /api/v2
// @securitydefinitions.apiKey CoderSessionToken
// @in header
// @name Coder-Session-Token
// New constructs a Coder API handler.
func New(options *Options) *API {
if options == nil {
@ -578,6 +601,13 @@ func New(options *Options) *API {
})
})
if options.SwaggerEndpoint {
// Swagger UI requires the URL trailing slash. Otherwise, the browser tries to load /assets
// from http://localhost:8080/assets instead of http://localhost:8080/swagger/assets.
r.Get("/swagger", http.RedirectHandler("/swagger/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/swagger/*", httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json")))
}
r.NotFound(compressHandler(http.HandlerFunc(api.siteHandler.ServeHTTP)).ServeHTTP)
return api
}

View File

@ -129,3 +129,81 @@ func TestHealthz(t *testing.T) {
assert.Equal(t, "OK", string(body))
}
func TestSwagger(t *testing.T) {
t.Parallel()
const swaggerEndpoint = "/swagger"
t.Run("endpoint enabled", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
SwaggerEndpoint: true,
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
resp, err := requestWithRetries(ctx, t, client, http.MethodGet, swaggerEndpoint, nil)
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
defer resp.Body.Close()
require.Contains(t, string(body), "Swagger UI")
})
t.Run("doc.json exposed", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
SwaggerEndpoint: true,
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
resp, err := requestWithRetries(ctx, t, client, http.MethodGet, swaggerEndpoint+"/doc.json", nil)
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
defer resp.Body.Close()
require.Contains(t, string(body), `"swagger": "2.0"`)
})
t.Run("endpoint disabled by default", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
resp, err := requestWithRetries(ctx, t, client, http.MethodGet, swaggerEndpoint, nil)
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, "<pre>\n</pre>\n", string(body))
})
t.Run("doc.json disabled by default", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
resp, err := requestWithRetries(ctx, t, client, http.MethodGet, swaggerEndpoint+"/doc.json", nil)
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, "<pre>\n</pre>\n", string(body))
})
}

View File

@ -115,6 +115,8 @@ type Options struct {
// test instances are running against the same database.
Database database.Store
Pubsub database.Pubsub
SwaggerEndpoint bool
}
// New constructs a codersdk client connected to an in-memory API instance.
@ -297,6 +299,7 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can
AgentStatsRefreshInterval: options.AgentStatsRefreshInterval,
DeploymentConfig: options.DeploymentConfig,
UpdateCheckOptions: options.UpdateCheckOptions,
SwaggerEndpoint: options.SwaggerEndpoint,
}
}

View File

@ -34,6 +34,14 @@ const (
AutoImportTemplateKubernetes AutoImportTemplate = "kubernetes"
)
// @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)
// @Success 200 {object} codersdk.Template
// @Router /templates/{id} [get]
// Returns a single template.
func (api *API) template(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -73,6 +81,14 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template, count, createdByNameMap[template.ID.String()]))
}
// @Summary Delete template by ID
// @ID delete-template-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Success 200 {object} codersdk.Response
// @Router /templates/{id} [delete]
func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -126,6 +142,17 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
})
}
// @Summary Create template by organization
// @ID create-template-by-organization
// @Security CoderSessionToken
// @Consume json
// @Produce json
// @Tags Templates
// @Param request body codersdk.CreateTemplateRequest true "Request body"
// @Param organization-id path string true "Organization ID"
// @Success 200 {object} codersdk.Template
// @Router /organizations/{organization-id}/templates/ [post]
// Returns a single template.
// Create a new template in an organization.
func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) {
var (
@ -314,6 +341,14 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
httpapi.Write(ctx, rw, http.StatusCreated, template)
}
// @Summary Get templates by organization
// @ID get-templates-by-organization
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param organization path string true "Organization ID" format(uuid)
// @Success 200 {object} []codersdk.Template
// @Router /organizations/{organization}/templates [get]
func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
@ -372,6 +407,15 @@ func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request)
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplates(templates, workspaceCounts, createdByNameMap))
}
// @Summary Get templates by organization and template name
// @ID get-templates-by-organization-and-template-name
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param organization path string true "Organization ID" format(uuid)
// @Param template-name path string true "Template name"
// @Success 200 {object} codersdk.Template
// @Router /organizations/{organization}/templates/{template-name} [get]
func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
@ -427,6 +471,14 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template, count, createdByNameMap[template.ID.String()]))
}
// @Summary Update template metadata by ID
// @ID update-template-metadata
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param id path string true "Template ID" format(uuid)
// @Success 200 {object} codersdk.Template
// @Router /templates/{id} [get]
func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()

View File

@ -43,6 +43,15 @@ var (
errDeadlineBeforeStart = xerrors.New("new deadline must be before workspace start time")
)
// @Summary Get workspace metadata by ID
// @ID get-workspace-metadata-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Workspaces
// @Param id 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]
func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
@ -92,6 +101,19 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
))
}
// @Summary List workspaces
// @ID get-workspaces
// @Security CoderSessionToken
// @Produce json
// @Tags Workspaces
// @Param owner query string false "Filter by owner username"
// @Param template query string false "Filter by template name"
// @Param name query string false "Filter with partial-match by workspace name"
// @Param status query string false "Filter by workspace status" Enums(pending,running,stopping,stopped,failed,canceling,canceled,deleted,deleting)
// @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) {
@ -170,6 +192,16 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
})
}
// @Summary Get workspace metadata by owner and workspace name
// @ID get-workspace-metadata-by-owner-and-workspace-name
// @Security CoderSessionToken
// @Produce json
// @Tags Workspaces
// @Param user path string true "Owner username"
// @Param workspacename path string true "Workspace name"
// @Param include_deleted query bool false "Return data instead of HTTP 404 if the workspace is deleted"
// @Success 200 {object} codersdk.Workspace
// @Router /users/{user}/workspace/{workspacename} [get]
func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
owner := httpmw.UserParam(r)
@ -234,6 +266,15 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
))
}
// @Summary Create workspace by organization
// @ID create-workspace-by-organization
// @Security CoderSessionToken
// @Produce json
// @Tags Workspaces
// @Param organization path string true "Organization ID" format(uuid)
// @Param user path string true "Username"
// @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 (
@ -520,6 +561,16 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
))
}
// @Summary Update workspace metadata by ID
// @ID update-workspace-metadata-by-id
// @Security CoderSessionToken
// @Consume json
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.UpdateWorkspaceRequest true "Metadata update request"
// @Success 204
// @Router /workspaces/{workspace} [patch]
func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -600,6 +651,16 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusNoContent)
}
// @Summary Update workspace autostart schedule by ID
// @ID update-workspace-autostart-schedule-by-id
// @Security CoderSessionToken
// @Consume json
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.UpdateWorkspaceAutostartRequest true "Schedule update request"
// @Success 204
// @Router /workspaces/{workspace}/autostart [put]
func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -653,6 +714,16 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusNoContent)
}
// @Summary Update workspace TTL by ID
// @ID update-workspace-ttl-by-id
// @Security CoderSessionToken
// @Consume json
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.UpdateWorkspaceTTLRequest true "Workspace TTL update request"
// @Success 204
// @Router /workspaces/{workspace}/ttl [put]
func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@ -719,6 +790,16 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusNoContent)
}
// @Summary Extend workspace deadline by ID
// @ID extend-workspace-deadline-by-id
// @Security CoderSessionToken
// @Consume json
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.PutExtendWorkspaceRequest true "Extend deadline update request"
// @Success 200 {object} codersdk.Response
// @Router /workspaces/{workspace}/extend [put]
func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
@ -800,6 +881,14 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, code, resp)
}
// @Summary Watch workspace by ID
// @ID watch-workspace-id
// @Security CoderSessionToken
// @Produce text/event-stream
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Success 200 {object} codersdk.Response
// @Router /workspaces/{workspace}/watch [get]
func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)

View File

@ -45,6 +45,7 @@ type DeploymentConfig struct {
Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"`
UpdateCheck *DeploymentConfigField[bool] `json:"update_check" typescript:",notnull"`
MaxTokenLifetime *DeploymentConfigField[time.Duration] `json:"max_token_lifetime" typescript:",notnull"`
Swagger *SwaggerConfig `json:"swagger" typescript:",notnull"`
}
type DERP struct {
@ -145,6 +146,10 @@ type ProvisionerConfig struct {
ForceCancelInterval *DeploymentConfigField[time.Duration] `json:"force_cancel_interval" typescript:",notnull"`
}
type SwaggerConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
}
type Flaggable interface {
string | time.Duration | bool | int | []string | []GitAuthConfig
}

View File

@ -14,10 +14,10 @@ import (
// Template is the JSON representation of a Coder template. This type matches the
// database object for now, but is abstracted for ease of change later on.
type Template struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OrganizationID uuid.UUID `json:"organization_id"`
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
Provisioner ProvisionerType `json:"provisioner"`
@ -29,15 +29,15 @@ type Template struct {
Description string `json:"description"`
Icon string `json:"icon"`
DefaultTTLMillis int64 `json:"default_ttl_ms"`
CreatedByID uuid.UUID `json:"created_by_id"`
CreatedByID uuid.UUID `json:"created_by_id" format:"uuid"`
CreatedByName string `json:"created_by_name"`
AllowUserCancelWorkspaceJobs bool `json:"allow_user_cancel_workspace_jobs"`
}
type TransitionStats struct {
P50 *int64
P95 *int64
P50 *int64 `example:"123"`
P95 *int64 `example:"146"`
}
type TemplateBuildTimeStats map[WorkspaceTransition]TransitionStats

View File

@ -51,32 +51,32 @@ const (
// WorkspaceBuild is an at-point representation of a workspace state.
// BuildNumbers start at 1 and increase by 1 for each subsequent build
type WorkspaceBuild struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
WorkspaceID uuid.UUID `json:"workspace_id"`
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
WorkspaceName string `json:"workspace_name"`
WorkspaceOwnerID uuid.UUID `json:"workspace_owner_id"`
WorkspaceOwnerID uuid.UUID `json:"workspace_owner_id" format:"uuid"`
WorkspaceOwnerName string `json:"workspace_owner_name"`
TemplateVersionID uuid.UUID `json:"template_version_id"`
TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"`
TemplateVersionName string `json:"template_version_name"`
BuildNumber int32 `json:"build_number"`
Transition WorkspaceTransition `json:"transition"`
InitiatorID uuid.UUID `json:"initiator_id"`
Transition WorkspaceTransition `json:"transition" enums:"start,stop,delete"`
InitiatorID uuid.UUID `json:"initiator_id" format:"uuid"`
InitiatorUsername string `json:"initiator_name"`
Job ProvisionerJob `json:"job"`
Reason BuildReason `db:"reason" json:"reason"`
Resources []WorkspaceResource `json:"resources"`
Deadline NullTime `json:"deadline,omitempty"`
Status WorkspaceStatus `json:"status"`
Status WorkspaceStatus `json:"status" enums:"pending,starting,running,stopping,stopped,failed,canceling,canceled,deleting,deleted"`
DailyCost int32 `json:"daily_cost"`
}
type WorkspaceResource struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
JobID uuid.UUID `json:"job_id"`
Transition WorkspaceTransition `json:"workspace_transition"`
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
JobID uuid.UUID `json:"job_id" format:"uuid"`
Transition WorkspaceTransition `json:"workspace_transition" enums:"start,stop,delete"`
Type string `json:"type"`
Name string `json:"name"`
Hide bool `json:"hide"`

View File

@ -17,12 +17,12 @@ import (
// Workspace is a deployment of a template. It references a specific
// version and can be updated.
type Workspace struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OwnerID uuid.UUID `json:"owner_id"`
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
OwnerID uuid.UUID `json:"owner_id" format:"uuid"`
OwnerName string `json:"owner_name"`
TemplateID uuid.UUID `json:"template_id"`
TemplateID uuid.UUID `json:"template_id" format:"uuid"`
TemplateName string `json:"template_name"`
TemplateDisplayName string `json:"template_display_name"`
TemplateIcon string `json:"template_icon"`
@ -32,7 +32,7 @@ type Workspace struct {
Name string `json:"name"`
AutostartSchedule *string `json:"autostart_schedule,omitempty"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`
LastUsedAt time.Time `json:"last_used_at"`
LastUsedAt time.Time `json:"last_used_at" format:"date-time"`
}
type WorkspacesRequest struct {

View File

@ -0,0 +1,4 @@
# Authentication
- API Key (CoderSessionToken)
- Parameter Name: **Coder-Session-Token**, in: header.

5
docs/api/index.md Normal file
View File

@ -0,0 +1,5 @@
Get started with Coder API:
<children>
This page is rendered on https://coder.com/docs/coder-oss/api. Refer to the other documents in the `api/` directory.
</children>

1020
docs/api/schemas.md Normal file

File diff suppressed because it is too large Load Diff

356
docs/api/templates.md Normal file
View File

@ -0,0 +1,356 @@
# Templates
> This page is incomplete, stay tuned.
## Create template by organization
### Code samples
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/organizations/{organization-id}/templates/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /organizations/{organization-id}/templates/`
> Body parameter
```json
{
"allow_user_cancel_workspace_jobs": true,
"default_ttl_ms": 0,
"description": "string",
"display_name": "string",
"icon": "string",
"name": "string",
"parameter_values": [
{
"copy_from_parameter": "string",
"destination_scheme": "environment_variable",
"name": "string",
"source_scheme": "data",
"source_value": "string"
}
],
"template_version_id": "string"
}
```
### Parameters
| Name | In | Type | Required | Description |
| --------------- | ---- | -------------------------------------------------------------------------- | -------- | --------------- |
| organization-id | path | string | true | Organization ID |
| body | body | [codersdk.CreateTemplateRequest](schemas.md#codersdkcreatetemplaterequest) | true | Request body |
### Example responses
> 200 Response
```json
{
"active_user_count": 0,
"active_version_id": "string",
"allow_user_cancel_workspace_jobs": true,
"build_time_stats": {
"property1": {
"p50": 123,
"p95": 146
},
"property2": {
"p50": 123,
"p95": 146
}
},
"created_at": "2019-08-24T14:15:22Z",
"created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f",
"created_by_name": "string",
"default_ttl_ms": 0,
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "string",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_owner_count": 0
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Template](schemas.md#codersdktemplate) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Get templates by organization
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templates \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /organizations/{organization}/templates`
### Parameters
| Name | In | Type | Required | Description |
| ------------ | ---- | ------------ | -------- | --------------- |
| organization | path | string(uuid) | true | Organization ID |
### Example responses
> 200 Response
```json
[
{
"active_user_count": 0,
"active_version_id": "string",
"allow_user_cancel_workspace_jobs": true,
"build_time_stats": {
"property1": {
"p50": 123,
"p95": 146
},
"property2": {
"p50": 123,
"p95": 146
}
},
"created_at": "2019-08-24T14:15:22Z",
"created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f",
"created_by_name": "string",
"default_ttl_ms": 0,
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "string",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_owner_count": 0
}
]
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | --------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.Template](schemas.md#codersdktemplate) |
<h3 id="get-templates-by-organization-responseschema">Response Schema</h3>
Status Code **200**
| Name | Type | Required | Restrictions | Description |
| ---------------------------------- | --------------------------------- | -------- | ------------ | ------------------------------------------ |
| _anonymous_ | array | false | none | none |
| » active_user_count | integer | false | none | ActiveUserCount is set to -1 when loading. |
| » active_version_id | string | false | none | none |
| » allow_user_cancel_workspace_jobs | boolean | false | none | none |
| » build_time_stats | `codersdk.TemplateBuildTimeStats` | false | none | none |
| »» **additionalProperties** | `codersdk.TransitionStats` | false | none | none |
| »»» p50 | integer | false | none | none |
| »»» p95 | integer | false | none | none |
| » created_at | string | false | none | none |
| » created_by_id | string | false | none | none |
| » created_by_name | string | false | none | none |
| » default_ttl_ms | integer | false | none | none |
| » description | string | false | none | none |
| » display_name | string | false | none | none |
| » icon | string | false | none | none |
| » id | string | false | none | none |
| » name | string | false | none | none |
| » organization_id | string | false | none | none |
| » provisioner | string | false | none | none |
| » updated_at | string | false | none | none |
| » workspace_owner_count | integer | false | none | none |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Get templates by organization and template name
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templates/{template-name} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /organizations/{organization}/templates/{template-name}`
### Parameters
| Name | In | Type | Required | Description |
| ------------- | ---- | ------------ | -------- | --------------- |
| organization | path | string(uuid) | true | Organization ID |
| template-name | path | string | true | Template name |
### Example responses
> 200 Response
```json
{
"active_user_count": 0,
"active_version_id": "string",
"allow_user_cancel_workspace_jobs": true,
"build_time_stats": {
"property1": {
"p50": 123,
"p95": 146
},
"property2": {
"p50": 123,
"p95": 146
}
},
"created_at": "2019-08-24T14:15:22Z",
"created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f",
"created_by_name": "string",
"default_ttl_ms": 0,
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "string",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_owner_count": 0
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Template](schemas.md#codersdktemplate) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Update template metadata by ID
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templates/{id} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templates/{id}`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | ------------ | -------- | ----------- |
| id | path | string(uuid) | true | Template ID |
### Example responses
> 200 Response
```json
{
"active_user_count": 0,
"active_version_id": "string",
"allow_user_cancel_workspace_jobs": true,
"build_time_stats": {
"property1": {
"p50": 123,
"p95": 146
},
"property2": {
"p50": 123,
"p95": 146
}
},
"created_at": "2019-08-24T14:15:22Z",
"created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f",
"created_by_name": "string",
"default_ttl_ms": 0,
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "string",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_owner_count": 0
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Template](schemas.md#codersdktemplate) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Delete template by ID
### Code samples
```shell
# Example request using curl
curl -X DELETE http://coder-server:8080/api/v2/templates/{id} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`DELETE /templates/{id}`
### Parameters
| Name | In | Type | Required | Description |
| ---- | ---- | ------------ | -------- | ----------- |
| id | path | string(uuid) | true | Template ID |
### Example responses
> 200 Response
```json
{
"detail": "string",
"message": "string",
"validations": [
{
"detail": "string",
"field": "string"
}
]
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.

880
docs/api/workspaces.md Normal file
View File

@ -0,0 +1,880 @@
# Workspaces
> This page is incomplete, stay tuned.
## Create workspace by organization
### Code samples
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/members/{user}/workspaces \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /organizations/{organization}/members/{user}/workspaces`
### Parameters
| Name | In | Type | Required | Description |
| ------------ | ---- | ------------ | -------- | --------------- |
| organization | path | string(uuid) | true | Organization ID |
| user | path | string | true | Username |
### Example responses
> 200 Response
```json
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
"build_number": 0,
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"deadline": {
"time": "string",
"valid": true
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
"initiator_name": "string",
"job": {
"canceled_at": "string",
"completed_at": "string",
"created_at": "string",
"error": "string",
"file_id": "string",
"id": "string",
"started_at": "string",
"status": "string",
"tags": {
"property1": "string",
"property2": "string"
},
"worker_id": "string"
},
"reason": "string",
"resources": [
{
"agents": [
{
"apps": [
{
"command": "string",
"display_name": "string",
"external": true,
"health": "string",
"healthcheck": {
"interval": 0,
"threshold": 0,
"url": "string"
},
"icon": "string",
"id": "string",
"sharing_level": "string",
"slug": "string",
"subdomain": true,
"url": "string"
}
],
"architecture": "string",
"connection_timeout_seconds": 0,
"created_at": "string",
"directory": "string",
"disconnected_at": "string",
"environment_variables": {
"property1": "string",
"property2": "string"
},
"first_connected_at": "string",
"id": "string",
"instance_id": "string",
"last_connected_at": "string",
"latency": {
"property1": {
"latency_ms": 0,
"preferred": true
},
"property2": {
"latency_ms": 0,
"preferred": true
}
},
"name": "string",
"operating_system": "string",
"resource_id": "string",
"startup_script": "string",
"status": "string",
"troubleshooting_url": "string",
"updated_at": "string",
"version": "string"
}
],
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"hide": true,
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f",
"metadata": [
{
"key": "string",
"sensitive": true,
"value": "string"
}
],
"name": "string",
"type": "string",
"workspace_transition": "start"
}
],
"status": "pending",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"transition": "start",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
"workspace_name": "string",
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
"workspace_owner_name": "string"
},
"name": "string",
"outdated": true,
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"template_allow_user_cancel_workspace_jobs": true,
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"ttl_ms": 0,
"updated_at": "2019-08-24T14:15:22Z"
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Workspace](schemas.md#codersdkworkspace) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Get workspace metadata by owner and workspace name
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacename} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /users/{user}/workspace/{workspacename}`
### Parameters
| Name | In | Type | Required | Description |
| --------------- | ----- | ------- | -------- | ----------------------------------------------------------- |
| user | path | string | true | Owner username |
| workspacename | path | string | true | Workspace name |
| include_deleted | query | boolean | false | Return data instead of HTTP 404 if the workspace is deleted |
### Example responses
> 200 Response
```json
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
"build_number": 0,
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"deadline": {
"time": "string",
"valid": true
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
"initiator_name": "string",
"job": {
"canceled_at": "string",
"completed_at": "string",
"created_at": "string",
"error": "string",
"file_id": "string",
"id": "string",
"started_at": "string",
"status": "string",
"tags": {
"property1": "string",
"property2": "string"
},
"worker_id": "string"
},
"reason": "string",
"resources": [
{
"agents": [
{
"apps": [
{
"command": "string",
"display_name": "string",
"external": true,
"health": "string",
"healthcheck": {
"interval": 0,
"threshold": 0,
"url": "string"
},
"icon": "string",
"id": "string",
"sharing_level": "string",
"slug": "string",
"subdomain": true,
"url": "string"
}
],
"architecture": "string",
"connection_timeout_seconds": 0,
"created_at": "string",
"directory": "string",
"disconnected_at": "string",
"environment_variables": {
"property1": "string",
"property2": "string"
},
"first_connected_at": "string",
"id": "string",
"instance_id": "string",
"last_connected_at": "string",
"latency": {
"property1": {
"latency_ms": 0,
"preferred": true
},
"property2": {
"latency_ms": 0,
"preferred": true
}
},
"name": "string",
"operating_system": "string",
"resource_id": "string",
"startup_script": "string",
"status": "string",
"troubleshooting_url": "string",
"updated_at": "string",
"version": "string"
}
],
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"hide": true,
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f",
"metadata": [
{
"key": "string",
"sensitive": true,
"value": "string"
}
],
"name": "string",
"type": "string",
"workspace_transition": "start"
}
],
"status": "pending",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"transition": "start",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
"workspace_name": "string",
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
"workspace_owner_name": "string"
},
"name": "string",
"outdated": true,
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"template_allow_user_cancel_workspace_jobs": true,
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"ttl_ms": 0,
"updated_at": "2019-08-24T14:15:22Z"
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Workspace](schemas.md#codersdkworkspace) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## List workspaces
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaces \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaces`
### Parameters
| Name | In | Type | Required | Description |
| --------- | ----- | ------ | -------- | ------------------------------------------- |
| owner | query | string | false | Filter by owner username |
| template | query | string | false | Filter by template name |
| name | query | string | false | Filter with partial-match by workspace name |
| status | query | string | false | Filter by workspace status |
| has_agent | query | string | false | Filter by agent status |
#### Enumerated Values
| Parameter | Value |
| --------- | ------------ |
| status | pending |
| status | running |
| status | stopping |
| status | stopped |
| status | failed |
| status | canceling |
| status | canceled |
| status | deleted |
| status | deleting |
| has_agent | connected |
| has_agent | connecting |
| has_agent | disconnected |
| has_agent | timeout |
### Example responses
> 200 Response
```json
{
"count": 0,
"workspaces": [
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
"build_number": 0,
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"deadline": {
"time": "string",
"valid": true
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
"initiator_name": "string",
"job": {
"canceled_at": "string",
"completed_at": "string",
"created_at": "string",
"error": "string",
"file_id": "string",
"id": "string",
"started_at": "string",
"status": "string",
"tags": {
"property1": "string",
"property2": "string"
},
"worker_id": "string"
},
"reason": "string",
"resources": [
{
"agents": [
{
"apps": [
{
"command": "string",
"display_name": "string",
"external": true,
"health": "string",
"healthcheck": {},
"icon": "string",
"id": "string",
"sharing_level": "string",
"slug": "string",
"subdomain": true,
"url": "string"
}
],
"architecture": "string",
"connection_timeout_seconds": 0,
"created_at": "string",
"directory": "string",
"disconnected_at": "string",
"environment_variables": {
"property1": "string",
"property2": "string"
},
"first_connected_at": "string",
"id": "string",
"instance_id": "string",
"last_connected_at": "string",
"latency": {
"property1": {
"latency_ms": 0,
"preferred": true
},
"property2": {
"latency_ms": 0,
"preferred": true
}
},
"name": "string",
"operating_system": "string",
"resource_id": "string",
"startup_script": "string",
"status": "string",
"troubleshooting_url": "string",
"updated_at": "string",
"version": "string"
}
],
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"hide": true,
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f",
"metadata": [
{
"key": "string",
"sensitive": true,
"value": "string"
}
],
"name": "string",
"type": "string",
"workspace_transition": "start"
}
],
"status": "pending",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"transition": "start",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
"workspace_name": "string",
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
"workspace_owner_name": "string"
},
"name": "string",
"outdated": true,
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"template_allow_user_cancel_workspace_jobs": true,
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"ttl_ms": 0,
"updated_at": "2019-08-24T14:15:22Z"
}
]
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspacesResponse](schemas.md#codersdkworkspacesresponse) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Get workspace metadata by ID
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaces/{id} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaces/{id}`
### Parameters
| Name | In | Type | Required | Description |
| --------------- | ----- | ------------ | -------- | ----------------------------------------------------------- |
| id | path | string(uuid) | true | Workspace ID |
| include_deleted | query | boolean | false | Return data instead of HTTP 404 if the workspace is deleted |
### Example responses
> 200 Response
```json
{
"autostart_schedule": "string",
"created_at": "2019-08-24T14:15:22Z",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_used_at": "2019-08-24T14:15:22Z",
"latest_build": {
"build_number": 0,
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"deadline": {
"time": "string",
"valid": true
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
"initiator_name": "string",
"job": {
"canceled_at": "string",
"completed_at": "string",
"created_at": "string",
"error": "string",
"file_id": "string",
"id": "string",
"started_at": "string",
"status": "string",
"tags": {
"property1": "string",
"property2": "string"
},
"worker_id": "string"
},
"reason": "string",
"resources": [
{
"agents": [
{
"apps": [
{
"command": "string",
"display_name": "string",
"external": true,
"health": "string",
"healthcheck": {
"interval": 0,
"threshold": 0,
"url": "string"
},
"icon": "string",
"id": "string",
"sharing_level": "string",
"slug": "string",
"subdomain": true,
"url": "string"
}
],
"architecture": "string",
"connection_timeout_seconds": 0,
"created_at": "string",
"directory": "string",
"disconnected_at": "string",
"environment_variables": {
"property1": "string",
"property2": "string"
},
"first_connected_at": "string",
"id": "string",
"instance_id": "string",
"last_connected_at": "string",
"latency": {
"property1": {
"latency_ms": 0,
"preferred": true
},
"property2": {
"latency_ms": 0,
"preferred": true
}
},
"name": "string",
"operating_system": "string",
"resource_id": "string",
"startup_script": "string",
"status": "string",
"troubleshooting_url": "string",
"updated_at": "string",
"version": "string"
}
],
"created_at": "2019-08-24T14:15:22Z",
"daily_cost": 0,
"hide": true,
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f",
"metadata": [
{
"key": "string",
"sensitive": true,
"value": "string"
}
],
"name": "string",
"type": "string",
"workspace_transition": "start"
}
],
"status": "pending",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"transition": "start",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
"workspace_name": "string",
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
"workspace_owner_name": "string"
},
"name": "string",
"outdated": true,
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"template_allow_user_cancel_workspace_jobs": true,
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"ttl_ms": 0,
"updated_at": "2019-08-24T14:15:22Z"
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Workspace](schemas.md#codersdkworkspace) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Update workspace metadata by ID
### Code samples
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/workspaces/{workspace} \
-H 'Content-Type: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /workspaces/{workspace}`
> Body parameter
```json
{
"name": "string"
}
```
### Parameters
| Name | In | Type | Required | Description |
| --------- | ---- | ---------------------------------------------------------------------------- | -------- | ----------------------- |
| workspace | path | string(uuid) | true | Workspace ID |
| body | body | [codersdk.UpdateWorkspaceRequest](schemas.md#codersdkupdateworkspacerequest) | true | Metadata update request |
### Responses
| Status | Meaning | Description | Schema |
| ------ | --------------------------------------------------------------- | ----------- | --------- |
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | no schema |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Update workspace autostart schedule by ID
### Code samples
```shell
# Example request using curl
curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/autostart \
-H 'Content-Type: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PUT /workspaces/{workspace}/autostart`
> Body parameter
```json
{
"schedule": "string"
}
```
### Parameters
| Name | In | Type | Required | Description |
| --------- | ---- | ---------------------------------------------------------------------------------------------- | -------- | ----------------------- |
| workspace | path | string(uuid) | true | Workspace ID |
| body | body | [codersdk.UpdateWorkspaceAutostartRequest](schemas.md#codersdkupdateworkspaceautostartrequest) | true | Schedule update request |
### Responses
| Status | Meaning | Description | Schema |
| ------ | --------------------------------------------------------------- | ----------- | --------- |
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | no schema |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Extend workspace deadline by ID
### Code samples
```shell
# Example request using curl
curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/extend \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PUT /workspaces/{workspace}/extend`
> Body parameter
```json
{
"deadline": "string"
}
```
### Parameters
| Name | In | Type | Required | Description |
| --------- | ---- | ---------------------------------------------------------------------------------- | -------- | ------------------------------ |
| workspace | path | string(uuid) | true | Workspace ID |
| body | body | [codersdk.PutExtendWorkspaceRequest](schemas.md#codersdkputextendworkspacerequest) | true | Extend deadline update request |
### Example responses
> 200 Response
```json
{
"detail": "string",
"message": "string",
"validations": [
{
"detail": "string",
"field": "string"
}
]
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Update workspace TTL by ID
### Code samples
```shell
# Example request using curl
curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/ttl \
-H 'Content-Type: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PUT /workspaces/{workspace}/ttl`
> Body parameter
```json
{
"ttl_ms": 0
}
```
### Parameters
| Name | In | Type | Required | Description |
| --------- | ---- | ---------------------------------------------------------------------------------- | -------- | ---------------------------- |
| workspace | path | string(uuid) | true | Workspace ID |
| body | body | [codersdk.UpdateWorkspaceTTLRequest](schemas.md#codersdkupdateworkspacettlrequest) | true | Workspace TTL update request |
### Responses
| Status | Meaning | Description | Schema |
| ------ | --------------------------------------------------------------- | ----------- | --------- |
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | no schema |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.
## Watch workspace by ID
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/watch \
-H 'Accept: text/event-stream' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /workspaces/{workspace}/watch`
### Parameters
| Name | In | Type | Required | Description |
| --------- | ---- | ------------ | -------- | ------------ |
| workspace | path | string(uuid) | true | Workspace ID |
### Example responses
> 200 Response
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
To perform this operation, you must be authenticated by means of one of the following methods: **CoderSessionToken**.

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20"><path d="m10 12-2-2 2-2 2 2ZM8.208 6.792l-2-2L10 1l3.792 3.792-2 2L10 5Zm-3.416 7L1 10l3.792-3.792 2 2L5 10l1.792 1.792Zm10.416 0-2-2L15 10l-1.792-1.792 2-2L19 10ZM10 19l-3.792-3.792 2-2L10 15l1.792-1.792 2 2Z"/></svg>

After

Width:  |  Height:  |  Size: 281 B

View File

@ -10,16 +10,16 @@
{
"title": "Architecture",
"description": "Learn how Coder works",
"icon_path": "./images/icons/protractor.svg",
"path": "./about/architecture.md"
"path": "./about/architecture.md",
"icon_path": "./images/icons/protractor.svg"
}
]
},
{
"title": "Installation",
"description": "How to install and deploy Coder",
"icon_path": "./images/icons/download.svg",
"path": "./install/index.md",
"icon_path": "./images/icons/download.svg",
"children": [
{
"title": "Install script",
@ -66,32 +66,32 @@
{
"title": "Quickstart",
"description": "Create your first template and workspace",
"icon_path": "./images/icons/star.svg",
"path": "./quickstart.md",
"icon_path": "./images/icons/star.svg",
"children": [
{
"title": "Docker",
"description": "Setup Coder with Docker",
"icon_path": "./images/icons/docker.svg",
"path": "./quickstart/docker.md"
"path": "./quickstart/docker.md",
"icon_path": "./images/icons/docker.svg"
},
{
"title": "Google Cloud Platform",
"description": "Setup Coder on a GCP Compute Engine VM",
"icon_path": "./images/google-cloud.svg",
"path": "./quickstart/google-cloud-platform.md"
"path": "./quickstart/google-cloud-platform.md",
"icon_path": "./images/google-cloud.svg"
},
{
"title": "AWS",
"description": "Setup Coder on an AWS EC2 VM",
"icon_path": "./images/aws.svg",
"path": "./quickstart/aws.md"
"path": "./quickstart/aws.md",
"icon_path": "./images/aws.svg"
},
{
"title": "Azure",
"description": "Setup Coder on an Azure VM",
"icon_path": "./images/azure.svg",
"path": "./quickstart/azure.md"
"path": "./quickstart/azure.md",
"icon_path": "./images/azure.svg"
}
]
},
@ -105,8 +105,7 @@
"title": "Resource Persistence",
"description": "Learn how resource persistence works in Coder",
"path": "./templates/resource-persistence.md",
"icon_path": "./images/icons/infinity.svg",
"last_updated": "2022-10-23"
"icon_path": "./images/icons/infinity.svg"
},
{
"title": "Provider Authentication",
@ -137,8 +136,8 @@
{
"title": "Workspaces",
"description": "Learn about Coder workspaces.",
"icon_path": "./images/icons/layers.svg",
"path": "./workspaces.md"
"path": "./workspaces.md",
"icon_path": "./images/icons/layers.svg"
},
{
"title": "IDEs",
@ -184,45 +183,45 @@
{
"title": "Dotfiles",
"description": "Learn how to personalize your workspace",
"icon_path": "./images/icons/art-pad.svg",
"path": "./dotfiles.md"
"path": "./dotfiles.md",
"icon_path": "./images/icons/art-pad.svg"
},
{
"title": "Secrets",
"description": "Learn how to use secrets in your worskpace",
"icon_path": "./images/icons/secrets.svg",
"path": "./secrets.md"
"description": "Learn how to use secrets in your workspace",
"path": "./secrets.md",
"icon_path": "./images/icons/secrets.svg"
},
{
"title": "Administration",
"description": "How to install and deploy Coder",
"icon_path": "./images/icons/wrench.svg",
"path": "./admin/index.md",
"icon_path": "./images/icons/wrench.svg",
"children": [
{
"title": "Authentication",
"description": "Learn how to set up authentication using GitHub or OpenID Connect",
"icon_path": "./images/icons/key.svg",
"path": "./admin/auth.md"
"path": "./admin/auth.md",
"icon_path": "./images/icons/key.svg"
},
{
"title": "Users",
"description": "Learn about user roles available in Coder and how to create and manage users",
"icon_path": "./images/icons/users.svg",
"path": "./admin/users.md"
"path": "./admin/users.md",
"icon_path": "./images/icons/users.svg"
},
{
"title": "Groups",
"description": "Learn how to manage user groups",
"icon_path": "./images/icons/group.svg",
"path": "./admin/groups.md",
"icon_path": "./images/icons/group.svg",
"state": "enterprise"
},
{
"title": "RBAC",
"description": "Learn how to use the role based access control",
"icon_path": "./images/icons/rbac.svg",
"path": "./admin/rbac.md",
"icon_path": "./images/icons/rbac.svg",
"state": "enterprise"
},
{
@ -234,74 +233,74 @@
{
"title": "Git Providers",
"description": "Learn how connect Coder with external git providers",
"icon_path": "./images/icons/git.svg",
"path": "./admin/git-providers.md"
"path": "./admin/git-providers.md",
"icon_path": "./images/icons/git.svg"
},
{
"title": "Upgrading",
"description": "Learn how to upgrade Coder",
"icon_path": "./images/icons/upgrade.svg",
"path": "./admin/upgrade.md"
"path": "./admin/upgrade.md",
"icon_path": "./images/icons/upgrade.svg"
},
{
"title": "Automation",
"description": "Learn how to automate Coder with the CLI and API",
"icon_path": "./images/icons/plug.svg",
"path": "./admin/automation.md"
"path": "./admin/automation.md",
"icon_path": "./images/icons/plug.svg"
},
{
"title": "Audit Logs",
"description": "Learn how to use Audit Logs in your Coder deployment",
"icon_path": "./images/icons/radar.svg",
"path": "./admin/audit-logs.md",
"icon_path": "./images/icons/radar.svg",
"state": "enterprise"
},
{
"title": "Quotas",
"description": "Learn how to use Workspace Quotas in Coder",
"icon_path": "./images/icons/dollar.svg",
"path": "./admin/quotas.md",
"icon_path": "./images/icons/dollar.svg",
"state": "enterprise"
},
{
"title": "High Availability",
"description": "Learn how to configure Coder for High Availability",
"icon_path": "./images/icons/hydra.svg",
"path": "./admin/high-availability.md",
"icon_path": "./images/icons/hydra.svg",
"state": "enterprise"
},
{
"title": "Prometheus",
"description": "Learn how to collect Prometheus metrics",
"icon_path": "./images/icons/speed.svg",
"path": "./admin/prometheus.md"
"path": "./admin/prometheus.md",
"icon_path": "./images/icons/speed.svg"
},
{
"title": "Service Banners",
"description": "Learn how to configure Service Banners",
"icon_path": "./images/icons/info.svg",
"path": "./admin/service-banners.md",
"icon_path": "./images/icons/info.svg",
"state": "enterprise"
},
{
"title": "Telemetry",
"description": "Learn what usage telemetry Coder collects",
"icon_path": "./images/icons/science.svg",
"path": "./admin/telemetry.md"
"path": "./admin/telemetry.md",
"icon_path": "./images/icons/science.svg"
}
]
},
{
"title": "Enterprise",
"description": "Learn how to enable Enterprise features",
"icon_path": "./images/icons/group.svg",
"path": "./enterprise.md"
"path": "./enterprise.md",
"icon_path": "./images/icons/group.svg"
},
{
"title": "Contributing",
"description": "Learn how to contribute to Coder",
"icon_path": "./images/icons/contributing.svg",
"path": "./CONTRIBUTING.md",
"icon_path": "./images/icons/contributing.svg",
"children": [
{
"title": "Code of Conduct",
@ -319,6 +318,30 @@
"path": "./contributing/SECURITY.md"
}
]
},
{
"title": "API",
"description": "Learn how to use Coderd API",
"path": "./api/index.md",
"icon_path": "./images/icons/api.svg",
"children": [
{
"title": "Authentication",
"path": "./api/authentication.md"
},
{
"title": "Templates",
"path": "./api/templates.md"
},
{
"title": "Workspaces",
"path": "./api/workspaces.md"
},
{
"title": "Schemas",
"path": "./api/schemas.md"
}
]
}
]
}

10
go.mod
View File

@ -125,6 +125,8 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.14.0
github.com/stretchr/testify v1.8.1
github.com/swaggo/http-swagger v1.3.3
github.com/swaggo/swag v1.8.6
github.com/tabbed/pqtype v0.1.1
github.com/u-root/u-root v0.10.0
github.com/unrolled/secure v1.13.0
@ -169,6 +171,7 @@ require (
cloud.google.com/go/compute v1.12.1 // indirect
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
@ -203,6 +206,10 @@ require (
github.com/gin-gonic/gin v1.7.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.6 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect
@ -226,6 +233,7 @@ require (
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e // indirect
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
@ -234,6 +242,7 @@ require (
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
@ -268,6 +277,7 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d // indirect
github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17 // indirect

17
go.sum
View File

@ -114,6 +114,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
@ -674,17 +676,24 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
@ -1121,6 +1130,7 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@ -1246,6 +1256,7 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
@ -1722,6 +1733,12 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw=
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc=
github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo=
github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg=
github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=

44
scripts/apidocgen/generate.sh Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# This script generates swagger description file and required Go docs files
# from the coderd API.
set -euo pipefail
# shellcheck source=scripts/lib.sh
source "$(dirname "$(dirname "${BASH_SOURCE[0]}")")/lib.sh"
APIDOCGEN_DIR=$(dirname "${BASH_SOURCE[0]}")
API_MD_TMP_FILE=$(mktemp /tmp/coder-apidocgen.XXXXXX)
cleanup() {
rm -f "${API_MD_TMP_FILE}"
}
trap cleanup EXIT
log "Use temporary file: ${API_MD_TMP_FILE}"
pushd "${PROJECT_ROOT}"
go run github.com/swaggo/swag/cmd/swag@v1.8.6 init \
--generalInfo="coderd.go" \
--dir="./coderd,./codersdk" \
--output="./coderd/apidoc" \
--outputTypes="go,json" \
--parseDependency=true
popd
pushd "${APIDOCGEN_DIR}"
npm ci
# Make sure that widdershins is installed correctly.
npm exec -- widdershins --version
# Render the Markdown file.
npm exec -- widdershins \
--user_templates "./markdown-template" \
--search false \
--omitHeader true \
--language_tabs "shell:curl" \
--summary "../../coderd/apidoc/swagger.json" \
--outfile "${API_MD_TMP_FILE}"
# Perform the postprocessing
go run postprocess/main.go -in-md-file-single "${API_MD_TMP_FILE}"
popd

View File

@ -0,0 +1,64 @@
## Swagger / OpenAPI 2 and OpenAPI 3 template parameters
Note that properties of OpenAPI objects will be in OpenAPI 3.0 form, as
Swagger / OpenAPI 2.0 definitions are converted automatically.
### Code templates
* `method` - the HTTP method of the operation (in lower-case)
* `methodUpper` - the HTTP method of the operation (in upper-case)
* `url` - the full URL of the operation (including protocol and host)
* `consumes[]` - an array of MIME-types the operation consumes
* `produces[]` - an array of MIME-types the operation produces
* `operation` - the current operation object
* `operationId` - the current operation id
* `opName` - the operationId if set, otherwise the method + path
* `tags[]` - the full list of tags applying to the operation
* `security` - the security definitions applying to the operation
* `resource` - the current tag/path object
* `parameters[]` - an array of parameters for the operation (see below)
* `queryString` - an example queryString, urlEncoded
* `requiredQueryString` - an example queryString for `required:true` parameters
* `queryParameters[]` - a subset of `parameters` that are `in:query`
* `requiredParameters[]` - a subset of `queryParameters` that are `required:true`
* `headerParameters[]` - a subset of `parameters` that are `in:header`
* `allHeaders[]` - a concatenation of `headerParameters` and pseudo-parameters `Accept` and `Content-Type`, and optionally `Authorization` (the latter has an `isAuth` boolean property set true so it can be omitted in templates if desired
### Parameter template
* `parameters[]` - an array of [parameters](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameterObject), including the following pseudo-properties
* `shortDesc` - a truncated version of the parameter description
* `safeType` - a computed version of the parameter type, including Body and schema names
* `originalType` - the original type of the parameter
* `exampleValues` - an object containing examples for use in code-templates
* `json` - example values in JSON compatible syntax
* `object` - example values in raw object form (unquoted strings etc)
* `depth` - a zero-based indicator of the depth of expanded request body parameters
* `enums[]` - an array of (parameter)name/value pairs
### Responses template
* `responses[]` - an array of [responses](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#responseObject), including `status` and `meaning` properties
### Authentication template
* `authenticationStr` - a simple string of methods (and scopes where appropriate)
* `securityDefinitions[]` - an array of applicable [securityDefinitions](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject)
### Schema Property template
* `schemaProperties[]` - an array of
* `name`
* `type`
* `required`
* `description`
* `enums[]` - an array of (schema property)name/value pairs
### Common to all templates
* `openapi` - the top-level OpenAPI / Swagger document
* `header` - the front-matter of the Slate/Shins markdown document
* `host` - the (computed) host of the API
* `protocol` - the default/first protocol of the API
* `baseUrl` - the (computed) baseUrl of the API (including protocol and host)
* `widdershins` - the contents of widdershins `package.json`

View File

@ -0,0 +1 @@
To perform this operation, you must be authenticated by means of one of the following methods: **{{= data.utils.getAuthenticationStr(data) }}**.

View File

@ -0,0 +1,4 @@
# Example request using curl
curl -X {{=data.methodUpper}} http://coder-server:8080{{=data.url}}{{=data.requiredQueryString}}{{?data.allHeaders.length}} \{{?}}
{{~data.allHeaders :p:index}} -H '{{=p.name}}: {{=p.exampleValues.object}}'{{?index < data.allHeaders.length-1}} \{{?}}
{{~}}

View File

@ -0,0 +1 @@
{{= data.utils.inspect(data) }}

View File

@ -0,0 +1,157 @@
{{
function renderSinglePropertyType(p) {
if (!p.$ref) {
return p.type;
}
const pRef = p.$ref.replace("#/components/schemas/","");
if (pRef == "codersdk.NullTime") {
return "string(time) or `null`";
}
return "[" + pRef + "](#" + pRef.replace(".","").toLowerCase() + ")";
}
function renderPropertyType(p) {
if (p.type == "array") {
return "array of " + renderSinglePropertyType(p.schema.items);
}
return renderSinglePropertyType(p);
}
function renderDisplayName(p) {
if (p.displayName == "» **additionalProperties**") {
return "» `[any property]`";
}
if (p.displayName == "**additionalProperties**") {
return "`[any property]`";
}
return "`" + p.displayName + "`";
}
function renderDescription(p) {
if (!p.description) {
return "none";
}
const toSnakeCase = str =>
str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map(x => x.toLowerCase())
.join('_');
const words = p.description.split(' ');
if (words.length == 0) {
return "none";
}
const countUppercase = words[0].length - words[0].replace(/[A-Z]/g, '').length;
if (countUppercase > 1) {
const displayName = p.displayName.charAt(0).toUpperCase() + p.displayName.replaceAll("_", " ").toLowerCase().slice(1);
return displayName + " " + words.slice(1).join(' ');
}
return p.description;
}
}}
{{? data.api.components && data.api.components.securitySchemes }}{{#def.security}}{{?}}
{{ for (var r in data.resources) { }}
{{ data.resource = data.resources[r]; }}
<!-- APIDOCGEN: BEGIN SECTION -->
{{= data.tags.section }}# {{= r}}
> This page is incomplete, stay tuned.
{{? data.resource.description }}{{= data.resource.description}}{{?}}
{{ for (var m in data.resource.methods) { }}
{{ data.operationUniqueName = m; }}
{{ data.method = data.resource.methods[m]; }}
{{ data.operationUniqueSlug = data.method.slug; }}
{{ data.operation = data.method.operation; }}
{{= data.templates.operation(data) }}
{{ } /* of methods */ }}
{{= data.tags.endSection }}
{{ } /* of resources */ }}
{{? data.api.components && data.api.components.schemas }}
{{= data.tags.section }}
<!-- APIDOCGEN: BEGIN SECTION -->
# Schemas
> This page is incomplete, stay tuned.
{{ for (var s in data.components.schemas) {
if (s == "codersdk.NullTime") {
continue;
}
}}
{{ var origSchema = data.components.schemas[s]; }}
{{ var schema = data.api.components.schemas[s]; }}
{{= data.tags.section }}
## {{=s}}
{{? data.options.yaml }}
```yaml
{{=data.utils.yaml.stringify(data.utils.getSample(schema,data.options,{quiet:true},data.api))}}
{{??}}
```json
{{=data.utils.safejson(data.utils.getSample(schema,data.options,{quiet:true},data.api),null,2)}}
{{?}}```
{{ var enums = []; }}
{{ var blocks = data.utils.schemaToArray(origSchema,-1,{trim:true,join:true},data); }}
{{ for (var block of blocks) {
for (var p of block.rows) {
if (p.schema && p.schema.enum) {
for (var e of p.schema.enum) {
enums.push({name:p.name,value:e});
}
}
}
}
}}
{{~ blocks :block}}
{{? block.title }}{{= block.title}}{{= '\n\n'}}{{?}}
{{? block.externalDocs}}
<a href="{{=block.externalDocs.url}}">{{=block.externalDocs.description||'External documentation'}}</a>
{{?}}
{{? block===blocks[0] }}
{{= data.tags.section }}
### Properties
{{?}}
{{? block.rows.length}}|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|{{?}}
{{~ block.rows :p}}|{{= renderDisplayName(p)}}|{{= renderPropertyType(p)}}|{{=p.required}}|{{=p.restrictions||'none'}}|{{= renderDescription(p)}}|
{{~}}
{{~}}
{{? (blocks[0].rows.length === 0) && (blocks.length === 1) }}
*None*
{{?}}
{{? enums.length > 0 }}
{{= data.tags.section }}
#### Enumerated Values
|Property|Value|
|---|---|
{{~ enums :e}}|{{=e.name}}|{{=data.utils.toPrimitive(e.value)}}|
{{~}}
{{= data.tags.endSection }}
{{?}}
{{= data.tags.endSection }}
{{= data.tags.endSection }}
{{ } /* of schemas */ }}
{{?}}

View File

@ -0,0 +1,48 @@
{{= data.tags.section }}
## {{= data.operationUniqueName}}
{{ data.methodUpper = data.method.verb.toUpperCase(); }}
{{ data.url = data.utils.slashes(data.baseUrl + data.method.path); }}
{{ data.parameters = data.operation.parameters; }}
{{ data.enums = []; }}
{{ data.utils.fakeProdCons(data); }}
{{ data.utils.fakeBodyParameter(data); }}
{{ data.utils.mergePathParameters(data); }}
{{ data.utils.getParameters(data); }}
{{? data.options.codeSamples || data.operation["x-code-samples"] }}
### Code samples
{{= data.utils.getCodeSamples(data)}}
{{?}}
`{{= data.methodUpper}} {{=data.method.path}}`
{{? data.operation.summary && !data.options.tocSummary}}*{{= data.operation.summary }}*{{?}}
{{? data.operation.description}}{{= data.operation.description }}{{?}}
{{? data.operation.requestBody}}
> Body parameter
{{? data.bodyParameter.exampleValues.description }}
> {{= data.bodyParameter.exampleValues.description }}
{{?}}
{{= data.utils.getBodyParameterExamples(data) }}
{{?}}
{{? data.parameters && data.parameters.length }}
{{#def.parameters}}
{{?}}
{{#def.responses}}
{{ data.security = data.operation.security ? data.operation.security : data.api.security; }}
{{? data.security && data.security.length }}
{{#def.authentication}}
{{??}}
{{#def.authentication_none}}
{{?}}
{{= data.tags.endSection }}

View File

@ -0,0 +1,50 @@
{{
function renderParameterType(p) {
if (p.schema['x-widdershins-oldRef']) {
const aType = p.schema['x-widdershins-oldRef'].replace("#/components/schemas/","");
const href = aType.replace(".","").toLowerCase();
return "[" + aType + "](schemas.md#" + href + ")";
}
return p.safeType;
}
}}
{{= data.tags.section }}
### Parameters
|Name|In|Type|Required|Description|
|---|---|---|---|---|
{{~ data.parameters :p}}|{{=p.name}}|{{=p.in}}|{{= renderParameterType(p)}}|{{=p.required}}|{{=p.shortDesc || 'none'}}|
{{~}}
{{? data.longDescs }}
#### Detailed descriptions
{{~ data.parameters :p}}{{? p.shortDesc !== p.description}}
**{{=p.name}}**: {{=p.description}}{{?}}
{{~}}
{{?}}
{{~ data.parameters :p}}
{{? p.schema && p.schema.enum }}
{{~ p.schema.enum :e}}
{{ var entry = {}; entry.name = p.name; entry.value = e; data.enums.push(entry); }}
{{~}}
{{?}}
{{? p.schema && p.schema.items && p.schema.items.enum }}
{{~ p.schema.items.enum :e}}
{{ var entry = {}; entry.name = p.name; entry.value = e; data.enums.push(entry); }}
{{~}}
{{?}}
{{~}}
{{? data.enums && data.enums.length }}
#### Enumerated Values
|Parameter|Value|
|---|---|
{{~ data.enums :e}}|{{=e.name}}|{{=data.utils.toPrimitive(e.value)}}|
{{~}}
{{?}}
{{= data.tags.endSection }}

View File

@ -0,0 +1,113 @@
{{
function renderSingleResponseType(r) {
var content;
for (var ct in r.content) {
content = r.content[ct];
break;
}
if (!content) {
return "no schema";
}
var ref = content.schema["x-widdershins-oldRef"];
if (!ref) {
ref = content.schema.items["x-widdershins-oldRef"];
}
const aType = ref.replace("#/components/schemas/","");
const href = aType.replace(".","").toLowerCase();
return "[" + aType + "](schemas.md#" + href + ")";
}
function renderResponseType(r) {
if (r.type == "array") {
return "array of " + renderSingleResponseType(r);
}
return renderSingleResponseType(r);
}
}}
{{ data.responses = data.utils.getResponses(data); }}
{{ data.responseSchemas = false; }}
{{~ data.responses :response }}
{{ if (response.content) data.responseSchemas = true; }}
{{~}}
{{? data.responseSchemas }}
### Example responses
{{= data.utils.getResponseExamples(data) }}
{{?}}
{{= data.tags.section }}
### Responses
|Status|Meaning|Description|Schema|
|---|---|---|---|
{{~ data.responses :r}}|{{=r.status}}|{{=r.meaning}}|{{=r.description || 'none'}}|{{= renderResponseType(r)}}|
{{~}}
{{ data.responseSchemas = false; }}
{{~ data.responses :response }}
{{ if (response.content && !response.$ref && !data.utils.isPrimitive(response.type)) data.responseSchemas = true; }}
{{~}}
{{? data.responseSchemas }}
<h3 id="{{=data.operationUniqueSlug}}-responseschema">Response Schema</h3>
{{~ data.responses :response}}
{{? response.content && !response.$ref && !data.utils.isPrimitive(response.type)}}
{{? Object.keys(response.content).length }}
{{ var responseKey = Object.keys(response.content)[0]; }}
{{ var responseSchema = response.content[responseKey].schema; }}
{{ var enums = []; }}
{{ var blocks = data.utils.schemaToArray(responseSchema,0,{trim:true,join:true},data); }}
{{ for (var block of blocks) {
for (var p of block.rows) {
if (p.schema && p.schema.enum) {
for (var e of p.schema.enum) {
enums.push({name:p.name,value:e});
}
}
}
}
}}
{{? blocks[0].rows.length || blocks[0].title }}
Status Code **{{=response.status}}**
{{~ blocks :block}}
{{? block.title }}*{{=block.title}}*
{{?}}
|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
{{~block.rows :p}}|{{=p.displayName}}|{{? p.$ref}}`{{=p.$ref}}`{{?}}{{? !p.$ref}}{{=p.type}}{{?}}|{{=p.required}}|{{=p.restrictions||'none'}}|{{=p.description||'none'}}|
{{~}}
{{~}}
{{?}}
{{? enums.length > 0 }}
#### Enumerated Values
|Property|Value|
|---|---|
{{~ enums :e}}|{{=e.name}}|{{=data.utils.toPrimitive(e.value)}}|
{{~}}
{{?}}
{{?}}
{{ data.response = response; }}
{{?}}
{{~}}
{{?}}
{{ data.responseHeaders = data.utils.getResponseHeaders(data); }}
{{? data.responseHeaders.length }}
### Response Headers
|Status|Header|Type|Format|Description|
|---|---|---|---|---|
{{~ data.responseHeaders :h}}|{{=h.status}}|{{=h.header}}|{{=h.type}}|{{=h.format||''}}|{{=h.description||'none'}}|
{{~}}
{{?}}
{{= data.tags.endSection }}

View File

@ -0,0 +1,27 @@
<!-- APIDOCGEN: BEGIN SECTION -->
{{= data.tags.section }}# Authentication
{{ for (var s in data.api.components.securitySchemes) { }}
{{ var sd = data.api.components.securitySchemes[s]; }}
{{? sd.type == 'apiKey' }}
- API Key ({{=s}})
- Parameter Name: **{{=sd.name}}**, in: {{=sd.in}}. {{=sd.description || ''}}
{{?}}
{{? sd.type == 'http'}}
- HTTP Authentication, scheme: {{=sd.scheme}}{{? sd.description }}<br/>{{=sd.description}}{{?}}
{{?}}
{{? sd.type == 'oauth2'}}
- oAuth2 authentication. {{=sd.description || ''}}
{{ for (var f in sd.flows) { }}
{{ var flow = sd.flows[f]; }}
- Flow: {{=f}}
{{? flow.authorizationUrl}} - Authorization URL = [{{=flow.authorizationUrl}}]({{=flow.authorizationUrl}}){{?}}
{{? flow.tokenUrl}} - Token URL = [{{=flow.tokenUrl}}]({{=flow.tokenUrl}}){{?}}
{{? flow.scopes && Object.keys(flow.scopes).length}}
|Scope|Scope Description|
|---|---|
{{ for (var sc in flow.scopes) { }}|{{=sc}}|{{=data.utils.join(flow.scopes[sc])}}|
{{ } /* of scopes */ }}
{{?}}
{{ } /* of flows */ }}
{{?}}
{{ } /* of securitySchemes */ }}

3369
scripts/apidocgen/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"widdershins": "^4.0.1"
}
}

View File

@ -0,0 +1,206 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"log"
"os"
"path"
"regexp"
"strings"
"golang.org/x/xerrors"
)
const (
apiSubdir = "api"
apiIndexFile = "index.md"
apiIndexContent = `Get started with Coder API:
<children>
This page is rendered on https://coder.com/docs/coder-oss/api. Refer to the other documents in the ` + "`" + `api/` + "`" + ` directory.
</children>
`
)
var (
docsDirectory string
inMdFileSingle string
sectionSeparator = []byte("<!-- APIDOCGEN: BEGIN SECTION -->\n")
nonAlphanumericRegex = regexp.MustCompile(`[^a-z0-9 ]+`)
)
func main() {
log.Println("Postprocess API docs")
flag.StringVar(&docsDirectory, "docs-directory", "../../docs", "Path to Coder docs directory")
flag.StringVar(&inMdFileSingle, "in-md-file-single", "", "Path to single Markdown file, output from widdershins.js")
flag.Parse()
if inMdFileSingle == "" {
flag.Usage()
log.Fatal("missing value for in-md-file-single")
}
sections, err := loadMarkdownSections()
if err != nil {
log.Fatal("can't load markdown sections: ", err)
}
err = prepareDocsDirectory()
if err != nil {
log.Fatal("can't prepare docs directory: ", err)
}
err = writeDocs(sections)
if err != nil {
log.Fatal("can't write docs directory: ", err)
}
log.Println("Done")
}
func loadMarkdownSections() ([][]byte, error) {
log.Printf("Read the md-file-single: %s", inMdFileSingle)
mdFile, err := os.ReadFile(inMdFileSingle)
if err != nil {
return nil, xerrors.Errorf("can't read the md-file-single: %w", err)
}
log.Printf("Read %dB", len(mdFile))
sections := bytes.Split(mdFile, sectionSeparator)
if len(sections) < 2 {
return nil, xerrors.Errorf("At least 1 section is expected: %w", err)
}
sections = sections[1:] // Skip the first element which is the empty byte array
log.Printf("Loaded %d sections", len(sections))
return sections, nil
}
func prepareDocsDirectory() error {
log.Println("Prepare docs directory")
apiPath := path.Join(docsDirectory, apiSubdir)
err := os.RemoveAll(apiPath)
if err != nil {
return xerrors.Errorf(`os.RemoveAll failed for "%s": %w`, apiPath, err)
}
err = os.MkdirAll(apiPath, 0755)
if err != nil {
return xerrors.Errorf(`os.MkdirAll failed for "%s": %w`, apiPath, err)
}
return nil
}
func writeDocs(sections [][]byte) error {
log.Println("Write docs to destination")
apiDir := path.Join(docsDirectory, apiSubdir)
err := os.WriteFile(path.Join(apiDir, apiIndexFile), []byte(apiIndexContent), 0644) // #nosec
if err != nil {
return xerrors.Errorf(`can't write the index file: %w`, err)
}
type mdFile struct {
title string
path string
}
var mdFiles []mdFile
// Write .md files for grouped API method (Templates, Workspaces, etc.)
for _, section := range sections {
sectionName, err := extractSectionName(section)
if err != nil {
return xerrors.Errorf("can't extract section name: %w", err)
}
log.Printf("Write section: %s", sectionName)
mdFilename := toMdFilename(sectionName)
docPath := path.Join(apiDir, mdFilename)
err = os.WriteFile(docPath, section, 0644) // #nosec
if err != nil {
return xerrors.Errorf(`can't write doc file "%s": %w`, docPath, err)
}
mdFiles = append(mdFiles, mdFile{
title: sectionName,
path: "./" + path.Join(apiSubdir, mdFilename),
})
}
// Update manifest.json
type route struct {
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Path string `json:"path,omitempty"`
IconPath string `json:"icon_path,omitempty"`
State string `json:"state,omitempty"`
Children []route `json:"children,omitempty"`
}
type manifest struct {
Versions []string `json:"versions,omitempty"`
Routes []route `json:"routes,omitempty"`
}
manifestPath := path.Join(docsDirectory, "manifest.json")
manifestFile, err := os.ReadFile(manifestPath)
if err != nil {
return xerrors.Errorf("can't read manifest file: %w", err)
}
log.Printf("Read manifest file: %dB", len(manifestFile))
var m manifest
err = json.Unmarshal(manifestFile, &m)
if err != nil {
return xerrors.Errorf("json.Unmarshal failed: %w", err)
}
for i, r := range m.Routes {
if r.Title != "API" {
continue
}
var children []route
for _, mdf := range mdFiles {
docRoute := route{
Title: mdf.title,
Path: mdf.path,
}
children = append(children, docRoute)
}
m.Routes[i].Children = children
break
}
manifestFile, err = json.MarshalIndent(m, "", " ")
if err != nil {
return xerrors.Errorf("json.Marshal failed: %w", err)
}
err = os.WriteFile(manifestPath, manifestFile, 0644) // #nosec
if err != nil {
return xerrors.Errorf("can't write manifest file: %w", err)
}
log.Printf("Write manifest file: %dB", len(manifestFile))
return nil
}
func extractSectionName(section []byte) (string, error) {
scanner := bufio.NewScanner(bytes.NewReader(section))
if !scanner.Scan() {
return "", xerrors.Errorf("section header was expected")
}
header := scanner.Text()[2:] // Skip #<space>
return strings.TrimSpace(header), nil
}
func toMdFilename(sectionName string) string {
return nonAlphanumericRegex.ReplaceAllLiteralString(strings.ToLower(sectionName), "-") + ".md"
}

View File

@ -121,7 +121,7 @@ fatal() {
trap 'fatal "Script encountered an error"' ERR
cdroot
start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000
start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable
echo '== Waiting for Coder to become ready'
# Start the timeout in the background so interrupting this script

View File

@ -26,7 +26,7 @@ var (
func main() {
flag.StringVar(&metricsFile, "metrics-file", "scripts/metricsdocgen/metrics", "Path to Prometheus metrics file")
flag.StringVar(&prometheusDocFile, "prometheus-doc-file", "docs/admin/prometheus.md", "Path to prometheus doc file")
flag.StringVar(&prometheusDocFile, "prometheus-doc-file", "docs/admin/prometheus.md", "Path to Prometheus doc file")
flag.BoolVar(&dryRun, "dry-run", false, "Dry run")
flag.Parse()

View File

@ -307,6 +307,7 @@ export interface DeploymentConfig {
readonly experimental: DeploymentConfigField<boolean>
readonly update_check: DeploymentConfigField<boolean>
readonly max_token_lifetime: DeploymentConfigField<number>
readonly swagger: SwaggerConfig
}
// From codersdk/deploymentconfig.go
@ -616,6 +617,11 @@ export interface ServiceBanner {
readonly background_color?: string
}
// From codersdk/deploymentconfig.go
export interface SwaggerConfig {
readonly enable: DeploymentConfigField<boolean>
}
// From codersdk/deploymentconfig.go
export interface TLSConfig {
readonly enable: DeploymentConfigField<boolean>

View File

@ -25,6 +25,10 @@ export default defineConfig({
ws: true,
secure: process.env.NODE_ENV === "production",
},
"/swagger": {
target: process.env.CODER_HOST || "http://localhost:3000",
secure: process.env.NODE_ENV === "production",
},
},
},
resolve: {