2022-01-13 22:55:28 +00:00
package coderd
import (
2022-11-16 22:34:06 +00:00
"context"
2022-10-17 13:43:30 +00:00
"crypto/tls"
2022-04-19 13:48:13 +00:00
"crypto/x509"
2023-12-18 16:44:52 +00:00
"database/sql"
feat: expose DERP server debug metrics (#12135)
Adds some debug endpoints for looking into the DERP server.
The `api/v2/debug/derp/traffic` endpoint requires the `ss` utility to be present in order to function. I have *not* added the `iproute2` package to our base image as it adds 11MB, so this endpoint won't be useful by default. However, in a debugging situation, we could exec into the container and then `apk add iproute2`, or build a special debug image.
The `api/v2/debug/expvar` handler contains DERP metrics as well as commandline and memstats.
Example:
```
{
"alert_failed": 0,
"alert_generated": 0,
"cmdline": ["/Users/spike/repos/coder/build/coder_darwin_arm64","--global-config","/Users/spike/repos/coder/.coderv2","server","--http-address","0.0.0.0:3000","--swagger-enable","--access-url","http://127.0.0.1:3000","--dangerous-allow-cors-requests=true"],
"derp": {"accepts": 1, "average_queue_duration_ms": 0, "bytes_received": 0, "bytes_sent": 0, "counter_packets_dropped_reason": {"gone_disconnected": 0, "gone_not_here": 0, "queue_head": 0, "queue_tail": 0, "unknown_dest": 0, "unknown_dest_on_fwd": 0, "write_error": 0}, "counter_packets_dropped_type": {"disco": 0, "other": 0}, "counter_packets_received_kind": {"disco": 0, "other": 0}, "counter_tcp_rtt": {}, "counter_total_dup_client_conns": 0, "gauge_clients_local": 1, "gauge_clients_remote": 0, "gauge_clients_total": 1, "gauge_current_connections": 1, "gauge_current_dup_client_conns": 0, "gauge_current_dup_client_keys": 0, "gauge_current_file_descriptors": 0, "gauge_current_home_connections": 1, "gauge_memstats_sys0": 20874504, "gauge_watchers": 0, "got_ping": 0, "home_moves_in": 0, "home_moves_out": 0, "multiforwarder_created": 0, "multiforwarder_deleted": 0, "packet_forwarder_delete_other_value": 0, "packets_dropped": 0, "packets_forwarded_in": 0, "packets_forwarded_out": 0, "packets_received": 0, "packets_sent": 0, "peer_gone_disconnected_frames": 0, "peer_gone_not_here_frames": 0, "sent_pong": 0, "unknown_frames": 0, "version": "1.47.0-dev20240214-t64db8c604"},
"memstats": {"Alloc":286506256,"TotalAlloc":297594632,"Sys":310621512,"Lookups":0,"Mallocs":304204,"Frees":171570,"HeapAlloc":286506256,"HeapSys":294060032,"HeapIdle":3694592,"HeapInuse":290365440,"HeapReleased":3620864,"HeapObjects":132634,"StackInuse":3735552,"StackSys":3735552,"MSpanInuse":347256,"MSpanSys":358512,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":1469877,"GCSys":9434896,"OtherSys":1547043,"NextGC":551867656,"LastGC":1707892877408883000,"PauseTotalNs":1247000,"PauseNs":[200333,229375,239875,209542,106958,203792,57125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1707892876217481000,1707892876219726000,1707892876222273000,1707892876226151000,1707892876234815000,1707892877398146000,1707892877408883000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":7,"NumForcedGC":0,"GCCPUFraction":0.0022425810335762954,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":14396,"Frees":9143},{"Size":16,"Mallocs":89090,"Frees":50507},{"Size":24,"Mallocs":40839,"Frees":24456},{"Size":32,"Mallocs":22404,"Frees":12379},{"Size":48,"Mallocs":51174,"Frees":23718},{"Size":64,"Mallocs":15406,"Frees":3501},{"Size":80,"Mallocs":6688,"Frees":2352},{"Size":96,"Mallocs":2567,"Frees":374},{"Size":112,"Mallocs":19371,"Frees":16883},{"Size":128,"Mallocs":2873,"Frees":1061},{"Size":144,"Mallocs":5600,"Frees":2742},{"Size":160,"Mallocs":2159,"Frees":622},{"Size":176,"Mallocs":454,"Frees":86},{"Size":192,"Mallocs":227,"Frees":128},{"Size":208,"Mallocs":1407,"Frees":732},{"Size":224,"Mallocs":1365,"Frees":1090},{"Size":240,"Mallocs":82,"Frees":48},{"Size":256,"Mallocs":310,"Frees":162},{"Size":288,"Mallocs":1945,"Frees":562},{"Size":320,"Mallocs":1200,"Frees":458},{"Size":352,"Mallocs":133,"Frees":33},{"Size":384,"Mallocs":582,"Frees":51},{"Size":416,"Mallocs":747,"Frees":200},{"Size":448,"Mallocs":113,"Frees":22},{"Size":480,"Mallocs":34,"Frees":21},{"Size":512,"Mallocs":951,"Frees":91},{"Size":576,"Mallocs":364,"Frees":122},{"Size":640,"Mallocs":532,"Frees":270},{"Size":704,"Mallocs":93,"Frees":39},{"Size":768,"Mallocs":83,"Frees":35},{"Size":896,"Mallocs":308,"Frees":175},{"Size":1024,"Mallocs":226,"Frees":122},{"Size":1152,"Mallocs":198,"Frees":100},{"Size":1280,"Mallocs":314,"Frees":171},{"Size":1408,"Mallocs":77,"Frees":47},{"Size":1536,"Mallocs":80,"Frees":54},{"Size":1792,"Mallocs":199,"Frees":107},{"Size":2048,"Mallocs":112,"Frees":48},{"Size":2304,"Mallocs":71,"Frees":32},{"Size":2688,"Mallocs":206,"Frees":81},{"Size":3072,"Mallocs":39,"Frees":15},{"Size":3200,"Mallocs":16,"Frees":7},{"Size":3456,"Mallocs":44,"Frees":29},{"Size":4096,"Mallocs":192,"Frees":83},{"Size":4864,"Mallocs":44,"Frees":25},{"Size":5376,"Mallocs":105,"Frees":43},{"Size":6144,"Mallocs":25,"Frees":5},{"Size":6528,"Mallocs":22,"Frees":7},{"Size":6784,"Mallocs":3,"Frees":0},{"Size":6912,"Mallocs":4,"Frees":2},{"Size":8192,"Mallocs":59,"Frees":10},{"Size":9472,"Mallocs":31,"Frees":12},{"Size":9728,"Mallocs":5,"Frees":2},{"Size":10240,"Mallocs":5,"Frees":0},{"Size":10880,"Mallocs":27,"Frees":11},{"Size":12288,"Mallocs":4,"Frees":1},{"Size":13568,"Mallocs":4,"Frees":2},{"Size":14336,"Mallocs":9,"Frees":2},{"Size":16384,"Mallocs":10,"Frees":2},{"Size":18432,"Mallocs":4,"Frees":2}]},
"warning_failed": 0,
"warning_generated": 0
}
```
If we find the DERP metrics useful we could consider how to include them in Prometheus scrapes based on the tailnet `varz` package. That's for a later PR if at all.
2024-02-14 11:11:45 +00:00
"expvar"
2023-05-11 19:30:51 +00:00
"flag"
2022-10-25 00:46:24 +00:00
"fmt"
2022-06-13 18:14:22 +00:00
"io"
2022-01-13 22:55:28 +00:00
"net/http"
2022-02-28 18:26:01 +00:00
"net/url"
2022-06-21 16:53:36 +00:00
"path/filepath"
2022-10-14 18:25:11 +00:00
"regexp"
2023-01-18 19:12:53 +00:00
"strings"
2022-02-20 22:29:16 +00:00
"sync"
2022-09-20 04:11:01 +00:00
"sync/atomic"
2022-03-22 19:17:50 +00:00
"time"
2022-01-13 22:55:28 +00:00
2022-06-13 18:14:22 +00:00
"github.com/andybalholm/brotli"
2022-01-31 03:13:14 +00:00
"github.com/go-chi/chi/v5"
2022-05-03 12:48:02 +00:00
"github.com/go-chi/chi/v5/middleware"
2022-11-06 21:27:09 +00:00
"github.com/google/uuid"
2022-06-13 18:14:22 +00:00
"github.com/klauspost/compress/zstd"
2022-08-08 15:09:46 +00:00
"github.com/prometheus/client_golang/prometheus"
2023-03-30 16:46:58 +00:00
httpSwagger "github.com/swaggo/http-swagger/v2"
2022-09-16 16:43:22 +00:00
"go.opentelemetry.io/otel/trace"
2022-05-03 21:10:19 +00:00
"golang.org/x/xerrors"
2022-02-21 20:36:29 +00:00
"google.golang.org/api/idtoken"
2022-11-16 22:34:06 +00:00
"storj.io/drpc/drpcmux"
"storj.io/drpc/drpcserver"
2022-09-01 01:09:44 +00:00
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
2023-04-03 06:28:42 +00:00
"tailscale.com/util/singleflight"
2022-01-20 13:46:51 +00:00
2023-06-15 19:22:31 +00:00
"cdr.dev/slog"
2024-01-29 08:17:31 +00:00
agentproto "github.com/coder/coder/v2/agent/proto"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/buildinfo"
2024-01-29 08:17:31 +00:00
_ "github.com/coder/coder/v2/coderd/apidoc" // Used for swagger docs.
"github.com/coder/coder/v2/coderd/appearance"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/awsidentity"
"github.com/coder/coder/v2/coderd/batchstats"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
2024-03-22 16:42:43 +00:00
"github.com/coder/coder/v2/coderd/database/dbrollup"
2023-12-18 16:44:52 +00:00
"github.com/coder/coder/v2/coderd/database/dbtime"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/database/pubsub"
2023-12-18 12:53:28 +00:00
"github.com/coder/coder/v2/coderd/externalauth"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/gitsshkey"
"github.com/coder/coder/v2/coderd/healthcheck"
2023-12-18 12:53:28 +00:00
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/metricscache"
2024-02-13 14:31:20 +00:00
"github.com/coder/coder/v2/coderd/portsharing"
2024-01-29 08:17:31 +00:00
"github.com/coder/coder/v2/coderd/prometheusmetrics"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/provisionerdserver"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/schedule"
"github.com/coder/coder/v2/coderd/telemetry"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/coderd/updatecheck"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/coderd/workspaceapps"
2024-03-20 16:44:12 +00:00
"github.com/coder/coder/v2/coderd/workspaceusage"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/codersdk"
2023-12-15 08:41:39 +00:00
"github.com/coder/coder/v2/codersdk/drpc"
2024-03-26 17:44:31 +00:00
"github.com/coder/coder/v2/codersdk/healthsdk"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/provisionerd/proto"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/site"
"github.com/coder/coder/v2/tailnet"
2024-03-15 16:24:38 +00:00
"github.com/coder/serpent"
2022-01-13 22:55:28 +00:00
)
2023-01-11 10:42:49 +00:00
// We must only ever instantiate one httpSwagger.Handler because of a data race
// inside the handler. This issue is triggered by tests that create multiple
// coderd instances.
//
// See https://github.com/swaggo/http-swagger/issues/78
var globalHTTPSwaggerHandler http . HandlerFunc
func init ( ) {
globalHTTPSwaggerHandler = httpSwagger . Handler ( httpSwagger . URL ( "/swagger/doc.json" ) )
}
feat: expose DERP server debug metrics (#12135)
Adds some debug endpoints for looking into the DERP server.
The `api/v2/debug/derp/traffic` endpoint requires the `ss` utility to be present in order to function. I have *not* added the `iproute2` package to our base image as it adds 11MB, so this endpoint won't be useful by default. However, in a debugging situation, we could exec into the container and then `apk add iproute2`, or build a special debug image.
The `api/v2/debug/expvar` handler contains DERP metrics as well as commandline and memstats.
Example:
```
{
"alert_failed": 0,
"alert_generated": 0,
"cmdline": ["/Users/spike/repos/coder/build/coder_darwin_arm64","--global-config","/Users/spike/repos/coder/.coderv2","server","--http-address","0.0.0.0:3000","--swagger-enable","--access-url","http://127.0.0.1:3000","--dangerous-allow-cors-requests=true"],
"derp": {"accepts": 1, "average_queue_duration_ms": 0, "bytes_received": 0, "bytes_sent": 0, "counter_packets_dropped_reason": {"gone_disconnected": 0, "gone_not_here": 0, "queue_head": 0, "queue_tail": 0, "unknown_dest": 0, "unknown_dest_on_fwd": 0, "write_error": 0}, "counter_packets_dropped_type": {"disco": 0, "other": 0}, "counter_packets_received_kind": {"disco": 0, "other": 0}, "counter_tcp_rtt": {}, "counter_total_dup_client_conns": 0, "gauge_clients_local": 1, "gauge_clients_remote": 0, "gauge_clients_total": 1, "gauge_current_connections": 1, "gauge_current_dup_client_conns": 0, "gauge_current_dup_client_keys": 0, "gauge_current_file_descriptors": 0, "gauge_current_home_connections": 1, "gauge_memstats_sys0": 20874504, "gauge_watchers": 0, "got_ping": 0, "home_moves_in": 0, "home_moves_out": 0, "multiforwarder_created": 0, "multiforwarder_deleted": 0, "packet_forwarder_delete_other_value": 0, "packets_dropped": 0, "packets_forwarded_in": 0, "packets_forwarded_out": 0, "packets_received": 0, "packets_sent": 0, "peer_gone_disconnected_frames": 0, "peer_gone_not_here_frames": 0, "sent_pong": 0, "unknown_frames": 0, "version": "1.47.0-dev20240214-t64db8c604"},
"memstats": {"Alloc":286506256,"TotalAlloc":297594632,"Sys":310621512,"Lookups":0,"Mallocs":304204,"Frees":171570,"HeapAlloc":286506256,"HeapSys":294060032,"HeapIdle":3694592,"HeapInuse":290365440,"HeapReleased":3620864,"HeapObjects":132634,"StackInuse":3735552,"StackSys":3735552,"MSpanInuse":347256,"MSpanSys":358512,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":1469877,"GCSys":9434896,"OtherSys":1547043,"NextGC":551867656,"LastGC":1707892877408883000,"PauseTotalNs":1247000,"PauseNs":[200333,229375,239875,209542,106958,203792,57125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1707892876217481000,1707892876219726000,1707892876222273000,1707892876226151000,1707892876234815000,1707892877398146000,1707892877408883000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":7,"NumForcedGC":0,"GCCPUFraction":0.0022425810335762954,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":14396,"Frees":9143},{"Size":16,"Mallocs":89090,"Frees":50507},{"Size":24,"Mallocs":40839,"Frees":24456},{"Size":32,"Mallocs":22404,"Frees":12379},{"Size":48,"Mallocs":51174,"Frees":23718},{"Size":64,"Mallocs":15406,"Frees":3501},{"Size":80,"Mallocs":6688,"Frees":2352},{"Size":96,"Mallocs":2567,"Frees":374},{"Size":112,"Mallocs":19371,"Frees":16883},{"Size":128,"Mallocs":2873,"Frees":1061},{"Size":144,"Mallocs":5600,"Frees":2742},{"Size":160,"Mallocs":2159,"Frees":622},{"Size":176,"Mallocs":454,"Frees":86},{"Size":192,"Mallocs":227,"Frees":128},{"Size":208,"Mallocs":1407,"Frees":732},{"Size":224,"Mallocs":1365,"Frees":1090},{"Size":240,"Mallocs":82,"Frees":48},{"Size":256,"Mallocs":310,"Frees":162},{"Size":288,"Mallocs":1945,"Frees":562},{"Size":320,"Mallocs":1200,"Frees":458},{"Size":352,"Mallocs":133,"Frees":33},{"Size":384,"Mallocs":582,"Frees":51},{"Size":416,"Mallocs":747,"Frees":200},{"Size":448,"Mallocs":113,"Frees":22},{"Size":480,"Mallocs":34,"Frees":21},{"Size":512,"Mallocs":951,"Frees":91},{"Size":576,"Mallocs":364,"Frees":122},{"Size":640,"Mallocs":532,"Frees":270},{"Size":704,"Mallocs":93,"Frees":39},{"Size":768,"Mallocs":83,"Frees":35},{"Size":896,"Mallocs":308,"Frees":175},{"Size":1024,"Mallocs":226,"Frees":122},{"Size":1152,"Mallocs":198,"Frees":100},{"Size":1280,"Mallocs":314,"Frees":171},{"Size":1408,"Mallocs":77,"Frees":47},{"Size":1536,"Mallocs":80,"Frees":54},{"Size":1792,"Mallocs":199,"Frees":107},{"Size":2048,"Mallocs":112,"Frees":48},{"Size":2304,"Mallocs":71,"Frees":32},{"Size":2688,"Mallocs":206,"Frees":81},{"Size":3072,"Mallocs":39,"Frees":15},{"Size":3200,"Mallocs":16,"Frees":7},{"Size":3456,"Mallocs":44,"Frees":29},{"Size":4096,"Mallocs":192,"Frees":83},{"Size":4864,"Mallocs":44,"Frees":25},{"Size":5376,"Mallocs":105,"Frees":43},{"Size":6144,"Mallocs":25,"Frees":5},{"Size":6528,"Mallocs":22,"Frees":7},{"Size":6784,"Mallocs":3,"Frees":0},{"Size":6912,"Mallocs":4,"Frees":2},{"Size":8192,"Mallocs":59,"Frees":10},{"Size":9472,"Mallocs":31,"Frees":12},{"Size":9728,"Mallocs":5,"Frees":2},{"Size":10240,"Mallocs":5,"Frees":0},{"Size":10880,"Mallocs":27,"Frees":11},{"Size":12288,"Mallocs":4,"Frees":1},{"Size":13568,"Mallocs":4,"Frees":2},{"Size":14336,"Mallocs":9,"Frees":2},{"Size":16384,"Mallocs":10,"Frees":2},{"Size":18432,"Mallocs":4,"Frees":2}]},
"warning_failed": 0,
"warning_generated": 0
}
```
If we find the DERP metrics useful we could consider how to include them in Prometheus scrapes based on the tailnet `varz` package. That's for a later PR if at all.
2024-02-14 11:11:45 +00:00
var expDERPOnce = sync . Once { }
2022-01-13 22:55:28 +00:00
// Options are requires parameters for Coder to start.
type Options struct {
2022-04-18 22:40:25 +00:00
AccessURL * url . URL
2022-09-22 22:30:32 +00:00
// AppHostname should be the wildcard hostname to use for workspace
2022-10-14 18:25:11 +00:00
// applications INCLUDING the asterisk, (optional) suffix and leading dot.
2022-12-15 18:43:00 +00:00
// It will use the same scheme and port number as the access URL.
2024-01-18 15:44:05 +00:00
// E.g. "*.apps.coder.com" or "*-apps.coder.com" or "*.apps.coder.com:8080".
2022-09-22 22:30:32 +00:00
AppHostname string
2022-10-14 18:25:11 +00:00
// AppHostnameRegex contains the regex version of options.AppHostname as
2024-01-17 16:41:42 +00:00
// generated by appurl.CompileHostnamePattern(). It MUST be set if
2022-10-14 18:25:11 +00:00
// options.AppHostname is set.
AppHostnameRegex * regexp . Regexp
Logger slog . Logger
Database database . Store
2023-06-14 15:34:54 +00:00
Pubsub pubsub . Pubsub
2022-03-31 17:31:06 +00:00
2022-06-21 16:53:36 +00:00
// CacheDir is used for caching files served by the API.
CacheDir string
2022-09-20 04:11:01 +00:00
Auditor audit . Auditor
2022-04-18 22:40:25 +00:00
AgentConnectionUpdateFrequency time . Duration
2022-06-27 15:06:51 +00:00
AgentInactiveDisconnectTimeout time . Duration
2023-01-05 18:05:20 +00:00
AWSCertificates awsidentity . Certificates
Authorizer rbac . Authorizer
AzureCertificates x509 . VerifyOptions
GoogleTokenValidator * idtoken . Validator
GithubOAuth2Config * GithubOAuth2Config
OIDCConfig * OIDCConfig
PrometheusRegistry * prometheus . Registry
SecureAuthCookie bool
2023-02-10 16:52:49 +00:00
StrictTransportSecurityCfg httpmw . HSTSConfig
2023-01-05 18:05:20 +00:00
SSHKeygenAlgorithm gitsshkey . Algorithm
Telemetry telemetry . Reporter
TracerProvider trace . TracerProvider
2023-09-30 19:30:01 +00:00
ExternalAuthConfigs [ ] * externalauth . Config
2023-01-05 18:05:20 +00:00
RealIPConfig * httpmw . RealIPConfig
2024-01-17 00:19:16 +00:00
TrialGenerator func ( ctx context . Context , body codersdk . LicensorTrialRequest ) error
2024-02-23 16:48:24 +00:00
// RefreshEntitlements is used to set correct entitlements after creating first user and generating trial license.
RefreshEntitlements func ( ctx context . Context ) error
2024-03-25 19:01:42 +00:00
// PostAuthAdditionalHeadersFunc is used to add additional headers to the response
// after a successful authentication.
// This is somewhat janky, but seemingly the only reasonable way to add a header
// for all authenticated users under a condition, only in Enterprise.
2024-03-29 15:14:27 +00:00
PostAuthAdditionalHeadersFunc func ( auth rbac . Subject , header http . Header )
2024-03-25 19:01:42 +00:00
2022-10-17 13:43:30 +00:00
// TLSCertificates is used to mesh DERP servers securely.
2023-07-26 16:21:04 +00:00
TLSCertificates [ ] tls . Certificate
TailnetCoordinator tailnet . Coordinator
DERPServer * derp . Server
// BaseDERPMap is used as the base DERP map for all clients and agents.
// Proxies are added to this list.
BaseDERPMap * tailcfg . DERPMap
DERPMapUpdateFrequency time . Duration
2023-07-20 13:35:41 +00:00
SwaggerEndpoint bool
2024-02-16 17:09:19 +00:00
SetUserGroups func ( ctx context . Context , logger slog . Logger , tx database . Store , userID uuid . UUID , orgGroupNames map [ uuid . UUID ] [ ] string , createMissingGroups bool ) error
2023-08-08 16:37:49 +00:00
SetUserSiteRoles func ( ctx context . Context , logger slog . Logger , tx database . Store , userID uuid . UUID , roles [ ] string ) error
2023-07-20 13:35:41 +00:00
TemplateScheduleStore * atomic . Pointer [ schedule . TemplateScheduleStore ]
UserQuietHoursScheduleStore * atomic . Pointer [ schedule . UserQuietHoursScheduleStore ]
2023-10-18 22:07:21 +00:00
AccessControlStore * atomic . Pointer [ dbauthz . AccessControlStore ]
2023-04-05 18:41:55 +00:00
// AppSecurityKey is the crypto key used to sign and encrypt tokens related to
// workspace applications. It consists of both a signing and encryption key.
2023-11-24 15:06:51 +00:00
AppSecurityKey workspaceapps . SecurityKey
2024-03-26 17:44:31 +00:00
HealthcheckFunc func ( ctx context . Context , apiKey string ) * healthsdk . HealthcheckReport
2023-11-24 15:06:51 +00:00
HealthcheckTimeout time . Duration
HealthcheckRefresh time . Duration
WorkspaceProxiesFetchUpdater * atomic . Pointer [ healthcheck . WorkspaceProxiesFetchUpdater ]
2022-09-01 19:58:23 +00:00
2023-06-30 12:38:48 +00:00
// OAuthSigningKey is the crypto key used to sign and encrypt state strings
// related to OAuth. This is a symmetric secret key using hmac to sign payloads.
// So this secret should **never** be exposed to the client.
OAuthSigningKey [ 32 ] byte
2023-01-05 18:05:20 +00:00
// APIRateLimit is the minutely throughput rate limit per user or ip.
// Setting a rate limit <0 will disable the rate limiter across the entire
// app. Some specific routes have their own configurable rate limits.
APIRateLimit int
LoginRateLimit int
FilesRateLimit int
2022-09-01 19:58:23 +00:00
MetricsCacheRefreshInterval time . Duration
AgentStatsRefreshInterval time . Duration
2023-03-07 21:10:01 +00:00
DeploymentValues * codersdk . DeploymentValues
2023-09-29 17:04:28 +00:00
// DeploymentOptions do contain the copy of DeploymentValues, and contain
// contextual information about how the values were set.
// Do not use DeploymentOptions to retrieve values, use DeploymentValues instead.
// All secrets values are stripped.
2024-03-15 16:24:38 +00:00
DeploymentOptions serpent . OptionSet
2023-09-29 17:04:28 +00:00
UpdateCheckOptions * updatecheck . Options // Set non-nil to enable update checking.
2022-12-19 17:43:46 +00:00
2023-03-16 18:03:37 +00:00
// SSHConfig is the response clients use to configure config-ssh locally.
SSHConfig codersdk . SSHConfigResponse
2022-12-19 17:43:46 +00:00
HTTPClient * http . Client
2023-04-27 10:34:00 +00:00
2023-12-18 12:53:28 +00:00
UpdateAgentMetrics func ( ctx context . Context , labels prometheusmetrics . AgentMetricLabels , metrics [ ] * agentproto . Stats_Metric )
2023-08-04 16:00:42 +00:00
StatsBatcher * batchstats . Batcher
2023-08-16 12:22:00 +00:00
WorkspaceAppsStatsCollectorOptions workspaceapps . StatsCollectorOptions
2023-12-14 21:52:52 +00:00
// This janky function is used in telemetry to parse fields out of the raw
// JWT. It needs to be passed through like this because license parsing is
// under the enterprise license, and can't be imported into AGPL.
2023-12-15 18:38:47 +00:00
ParseLicenseClaims func ( rawJWT string ) ( email string , trial bool , err error )
AllowWorkspaceRenames bool
2024-01-12 20:27:22 +00:00
// NewTicker is used for unit tests to replace "time.NewTicker".
NewTicker func ( duration time . Duration ) ( tick <- chan time . Time , done func ( ) )
2024-03-20 16:44:12 +00:00
2024-03-22 16:42:43 +00:00
// DatabaseRolluper rolls up template usage stats from raw agent and app
// stats. This is used to provide insights in the WebUI.
DatabaseRolluper * dbrollup . Rolluper
2024-03-20 16:44:12 +00:00
// WorkspaceUsageTracker tracks workspace usage by the CLI.
WorkspaceUsageTracker * workspaceusage . Tracker
2022-01-13 22:55:28 +00:00
}
2022-12-19 17:43:46 +00:00
// @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
2022-05-26 03:14:08 +00:00
// New constructs a Coder API handler.
func New ( options * Options ) * API {
2022-09-20 04:11:01 +00:00
if options == nil {
options = & Options { }
}
2024-01-12 20:27:22 +00:00
if options . NewTicker == nil {
options . NewTicker = func ( duration time . Duration ) ( tick <- chan time . Time , done func ( ) ) {
ticker := time . NewTicker ( duration )
return ticker . C , ticker . Stop
}
}
2023-03-21 14:10:22 +00:00
2023-05-31 13:55:57 +00:00
// Safety check: if we're not running a unit test, we *must* have a Prometheus registry.
if options . PrometheusRegistry == nil && flag . Lookup ( "test.v" ) == nil {
panic ( "developer error: options.PrometheusRegistry is nil and not running a unit test" )
}
2023-04-11 13:57:23 +00:00
if options . DeploymentValues . DisableOwnerWorkspaceExec {
rbac . ReloadBuiltinRoles ( & rbac . RoleOptions {
NoOwnerWorkspaceExec : true ,
} )
}
2023-03-21 14:10:22 +00:00
if options . Authorizer == nil {
options . Authorizer = rbac . NewCachingAuthorizer ( options . PrometheusRegistry )
}
2023-10-18 22:07:21 +00:00
if options . AccessControlStore == nil {
options . AccessControlStore = & atomic . Pointer [ dbauthz . AccessControlStore ] { }
var tacs dbauthz . AccessControlStore = dbauthz . AGPLTemplateAccessControlStore { }
options . AccessControlStore . Store ( & tacs )
}
2023-03-21 14:10:22 +00:00
options . Database = dbauthz . New (
options . Database ,
options . Authorizer ,
options . Logger . Named ( "authz_querier" ) ,
2023-10-18 22:07:21 +00:00
options . AccessControlStore ,
2023-03-21 14:10:22 +00:00
)
2023-10-18 22:07:21 +00:00
2023-07-19 16:11:11 +00:00
experiments := ReadExperiments (
2023-03-07 21:10:01 +00:00
options . Logger , options . DeploymentValues . Experiments . Value ( ) ,
)
2022-10-14 18:25:11 +00:00
if options . AppHostname != "" && options . AppHostnameRegex == nil || options . AppHostname == "" && options . AppHostnameRegex != nil {
panic ( "coderd: both AppHostname and AppHostnameRegex must be set or unset" )
}
2022-03-22 19:17:50 +00:00
if options . AgentConnectionUpdateFrequency == 0 {
2023-04-06 01:58:54 +00:00
options . AgentConnectionUpdateFrequency = 15 * time . Second
2022-03-22 19:17:50 +00:00
}
2022-06-27 15:06:51 +00:00
if options . AgentInactiveDisconnectTimeout == 0 {
// Multiply the update by two to allow for some lag-time.
options . AgentInactiveDisconnectTimeout = options . AgentConnectionUpdateFrequency * 2
2023-03-14 13:14:47 +00:00
// Set a minimum timeout to avoid disconnecting too soon.
if options . AgentInactiveDisconnectTimeout < 2 * time . Second {
options . AgentInactiveDisconnectTimeout = 2 * time . Second
}
2022-06-27 15:06:51 +00:00
}
2022-09-20 00:46:29 +00:00
if options . AgentStatsRefreshInterval == 0 {
2022-12-01 17:43:28 +00:00
options . AgentStatsRefreshInterval = 5 * time . Minute
2022-09-20 00:46:29 +00:00
}
if options . MetricsCacheRefreshInterval == 0 {
options . MetricsCacheRefreshInterval = time . Hour
}
2022-04-22 20:27:55 +00:00
if options . APIRateLimit == 0 {
options . APIRateLimit = 512
}
2023-01-05 18:05:20 +00:00
if options . LoginRateLimit == 0 {
options . LoginRateLimit = 60
}
if options . FilesRateLimit == 0 {
options . FilesRateLimit = 12
}
2022-11-05 00:03:01 +00:00
if options . PrometheusRegistry == nil {
options . PrometheusRegistry = prometheus . NewRegistry ( )
2022-08-08 15:09:46 +00:00
}
2024-03-21 21:53:41 +00:00
if options . DERPServer == nil && options . DeploymentValues . DERP . Server . Enable {
2022-10-17 13:43:30 +00:00
options . DERPServer = derp . NewServer ( key . NewNode ( ) , tailnet . Logger ( options . Logger . Named ( "derp" ) ) )
}
2023-07-26 16:21:04 +00:00
if options . DERPMapUpdateFrequency == 0 {
options . DERPMapUpdateFrequency = 5 * time . Second
}
if options . TailnetCoordinator == nil {
options . TailnetCoordinator = tailnet . NewCoordinator ( options . Logger )
}
2022-09-20 04:11:01 +00:00
if options . Auditor == nil {
options . Auditor = audit . NewNop ( )
2022-08-29 23:45:40 +00:00
}
2023-03-16 18:03:37 +00:00
if options . SSHConfig . HostnamePrefix == "" {
options . SSHConfig . HostnamePrefix = "coder."
}
2023-05-03 23:02:35 +00:00
if options . TracerProvider == nil {
options . TracerProvider = trace . NewNoopTracerProvider ( )
}
2023-02-02 19:53:48 +00:00
if options . SetUserGroups == nil {
2024-02-16 17:09:19 +00:00
options . SetUserGroups = func ( ctx context . Context , logger slog . Logger , _ database . Store , userID uuid . UUID , orgGroupNames map [ uuid . UUID ] [ ] string , createMissingGroups bool ) error {
2023-08-08 16:37:49 +00:00
logger . Warn ( ctx , "attempted to assign OIDC groups without enterprise license" ,
2024-02-16 17:09:19 +00:00
slog . F ( "user_id" , userID ) ,
slog . F ( "groups" , orgGroupNames ) ,
slog . F ( "create_missing_groups" , createMissingGroups ) ,
2023-03-21 19:25:45 +00:00
)
return nil
}
2023-02-02 19:53:48 +00:00
}
2023-07-24 12:34:24 +00:00
if options . SetUserSiteRoles == nil {
2023-08-08 16:37:49 +00:00
options . SetUserSiteRoles = func ( ctx context . Context , logger slog . Logger , _ database . Store , userID uuid . UUID , roles [ ] string ) error {
logger . Warn ( ctx , "attempted to assign OIDC user roles without enterprise license" ,
2023-07-24 12:34:24 +00:00
slog . F ( "user_id" , userID ) , slog . F ( "roles" , roles ) ,
)
return nil
}
}
2023-03-07 14:14:58 +00:00
if options . TemplateScheduleStore == nil {
2023-04-04 12:48:35 +00:00
options . TemplateScheduleStore = & atomic . Pointer [ schedule . TemplateScheduleStore ] { }
}
if options . TemplateScheduleStore . Load ( ) == nil {
v := schedule . NewAGPLTemplateScheduleStore ( )
options . TemplateScheduleStore . Store ( & v )
2023-03-07 14:14:58 +00:00
}
2023-07-20 13:35:41 +00:00
if options . UserQuietHoursScheduleStore == nil {
options . UserQuietHoursScheduleStore = & atomic . Pointer [ schedule . UserQuietHoursScheduleStore ] { }
}
if options . UserQuietHoursScheduleStore . Load ( ) == nil {
v := schedule . NewAGPLUserQuietHoursScheduleStore ( )
options . UserQuietHoursScheduleStore . Store ( & v )
}
2022-05-26 03:14:08 +00:00
2023-08-04 16:00:42 +00:00
if options . StatsBatcher == nil {
panic ( "developer error: options.StatsBatcher is nil" )
}
2022-06-21 16:53:36 +00:00
siteCacheDir := options . CacheDir
if siteCacheDir != "" {
siteCacheDir = filepath . Join ( siteCacheDir , "site" )
}
2023-01-17 18:38:08 +00:00
binFS , binHashes , err := site . ExtractOrReadBinFS ( siteCacheDir , site . FS ( ) )
2022-06-21 16:53:36 +00:00
if err != nil {
panic ( xerrors . Errorf ( "read site bin failed: %w" , err ) )
}
2022-09-01 19:58:23 +00:00
metricsCache := metricscache . New (
options . Database ,
options . Logger . Named ( "metrics_cache" ) ,
2023-03-09 03:05:45 +00:00
metricscache . Intervals {
2024-03-27 16:10:14 +00:00
TemplateBuildTimes : options . MetricsCacheRefreshInterval ,
DeploymentStats : options . AgentStatsRefreshInterval ,
2023-03-09 03:05:45 +00:00
} ,
2022-09-01 19:58:23 +00:00
)
2023-03-07 19:38:11 +00:00
oauthConfigs := & httpmw . OAuth2Configs {
Github : options . GithubOAuth2Config ,
OIDC : options . OIDCConfig ,
}
2024-03-22 16:42:43 +00:00
if options . DatabaseRolluper == nil {
options . DatabaseRolluper = dbrollup . New ( options . Logger . Named ( "dbrollup" ) , options . Database )
}
2024-03-20 16:44:12 +00:00
if options . WorkspaceUsageTracker == nil {
options . WorkspaceUsageTracker = workspaceusage . New ( options . Database ,
workspaceusage . WithLogger ( options . Logger . Named ( "workspace_usage_tracker" ) ) ,
)
}
2023-02-10 18:23:02 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
2023-04-17 19:57:21 +00:00
r := chi . NewRouter ( )
2023-06-30 12:38:48 +00:00
// nolint:gocritic // Load deployment ID. This never changes
depID , err := options . Database . GetDeploymentID ( dbauthz . AsSystemRestricted ( ctx ) )
if err != nil {
panic ( xerrors . Errorf ( "get deployment ID: %w" , err ) )
}
2022-05-26 03:14:08 +00:00
api := & API {
2023-06-30 12:38:48 +00:00
ctx : ctx ,
cancel : cancel ,
DeploymentID : depID ,
2023-02-10 18:23:02 +00:00
2022-11-06 21:27:09 +00:00
ID : uuid . New ( ) ,
2022-06-04 20:13:37 +00:00
Options : options ,
2022-09-20 04:11:01 +00:00
RootHandler : r ,
HTTPAuth : & HTTPAuthorizer {
2022-08-22 22:02:50 +00:00
Authorizer : options . Authorizer ,
Logger : options . Logger ,
} ,
2023-04-04 00:59:41 +00:00
WorkspaceAppsProvider : workspaceapps . NewDBTokenProvider (
2023-03-07 19:38:11 +00:00
options . Logger . Named ( "workspaceapps" ) ,
options . AccessURL ,
options . Authorizer ,
options . Database ,
2023-03-07 21:10:01 +00:00
options . DeploymentValues ,
2023-03-07 19:38:11 +00:00
oauthConfigs ,
2023-03-30 13:24:51 +00:00
options . AgentInactiveDisconnectTimeout ,
2023-04-05 18:41:55 +00:00
options . AppSecurityKey ,
2023-03-07 19:38:11 +00:00
) ,
2023-07-20 13:35:41 +00:00
metricsCache : metricsCache ,
Auditor : atomic . Pointer [ audit . Auditor ] { } ,
2023-12-18 12:53:28 +00:00
TailnetCoordinator : atomic . Pointer [ tailnet . Coordinator ] { } ,
2023-07-20 13:35:41 +00:00
TemplateScheduleStore : options . TemplateScheduleStore ,
UserQuietHoursScheduleStore : options . UserQuietHoursScheduleStore ,
2023-10-18 22:07:21 +00:00
AccessControlStore : options . AccessControlStore ,
2023-07-20 13:35:41 +00:00
Experiments : experiments ,
2024-03-26 17:44:31 +00:00
healthCheckGroup : & singleflight . Group [ string , * healthsdk . HealthcheckReport ] { } ,
2023-09-19 06:25:57 +00:00
Acquirer : provisionerdserver . NewAcquirer (
ctx ,
options . Logger . Named ( "acquirer" ) ,
options . Database ,
2024-03-22 16:42:43 +00:00
options . Pubsub ,
) ,
dbRolluper : options . DatabaseRolluper ,
2024-03-20 16:44:12 +00:00
workspaceUsageTracker : options . WorkspaceUsageTracker ,
2022-05-26 03:14:08 +00:00
}
2024-01-29 08:17:31 +00:00
api . AppearanceFetcher . Store ( & appearance . DefaultFetcher )
2024-02-13 14:31:20 +00:00
api . PortSharer . Store ( & portsharing . DefaultPortSharer )
2024-01-29 08:17:31 +00:00
api . SiteHandler = site . New ( & site . Options {
BinFS : binFS ,
BinHashes : binHashes ,
Database : options . Database ,
SiteFS : site . FS ( ) ,
OAuth2Configs : oauthConfigs ,
DocsURL : options . DeploymentValues . DocsURL . String ( ) ,
AppearanceFetcher : & api . AppearanceFetcher ,
} )
api . SiteHandler . Experiments . Store ( & experiments )
2022-12-01 17:43:28 +00:00
if options . UpdateCheckOptions != nil {
api . updateChecker = updatecheck . New (
options . Database ,
options . Logger . Named ( "update_checker" ) ,
* options . UpdateCheckOptions ,
)
}
2023-11-24 15:06:51 +00:00
if options . WorkspaceProxiesFetchUpdater == nil {
options . WorkspaceProxiesFetchUpdater = & atomic . Pointer [ healthcheck . WorkspaceProxiesFetchUpdater ] { }
var wpfu healthcheck . WorkspaceProxiesFetchUpdater = & healthcheck . AGPLWorkspaceProxiesFetchUpdater { }
options . WorkspaceProxiesFetchUpdater . Store ( & wpfu )
}
2023-07-26 16:21:04 +00:00
if options . HealthcheckFunc == nil {
2024-03-26 17:44:31 +00:00
options . HealthcheckFunc = func ( ctx context . Context , apiKey string ) * healthsdk . HealthcheckReport {
2023-12-06 08:38:03 +00:00
// NOTE: dismissed healthchecks are marked in formatHealthcheck.
// Not here, as this result gets cached.
2023-07-26 16:21:04 +00:00
return healthcheck . Run ( ctx , & healthcheck . ReportOptions {
2023-11-13 14:14:43 +00:00
Database : healthcheck . DatabaseReportOptions {
DB : options . Database ,
Threshold : options . DeploymentValues . Healthcheck . ThresholdDatabase . Value ( ) ,
} ,
Websocket : healthcheck . WebsocketReportOptions {
AccessURL : options . AccessURL ,
APIKey : apiKey ,
} ,
AccessURL : healthcheck . AccessURLReportOptions {
AccessURL : options . AccessURL ,
} ,
DerpHealth : derphealth . ReportOptions {
2023-12-06 08:38:03 +00:00
DERPMap : api . DERPMap ( ) ,
2023-11-13 14:14:43 +00:00
} ,
2023-11-24 15:06:51 +00:00
WorkspaceProxy : healthcheck . WorkspaceProxyReportOptions {
WorkspaceProxiesFetchUpdater : * ( options . WorkspaceProxiesFetchUpdater ) . Load ( ) ,
} ,
2024-01-08 09:29:04 +00:00
ProvisionerDaemons : healthcheck . ProvisionerDaemonsReportDeps {
CurrentVersion : buildinfo . Version ( ) ,
2024-02-20 12:44:19 +00:00
CurrentAPIMajorVersion : proto . CurrentMajor ,
2024-01-08 09:29:04 +00:00
Store : options . Database ,
// TimeNow and StaleInterval set to defaults, see healthcheck/provisioner.go
} ,
2023-07-26 16:21:04 +00:00
} )
}
}
2023-11-24 15:06:51 +00:00
2023-07-26 16:21:04 +00:00
if options . HealthcheckTimeout == 0 {
options . HealthcheckTimeout = 30 * time . Second
}
if options . HealthcheckRefresh == 0 {
2023-11-13 14:14:43 +00:00
options . HealthcheckRefresh = options . DeploymentValues . Healthcheck . Refresh . Value ( )
2023-07-26 16:21:04 +00:00
}
2023-03-30 08:36:57 +00:00
var oidcAuthURLParams map [ string ] string
if options . OIDCConfig != nil {
oidcAuthURLParams = options . OIDCConfig . AuthURLParams
}
2022-09-20 04:11:01 +00:00
api . Auditor . Store ( & options . Auditor )
2022-10-17 13:43:30 +00:00
api . TailnetCoordinator . Store ( & options . TailnetCoordinator )
2024-02-06 05:57:18 +00:00
stn , err := NewServerTailnet ( api . ctx ,
2024-01-04 05:27:36 +00:00
options . Logger ,
options . DERPServer ,
api . DERPMap ,
options . DeploymentValues . DERP . Config . ForceWebSockets . Value ( ) ,
func ( context . Context ) ( tailnet . MultiAgentConn , error ) {
return ( * api . TailnetCoordinator . Load ( ) ) . ServeMultiAgent ( uuid . New ( ) ) , nil
} ,
2024-03-08 05:29:54 +00:00
options . DeploymentValues . DERP . Config . BlockDirect . Value ( ) ,
2024-01-04 05:27:36 +00:00
api . TracerProvider ,
)
if err != nil {
panic ( "failed to setup server tailnet: " + err . Error ( ) )
2023-07-12 22:37:31 +00:00
}
2024-02-06 05:57:18 +00:00
api . agentProvider = stn
if options . DeploymentValues . Prometheus . Enable {
options . PrometheusRegistry . MustRegister ( stn )
}
2023-12-15 10:10:24 +00:00
api . TailnetClientService , err = tailnet . NewClientService (
2024-01-02 04:07:57 +00:00
api . Logger . Named ( "tailnetclient" ) ,
& api . TailnetCoordinator ,
api . Options . DERPMapUpdateFrequency ,
api . DERPMap ,
)
2023-12-15 10:10:24 +00:00
if err != nil {
api . Logger . Fatal ( api . ctx , "failed to initialize tailnet client service" , slog . Error ( err ) )
}
2022-09-22 22:30:32 +00:00
2023-08-16 12:22:00 +00:00
workspaceAppsLogger := options . Logger . Named ( "workspaceapps" )
if options . WorkspaceAppsStatsCollectorOptions . Logger == nil {
named := workspaceAppsLogger . Named ( "stats_collector" )
options . WorkspaceAppsStatsCollectorOptions . Logger = & named
}
if options . WorkspaceAppsStatsCollectorOptions . Reporter == nil {
options . WorkspaceAppsStatsCollectorOptions . Reporter = workspaceapps . NewStatsDBReporter ( options . Database , workspaceapps . DefaultStatsDBReporterBatchSize )
}
2023-04-05 18:41:55 +00:00
api . workspaceAppServer = & workspaceapps . Server {
2023-08-16 12:22:00 +00:00
Logger : workspaceAppsLogger ,
2023-04-05 18:41:55 +00:00
2023-04-17 19:57:21 +00:00
DashboardURL : api . AccessURL ,
AccessURL : api . AccessURL ,
Hostname : api . AppHostname ,
HostnameRegex : api . AppHostnameRegex ,
RealIPConfig : options . RealIPConfig ,
2023-04-05 18:41:55 +00:00
SignedTokenProvider : api . WorkspaceAppsProvider ,
2023-07-12 22:37:31 +00:00
AgentProvider : api . agentProvider ,
2023-04-05 18:41:55 +00:00
AppSecurityKey : options . AppSecurityKey ,
2023-08-16 12:22:00 +00:00
StatsCollector : workspaceapps . NewStatsCollector ( options . WorkspaceAppsStatsCollectorOptions ) ,
2023-04-17 19:57:21 +00:00
DisablePathApps : options . DeploymentValues . DisablePathApps . Value ( ) ,
SecureAuthCookie : options . DeploymentValues . SecureAuthCookie . Value ( ) ,
2023-04-05 18:41:55 +00:00
}
2023-03-07 19:38:11 +00:00
apiKeyMiddleware := httpmw . ExtractAPIKeyMW ( httpmw . ExtractAPIKeyConfig {
2024-03-25 19:01:42 +00:00
DB : options . Database ,
OAuth2Configs : oauthConfigs ,
RedirectToLogin : false ,
2024-04-10 15:34:49 +00:00
DisableSessionExpiryRefresh : options . DeploymentValues . Sessions . DisableExpiryRefresh . Value ( ) ,
2024-03-25 19:01:42 +00:00
Optional : false ,
SessionTokenFunc : nil , // Default behavior
PostAuthAdditionalHeadersFunc : options . PostAuthAdditionalHeadersFunc ,
2022-09-22 22:30:32 +00:00
} )
// Same as above but it redirects to the login page.
2023-03-07 19:38:11 +00:00
apiKeyMiddlewareRedirect := httpmw . ExtractAPIKeyMW ( httpmw . ExtractAPIKeyConfig {
2024-03-25 19:01:42 +00:00
DB : options . Database ,
OAuth2Configs : oauthConfigs ,
RedirectToLogin : true ,
2024-04-10 15:34:49 +00:00
DisableSessionExpiryRefresh : options . DeploymentValues . Sessions . DisableExpiryRefresh . Value ( ) ,
2024-03-25 19:01:42 +00:00
Optional : false ,
SessionTokenFunc : nil , // Default behavior
PostAuthAdditionalHeadersFunc : options . PostAuthAdditionalHeadersFunc ,
2022-09-22 22:30:32 +00:00
} )
2023-04-17 19:57:21 +00:00
// Same as the first but it's optional.
apiKeyMiddlewareOptional := httpmw . ExtractAPIKeyMW ( httpmw . ExtractAPIKeyConfig {
2024-03-25 19:01:42 +00:00
DB : options . Database ,
OAuth2Configs : oauthConfigs ,
RedirectToLogin : false ,
2024-04-10 15:34:49 +00:00
DisableSessionExpiryRefresh : options . DeploymentValues . Sessions . DisableExpiryRefresh . Value ( ) ,
2024-03-25 19:01:42 +00:00
Optional : true ,
SessionTokenFunc : nil , // Default behavior
PostAuthAdditionalHeadersFunc : options . PostAuthAdditionalHeadersFunc ,
2023-04-17 19:57:21 +00:00
} )
2022-05-03 21:10:19 +00:00
2023-01-05 18:05:20 +00:00
// API rate limit middleware. The counter is local and not shared between
// replicas or instances of this middleware.
apiRateLimiter := httpmw . RateLimit ( options . APIRateLimit , time . Minute )
feat: expose DERP server debug metrics (#12135)
Adds some debug endpoints for looking into the DERP server.
The `api/v2/debug/derp/traffic` endpoint requires the `ss` utility to be present in order to function. I have *not* added the `iproute2` package to our base image as it adds 11MB, so this endpoint won't be useful by default. However, in a debugging situation, we could exec into the container and then `apk add iproute2`, or build a special debug image.
The `api/v2/debug/expvar` handler contains DERP metrics as well as commandline and memstats.
Example:
```
{
"alert_failed": 0,
"alert_generated": 0,
"cmdline": ["/Users/spike/repos/coder/build/coder_darwin_arm64","--global-config","/Users/spike/repos/coder/.coderv2","server","--http-address","0.0.0.0:3000","--swagger-enable","--access-url","http://127.0.0.1:3000","--dangerous-allow-cors-requests=true"],
"derp": {"accepts": 1, "average_queue_duration_ms": 0, "bytes_received": 0, "bytes_sent": 0, "counter_packets_dropped_reason": {"gone_disconnected": 0, "gone_not_here": 0, "queue_head": 0, "queue_tail": 0, "unknown_dest": 0, "unknown_dest_on_fwd": 0, "write_error": 0}, "counter_packets_dropped_type": {"disco": 0, "other": 0}, "counter_packets_received_kind": {"disco": 0, "other": 0}, "counter_tcp_rtt": {}, "counter_total_dup_client_conns": 0, "gauge_clients_local": 1, "gauge_clients_remote": 0, "gauge_clients_total": 1, "gauge_current_connections": 1, "gauge_current_dup_client_conns": 0, "gauge_current_dup_client_keys": 0, "gauge_current_file_descriptors": 0, "gauge_current_home_connections": 1, "gauge_memstats_sys0": 20874504, "gauge_watchers": 0, "got_ping": 0, "home_moves_in": 0, "home_moves_out": 0, "multiforwarder_created": 0, "multiforwarder_deleted": 0, "packet_forwarder_delete_other_value": 0, "packets_dropped": 0, "packets_forwarded_in": 0, "packets_forwarded_out": 0, "packets_received": 0, "packets_sent": 0, "peer_gone_disconnected_frames": 0, "peer_gone_not_here_frames": 0, "sent_pong": 0, "unknown_frames": 0, "version": "1.47.0-dev20240214-t64db8c604"},
"memstats": {"Alloc":286506256,"TotalAlloc":297594632,"Sys":310621512,"Lookups":0,"Mallocs":304204,"Frees":171570,"HeapAlloc":286506256,"HeapSys":294060032,"HeapIdle":3694592,"HeapInuse":290365440,"HeapReleased":3620864,"HeapObjects":132634,"StackInuse":3735552,"StackSys":3735552,"MSpanInuse":347256,"MSpanSys":358512,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":1469877,"GCSys":9434896,"OtherSys":1547043,"NextGC":551867656,"LastGC":1707892877408883000,"PauseTotalNs":1247000,"PauseNs":[200333,229375,239875,209542,106958,203792,57125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1707892876217481000,1707892876219726000,1707892876222273000,1707892876226151000,1707892876234815000,1707892877398146000,1707892877408883000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":7,"NumForcedGC":0,"GCCPUFraction":0.0022425810335762954,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":14396,"Frees":9143},{"Size":16,"Mallocs":89090,"Frees":50507},{"Size":24,"Mallocs":40839,"Frees":24456},{"Size":32,"Mallocs":22404,"Frees":12379},{"Size":48,"Mallocs":51174,"Frees":23718},{"Size":64,"Mallocs":15406,"Frees":3501},{"Size":80,"Mallocs":6688,"Frees":2352},{"Size":96,"Mallocs":2567,"Frees":374},{"Size":112,"Mallocs":19371,"Frees":16883},{"Size":128,"Mallocs":2873,"Frees":1061},{"Size":144,"Mallocs":5600,"Frees":2742},{"Size":160,"Mallocs":2159,"Frees":622},{"Size":176,"Mallocs":454,"Frees":86},{"Size":192,"Mallocs":227,"Frees":128},{"Size":208,"Mallocs":1407,"Frees":732},{"Size":224,"Mallocs":1365,"Frees":1090},{"Size":240,"Mallocs":82,"Frees":48},{"Size":256,"Mallocs":310,"Frees":162},{"Size":288,"Mallocs":1945,"Frees":562},{"Size":320,"Mallocs":1200,"Frees":458},{"Size":352,"Mallocs":133,"Frees":33},{"Size":384,"Mallocs":582,"Frees":51},{"Size":416,"Mallocs":747,"Frees":200},{"Size":448,"Mallocs":113,"Frees":22},{"Size":480,"Mallocs":34,"Frees":21},{"Size":512,"Mallocs":951,"Frees":91},{"Size":576,"Mallocs":364,"Frees":122},{"Size":640,"Mallocs":532,"Frees":270},{"Size":704,"Mallocs":93,"Frees":39},{"Size":768,"Mallocs":83,"Frees":35},{"Size":896,"Mallocs":308,"Frees":175},{"Size":1024,"Mallocs":226,"Frees":122},{"Size":1152,"Mallocs":198,"Frees":100},{"Size":1280,"Mallocs":314,"Frees":171},{"Size":1408,"Mallocs":77,"Frees":47},{"Size":1536,"Mallocs":80,"Frees":54},{"Size":1792,"Mallocs":199,"Frees":107},{"Size":2048,"Mallocs":112,"Frees":48},{"Size":2304,"Mallocs":71,"Frees":32},{"Size":2688,"Mallocs":206,"Frees":81},{"Size":3072,"Mallocs":39,"Frees":15},{"Size":3200,"Mallocs":16,"Frees":7},{"Size":3456,"Mallocs":44,"Frees":29},{"Size":4096,"Mallocs":192,"Frees":83},{"Size":4864,"Mallocs":44,"Frees":25},{"Size":5376,"Mallocs":105,"Frees":43},{"Size":6144,"Mallocs":25,"Frees":5},{"Size":6528,"Mallocs":22,"Frees":7},{"Size":6784,"Mallocs":3,"Frees":0},{"Size":6912,"Mallocs":4,"Frees":2},{"Size":8192,"Mallocs":59,"Frees":10},{"Size":9472,"Mallocs":31,"Frees":12},{"Size":9728,"Mallocs":5,"Frees":2},{"Size":10240,"Mallocs":5,"Frees":0},{"Size":10880,"Mallocs":27,"Frees":11},{"Size":12288,"Mallocs":4,"Frees":1},{"Size":13568,"Mallocs":4,"Frees":2},{"Size":14336,"Mallocs":9,"Frees":2},{"Size":16384,"Mallocs":10,"Frees":2},{"Size":18432,"Mallocs":4,"Frees":2}]},
"warning_failed": 0,
"warning_generated": 0
}
```
If we find the DERP metrics useful we could consider how to include them in Prometheus scrapes based on the tailnet `varz` package. That's for a later PR if at all.
2024-02-14 11:11:45 +00:00
// Register DERP on expvar HTTP handler, which we serve below in the router, c.f. expvar.Handler()
// These are the metrics the DERP server exposes.
// TODO: export via prometheus
expDERPOnce . Do ( func ( ) {
// We need to do this via a global Once because expvar registry is global and panics if we
// register multiple times. In production there is only one Coderd and one DERP server per
// process, but in testing, we create multiple of both, so the Once protects us from
// panicking.
2024-03-21 21:53:41 +00:00
if options . DERPServer != nil {
expvar . Publish ( "derp" , api . DERPServer . ExpVar ( ) )
}
feat: expose DERP server debug metrics (#12135)
Adds some debug endpoints for looking into the DERP server.
The `api/v2/debug/derp/traffic` endpoint requires the `ss` utility to be present in order to function. I have *not* added the `iproute2` package to our base image as it adds 11MB, so this endpoint won't be useful by default. However, in a debugging situation, we could exec into the container and then `apk add iproute2`, or build a special debug image.
The `api/v2/debug/expvar` handler contains DERP metrics as well as commandline and memstats.
Example:
```
{
"alert_failed": 0,
"alert_generated": 0,
"cmdline": ["/Users/spike/repos/coder/build/coder_darwin_arm64","--global-config","/Users/spike/repos/coder/.coderv2","server","--http-address","0.0.0.0:3000","--swagger-enable","--access-url","http://127.0.0.1:3000","--dangerous-allow-cors-requests=true"],
"derp": {"accepts": 1, "average_queue_duration_ms": 0, "bytes_received": 0, "bytes_sent": 0, "counter_packets_dropped_reason": {"gone_disconnected": 0, "gone_not_here": 0, "queue_head": 0, "queue_tail": 0, "unknown_dest": 0, "unknown_dest_on_fwd": 0, "write_error": 0}, "counter_packets_dropped_type": {"disco": 0, "other": 0}, "counter_packets_received_kind": {"disco": 0, "other": 0}, "counter_tcp_rtt": {}, "counter_total_dup_client_conns": 0, "gauge_clients_local": 1, "gauge_clients_remote": 0, "gauge_clients_total": 1, "gauge_current_connections": 1, "gauge_current_dup_client_conns": 0, "gauge_current_dup_client_keys": 0, "gauge_current_file_descriptors": 0, "gauge_current_home_connections": 1, "gauge_memstats_sys0": 20874504, "gauge_watchers": 0, "got_ping": 0, "home_moves_in": 0, "home_moves_out": 0, "multiforwarder_created": 0, "multiforwarder_deleted": 0, "packet_forwarder_delete_other_value": 0, "packets_dropped": 0, "packets_forwarded_in": 0, "packets_forwarded_out": 0, "packets_received": 0, "packets_sent": 0, "peer_gone_disconnected_frames": 0, "peer_gone_not_here_frames": 0, "sent_pong": 0, "unknown_frames": 0, "version": "1.47.0-dev20240214-t64db8c604"},
"memstats": {"Alloc":286506256,"TotalAlloc":297594632,"Sys":310621512,"Lookups":0,"Mallocs":304204,"Frees":171570,"HeapAlloc":286506256,"HeapSys":294060032,"HeapIdle":3694592,"HeapInuse":290365440,"HeapReleased":3620864,"HeapObjects":132634,"StackInuse":3735552,"StackSys":3735552,"MSpanInuse":347256,"MSpanSys":358512,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":1469877,"GCSys":9434896,"OtherSys":1547043,"NextGC":551867656,"LastGC":1707892877408883000,"PauseTotalNs":1247000,"PauseNs":[200333,229375,239875,209542,106958,203792,57125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1707892876217481000,1707892876219726000,1707892876222273000,1707892876226151000,1707892876234815000,1707892877398146000,1707892877408883000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":7,"NumForcedGC":0,"GCCPUFraction":0.0022425810335762954,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":14396,"Frees":9143},{"Size":16,"Mallocs":89090,"Frees":50507},{"Size":24,"Mallocs":40839,"Frees":24456},{"Size":32,"Mallocs":22404,"Frees":12379},{"Size":48,"Mallocs":51174,"Frees":23718},{"Size":64,"Mallocs":15406,"Frees":3501},{"Size":80,"Mallocs":6688,"Frees":2352},{"Size":96,"Mallocs":2567,"Frees":374},{"Size":112,"Mallocs":19371,"Frees":16883},{"Size":128,"Mallocs":2873,"Frees":1061},{"Size":144,"Mallocs":5600,"Frees":2742},{"Size":160,"Mallocs":2159,"Frees":622},{"Size":176,"Mallocs":454,"Frees":86},{"Size":192,"Mallocs":227,"Frees":128},{"Size":208,"Mallocs":1407,"Frees":732},{"Size":224,"Mallocs":1365,"Frees":1090},{"Size":240,"Mallocs":82,"Frees":48},{"Size":256,"Mallocs":310,"Frees":162},{"Size":288,"Mallocs":1945,"Frees":562},{"Size":320,"Mallocs":1200,"Frees":458},{"Size":352,"Mallocs":133,"Frees":33},{"Size":384,"Mallocs":582,"Frees":51},{"Size":416,"Mallocs":747,"Frees":200},{"Size":448,"Mallocs":113,"Frees":22},{"Size":480,"Mallocs":34,"Frees":21},{"Size":512,"Mallocs":951,"Frees":91},{"Size":576,"Mallocs":364,"Frees":122},{"Size":640,"Mallocs":532,"Frees":270},{"Size":704,"Mallocs":93,"Frees":39},{"Size":768,"Mallocs":83,"Frees":35},{"Size":896,"Mallocs":308,"Frees":175},{"Size":1024,"Mallocs":226,"Frees":122},{"Size":1152,"Mallocs":198,"Frees":100},{"Size":1280,"Mallocs":314,"Frees":171},{"Size":1408,"Mallocs":77,"Frees":47},{"Size":1536,"Mallocs":80,"Frees":54},{"Size":1792,"Mallocs":199,"Frees":107},{"Size":2048,"Mallocs":112,"Frees":48},{"Size":2304,"Mallocs":71,"Frees":32},{"Size":2688,"Mallocs":206,"Frees":81},{"Size":3072,"Mallocs":39,"Frees":15},{"Size":3200,"Mallocs":16,"Frees":7},{"Size":3456,"Mallocs":44,"Frees":29},{"Size":4096,"Mallocs":192,"Frees":83},{"Size":4864,"Mallocs":44,"Frees":25},{"Size":5376,"Mallocs":105,"Frees":43},{"Size":6144,"Mallocs":25,"Frees":5},{"Size":6528,"Mallocs":22,"Frees":7},{"Size":6784,"Mallocs":3,"Frees":0},{"Size":6912,"Mallocs":4,"Frees":2},{"Size":8192,"Mallocs":59,"Frees":10},{"Size":9472,"Mallocs":31,"Frees":12},{"Size":9728,"Mallocs":5,"Frees":2},{"Size":10240,"Mallocs":5,"Frees":0},{"Size":10880,"Mallocs":27,"Frees":11},{"Size":12288,"Mallocs":4,"Frees":1},{"Size":13568,"Mallocs":4,"Frees":2},{"Size":14336,"Mallocs":9,"Frees":2},{"Size":16384,"Mallocs":10,"Frees":2},{"Size":18432,"Mallocs":4,"Frees":2}]},
"warning_failed": 0,
"warning_generated": 0
}
```
If we find the DERP metrics useful we could consider how to include them in Prometheus scrapes based on the tailnet `varz` package. That's for a later PR if at all.
2024-02-14 11:11:45 +00:00
} )
2023-05-22 18:02:39 +00:00
cors := httpmw . Cors ( options . DeploymentValues . Dangerous . AllowAllCors . Value ( ) )
2023-06-06 15:26:13 +00:00
prometheusMW := httpmw . Prometheus ( options . PrometheusRegistry )
2023-03-01 22:18:14 +00:00
2023-08-04 16:00:42 +00:00
api . statsBatcher = options . StatsBatcher
2022-05-03 12:48:02 +00:00
r . Use (
2022-08-29 22:00:52 +00:00
httpmw . Recover ( api . Logger ) ,
2022-11-28 21:22:10 +00:00
tracing . StatusWriterMiddleware ,
tracing . Middleware ( api . TracerProvider ) ,
httpmw . AttachRequestID ,
2022-10-23 18:21:49 +00:00
httpmw . ExtractRealIP ( api . RealIPConfig ) ,
2022-08-29 22:00:52 +00:00
httpmw . Logger ( api . Logger ) ,
2023-06-06 15:26:13 +00:00
prometheusMW ,
2022-09-01 19:58:23 +00:00
// Build-Version is helpful for debugging.
func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2024-01-03 09:01:57 +00:00
w . Header ( ) . Add ( codersdk . BuildVersionHeader , buildinfo . Version ( ) )
2022-09-01 19:58:23 +00:00
next . ServeHTTP ( w , r )
} )
} ,
2023-12-13 17:45:27 +00:00
// SubdomainAppMW checks if the first subdomain is a valid app URL. If
// it is, it will serve that application.
//
// Workspace apps do their own auth and CORS and must be BEFORE the auth
// and CORS middleware.
api . workspaceAppServer . HandleSubdomain ( apiRateLimiter ) ,
cors ,
2023-02-25 17:18:45 +00:00
// This header stops a browser from trying to MIME-sniff the content type and
// forces it to stick with the declared content-type. This is the only valid
// value for this header.
// See: https://github.com/coder/security/issues/12
func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Add ( "X-Content-Type-Options" , "nosniff" )
next . ServeHTTP ( w , r )
} )
} ,
2022-09-13 19:26:46 +00:00
httpmw . CSRF ( options . SecureAuthCookie ) ,
2022-05-03 12:48:02 +00:00
)
2024-03-28 20:49:43 +00:00
// This incurs a performance hit from the middleware, but is required to make sure
// we do not override subdomain app routes.
r . Get ( "/latency-check" , tracing . StatusWriterMiddleware ( prometheusMW ( LatencyCheck ( ) ) ) . ServeHTTP )
2022-11-07 19:35:52 +00:00
r . Get ( "/healthz" , func ( w http . ResponseWriter , r * http . Request ) { _ , _ = w . Write ( [ ] byte ( "OK" ) ) } )
2023-04-05 18:41:55 +00:00
// Attach workspace apps routes.
r . Group ( func ( r chi . Router ) {
2023-03-07 19:38:11 +00:00
r . Use ( apiRateLimiter )
2023-04-05 18:41:55 +00:00
api . workspaceAppServer . Attach ( r )
} )
2024-03-21 21:53:41 +00:00
if options . DERPServer != nil {
derpHandler := derphttp . Handler ( api . DERPServer )
derpHandler , api . derpCloseFunc = tailnet . WithWebsocketSupport ( api . DERPServer , derpHandler )
r . Route ( "/derp" , func ( r chi . Router ) {
r . Get ( "/" , derpHandler . ServeHTTP )
// This is used when UDP is blocked, and latency must be checked via HTTP(s).
r . Get ( "/latency-check" , func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
} )
2022-09-01 16:41:47 +00:00
} )
2024-03-21 21:53:41 +00:00
}
2022-06-04 20:13:37 +00:00
2023-06-29 18:58:01 +00:00
// Register callback handlers for each OAuth2 provider.
2023-09-30 19:30:01 +00:00
// We must support gitauth and externalauth for backwards compatibility.
2023-10-03 14:04:39 +00:00
for _ , route := range [ ] string { "gitauth" , "external-auth" } {
2023-09-30 19:30:01 +00:00
r . Route ( "/" + route , func ( r chi . Router ) {
for _ , externalAuthConfig := range options . ExternalAuthConfigs {
// We don't need to register a callback handler for device auth.
if externalAuthConfig . DeviceAuth != nil {
continue
}
r . Route ( fmt . Sprintf ( "/%s/callback" , externalAuthConfig . ID ) , func ( r chi . Router ) {
r . Use (
apiKeyMiddlewareRedirect ,
httpmw . ExtractOAuth2 ( externalAuthConfig , options . HTTPClient , nil ) ,
)
r . Get ( "/" , api . externalAuthCallback ( externalAuthConfig ) )
} )
2023-06-29 18:58:01 +00:00
}
2023-09-30 19:30:01 +00:00
} )
}
2023-06-29 18:58:01 +00:00
2024-03-25 19:52:22 +00:00
// OAuth2 linking routes do not make sense under the /api/v2 path. These are
// for an external application to use Coder as an OAuth2 provider, not for
// logging into Coder with an external OAuth2 provider.
r . Route ( "/oauth2" , func ( r chi . Router ) {
r . Use (
api . oAuth2ProviderMiddleware ,
// Fetch the app as system because in the /tokens route there will be no
// authenticated user.
httpmw . AsAuthzSystem ( httpmw . ExtractOAuth2ProviderApp ( options . Database ) ) ,
)
r . Route ( "/authorize" , func ( r chi . Router ) {
r . Use ( apiKeyMiddlewareRedirect )
r . Get ( "/" , api . getOAuth2ProviderAppAuthorize ( ) )
} )
r . Route ( "/tokens" , func ( r chi . Router ) {
r . Group ( func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
// DELETE on /tokens is not part of the OAuth2 spec. It is our own
// route used to revoke permissions from an application. It is here for
// parity with POST on /tokens.
r . Delete ( "/" , api . deleteOAuth2ProviderAppTokens ( ) )
} )
// The POST /tokens endpoint will be called from an unauthorized client so
// we cannot require an API key.
r . Post ( "/" , api . postOAuth2ProviderAppToken ( ) )
} )
} )
2022-01-13 22:55:28 +00:00
r . Route ( "/api/v2" , func ( r chi . Router ) {
2022-09-20 04:11:01 +00:00
api . APIHandler = r
2022-09-20 20:16:26 +00:00
r . NotFound ( func ( rw http . ResponseWriter , r * http . Request ) { httpapi . RouteNotFound ( rw ) } )
2022-04-04 22:32:05 +00:00
r . Use (
2023-01-05 18:05:20 +00:00
// Specific routes can specify different limits, but every rate
// limit must be configurable by the admin.
apiRateLimiter ,
2023-05-24 16:08:03 +00:00
httpmw . ReportCLITelemetry ( api . Logger , options . Telemetry ) ,
2022-04-04 22:32:05 +00:00
)
2022-12-22 14:53:14 +00:00
r . Get ( "/" , apiRoot )
2022-05-27 14:59:13 +00:00
// All CSP errors will be logged
r . Post ( "/csp/reports" , api . logReportCSPViolations )
2024-01-30 23:11:37 +00:00
r . Get ( "/buildinfo" , buildInfo ( api . AccessURL , api . DeploymentValues . CLIUpgradeMessage . String ( ) ) )
2023-04-25 14:37:52 +00:00
// /regions is overridden in the enterprise version
r . Group ( func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Get ( "/regions" , api . regions )
} )
2023-07-26 16:21:04 +00:00
r . Route ( "/derp-map" , func ( r chi . Router ) {
// r.Use(apiKeyMiddleware)
r . Get ( "/" , api . derpMapUpdates )
} )
2023-03-09 03:05:45 +00:00
r . Route ( "/deployment" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Get ( "/config" , api . deploymentValues )
r . Get ( "/stats" , api . deploymentStats )
2023-03-16 18:03:37 +00:00
r . Get ( "/ssh" , api . sshConfig )
2023-03-09 03:05:45 +00:00
} )
2023-01-18 19:12:53 +00:00
r . Route ( "/experiments" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
2023-10-17 18:49:19 +00:00
r . Get ( "/available" , handleExperimentsSafe )
2023-01-18 19:12:53 +00:00
r . Get ( "/" , api . handleExperimentsGet )
} )
2022-12-22 14:53:14 +00:00
r . Get ( "/updatecheck" , api . updateCheck )
2022-09-07 16:38:19 +00:00
r . Route ( "/audit" , func ( r chi . Router ) {
r . Use (
apiKeyMiddleware ,
)
r . Get ( "/" , api . auditLogs )
r . Post ( "/testgenerate" , api . generateFakeAuditLog )
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/files" , func ( r chi . Router ) {
2022-04-04 22:32:05 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2023-01-05 18:05:20 +00:00
httpmw . RateLimit ( options . FilesRateLimit , time . Minute ) ,
2022-04-04 22:32:05 +00:00
)
2022-10-13 23:02:52 +00:00
r . Get ( "/{fileID}" , api . fileByID )
2022-05-26 03:14:08 +00:00
r . Post ( "/" , api . postFile )
2022-03-07 17:40:54 +00:00
} )
2023-12-05 20:03:44 +00:00
r . Route ( "/external-auth" , func ( r chi . Router ) {
2023-06-29 18:58:01 +00:00
r . Use (
apiKeyMiddleware ,
)
2023-12-05 20:03:44 +00:00
// Get without a specific external auth ID will return all external auths.
r . Get ( "/" , api . listUserExternalAuths )
r . Route ( "/{externalauth}" , func ( r chi . Router ) {
r . Use (
httpmw . ExtractExternalAuthParam ( options . ExternalAuthConfigs ) ,
)
r . Delete ( "/" , api . deleteExternalAuthByID )
r . Get ( "/" , api . externalAuthByID )
r . Post ( "/device" , api . postExternalAuthDeviceByID )
r . Get ( "/device" , api . externalAuthDeviceByID )
} )
2023-06-29 18:58:01 +00:00
} )
2022-05-27 16:19:13 +00:00
r . Route ( "/organizations" , func ( r chi . Router ) {
2022-01-25 19:52:58 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-01-25 19:52:58 +00:00
)
2022-05-27 16:19:13 +00:00
r . Post ( "/" , api . postOrganizations )
r . Route ( "/{organization}" , func ( r chi . Router ) {
r . Use (
httpmw . ExtractOrganizationParam ( options . Database ) ,
)
r . Get ( "/" , api . organization )
2023-02-02 21:47:53 +00:00
r . Post ( "/templateversions" , api . postTemplateVersionsByOrganization )
2022-05-27 16:19:13 +00:00
r . Route ( "/templates" , func ( r chi . Router ) {
r . Post ( "/" , api . postTemplateByOrganization )
r . Get ( "/" , api . templatesByOrganization )
2022-12-09 19:29:50 +00:00
r . Get ( "/examples" , api . templateExamples )
2023-02-02 21:47:53 +00:00
r . Route ( "/{templatename}" , func ( r chi . Router ) {
r . Get ( "/" , api . templateByOrganizationAndName )
r . Route ( "/versions/{templateversionname}" , func ( r chi . Router ) {
r . Get ( "/" , api . templateVersionByOrganizationTemplateAndName )
r . Get ( "/previous" , api . previousTemplateVersionByOrganizationTemplateAndName )
} )
} )
2022-04-25 21:11:03 +00:00
} )
2022-05-27 16:19:13 +00:00
r . Route ( "/members" , func ( r chi . Router ) {
r . Get ( "/roles" , api . assignableOrgRoles )
r . Route ( "/{user}" , func ( r chi . Router ) {
r . Use (
httpmw . ExtractOrganizationMemberParam ( options . Database ) ,
)
r . Put ( "/roles" , api . putMemberRoles )
2022-09-24 01:17:10 +00:00
r . Post ( "/workspaces" , api . postWorkspacesByOrganization )
2022-05-27 16:19:13 +00:00
} )
2022-04-29 14:04:19 +00:00
} )
} )
2022-03-07 17:40:54 +00:00
} )
2022-04-06 17:42:40 +00:00
r . Route ( "/templates/{template}" , func ( r chi . Router ) {
2022-01-24 17:07:42 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-04-06 17:42:40 +00:00
httpmw . ExtractTemplateParam ( options . Database ) ,
2022-01-24 17:07:42 +00:00
)
2022-09-01 19:58:23 +00:00
r . Get ( "/daus" , api . templateDAUs )
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . template )
r . Delete ( "/" , api . deleteTemplate )
2022-06-08 14:14:57 +00:00
r . Patch ( "/" , api . patchTemplateMeta )
2022-03-07 17:40:54 +00:00
r . Route ( "/versions" , func ( r chi . Router ) {
2023-10-11 14:26:22 +00:00
r . Post ( "/archive" , api . postArchiveTemplateVersions )
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . templateVersionsByTemplate )
r . Patch ( "/" , api . patchActiveTemplateVersion )
r . Get ( "/{templateversionname}" , api . templateVersionByName )
2022-01-25 19:52:58 +00:00
} )
} )
2022-04-06 17:42:40 +00:00
r . Route ( "/templateversions/{templateversion}" , func ( r chi . Router ) {
2022-03-07 17:40:54 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-04-06 17:42:40 +00:00
httpmw . ExtractTemplateVersionParam ( options . Database ) ,
2022-03-07 17:40:54 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . templateVersion )
2023-03-23 16:26:50 +00:00
r . Patch ( "/" , api . patchTemplateVersion )
2022-05-26 03:14:08 +00:00
r . Patch ( "/cancel" , api . patchCancelTemplateVersion )
2023-10-11 14:26:22 +00:00
r . Post ( "/archive" , api . postArchiveTemplateVersion ( ) )
r . Post ( "/unarchive" , api . postUnarchiveTemplateVersion ( ) )
2023-06-02 09:16:46 +00:00
// Old agents may expect a non-error response from /schema and /parameters endpoints.
// The idea is to return an empty [], so that the coder CLI won't get blocked accidentally.
r . Get ( "/schema" , templateVersionSchemaDeprecated )
r . Get ( "/parameters" , templateVersionParametersDeprecated )
2023-01-17 10:22:11 +00:00
r . Get ( "/rich-parameters" , api . templateVersionRichParameters )
2023-10-03 14:04:39 +00:00
r . Get ( "/external-auth" , api . templateVersionExternalAuth )
2023-02-15 17:24:15 +00:00
r . Get ( "/variables" , api . templateVersionVariables )
2022-05-26 03:14:08 +00:00
r . Get ( "/resources" , api . templateVersionResources )
r . Get ( "/logs" , api . templateVersionLogs )
2022-06-01 14:44:53 +00:00
r . Route ( "/dry-run" , func ( r chi . Router ) {
r . Post ( "/" , api . postTemplateVersionDryRun )
r . Get ( "/{jobID}" , api . templateVersionDryRun )
r . Get ( "/{jobID}/resources" , api . templateVersionDryRunResources )
r . Get ( "/{jobID}/logs" , api . templateVersionDryRunLogs )
r . Patch ( "/{jobID}/cancel" , api . patchTemplateVersionDryRunCancel )
} )
2022-03-07 17:40:54 +00:00
} )
r . Route ( "/users" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/first" , api . firstUser )
r . Post ( "/first" , api . postFirstUser )
2023-01-05 18:05:20 +00:00
r . Get ( "/authmethods" , api . userAuthMethods )
2023-06-30 12:38:48 +00:00
2022-10-20 17:01:23 +00:00
r . Group ( func ( r chi . Router ) {
2023-01-05 18:05:20 +00:00
// We use a tight limit for password login to protect against
// audit-log write DoS, pbkdf2 DoS, and simple brute-force
// attacks.
2022-10-20 17:01:23 +00:00
//
2023-01-05 18:05:20 +00:00
// This value is intentionally increased during tests.
r . Use ( httpmw . RateLimit ( options . LoginRateLimit , time . Minute ) )
2022-10-20 17:01:23 +00:00
r . Post ( "/login" , api . postLogin )
2023-01-05 18:05:20 +00:00
r . Route ( "/oauth2" , func ( r chi . Router ) {
r . Route ( "/github" , func ( r chi . Router ) {
2023-07-10 14:25:41 +00:00
r . Use (
httpmw . ExtractOAuth2 ( options . GithubOAuth2Config , options . HTTPClient , nil ) ,
)
2023-01-05 18:05:20 +00:00
r . Get ( "/callback" , api . userOAuth2Github )
} )
} )
r . Route ( "/oidc/callback" , func ( r chi . Router ) {
2023-07-10 14:25:41 +00:00
r . Use (
httpmw . ExtractOAuth2 ( options . OIDCConfig , options . HTTPClient , oidcAuthURLParams ) ,
)
2023-01-05 18:05:20 +00:00
r . Get ( "/" , api . userOIDC )
2022-04-23 22:58:57 +00:00
} )
2022-08-01 04:05:35 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Group ( func ( r chi . Router ) {
2022-05-03 21:10:19 +00:00
r . Use (
apiKeyMiddleware ,
)
2022-05-26 03:14:08 +00:00
r . Post ( "/" , api . postUser )
r . Get ( "/" , api . users )
2022-05-27 20:47:03 +00:00
r . Post ( "/logout" , api . postLogout )
2022-05-03 21:10:19 +00:00
// These routes query information about site wide roles.
r . Route ( "/roles" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . assignableSiteRoles )
2022-05-03 21:10:19 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/{user}" , func ( r chi . Router ) {
2023-10-10 12:13:00 +00:00
r . Use ( httpmw . ExtractUserParam ( options . Database ) )
2023-06-30 12:38:48 +00:00
r . Post ( "/convert-login" , api . postConvertLoginType )
2022-09-12 23:24:20 +00:00
r . Delete ( "/" , api . deleteUser )
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . userByName )
2024-01-30 22:02:21 +00:00
r . Get ( "/autofill-parameters" , api . userAutofillParameters )
2023-06-30 12:38:48 +00:00
r . Get ( "/login-type" , api . userLoginType )
2022-05-26 03:14:08 +00:00
r . Put ( "/profile" , api . putUserProfile )
2022-05-16 20:29:27 +00:00
r . Route ( "/status" , func ( r chi . Router ) {
2023-01-11 13:08:04 +00:00
r . Put ( "/suspend" , api . putSuspendUserAccount ( ) )
r . Put ( "/activate" , api . putActivateUserAccount ( ) )
2022-05-16 20:29:27 +00:00
} )
2023-12-14 17:38:44 +00:00
r . Put ( "/appearance" , api . putUserAppearanceSettings )
2022-05-06 14:20:08 +00:00
r . Route ( "/password" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Put ( "/" , api . putUserPassword )
2022-05-06 14:20:08 +00:00
} )
2022-05-03 21:10:19 +00:00
// These roles apply to the site wide permissions.
2022-05-26 03:14:08 +00:00
r . Put ( "/roles" , api . putUserRoles )
r . Get ( "/roles" , api . userRoles )
2022-05-03 21:10:19 +00:00
2022-06-27 18:50:52 +00:00
r . Route ( "/keys" , func ( r chi . Router ) {
2022-10-06 21:56:43 +00:00
r . Post ( "/" , api . postAPIKey )
2022-10-06 19:02:27 +00:00
r . Route ( "/tokens" , func ( r chi . Router ) {
r . Post ( "/" , api . postToken )
r . Get ( "/" , api . tokens )
2023-03-16 15:25:08 +00:00
r . Get ( "/tokenconfig" , api . tokenConfig )
2023-03-02 17:39:38 +00:00
r . Route ( "/{keyname}" , func ( r chi . Router ) {
r . Get ( "/" , api . apiKeyByName )
} )
2022-10-06 19:02:27 +00:00
} )
r . Route ( "/{keyid}" , func ( r chi . Router ) {
2023-03-02 17:39:38 +00:00
r . Get ( "/" , api . apiKeyByID )
2022-10-06 19:02:27 +00:00
r . Delete ( "/" , api . deleteAPIKey )
} )
2022-06-27 18:50:52 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/organizations" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . organizationsByUser )
r . Get ( "/{organizationname}" , api . organizationByUserAndName )
2022-03-07 17:40:54 +00:00
} )
2022-06-10 16:08:50 +00:00
r . Route ( "/workspace/{workspacename}" , func ( r chi . Router ) {
r . Get ( "/" , api . workspaceByOwnerAndName )
r . Get ( "/builds/{buildnumber}" , api . workspaceBuildByBuildNumber )
} )
2022-05-26 03:14:08 +00:00
r . Get ( "/gitsshkey" , api . gitSSHKey )
r . Put ( "/gitsshkey" , api . regenerateGitSSHKey )
2022-01-24 17:07:42 +00:00
} )
} )
} )
2022-04-11 21:06:15 +00:00
r . Route ( "/workspaceagents" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Post ( "/azure-instance-identity" , api . postWorkspaceAuthAzureInstanceIdentity )
r . Post ( "/aws-instance-identity" , api . postWorkspaceAuthAWSInstanceIdentity )
r . Post ( "/google-instance-identity" , api . postWorkspaceAuthGoogleInstanceIdentity )
2023-07-17 19:40:12 +00:00
r . With (
apiKeyMiddlewareOptional ,
httpmw . ExtractWorkspaceProxy ( httpmw . ExtractWorkspaceProxyConfig {
DB : options . Database ,
Optional : true ,
} ) ,
httpmw . RequireAPIKeyOrWorkspaceProxyAuth ( ) ,
) . Get ( "/connection" , api . workspaceAgentConnectionGeneric )
2022-04-11 21:06:15 +00:00
r . Route ( "/me" , func ( r chi . Router ) {
2024-03-14 16:27:32 +00:00
r . Use ( httpmw . ExtractWorkspaceAgentAndLatestBuild ( httpmw . ExtractWorkspaceAgentAndLatestBuildConfig {
2023-06-30 18:41:29 +00:00
DB : options . Database ,
Optional : false ,
} ) )
2023-12-18 12:53:28 +00:00
r . Get ( "/rpc" , api . workspaceAgentRPC )
2023-03-31 20:26:19 +00:00
r . Get ( "/manifest" , api . workspaceAgentManifest )
// This route is deprecated and will be removed in a future release.
// New agents will use /me/manifest instead.
r . Get ( "/metadata" , api . workspaceAgentManifest )
2023-02-07 21:35:09 +00:00
r . Post ( "/startup" , api . postWorkspaceAgentStartup )
2023-07-28 15:57:23 +00:00
r . Patch ( "/startup-logs" , api . patchWorkspaceAgentLogsDeprecated )
r . Patch ( "/logs" , api . patchWorkspaceAgentLogs )
2022-09-23 19:51:04 +00:00
r . Post ( "/app-health" , api . postWorkspaceAppHealth )
2023-10-09 23:04:35 +00:00
// Deprecated: Required to support legacy agents
2022-10-25 00:46:24 +00:00
r . Get ( "/gitauth" , api . workspaceAgentsGitAuth )
2023-10-09 23:04:35 +00:00
r . Get ( "/external-auth" , api . workspaceAgentsExternalAuth )
2022-05-26 03:14:08 +00:00
r . Get ( "/gitsshkey" , api . agentGitSSHKey )
2022-09-01 01:09:44 +00:00
r . Get ( "/coordinate" , api . workspaceAgentCoordinate )
2022-11-18 22:46:53 +00:00
r . Post ( "/report-stats" , api . workspaceAgentReportStats )
2023-01-24 12:24:27 +00:00
r . Post ( "/report-lifecycle" , api . workspaceAgentReportLifecycle )
2023-10-13 13:37:55 +00:00
r . Post ( "/metadata" , api . workspaceAgentPostMetadata )
r . Post ( "/metadata/{key}" , api . workspaceAgentPostMetadataDeprecated )
2022-03-07 17:40:54 +00:00
} )
2022-04-11 21:06:15 +00:00
r . Route ( "/{workspaceagent}" , func ( r chi . Router ) {
2022-03-07 17:40:54 +00:00
r . Use (
2023-04-17 19:57:21 +00:00
// Allow either API key or external workspace proxy auth and require it.
apiKeyMiddlewareOptional ,
httpmw . ExtractWorkspaceProxy ( httpmw . ExtractWorkspaceProxyConfig {
DB : options . Database ,
Optional : true ,
} ) ,
httpmw . RequireAPIKeyOrWorkspaceProxyAuth ( ) ,
2022-04-11 21:06:15 +00:00
httpmw . ExtractWorkspaceAgentParam ( options . Database ) ,
2022-06-10 14:46:48 +00:00
httpmw . ExtractWorkspaceParam ( options . Database ) ,
2022-03-07 17:40:54 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaceAgent )
2023-03-31 20:26:19 +00:00
r . Get ( "/watch-metadata" , api . watchWorkspaceAgentMetadata )
2023-07-28 15:57:23 +00:00
r . Get ( "/startup-logs" , api . workspaceAgentLogsDeprecated )
r . Get ( "/logs" , api . workspaceAgentLogs )
2022-10-06 12:38:22 +00:00
r . Get ( "/listening-ports" , api . workspaceAgentListeningPorts )
2022-09-01 01:09:44 +00:00
r . Get ( "/connection" , api . workspaceAgentConnection )
r . Get ( "/coordinate" , api . workspaceAgentClientCoordinate )
2023-04-05 18:41:55 +00:00
// PTY is part of workspaceAppServer.
2022-02-21 20:36:29 +00:00
} )
} )
2022-05-18 15:09:07 +00:00
r . Route ( "/workspaces" , func ( r chi . Router ) {
2022-02-12 19:34:04 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-02-12 19:34:04 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaces )
2022-05-18 15:09:07 +00:00
r . Route ( "/{workspace}" , func ( r chi . Router ) {
r . Use (
httpmw . ExtractWorkspaceParam ( options . Database ) ,
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspace )
2022-08-26 09:28:38 +00:00
r . Patch ( "/" , api . patchWorkspace )
2022-05-18 15:09:07 +00:00
r . Route ( "/builds" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaceBuilds )
r . Post ( "/" , api . postWorkspaceBuilds )
2022-05-18 15:09:07 +00:00
} )
r . Route ( "/autostart" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Put ( "/" , api . putWorkspaceAutostart )
2022-05-18 15:09:07 +00:00
} )
2022-05-19 19:09:27 +00:00
r . Route ( "/ttl" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Put ( "/" , api . putWorkspaceTTL )
2022-05-18 15:09:07 +00:00
} )
2022-05-26 03:14:08 +00:00
r . Get ( "/watch" , api . watchWorkspace )
2022-05-26 17:08:11 +00:00
r . Put ( "/extend" , api . putExtendWorkspace )
2024-03-20 16:44:12 +00:00
r . Post ( "/usage" , api . postWorkspaceUsage )
2023-08-24 18:25:54 +00:00
r . Put ( "/dormant" , api . putWorkspaceDormant )
2024-01-24 13:39:19 +00:00
r . Put ( "/favorite" , api . putFavoriteWorkspace )
r . Delete ( "/favorite" , api . deleteFavoriteWorkspace )
2023-10-06 09:27:12 +00:00
r . Put ( "/autoupdates" , api . putWorkspaceAutoupdates )
2023-11-09 05:24:56 +00:00
r . Get ( "/resolve-autostart" , api . resolveAutostart )
2024-02-13 14:31:20 +00:00
r . Route ( "/port-share" , func ( r chi . Router ) {
r . Use (
httpmw . RequireExperiment ( api . Experiments , codersdk . ExperimentSharedPorts ) ,
)
r . Get ( "/" , api . workspaceAgentPortShares )
r . Post ( "/" , api . postWorkspaceAgentPortShare )
r . Delete ( "/" , api . deleteWorkspaceAgentPortShare )
} )
2022-04-07 09:03:35 +00:00
} )
2022-02-12 19:34:04 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/workspacebuilds/{workspacebuild}" , func ( r chi . Router ) {
2022-02-12 19:34:04 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-03-07 17:40:54 +00:00
httpmw . ExtractWorkspaceBuildParam ( options . Database ) ,
httpmw . ExtractWorkspaceParam ( options . Database ) ,
2022-02-12 19:34:04 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaceBuild )
r . Patch ( "/cancel" , api . patchCancelWorkspaceBuild )
r . Get ( "/logs" , api . workspaceBuildLogs )
2023-01-17 15:24:45 +00:00
r . Get ( "/parameters" , api . workspaceBuildParameters )
2024-02-15 16:13:44 +00:00
r . Get ( "/resources" , api . workspaceBuildResourcesDeprecated )
2022-05-26 03:14:08 +00:00
r . Get ( "/state" , api . workspaceBuildState )
2022-02-12 19:34:04 +00:00
} )
2022-09-22 22:30:32 +00:00
r . Route ( "/authcheck" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Post ( "/" , api . checkAuthorization )
} )
r . Route ( "/applications" , func ( r chi . Router ) {
r . Route ( "/host" , func ( r chi . Router ) {
// Don't leak the hostname to unauthenticated users.
r . Use ( apiKeyMiddleware )
r . Get ( "/" , api . appHost )
} )
r . Route ( "/auth-redirect" , func ( r chi . Router ) {
// We want to redirect to login if they are not authenticated.
r . Use ( apiKeyMiddlewareRedirect )
// This is a GET request as it's redirected to by the subdomain app
// handler and the login page.
r . Get ( "/" , api . workspaceApplicationAuth )
} )
} )
2023-01-26 01:03:47 +00:00
r . Route ( "/insights" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Get ( "/daus" , api . deploymentDAUs )
2023-09-26 16:42:16 +00:00
r . Get ( "/user-activity" , api . insightsUserActivity )
2023-07-21 18:00:19 +00:00
r . Get ( "/user-latency" , api . insightsUserLatency )
r . Get ( "/templates" , api . insightsTemplates )
2023-01-26 01:03:47 +00:00
} )
2023-01-25 21:27:36 +00:00
r . Route ( "/debug" , func ( r chi . Router ) {
r . Use (
apiKeyMiddleware ,
// Ensure only owners can access debug endpoints.
func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( rw http . ResponseWriter , r * http . Request ) {
if ! api . Authorize ( r , rbac . ActionRead , rbac . ResourceDebugInfo ) {
httpapi . ResourceNotFound ( rw )
return
}
next . ServeHTTP ( rw , r )
} )
} ,
)
r . Get ( "/coordinator" , api . debugCoordinator )
2023-11-13 23:14:12 +00:00
r . Get ( "/tailnet" , api . debugTailnet )
2023-11-28 17:15:17 +00:00
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 )
} )
} )
2023-05-30 19:22:32 +00:00
r . Get ( "/ws" , ( & healthcheck . WebsocketEchoServer { } ) . ServeHTTP )
2023-11-27 16:47:23 +00:00
r . Route ( "/{user}" , func ( r chi . Router ) {
r . Use ( httpmw . ExtractUserParam ( options . Database ) )
r . Get ( "/debug-link" , api . userDebugOIDC )
} )
2024-03-21 21:53:41 +00:00
if options . DERPServer != nil {
r . Route ( "/derp" , func ( r chi . Router ) {
r . Get ( "/traffic" , options . DERPServer . ServeDebugTraffic )
} )
}
feat: expose DERP server debug metrics (#12135)
Adds some debug endpoints for looking into the DERP server.
The `api/v2/debug/derp/traffic` endpoint requires the `ss` utility to be present in order to function. I have *not* added the `iproute2` package to our base image as it adds 11MB, so this endpoint won't be useful by default. However, in a debugging situation, we could exec into the container and then `apk add iproute2`, or build a special debug image.
The `api/v2/debug/expvar` handler contains DERP metrics as well as commandline and memstats.
Example:
```
{
"alert_failed": 0,
"alert_generated": 0,
"cmdline": ["/Users/spike/repos/coder/build/coder_darwin_arm64","--global-config","/Users/spike/repos/coder/.coderv2","server","--http-address","0.0.0.0:3000","--swagger-enable","--access-url","http://127.0.0.1:3000","--dangerous-allow-cors-requests=true"],
"derp": {"accepts": 1, "average_queue_duration_ms": 0, "bytes_received": 0, "bytes_sent": 0, "counter_packets_dropped_reason": {"gone_disconnected": 0, "gone_not_here": 0, "queue_head": 0, "queue_tail": 0, "unknown_dest": 0, "unknown_dest_on_fwd": 0, "write_error": 0}, "counter_packets_dropped_type": {"disco": 0, "other": 0}, "counter_packets_received_kind": {"disco": 0, "other": 0}, "counter_tcp_rtt": {}, "counter_total_dup_client_conns": 0, "gauge_clients_local": 1, "gauge_clients_remote": 0, "gauge_clients_total": 1, "gauge_current_connections": 1, "gauge_current_dup_client_conns": 0, "gauge_current_dup_client_keys": 0, "gauge_current_file_descriptors": 0, "gauge_current_home_connections": 1, "gauge_memstats_sys0": 20874504, "gauge_watchers": 0, "got_ping": 0, "home_moves_in": 0, "home_moves_out": 0, "multiforwarder_created": 0, "multiforwarder_deleted": 0, "packet_forwarder_delete_other_value": 0, "packets_dropped": 0, "packets_forwarded_in": 0, "packets_forwarded_out": 0, "packets_received": 0, "packets_sent": 0, "peer_gone_disconnected_frames": 0, "peer_gone_not_here_frames": 0, "sent_pong": 0, "unknown_frames": 0, "version": "1.47.0-dev20240214-t64db8c604"},
"memstats": {"Alloc":286506256,"TotalAlloc":297594632,"Sys":310621512,"Lookups":0,"Mallocs":304204,"Frees":171570,"HeapAlloc":286506256,"HeapSys":294060032,"HeapIdle":3694592,"HeapInuse":290365440,"HeapReleased":3620864,"HeapObjects":132634,"StackInuse":3735552,"StackSys":3735552,"MSpanInuse":347256,"MSpanSys":358512,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":1469877,"GCSys":9434896,"OtherSys":1547043,"NextGC":551867656,"LastGC":1707892877408883000,"PauseTotalNs":1247000,"PauseNs":[200333,229375,239875,209542,106958,203792,57125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1707892876217481000,1707892876219726000,1707892876222273000,1707892876226151000,1707892876234815000,1707892877398146000,1707892877408883000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":7,"NumForcedGC":0,"GCCPUFraction":0.0022425810335762954,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":14396,"Frees":9143},{"Size":16,"Mallocs":89090,"Frees":50507},{"Size":24,"Mallocs":40839,"Frees":24456},{"Size":32,"Mallocs":22404,"Frees":12379},{"Size":48,"Mallocs":51174,"Frees":23718},{"Size":64,"Mallocs":15406,"Frees":3501},{"Size":80,"Mallocs":6688,"Frees":2352},{"Size":96,"Mallocs":2567,"Frees":374},{"Size":112,"Mallocs":19371,"Frees":16883},{"Size":128,"Mallocs":2873,"Frees":1061},{"Size":144,"Mallocs":5600,"Frees":2742},{"Size":160,"Mallocs":2159,"Frees":622},{"Size":176,"Mallocs":454,"Frees":86},{"Size":192,"Mallocs":227,"Frees":128},{"Size":208,"Mallocs":1407,"Frees":732},{"Size":224,"Mallocs":1365,"Frees":1090},{"Size":240,"Mallocs":82,"Frees":48},{"Size":256,"Mallocs":310,"Frees":162},{"Size":288,"Mallocs":1945,"Frees":562},{"Size":320,"Mallocs":1200,"Frees":458},{"Size":352,"Mallocs":133,"Frees":33},{"Size":384,"Mallocs":582,"Frees":51},{"Size":416,"Mallocs":747,"Frees":200},{"Size":448,"Mallocs":113,"Frees":22},{"Size":480,"Mallocs":34,"Frees":21},{"Size":512,"Mallocs":951,"Frees":91},{"Size":576,"Mallocs":364,"Frees":122},{"Size":640,"Mallocs":532,"Frees":270},{"Size":704,"Mallocs":93,"Frees":39},{"Size":768,"Mallocs":83,"Frees":35},{"Size":896,"Mallocs":308,"Frees":175},{"Size":1024,"Mallocs":226,"Frees":122},{"Size":1152,"Mallocs":198,"Frees":100},{"Size":1280,"Mallocs":314,"Frees":171},{"Size":1408,"Mallocs":77,"Frees":47},{"Size":1536,"Mallocs":80,"Frees":54},{"Size":1792,"Mallocs":199,"Frees":107},{"Size":2048,"Mallocs":112,"Frees":48},{"Size":2304,"Mallocs":71,"Frees":32},{"Size":2688,"Mallocs":206,"Frees":81},{"Size":3072,"Mallocs":39,"Frees":15},{"Size":3200,"Mallocs":16,"Frees":7},{"Size":3456,"Mallocs":44,"Frees":29},{"Size":4096,"Mallocs":192,"Frees":83},{"Size":4864,"Mallocs":44,"Frees":25},{"Size":5376,"Mallocs":105,"Frees":43},{"Size":6144,"Mallocs":25,"Frees":5},{"Size":6528,"Mallocs":22,"Frees":7},{"Size":6784,"Mallocs":3,"Frees":0},{"Size":6912,"Mallocs":4,"Frees":2},{"Size":8192,"Mallocs":59,"Frees":10},{"Size":9472,"Mallocs":31,"Frees":12},{"Size":9728,"Mallocs":5,"Frees":2},{"Size":10240,"Mallocs":5,"Frees":0},{"Size":10880,"Mallocs":27,"Frees":11},{"Size":12288,"Mallocs":4,"Frees":1},{"Size":13568,"Mallocs":4,"Frees":2},{"Size":14336,"Mallocs":9,"Frees":2},{"Size":16384,"Mallocs":10,"Frees":2},{"Size":18432,"Mallocs":4,"Frees":2}]},
"warning_failed": 0,
"warning_generated": 0
}
```
If we find the DERP metrics useful we could consider how to include them in Prometheus scrapes based on the tailnet `varz` package. That's for a later PR if at all.
2024-02-14 11:11:45 +00:00
r . Method ( "GET" , "/expvar" , expvar . Handler ( ) ) // contains DERP metrics as well as cmdline and memstats
2023-01-25 21:27:36 +00:00
} )
2024-03-25 19:52:22 +00:00
// Manage OAuth2 applications that can use Coder as an OAuth2 provider.
r . Route ( "/oauth2-provider" , func ( r chi . Router ) {
r . Use (
apiKeyMiddleware ,
api . oAuth2ProviderMiddleware ,
)
r . Route ( "/apps" , func ( r chi . Router ) {
r . Get ( "/" , api . oAuth2ProviderApps )
r . Post ( "/" , api . postOAuth2ProviderApp )
r . Route ( "/{app}" , func ( r chi . Router ) {
r . Use ( httpmw . ExtractOAuth2ProviderApp ( options . Database ) )
r . Get ( "/" , api . oAuth2ProviderApp )
r . Put ( "/" , api . putOAuth2ProviderApp )
r . Delete ( "/" , api . deleteOAuth2ProviderApp )
r . Route ( "/secrets" , func ( r chi . Router ) {
r . Get ( "/" , api . oAuth2ProviderAppSecrets )
r . Post ( "/" , api . postOAuth2ProviderAppSecret )
r . Route ( "/{secretID}" , func ( r chi . Router ) {
r . Use ( httpmw . ExtractOAuth2ProviderAppSecret ( options . Database ) )
r . Delete ( "/" , api . deleteOAuth2ProviderAppSecret )
} )
} )
} )
} )
} )
2022-01-13 22:55:28 +00:00
} )
2022-06-13 18:14:22 +00:00
2022-12-19 17:43:46 +00:00
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 )
2023-01-11 10:42:49 +00:00
// See globalHTTPSwaggerHandler comment as to why we use a package
// global variable here.
r . Get ( "/swagger/*" , globalHTTPSwaggerHandler )
2024-02-20 21:50:30 +00:00
} else {
swaggerDisabled := http . HandlerFunc ( func ( rw http . ResponseWriter , r * http . Request ) {
httpapi . Write ( context . Background ( ) , rw , http . StatusNotFound , codersdk . Response {
Message : "Swagger documentation is disabled." ,
} )
} )
r . Get ( "/swagger" , swaggerDisabled )
r . Get ( "/swagger/*" , swaggerDisabled )
2022-12-19 17:43:46 +00:00
}
2023-05-02 13:30:44 +00:00
// Add CSP headers to all static assets and pages. CSP headers only affect
// browsers, so these don't make sense on api routes.
cspMW := httpmw . CSPHeaders ( func ( ) [ ] string {
2023-05-22 18:02:39 +00:00
if api . DeploymentValues . Dangerous . AllowAllCors {
// In this mode, allow all external requests
return [ ] string { "*" }
}
2023-05-02 13:30:44 +00:00
if f := api . WorkspaceProxyHostsFn . Load ( ) ; f != nil {
return ( * f ) ( )
}
// By default we do not add extra websocket connections to the CSP
return [ ] string { }
} )
2023-06-18 18:57:27 +00:00
// Static file handler must be wrapped with HSTS handler if the
// StrictTransportSecurityAge is set. We only need to set this header on
// static files since it only affects browsers.
r . NotFound ( cspMW ( compressHandler ( httpmw . HSTS ( api . SiteHandler , options . StrictTransportSecurityCfg ) ) ) . ServeHTTP )
2023-05-11 20:42:30 +00:00
2024-03-28 20:49:43 +00:00
api . RootHandler = r
2023-05-11 20:42:30 +00:00
2022-05-26 03:14:08 +00:00
return api
2022-01-13 22:55:28 +00:00
}
2022-02-01 22:15:26 +00:00
2022-05-26 03:14:08 +00:00
type API struct {
2023-02-10 18:23:02 +00:00
// ctx is canceled immediately on shutdown, it can be used to abort
// interruptible tasks.
ctx context . Context
cancel context . CancelFunc
2023-06-30 12:38:48 +00:00
// DeploymentID is loaded from the database on startup.
DeploymentID string
2022-02-20 22:29:16 +00:00
* Options
2022-11-06 21:27:09 +00:00
// ID is a uniquely generated ID on initialization.
// This is used to associate objects with a specific
// Coder API instance, like workspace agents to a
// specific replica.
ID uuid . UUID
2022-09-22 15:14:22 +00:00
Auditor atomic . Pointer [ audit . Auditor ]
WorkspaceClientCoordinateOverride atomic . Pointer [ func ( rw http . ResponseWriter ) bool ]
2022-10-17 13:43:30 +00:00
TailnetCoordinator atomic . Pointer [ tailnet . Coordinator ]
2023-12-15 10:10:24 +00:00
TailnetClientService * tailnet . ClientService
2022-11-14 17:57:33 +00:00
QuotaCommitter atomic . Pointer [ proto . QuotaCommitter ]
2024-01-29 08:17:31 +00:00
AppearanceFetcher atomic . Pointer [ appearance . Fetcher ]
2023-05-02 13:30:44 +00:00
// WorkspaceProxyHostsFn returns the hosts of healthy workspace proxies
// for header reasons.
WorkspaceProxyHostsFn atomic . Pointer [ func ( ) [ ] string ]
// TemplateScheduleStore is a pointer to an atomic pointer because this is
// passed to another struct, and we want them all to be the same reference.
TemplateScheduleStore * atomic . Pointer [ schedule . TemplateScheduleStore ]
2023-07-20 13:35:41 +00:00
// UserQuietHoursScheduleStore is a pointer to an atomic pointer for the
// same reason as TemplateScheduleStore.
UserQuietHoursScheduleStore * atomic . Pointer [ schedule . UserQuietHoursScheduleStore ]
2023-07-26 16:21:04 +00:00
// DERPMapper mutates the DERPMap to include workspace proxies.
DERPMapper atomic . Pointer [ func ( derpMap * tailcfg . DERPMap ) * tailcfg . DERPMap ]
2023-10-18 22:07:21 +00:00
// AccessControlStore is a pointer to an atomic pointer since it is
// passed to dbauthz.
AccessControlStore * atomic . Pointer [ dbauthz . AccessControlStore ]
2024-02-13 14:31:20 +00:00
PortSharer atomic . Pointer [ portsharing . PortSharer ]
2023-02-03 17:38:36 +00:00
HTTPAuth * HTTPAuthorizer
2022-02-20 22:29:16 +00:00
2022-09-20 04:11:01 +00:00
// APIHandler serves "/api/v2"
APIHandler chi . Router
// RootHandler serves "/"
RootHandler chi . Router
2022-09-01 01:09:44 +00:00
2023-06-18 18:57:27 +00:00
// SiteHandler serves static files for the dashboard.
SiteHandler * site . Handler
2022-11-16 22:34:06 +00:00
WebsocketWaitMutex sync . Mutex
WebsocketWaitGroup sync . WaitGroup
2023-03-01 22:18:14 +00:00
derpCloseFunc func ( )
2022-11-16 22:34:06 +00:00
2023-03-07 19:38:11 +00:00
metricsCache * metricscache . Cache
updateChecker * updatecheck . Checker
2023-04-04 00:59:41 +00:00
WorkspaceAppsProvider workspaceapps . SignedTokenProvider
2023-04-05 18:41:55 +00:00
workspaceAppServer * workspaceapps . Server
2023-07-12 22:37:31 +00:00
agentProvider workspaceapps . AgentProvider
2023-01-18 19:12:53 +00:00
// Experiments contains the list of experiments currently enabled.
// This is used to gate features that are not yet ready for production.
Experiments codersdk . Experiments
2023-04-03 06:28:42 +00:00
2024-03-26 17:44:31 +00:00
healthCheckGroup * singleflight . Group [ string , * healthsdk . HealthcheckReport ]
healthCheckCache atomic . Pointer [ healthsdk . HealthcheckReport ]
2023-08-04 16:00:42 +00:00
statsBatcher * batchstats . Batcher
2023-09-19 06:25:57 +00:00
Acquirer * provisionerdserver . Acquirer
2024-03-22 16:42:43 +00:00
// dbRolluper rolls up template usage stats from raw agent and app
// stats. This is used to provide insights in the WebUI.
dbRolluper * dbrollup . Rolluper
2024-03-20 16:44:12 +00:00
workspaceUsageTracker * workspaceusage . Tracker
2022-02-01 22:15:26 +00:00
}
2022-04-08 14:35:29 +00:00
2022-05-26 03:14:08 +00:00
// Close waits for all WebSocket connections to drain before returning.
2022-06-04 20:13:37 +00:00
func ( api * API ) Close ( ) error {
2023-02-10 18:23:02 +00:00
api . cancel ( )
2024-03-21 21:53:41 +00:00
if api . derpCloseFunc != nil {
api . derpCloseFunc ( )
}
2023-02-10 18:23:02 +00:00
2024-03-26 03:04:15 +00:00
wsDone := make ( chan struct { } )
timer := time . NewTimer ( 10 * time . Second )
defer timer . Stop ( )
go func ( ) {
api . WebsocketWaitMutex . Lock ( )
defer api . WebsocketWaitMutex . Unlock ( )
api . WebsocketWaitGroup . Wait ( )
close ( wsDone )
} ( )
// This will technically leak the above func if the timer fires, but this is
// maintly a last ditch effort to un-stuck coderd on shutdown. This
// shouldn't affect tests at all.
select {
case <- wsDone :
case <- timer . C :
api . Logger . Warn ( api . ctx , "websocket shutdown timed out after 10 seconds" )
}
2022-06-04 20:13:37 +00:00
2024-03-22 16:42:43 +00:00
api . dbRolluper . Close ( )
2022-09-01 19:58:23 +00:00
api . metricsCache . Close ( )
2022-12-01 17:43:28 +00:00
if api . updateChecker != nil {
api . updateChecker . Close ( )
}
2023-08-16 12:22:00 +00:00
_ = api . workspaceAppServer . Close ( )
2022-10-17 13:43:30 +00:00
coordinator := api . TailnetCoordinator . Load ( )
if coordinator != nil {
_ = ( * coordinator ) . Close ( )
}
2023-07-12 22:37:31 +00:00
_ = api . agentProvider . Close ( )
2024-03-20 16:44:12 +00:00
api . workspaceUsageTracker . Close ( )
2023-07-12 22:37:31 +00:00
return nil
2022-05-26 03:14:08 +00:00
}
2022-06-13 18:14:22 +00:00
func compressHandler ( h http . Handler ) http . Handler {
2023-05-11 19:30:51 +00:00
level := 5
if flag . Lookup ( "test.v" ) != nil {
level = 1
}
cmp := middleware . NewCompressor ( level ,
2022-06-13 18:14:22 +00:00
"text/*" ,
"application/*" ,
"image/*" ,
)
cmp . SetEncoder ( "br" , func ( w io . Writer , level int ) io . Writer {
return brotli . NewWriterLevel ( w , level )
} )
cmp . SetEncoder ( "zstd" , func ( w io . Writer , level int ) io . Writer {
zw , err := zstd . NewWriter ( w , zstd . WithEncoderLevel ( zstd . EncoderLevelFromZstd ( level ) ) )
if err != nil {
panic ( "invalid zstd compressor: " + err . Error ( ) )
}
return zw
} )
return cmp . Handler ( h )
}
2022-11-16 22:34:06 +00:00
2022-12-01 22:54:53 +00:00
// CreateInMemoryProvisionerDaemon is an in-memory connection to a provisionerd.
// Useful when starting coderd and provisionerd in the same process.
2024-01-10 11:29:57 +00:00
func ( api * API ) CreateInMemoryProvisionerDaemon ( dialCtx context . Context , name string ) ( client proto . DRPCProvisionerDaemonClient , err error ) {
2023-05-03 23:02:35 +00:00
tracer := api . TracerProvider . Tracer ( tracing . TracerName )
2023-12-15 08:41:39 +00:00
clientSession , serverSession := drpc . MemTransportPipe ( )
2022-11-16 22:34:06 +00:00
defer func ( ) {
if err != nil {
_ = clientSession . Close ( )
_ = serverSession . Close ( )
}
} ( )
2024-03-06 18:04:50 +00:00
// All in memory provisioners will be part of the default org for now.
//nolint:gocritic // in-memory provisioners are owned by system
defaultOrg , err := api . Database . GetDefaultOrganization ( dbauthz . AsSystemRestricted ( dialCtx ) )
if err != nil {
return nil , xerrors . Errorf ( "unable to fetch default org for in memory provisioner: %w" , err )
}
2023-12-18 16:44:52 +00:00
//nolint:gocritic // in-memory provisioners are owned by system
2024-01-10 11:29:57 +00:00
daemon , err := api . Database . UpsertProvisionerDaemon ( dbauthz . AsSystemRestricted ( dialCtx ) , database . UpsertProvisionerDaemonParams {
2024-03-06 18:04:50 +00:00
Name : name ,
OrganizationID : defaultOrg . ID ,
CreatedAt : dbtime . Now ( ) ,
2023-12-18 16:44:52 +00:00
Provisioners : [ ] database . ProvisionerType {
database . ProvisionerTypeEcho , database . ProvisionerTypeTerraform ,
} ,
Tags : provisionersdk . MutateTags ( uuid . Nil , nil ) ,
LastSeenAt : sql . NullTime { Time : dbtime . Now ( ) , Valid : true } ,
Version : buildinfo . Version ( ) ,
2024-02-21 18:18:38 +00:00
APIVersion : proto . CurrentVersion . String ( ) ,
2023-12-18 16:44:52 +00:00
} )
if err != nil {
return nil , xerrors . Errorf ( "failed to create in-memory provisioner daemon: %w" , err )
2022-11-16 22:34:06 +00:00
}
mux := drpcmux . New ( )
2024-01-10 11:29:57 +00:00
api . Logger . Info ( dialCtx , "starting in-memory provisioner daemon" , slog . F ( "name" , name ) )
2023-09-08 10:37:36 +00:00
logger := api . Logger . Named ( fmt . Sprintf ( "inmem-provisionerd-%s" , name ) )
2023-08-30 10:48:35 +00:00
srv , err := provisionerdserver . NewServer (
2023-12-18 16:44:52 +00:00
api . ctx , // use the same ctx as the API
2023-08-30 10:48:35 +00:00
api . AccessURL ,
2023-12-18 16:44:52 +00:00
daemon . ID ,
2024-03-18 17:48:13 +00:00
defaultOrg . ID ,
2023-09-08 10:37:36 +00:00
logger ,
2023-12-18 16:44:52 +00:00
daemon . Provisioners ,
provisionerdserver . Tags ( daemon . Tags ) ,
2023-08-30 10:48:35 +00:00
api . Database ,
api . Pubsub ,
2023-09-19 06:25:57 +00:00
api . Acquirer ,
2023-08-30 10:48:35 +00:00
api . Telemetry ,
tracer ,
& api . QuotaCommitter ,
& api . Auditor ,
api . TemplateScheduleStore ,
api . UserQuietHoursScheduleStore ,
api . DeploymentValues ,
provisionerdserver . Options {
2023-09-29 19:13:20 +00:00
OIDCConfig : api . OIDCConfig ,
ExternalAuthConfigs : api . ExternalAuthConfigs ,
2023-08-30 10:48:35 +00:00
} ,
)
if err != nil {
return nil , err
}
err = proto . DRPCRegisterProvisionerDaemon ( mux , srv )
2022-11-16 22:34:06 +00:00
if err != nil {
return nil , err
}
2023-05-03 23:02:35 +00:00
server := drpcserver . NewWithOptions ( & tracing . DRPCHandler { Handler : mux } ,
drpcserver . Options {
Log : func ( err error ) {
if xerrors . Is ( err , io . EOF ) {
return
}
2024-01-10 11:29:57 +00:00
logger . Debug ( dialCtx , "drpc server error" , slog . Error ( err ) )
2023-05-03 23:02:35 +00:00
} ,
2022-11-16 22:34:06 +00:00
} ,
2023-05-03 23:02:35 +00:00
)
2024-01-10 11:29:57 +00:00
// in-mem pipes aren't technically "websockets" but they have the same properties as far as the
// API is concerned: they are long-lived connections that we need to close before completing
// shutdown of the API.
api . WebsocketWaitMutex . Lock ( )
api . WebsocketWaitGroup . Add ( 1 )
api . WebsocketWaitMutex . Unlock ( )
2022-11-16 22:34:06 +00:00
go func ( ) {
2024-01-10 11:29:57 +00:00
defer api . WebsocketWaitGroup . Done ( )
// here we pass the background context, since we want the server to keep serving until the
// client hangs up. If we, say, pass the API context, then when it is canceled, we could
// drop a job that we locked in the database but never passed to the provisionerd. The
// provisionerd is local, in-mem, so there isn't a danger of losing contact with it and
// having a dead connection we don't know the status of.
err := server . Serve ( context . Background ( ) , serverSession )
logger . Info ( dialCtx , "provisioner daemon disconnected" , slog . Error ( err ) )
2023-09-08 10:37:36 +00:00
// close the sessions, so we don't leak goroutines serving them.
2022-11-16 22:34:06 +00:00
_ = clientSession . Close ( )
_ = serverSession . Close ( )
} ( )
2022-11-22 18:19:32 +00:00
return proto . NewDRPCProvisionerDaemonClient ( clientSession ) , nil
2022-11-16 22:34:06 +00:00
}
2023-01-18 19:12:53 +00:00
2023-07-26 16:21:04 +00:00
func ( api * API ) DERPMap ( ) * tailcfg . DERPMap {
fn := api . DERPMapper . Load ( )
if fn != nil {
return ( * fn ) ( api . Options . BaseDERPMap )
}
return api . Options . BaseDERPMap
}
2023-01-18 19:12:53 +00:00
// nolint:revive
2023-07-19 16:11:11 +00:00
func ReadExperiments ( log slog . Logger , raw [ ] string ) codersdk . Experiments {
2023-01-18 19:12:53 +00:00
exps := make ( [ ] codersdk . Experiment , 0 , len ( raw ) )
for _ , v := range raw {
switch v {
case "*" :
exps = append ( exps , codersdk . ExperimentsAll ... )
default :
ex := codersdk . Experiment ( strings . ToLower ( v ) )
if ! slice . Contains ( codersdk . ExperimentsAll , ex ) {
log . Warn ( context . Background ( ) , "🐉 HERE BE DRAGONS: opting into hidden experiment" , slog . F ( "experiment" , ex ) )
}
exps = append ( exps , ex )
}
}
return exps
}