mirror of https://github.com/coder/coder.git
feat: manage health settings using Coder API (#10861)
This commit is contained in:
parent
452668c893
commit
19b6d194fc
|
@ -430,6 +430,68 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/debug/health/settings": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Debug"
|
||||
],
|
||||
"summary": "Get health settings",
|
||||
"operationId": "get-health-settings",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.HealthSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Debug"
|
||||
],
|
||||
"summary": "Update health settings",
|
||||
"operationId": "update-health-settings",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Update health settings",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateHealthSettings"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateHealthSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/tailnet": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -8904,6 +8966,17 @@ const docTemplate = `{
|
|||
"GroupSourceOIDC"
|
||||
]
|
||||
},
|
||||
"codersdk.HealthSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dismissed_healthchecks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.Healthcheck": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -9851,6 +9924,7 @@ const docTemplate = `{
|
|||
"group",
|
||||
"license",
|
||||
"convert_login",
|
||||
"health_settings",
|
||||
"workspace_proxy",
|
||||
"organization"
|
||||
],
|
||||
|
@ -9865,6 +9939,7 @@ const docTemplate = `{
|
|||
"ResourceTypeGroup",
|
||||
"ResourceTypeLicense",
|
||||
"ResourceTypeConvertLogin",
|
||||
"ResourceTypeHealthSettings",
|
||||
"ResourceTypeWorkspaceProxy",
|
||||
"ResourceTypeOrganization"
|
||||
]
|
||||
|
@ -10771,6 +10846,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateHealthSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dismissed_healthchecks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateRoles": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -366,6 +366,58 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/debug/health/settings": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Debug"],
|
||||
"summary": "Get health settings",
|
||||
"operationId": "get-health-settings",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.HealthSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Debug"],
|
||||
"summary": "Update health settings",
|
||||
"operationId": "update-health-settings",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Update health settings",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateHealthSettings"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.UpdateHealthSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/tailnet": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -8000,6 +8052,17 @@
|
|||
"enum": ["user", "oidc"],
|
||||
"x-enum-varnames": ["GroupSourceUser", "GroupSourceOIDC"]
|
||||
},
|
||||
"codersdk.HealthSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dismissed_healthchecks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.Healthcheck": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8877,6 +8940,7 @@
|
|||
"group",
|
||||
"license",
|
||||
"convert_login",
|
||||
"health_settings",
|
||||
"workspace_proxy",
|
||||
"organization"
|
||||
],
|
||||
|
@ -8891,6 +8955,7 @@
|
|||
"ResourceTypeGroup",
|
||||
"ResourceTypeLicense",
|
||||
"ResourceTypeConvertLogin",
|
||||
"ResourceTypeHealthSettings",
|
||||
"ResourceTypeWorkspaceProxy",
|
||||
"ResourceTypeOrganization"
|
||||
]
|
||||
|
@ -9754,6 +9819,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateHealthSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dismissed_healthchecks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateRoles": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -18,7 +18,8 @@ type Auditable interface {
|
|||
database.AuditableGroup |
|
||||
database.License |
|
||||
database.WorkspaceProxy |
|
||||
database.AuditOAuthConvertState
|
||||
database.AuditOAuthConvertState |
|
||||
database.HealthSettings
|
||||
}
|
||||
|
||||
// Map is a map of changed fields in an audited resource. It maps field names to
|
||||
|
|
|
@ -93,6 +93,8 @@ func ResourceTarget[T Auditable](tgt T) string {
|
|||
return typed.Name
|
||||
case database.AuditOAuthConvertState:
|
||||
return string(typed.ToLoginType)
|
||||
case database.HealthSettings:
|
||||
return "" // no target?
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown resource %T", tgt))
|
||||
}
|
||||
|
@ -123,6 +125,9 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
|
|||
case database.AuditOAuthConvertState:
|
||||
// The merge state is for the given user
|
||||
return typed.UserID
|
||||
case database.HealthSettings:
|
||||
// Artificial ID for auditing purposes
|
||||
return typed.ID
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown resource %T", tgt))
|
||||
}
|
||||
|
@ -152,6 +157,8 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
|
|||
return database.ResourceTypeWorkspaceProxy
|
||||
case database.AuditOAuthConvertState:
|
||||
return database.ResourceTypeConvertLogin
|
||||
case database.HealthSettings:
|
||||
return database.ResourceTypeHealthSettings
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown resource %T", typed))
|
||||
}
|
||||
|
|
|
@ -970,7 +970,13 @@ func New(options *Options) *API {
|
|||
|
||||
r.Get("/coordinator", api.debugCoordinator)
|
||||
r.Get("/tailnet", api.debugTailnet)
|
||||
r.Get("/health", api.debugDeploymentHealth)
|
||||
r.Route("/health", func(r chi.Router) {
|
||||
r.Get("/", api.debugDeploymentHealth)
|
||||
r.Route("/settings", func(r chi.Router) {
|
||||
r.Get("/", api.deploymentHealthSettings)
|
||||
r.Put("/", api.putDeploymentHealthSettings)
|
||||
})
|
||||
})
|
||||
r.Get("/ws", (&healthcheck.WebsocketEchoServer{}).ServeHTTP)
|
||||
r.Route("/{user}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractUserParam(options.Database))
|
||||
|
|
|
@ -133,7 +133,8 @@ CREATE TYPE resource_type AS ENUM (
|
|||
'workspace_build',
|
||||
'license',
|
||||
'workspace_proxy',
|
||||
'convert_login'
|
||||
'convert_login',
|
||||
'health_settings'
|
||||
);
|
||||
|
||||
CREATE TYPE startup_script_behavior AS ENUM (
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
-- Nothing to do
|
|
@ -0,0 +1,2 @@
|
|||
-- This has to be outside a transaction
|
||||
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'health_settings';
|
|
@ -1158,6 +1158,7 @@ const (
|
|||
ResourceTypeLicense ResourceType = "license"
|
||||
ResourceTypeWorkspaceProxy ResourceType = "workspace_proxy"
|
||||
ResourceTypeConvertLogin ResourceType = "convert_login"
|
||||
ResourceTypeHealthSettings ResourceType = "health_settings"
|
||||
)
|
||||
|
||||
func (e *ResourceType) Scan(src interface{}) error {
|
||||
|
@ -1208,7 +1209,8 @@ func (e ResourceType) Valid() bool {
|
|||
ResourceTypeWorkspaceBuild,
|
||||
ResourceTypeLicense,
|
||||
ResourceTypeWorkspaceProxy,
|
||||
ResourceTypeConvertLogin:
|
||||
ResourceTypeConvertLogin,
|
||||
ResourceTypeHealthSettings:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -1228,6 +1230,7 @@ func AllResourceTypeValues() []ResourceType {
|
|||
ResourceTypeLicense,
|
||||
ResourceTypeWorkspaceProxy,
|
||||
ResourceTypeConvertLogin,
|
||||
ResourceTypeHealthSettings,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,11 @@ type AuditOAuthConvertState struct {
|
|||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
type HealthSettings struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
DismissedHealthchecks []string `db:"dismissed_healthchecks" json:"dismissed_healthchecks"`
|
||||
}
|
||||
|
||||
type Actions []rbac.Action
|
||||
|
||||
func (a *Actions) Scan(src interface{}) error {
|
||||
|
|
135
coderd/debug.go
135
coderd/debug.go
|
@ -1,14 +1,24 @@
|
|||
package coderd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/audit"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
|
@ -107,6 +117,131 @@ func formatHealthcheck(ctx context.Context, rw http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
}
|
||||
|
||||
// @Summary Get health settings
|
||||
// @ID get-health-settings
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Debug
|
||||
// @Success 200 {object} codersdk.HealthSettings
|
||||
// @Router /debug/health/settings [get]
|
||||
func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request) {
|
||||
settingsJSON, err := api.Database.GetHealthSettings(r.Context())
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to fetch health settings.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var settings codersdk.HealthSettings
|
||||
err = json.Unmarshal([]byte(settingsJSON), &settings)
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to unmarshal health settings.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if len(settings.DismissedHealthchecks) == 0 {
|
||||
settings.DismissedHealthchecks = []string{}
|
||||
}
|
||||
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, settings)
|
||||
}
|
||||
|
||||
// @Summary Update health settings
|
||||
// @ID update-health-settings
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Debug
|
||||
// @Param request body codersdk.UpdateHealthSettings true "Update health settings"
|
||||
// @Success 200 {object} codersdk.UpdateHealthSettings
|
||||
// @Router /debug/health/settings [put]
|
||||
func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if !api.Authorize(r, rbac.ActionUpdate, rbac.ResourceDeploymentValues) {
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: "Insufficient permissions to update health settings.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var settings codersdk.HealthSettings
|
||||
if !httpapi.Read(ctx, rw, r, &settings) {
|
||||
return
|
||||
}
|
||||
|
||||
err := validateHealthSettings(settings)
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to validate health settings.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
settingsJSON, err := json.Marshal(&settings)
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to marshal health settings.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
currentSettingsJSON, err := api.Database.GetHealthSettings(r.Context())
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to fetch current health settings.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if bytes.Equal(settingsJSON, []byte(currentSettingsJSON)) {
|
||||
httpapi.Write(r.Context(), rw, http.StatusNotModified, nil)
|
||||
return
|
||||
}
|
||||
|
||||
auditor := api.Auditor.Load()
|
||||
aReq, commitAudit := audit.InitRequest[database.HealthSettings](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionWrite,
|
||||
})
|
||||
defer commitAudit()
|
||||
aReq.New = database.HealthSettings{
|
||||
ID: uuid.New(),
|
||||
DismissedHealthchecks: settings.DismissedHealthchecks,
|
||||
}
|
||||
|
||||
err = api.Database.UpsertHealthSettings(ctx, string(settingsJSON))
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to update health settings.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, settings)
|
||||
}
|
||||
|
||||
func validateHealthSettings(settings codersdk.HealthSettings) error {
|
||||
for _, dismissed := range settings.DismissedHealthchecks {
|
||||
ok := slices.Contains(healthcheck.Sections, dismissed)
|
||||
if !ok {
|
||||
return xerrors.Errorf("unknown healthcheck section: %s", dismissed)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// For some reason the swagger docs need to be attached to a function.
|
||||
//
|
||||
// @Summary Debug Info Websocket Test
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
|
@ -232,6 +233,108 @@ func TestDebugHealth(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHealthSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("InitialState", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
// given
|
||||
adminClient := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||
|
||||
// when
|
||||
settings, err := adminClient.HealthSettings(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// then
|
||||
require.Equal(t, codersdk.HealthSettings{DismissedHealthchecks: []string{}}, settings)
|
||||
})
|
||||
|
||||
t.Run("DismissSection", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
// given
|
||||
adminClient := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||
|
||||
expected := codersdk.HealthSettings{
|
||||
DismissedHealthchecks: []string{healthcheck.SectionDERP, healthcheck.SectionWebsocket},
|
||||
}
|
||||
|
||||
// when: dismiss "derp" and "websocket"
|
||||
err := adminClient.PutHealthSettings(ctx, expected)
|
||||
require.NoError(t, err)
|
||||
|
||||
// then
|
||||
settings, err := adminClient.HealthSettings(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, settings)
|
||||
})
|
||||
|
||||
t.Run("UnDismissSection", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
// given
|
||||
adminClient := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||
|
||||
initial := codersdk.HealthSettings{
|
||||
DismissedHealthchecks: []string{healthcheck.SectionDERP, healthcheck.SectionWebsocket},
|
||||
}
|
||||
|
||||
err := adminClient.PutHealthSettings(ctx, initial)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := codersdk.HealthSettings{
|
||||
DismissedHealthchecks: []string{healthcheck.SectionDERP},
|
||||
}
|
||||
|
||||
// when: undismiss "websocket"
|
||||
err = adminClient.PutHealthSettings(ctx, expected)
|
||||
require.NoError(t, err)
|
||||
|
||||
// then
|
||||
settings, err := adminClient.HealthSettings(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, settings)
|
||||
})
|
||||
|
||||
t.Run("NotModified", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
// given
|
||||
adminClient := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||
|
||||
expected := codersdk.HealthSettings{
|
||||
DismissedHealthchecks: []string{healthcheck.SectionDERP, healthcheck.SectionWebsocket},
|
||||
}
|
||||
|
||||
err := adminClient.PutHealthSettings(ctx, expected)
|
||||
require.NoError(t, err)
|
||||
|
||||
// when
|
||||
err = adminClient.PutHealthSettings(ctx, expected)
|
||||
|
||||
// then
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "health settings not modified")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDebugWebsocket(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
SectionWorkspaceProxy string = "WorkspaceProxy"
|
||||
)
|
||||
|
||||
var Sections = []string{SectionAccessURL, SectionDatabase, SectionDERP, SectionWebsocket, SectionWorkspaceProxy}
|
||||
|
||||
type Checker interface {
|
||||
DERP(ctx context.Context, opts *derphealth.ReportOptions) derphealth.Report
|
||||
AccessURL(ctx context.Context, opts *AccessURLReportOptions) AccessURLReport
|
||||
|
|
|
@ -24,6 +24,7 @@ const (
|
|||
ResourceTypeGroup ResourceType = "group"
|
||||
ResourceTypeLicense ResourceType = "license"
|
||||
ResourceTypeConvertLogin ResourceType = "convert_login"
|
||||
ResourceTypeHealthSettings ResourceType = "health_settings"
|
||||
ResourceTypeWorkspaceProxy ResourceType = "workspace_proxy"
|
||||
ResourceTypeOrganization ResourceType = "organization"
|
||||
)
|
||||
|
@ -56,6 +57,8 @@ func (r ResourceType) FriendlyString() string {
|
|||
return "workspace proxy"
|
||||
case ResourceTypeOrganization:
|
||||
return "organization"
|
||||
case ResourceTypeHealthSettings:
|
||||
return "health_settings"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package codersdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type HealthSettings struct {
|
||||
DismissedHealthchecks []string `json:"dismissed_healthchecks"`
|
||||
}
|
||||
|
||||
type UpdateHealthSettings struct {
|
||||
DismissedHealthchecks []string `json:"dismissed_healthchecks"`
|
||||
}
|
||||
|
||||
func (c *Client) HealthSettings(ctx context.Context) (HealthSettings, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/debug/health/settings", nil)
|
||||
if err != nil {
|
||||
return HealthSettings{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return HealthSettings{}, ReadBodyAsError(res)
|
||||
}
|
||||
var settings HealthSettings
|
||||
return settings, json.NewDecoder(res.Body).Decode(&settings)
|
||||
}
|
||||
|
||||
func (c *Client) PutHealthSettings(ctx context.Context, settings HealthSettings) error {
|
||||
res, err := c.Request(ctx, http.MethodPut, "/api/v2/debug/health/settings", settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotModified {
|
||||
return xerrors.New("health settings not modified")
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -14,6 +14,7 @@ We track the following resources:
|
|||
| AuditOAuthConvertState<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>expires_at</td><td>true</td></tr><tr><td>from_login_type</td><td>true</td></tr><tr><td>to_login_type</td><td>true</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
|
||||
| Group<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>members</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>quota_allowance</td><td>true</td></tr><tr><td>source</td><td>false</td></tr></tbody></table> |
|
||||
| GitSSHKey<br><i>create</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
|
||||
| HealthSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>dismissed_healthchecks</td><td>true</td></tr><tr><td>id</td><td>false</td></tr></tbody></table> |
|
||||
| License<br><i>create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>exp</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>jwt</td><td>false</td></tr><tr><td>uploaded_at</td><td>true</td></tr><tr><td>uuid</td><td>true</td></tr></tbody></table> |
|
||||
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>autostart_block_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_weeks</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deprecated</td><td>true</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>max_ttl</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>require_active_version</td><td>true</td></tr><tr><td>time_til_dormant</td><td>true</td></tr><tr><td>time_til_dormant_autodelete</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
|
||||
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
|
|
|
@ -298,6 +298,83 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
|||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get health settings
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/debug/health/settings \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /debug/health/settings`
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ |
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.HealthSettings](schemas.md#codersdkhealthsettings) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Update health settings
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`PUT /debug/health/settings`
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | ------------------------------------------------------------------------ | -------- | ---------------------- |
|
||||
| `body` | body | [codersdk.UpdateHealthSettings](schemas.md#codersdkupdatehealthsettings) | true | Update health settings |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------------ |
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UpdateHealthSettings](schemas.md#codersdkupdatehealthsettings) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Debug Info Tailnet
|
||||
|
||||
### Code samples
|
||||
|
|
|
@ -3166,6 +3166,20 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
| `user` |
|
||||
| `oidc` |
|
||||
|
||||
## codersdk.HealthSettings
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------------ | --------------- | -------- | ------------ | ----------- |
|
||||
| `dismissed_healthchecks` | array of string | false | | |
|
||||
|
||||
## codersdk.Healthcheck
|
||||
|
||||
```json
|
||||
|
@ -4162,6 +4176,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
| `group` |
|
||||
| `license` |
|
||||
| `convert_login` |
|
||||
| `health_settings` |
|
||||
| `workspace_proxy` |
|
||||
| `organization` |
|
||||
|
||||
|
@ -5158,6 +5173,20 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
| `url` | string | false | | URL to download the latest release of Coder. |
|
||||
| `version` | string | false | | Version is the semantic version for the latest release of Coder. |
|
||||
|
||||
## codersdk.UpdateHealthSettings
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------------ | --------------- | -------- | ------------ | ----------- |
|
||||
| `dismissed_healthchecks` | array of string | false | | |
|
||||
|
||||
## codersdk.UpdateRoles
|
||||
|
||||
```json
|
||||
|
|
|
@ -184,6 +184,10 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
|||
"to_login_type": ActionTrack,
|
||||
"user_id": ActionTrack,
|
||||
},
|
||||
&database.HealthSettings{}: {
|
||||
"id": ActionIgnore,
|
||||
"dismissed_healthchecks": ActionTrack,
|
||||
},
|
||||
// TODO: track an ID here when the below ticket is completed:
|
||||
// https://github.com/coder/coder/pull/6012
|
||||
&database.License{}: {
|
||||
|
|
|
@ -542,6 +542,11 @@ export interface Group {
|
|||
readonly source: GroupSource;
|
||||
}
|
||||
|
||||
// From codersdk/health.go
|
||||
export interface HealthSettings {
|
||||
readonly dismissed_healthchecks: string[];
|
||||
}
|
||||
|
||||
// From codersdk/workspaceapps.go
|
||||
export interface Healthcheck {
|
||||
readonly url: string;
|
||||
|
@ -1155,6 +1160,11 @@ export interface UpdateCheckResponse {
|
|||
readonly url: string;
|
||||
}
|
||||
|
||||
// From codersdk/health.go
|
||||
export interface UpdateHealthSettings {
|
||||
readonly dismissed_healthchecks: string[];
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface UpdateRoles {
|
||||
readonly roles: string[];
|
||||
|
@ -1920,6 +1930,7 @@ export type ResourceType =
|
|||
| "convert_login"
|
||||
| "git_ssh_key"
|
||||
| "group"
|
||||
| "health_settings"
|
||||
| "license"
|
||||
| "organization"
|
||||
| "template"
|
||||
|
@ -1933,6 +1944,7 @@ export const ResourceTypes: ResourceType[] = [
|
|||
"convert_login",
|
||||
"git_ssh_key",
|
||||
"group",
|
||||
"health_settings",
|
||||
"license",
|
||||
"organization",
|
||||
"template",
|
||||
|
|
Loading…
Reference in New Issue