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"
|
2022-11-16 22:34:06 +00:00
|
|
|
"encoding/json"
|
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"
|
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-11-16 22:34:06 +00:00
|
|
|
"github.com/moby/moby/pkg/namesgenerator"
|
2022-08-08 15:09:46 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
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"
|
2022-01-20 13:46:51 +00:00
|
|
|
|
2022-01-13 22:55:28 +00:00
|
|
|
"cdr.dev/slog"
|
2022-04-07 17:18:58 +00:00
|
|
|
"github.com/coder/coder/buildinfo"
|
2022-09-20 04:11:01 +00:00
|
|
|
"github.com/coder/coder/coderd/audit"
|
2022-03-28 19:31:03 +00:00
|
|
|
"github.com/coder/coder/coderd/awsidentity"
|
2022-03-25 21:07:45 +00:00
|
|
|
"github.com/coder/coder/coderd/database"
|
2022-11-16 22:34:06 +00:00
|
|
|
"github.com/coder/coder/coderd/database/dbtype"
|
2022-10-25 00:46:24 +00:00
|
|
|
"github.com/coder/coder/coderd/gitauth"
|
2022-04-06 00:18:26 +00:00
|
|
|
"github.com/coder/coder/coderd/gitsshkey"
|
2022-03-25 21:07:45 +00:00
|
|
|
"github.com/coder/coder/coderd/httpapi"
|
|
|
|
"github.com/coder/coder/coderd/httpmw"
|
2022-09-01 19:58:23 +00:00
|
|
|
"github.com/coder/coder/coderd/metricscache"
|
2022-11-16 22:34:06 +00:00
|
|
|
"github.com/coder/coder/coderd/provisionerdserver"
|
2022-05-03 21:10:19 +00:00
|
|
|
"github.com/coder/coder/coderd/rbac"
|
2022-06-17 05:26:40 +00:00
|
|
|
"github.com/coder/coder/coderd/telemetry"
|
2022-05-19 22:43:07 +00:00
|
|
|
"github.com/coder/coder/coderd/tracing"
|
2022-12-01 17:43:28 +00:00
|
|
|
"github.com/coder/coder/coderd/updatecheck"
|
2022-06-04 20:13:37 +00:00
|
|
|
"github.com/coder/coder/coderd/wsconncache"
|
2022-04-07 17:18:58 +00:00
|
|
|
"github.com/coder/coder/codersdk"
|
2022-11-14 17:57:33 +00:00
|
|
|
"github.com/coder/coder/provisionerd/proto"
|
2022-11-16 22:34:06 +00:00
|
|
|
"github.com/coder/coder/provisionersdk"
|
2022-01-18 21:13:19 +00:00
|
|
|
"github.com/coder/coder/site"
|
2022-09-01 01:09:44 +00:00
|
|
|
"github.com/coder/coder/tailnet"
|
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.
|
2022-10-14 18:25:11 +00:00
|
|
|
// E.g. "*.apps.coder.com" or "*-apps.coder.com".
|
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
|
|
|
|
// generated by httpapi.CompileHostnamePattern(). It MUST be set if
|
|
|
|
// options.AppHostname is set.
|
|
|
|
AppHostnameRegex *regexp.Regexp
|
|
|
|
Logger slog.Logger
|
|
|
|
Database database.Store
|
|
|
|
Pubsub database.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
|
2022-04-22 20:27:55 +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. Specific routes may have their own limiters.
|
|
|
|
APIRateLimit int
|
|
|
|
AWSCertificates awsidentity.Certificates
|
2022-06-04 20:13:37 +00:00
|
|
|
Authorizer rbac.Authorizer
|
2022-04-22 20:27:55 +00:00
|
|
|
AzureCertificates x509.VerifyOptions
|
|
|
|
GoogleTokenValidator *idtoken.Validator
|
2022-04-23 22:58:57 +00:00
|
|
|
GithubOAuth2Config *GithubOAuth2Config
|
2022-08-01 04:05:35 +00:00
|
|
|
OIDCConfig *OIDCConfig
|
2022-11-05 00:03:01 +00:00
|
|
|
PrometheusRegistry *prometheus.Registry
|
2022-04-22 20:27:55 +00:00
|
|
|
SecureAuthCookie bool
|
|
|
|
SSHKeygenAlgorithm gitsshkey.Algorithm
|
2022-06-17 05:26:40 +00:00
|
|
|
Telemetry telemetry.Reporter
|
2022-09-16 16:43:22 +00:00
|
|
|
TracerProvider trace.TracerProvider
|
2022-08-25 19:32:35 +00:00
|
|
|
AutoImportTemplates []AutoImportTemplate
|
2022-10-25 00:46:24 +00:00
|
|
|
GitAuthConfigs []*gitauth.Config
|
2022-10-23 18:21:49 +00:00
|
|
|
RealIPConfig *httpmw.RealIPConfig
|
2022-11-16 23:09:49 +00:00
|
|
|
TrialGenerator func(ctx context.Context, email string) error
|
2022-10-17 13:43:30 +00:00
|
|
|
// TLSCertificates is used to mesh DERP servers securely.
|
|
|
|
TLSCertificates []tls.Certificate
|
|
|
|
TailnetCoordinator tailnet.Coordinator
|
|
|
|
DERPServer *derp.Server
|
2022-09-01 01:09:44 +00:00
|
|
|
DERPMap *tailcfg.DERPMap
|
2022-09-01 19:58:23 +00:00
|
|
|
|
|
|
|
MetricsCacheRefreshInterval time.Duration
|
|
|
|
AgentStatsRefreshInterval time.Duration
|
2022-10-04 19:45:00 +00:00
|
|
|
Experimental bool
|
2022-10-21 22:08:23 +00:00
|
|
|
DeploymentConfig *codersdk.DeploymentConfig
|
2022-12-01 17:43:28 +00:00
|
|
|
UpdateCheckOptions *updatecheck.Options // Set non-nil to enable update checking.
|
2022-12-14 14:44:29 +00:00
|
|
|
HTTPClient *http.Client
|
2022-01-13 22:55:28 +00:00
|
|
|
}
|
|
|
|
|
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{}
|
|
|
|
}
|
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 {
|
|
|
|
options.AgentConnectionUpdateFrequency = 3 * time.Second
|
|
|
|
}
|
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
|
|
|
|
}
|
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
|
|
|
|
}
|
2022-05-03 21:10:19 +00:00
|
|
|
if options.Authorizer == nil {
|
2022-09-23 16:26:04 +00:00
|
|
|
options.Authorizer = rbac.NewAuthorizer()
|
2022-05-03 21:10:19 +00:00
|
|
|
}
|
2022-11-05 00:03:01 +00:00
|
|
|
if options.PrometheusRegistry == nil {
|
|
|
|
options.PrometheusRegistry = prometheus.NewRegistry()
|
2022-08-08 15:09:46 +00:00
|
|
|
}
|
2022-09-01 01:09:44 +00:00
|
|
|
if options.TailnetCoordinator == nil {
|
|
|
|
options.TailnetCoordinator = tailnet.NewCoordinator()
|
|
|
|
}
|
2022-10-17 13:43:30 +00:00
|
|
|
if options.DERPServer == nil {
|
|
|
|
options.DERPServer = derp.NewServer(key.NewNode(), tailnet.Logger(options.Logger.Named("derp")))
|
|
|
|
}
|
2022-09-20 04:11:01 +00:00
|
|
|
if options.Auditor == nil {
|
|
|
|
options.Auditor = audit.NewNop()
|
2022-08-29 23:45:40 +00:00
|
|
|
}
|
2022-05-26 03:14:08 +00:00
|
|
|
|
2022-06-21 16:53:36 +00:00
|
|
|
siteCacheDir := options.CacheDir
|
|
|
|
if siteCacheDir != "" {
|
|
|
|
siteCacheDir = filepath.Join(siteCacheDir, "site")
|
|
|
|
}
|
|
|
|
binFS, err := site.ExtractOrReadBinFS(siteCacheDir, site.FS())
|
|
|
|
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"),
|
|
|
|
options.MetricsCacheRefreshInterval,
|
|
|
|
)
|
|
|
|
|
2022-05-26 03:14:08 +00:00
|
|
|
r := chi.NewRouter()
|
|
|
|
api := &API{
|
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,
|
2022-06-21 16:53:36 +00:00
|
|
|
siteHandler: site.Handler(site.FS(), binFS),
|
2022-09-20 04:11:01 +00:00
|
|
|
HTTPAuth: &HTTPAuthorizer{
|
2022-08-22 22:02:50 +00:00
|
|
|
Authorizer: options.Authorizer,
|
|
|
|
Logger: options.Logger,
|
|
|
|
},
|
2022-11-14 17:57:33 +00:00
|
|
|
metricsCache: metricsCache,
|
|
|
|
Auditor: atomic.Pointer[audit.Auditor]{},
|
2022-05-26 03:14:08 +00:00
|
|
|
}
|
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,
|
|
|
|
)
|
|
|
|
}
|
2022-09-20 04:11:01 +00:00
|
|
|
api.Auditor.Store(&options.Auditor)
|
2022-09-20 00:46:29 +00:00
|
|
|
api.workspaceAgentCache = wsconncache.New(api.dialWorkspaceAgentTailnet, 0)
|
2022-10-17 13:43:30 +00:00
|
|
|
api.TailnetCoordinator.Store(&options.TailnetCoordinator)
|
2022-07-11 18:46:01 +00:00
|
|
|
oauthConfigs := &httpmw.OAuth2Configs{
|
2022-04-23 22:58:57 +00:00
|
|
|
Github: options.GithubOAuth2Config,
|
2022-08-01 04:05:35 +00:00
|
|
|
OIDC: options.OIDCConfig,
|
2022-07-11 18:46:01 +00:00
|
|
|
}
|
2022-09-22 22:30:32 +00:00
|
|
|
|
|
|
|
apiKeyMiddleware := httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
|
|
|
|
DB: options.Database,
|
|
|
|
OAuth2Configs: oauthConfigs,
|
|
|
|
RedirectToLogin: false,
|
|
|
|
Optional: false,
|
|
|
|
})
|
|
|
|
// Same as above but it redirects to the login page.
|
|
|
|
apiKeyMiddlewareRedirect := httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
|
|
|
|
DB: options.Database,
|
|
|
|
OAuth2Configs: oauthConfigs,
|
|
|
|
RedirectToLogin: true,
|
|
|
|
Optional: false,
|
|
|
|
})
|
2022-05-03 21:10:19 +00:00
|
|
|
|
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),
|
2022-11-05 00:03:01 +00:00
|
|
|
httpmw.Prometheus(options.PrometheusRegistry),
|
2022-09-13 17:31:33 +00:00
|
|
|
// handleSubdomainApplications checks if the first subdomain is a valid
|
|
|
|
// app URL. If it is, it will serve that application.
|
|
|
|
api.handleSubdomainApplications(
|
|
|
|
// Middleware to impose on the served application.
|
2022-10-20 17:01:23 +00:00
|
|
|
httpmw.RateLimit(options.APIRateLimit, time.Minute),
|
2022-09-22 22:30:32 +00:00
|
|
|
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
|
|
|
|
DB: options.Database,
|
|
|
|
OAuth2Configs: oauthConfigs,
|
|
|
|
// The code handles the the case where the user is not
|
|
|
|
// authenticated automatically.
|
|
|
|
RedirectToLogin: false,
|
|
|
|
Optional: true,
|
|
|
|
}),
|
2022-10-14 16:46:38 +00:00
|
|
|
httpmw.ExtractUserParam(api.Database, false),
|
2022-09-13 17:31:33 +00:00
|
|
|
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
|
|
|
|
),
|
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) {
|
2022-09-21 22:07:00 +00:00
|
|
|
w.Header().Add("X-Coder-Build-Version", buildinfo.Version())
|
2022-09-01 19:58:23 +00:00
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
},
|
2022-09-13 19:26:46 +00:00
|
|
|
httpmw.CSRF(options.SecureAuthCookie),
|
2022-05-03 12:48:02 +00:00
|
|
|
)
|
|
|
|
|
2022-11-07 19:35:52 +00:00
|
|
|
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("OK")) })
|
|
|
|
|
2022-06-04 20:13:37 +00:00
|
|
|
apps := func(r chi.Router) {
|
|
|
|
r.Use(
|
2022-10-20 17:01:23 +00:00
|
|
|
httpmw.RateLimit(options.APIRateLimit, time.Minute),
|
2022-10-14 16:46:38 +00:00
|
|
|
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
|
|
|
|
DB: options.Database,
|
|
|
|
OAuth2Configs: oauthConfigs,
|
|
|
|
// Optional is true to allow for public apps. If an
|
|
|
|
// authorization check fails and the user is not authenticated,
|
|
|
|
// they will be redirected to the login page by the app handler.
|
|
|
|
RedirectToLogin: false,
|
|
|
|
Optional: true,
|
|
|
|
}),
|
|
|
|
// Redirect to the login page if the user tries to open an app with
|
|
|
|
// "me" as the username and they are not logged in.
|
|
|
|
httpmw.ExtractUserParam(api.Database, true),
|
2022-08-29 12:56:52 +00:00
|
|
|
// Extracts the <workspace.agent> from the url
|
|
|
|
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
|
2022-06-04 20:13:37 +00:00
|
|
|
)
|
2022-07-08 20:45:28 +00:00
|
|
|
r.HandleFunc("/*", api.workspaceAppsProxyPath)
|
2022-06-04 20:13:37 +00:00
|
|
|
}
|
|
|
|
// %40 is the encoded character of the @ symbol. VS Code Web does
|
|
|
|
// not handle character encoding properly, so it's safe to assume
|
|
|
|
// other applications might not as well.
|
2022-08-29 12:56:52 +00:00
|
|
|
r.Route("/%40{user}/{workspace_and_agent}/apps/{workspaceapp}", apps)
|
|
|
|
r.Route("/@{user}/{workspace_and_agent}/apps/{workspaceapp}", apps)
|
2022-09-01 16:41:47 +00:00
|
|
|
r.Route("/derp", func(r chi.Router) {
|
2022-10-17 13:43:30 +00:00
|
|
|
r.Get("/", derphttp.Handler(api.DERPServer).ServeHTTP)
|
2022-09-01 16:41:47 +00:00
|
|
|
// 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-06-04 20:13:37 +00:00
|
|
|
|
2022-10-25 00:46:24 +00:00
|
|
|
r.Route("/gitauth", func(r chi.Router) {
|
|
|
|
for _, gitAuthConfig := range options.GitAuthConfigs {
|
|
|
|
r.Route(fmt.Sprintf("/%s", gitAuthConfig.ID), func(r chi.Router) {
|
|
|
|
r.Use(
|
2022-12-14 14:44:29 +00:00
|
|
|
httpmw.ExtractOAuth2(gitAuthConfig, options.HTTPClient),
|
2022-10-25 00:46:24 +00:00
|
|
|
apiKeyMiddleware,
|
|
|
|
)
|
|
|
|
r.Get("/callback", api.gitAuthCallback(gitAuthConfig))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
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(
|
|
|
|
// Specific routes can specify smaller limits.
|
2022-10-20 17:01:23 +00:00
|
|
|
httpmw.RateLimit(options.APIRateLimit, time.Minute),
|
2022-04-04 22:32:05 +00:00
|
|
|
)
|
2022-01-13 22:55:28 +00:00
|
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
2022-09-21 22:07:00 +00:00
|
|
|
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Response{
|
2022-06-07 14:33:06 +00:00
|
|
|
//nolint:gocritic
|
2022-01-13 22:55:28 +00:00
|
|
|
Message: "👋",
|
|
|
|
})
|
|
|
|
})
|
2022-05-27 14:59:13 +00:00
|
|
|
// All CSP errors will be logged
|
|
|
|
r.Post("/csp/reports", api.logReportCSPViolations)
|
|
|
|
|
2022-04-07 17:18:58 +00:00
|
|
|
r.Route("/buildinfo", func(r chi.Router) {
|
|
|
|
r.Get("/", func(rw http.ResponseWriter, r *http.Request) {
|
2022-09-21 22:07:00 +00:00
|
|
|
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.BuildInfoResponse{
|
2022-04-07 17:18:58 +00:00
|
|
|
ExternalURL: buildinfo.ExternalURL(),
|
|
|
|
Version: buildinfo.Version(),
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2022-12-01 17:43:28 +00:00
|
|
|
r.Route("/updatecheck", func(r chi.Router) {
|
|
|
|
r.Get("/", api.updateCheck)
|
|
|
|
})
|
2022-10-21 22:08:23 +00:00
|
|
|
r.Route("/config", func(r chi.Router) {
|
2022-10-10 19:04:15 +00:00
|
|
|
r.Use(apiKeyMiddleware)
|
2022-10-21 22:08:23 +00:00
|
|
|
r.Get("/deployment", api.deploymentConfig)
|
2022-10-10 19:04:15 +00:00
|
|
|
})
|
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,
|
2022-04-04 22:32:05 +00:00
|
|
|
// This number is arbitrary, but reading/writing
|
|
|
|
// file content is expensive so it should be small.
|
2022-10-20 17:01:23 +00:00
|
|
|
httpmw.RateLimit(12, 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
|
|
|
})
|
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)
|
2022-11-15 16:24:13 +00:00
|
|
|
r.Route("/templateversions", func(r chi.Router) {
|
|
|
|
r.Post("/", api.postTemplateVersionsByOrganization)
|
|
|
|
r.Get("/{templateversionname}", api.templateVersionByOrganizationAndName)
|
2022-12-06 14:15:03 +00:00
|
|
|
r.Get("/{templateversionname}/previous", api.previousTemplateVersionByOrganizationAndName)
|
2022-11-15 16:24:13 +00:00
|
|
|
})
|
2022-05-27 16:19:13 +00:00
|
|
|
r.Route("/templates", func(r chi.Router) {
|
|
|
|
r.Post("/", api.postTemplateByOrganization)
|
|
|
|
r.Get("/", api.templatesByOrganization)
|
|
|
|
r.Get("/{templatename}", api.templateByOrganizationAndName)
|
2022-12-09 19:29:50 +00:00
|
|
|
r.Get("/examples", api.templateExamples)
|
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(
|
2022-10-14 16:46:38 +00:00
|
|
|
httpmw.ExtractUserParam(options.Database, false),
|
2022-05-27 16:19:13 +00:00
|
|
|
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
|
|
|
})
|
|
|
|
r.Route("/parameters/{scope}/{id}", func(r chi.Router) {
|
2022-05-31 13:06:42 +00:00
|
|
|
r.Use(apiKeyMiddleware)
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Post("/", api.postParameter)
|
|
|
|
r.Get("/", api.parameters)
|
2022-03-07 17:40:54 +00:00
|
|
|
r.Route("/{name}", func(r chi.Router) {
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Delete("/", api.deleteParameter)
|
2022-01-23 05:58:10 +00:00
|
|
|
})
|
2022-01-20 13:46:51 +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) {
|
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-01-25 19:52:58 +00:00
|
|
|
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Get("/", api.templateVersion)
|
|
|
|
r.Patch("/cancel", api.patchCancelTemplateVersion)
|
|
|
|
r.Get("/schema", api.templateVersionSchema)
|
|
|
|
r.Get("/parameters", api.templateVersionParameters)
|
|
|
|
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)
|
2022-10-20 17:01:23 +00:00
|
|
|
r.Group(func(r chi.Router) {
|
|
|
|
// We use a tight limit for password login to protect
|
|
|
|
// against audit-log write DoS, pbkdf2 DoS, and simple
|
|
|
|
// brute-force attacks.
|
|
|
|
//
|
|
|
|
// Making this too small can break tests.
|
|
|
|
r.Use(httpmw.RateLimit(60, time.Minute))
|
|
|
|
r.Post("/login", api.postLogin)
|
|
|
|
})
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Get("/authmethods", api.userAuthMethods)
|
2022-04-23 22:58:57 +00:00
|
|
|
r.Route("/oauth2", func(r chi.Router) {
|
|
|
|
r.Route("/github", func(r chi.Router) {
|
2022-12-14 14:44:29 +00:00
|
|
|
r.Use(httpmw.ExtractOAuth2(options.GithubOAuth2Config, options.HTTPClient))
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Get("/callback", api.userOAuth2Github)
|
2022-04-23 22:58:57 +00:00
|
|
|
})
|
|
|
|
})
|
2022-08-01 04:05:35 +00:00
|
|
|
r.Route("/oidc/callback", func(r chi.Router) {
|
2022-12-14 14:44:29 +00:00
|
|
|
r.Use(httpmw.ExtractOAuth2(options.OIDCConfig, options.HTTPClient))
|
2022-08-01 04:05:35 +00:00
|
|
|
r.Get("/", api.userOIDC)
|
|
|
|
})
|
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) {
|
2022-10-14 16:46:38 +00:00
|
|
|
r.Use(httpmw.ExtractUserParam(options.Database, false))
|
2022-09-12 23:24:20 +00:00
|
|
|
r.Delete("/", api.deleteUser)
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Get("/", api.userByName)
|
|
|
|
r.Put("/profile", api.putUserProfile)
|
2022-05-16 20:29:27 +00:00
|
|
|
r.Route("/status", func(r chi.Router) {
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Put("/suspend", api.putUserStatus(database.UserStatusSuspended))
|
2022-05-31 13:06:42 +00:00
|
|
|
r.Put("/activate", api.putUserStatus(database.UserStatusActive))
|
2022-05-16 20:29:27 +00:00
|
|
|
})
|
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)
|
|
|
|
})
|
|
|
|
r.Route("/{keyid}", func(r chi.Router) {
|
|
|
|
r.Get("/", api.apiKey)
|
|
|
|
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)
|
2022-04-11 21:06:15 +00:00
|
|
|
r.Route("/me", func(r chi.Router) {
|
2022-03-07 17:40:54 +00:00
|
|
|
r.Use(httpmw.ExtractWorkspaceAgent(options.Database))
|
2022-05-26 03:14:08 +00:00
|
|
|
r.Get("/metadata", api.workspaceAgentMetadata)
|
2022-08-31 15:33:50 +00:00
|
|
|
r.Post("/version", api.postWorkspaceAgentVersion)
|
2022-09-23 19:51:04 +00:00
|
|
|
r.Post("/app-health", api.postWorkspaceAppHealth)
|
2022-10-25 00:46:24 +00:00
|
|
|
r.Get("/gitauth", api.workspaceAgentsGitAuth)
|
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)
|
|
|
|
// DEPRECATED in favor of the POST endpoint above.
|
|
|
|
// TODO: remove in January 2023
|
|
|
|
r.Get("/report-stats", api.workspaceAgentReportStatsWebsocket)
|
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(
|
2022-04-23 22:58:57 +00:00
|
|
|
apiKeyMiddleware,
|
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)
|
|
|
|
r.Get("/pty", api.workspaceAgentPTY)
|
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)
|
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)
|
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)
|
|
|
|
r.Get("/resources", api.workspaceBuildResources)
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
})
|
2022-01-13 22:55:28 +00:00
|
|
|
})
|
2022-06-13 18:14:22 +00:00
|
|
|
|
|
|
|
r.NotFound(compressHandler(http.HandlerFunc(api.siteHandler.ServeHTTP)).ServeHTTP)
|
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 {
|
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]
|
2022-11-14 17:57:33 +00:00
|
|
|
QuotaCommitter atomic.Pointer[proto.QuotaCommitter]
|
2022-09-22 15:14:22 +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
|
|
|
|
2022-12-01 17:43:28 +00:00
|
|
|
siteHandler http.Handler
|
2022-11-16 22:34:06 +00:00
|
|
|
|
|
|
|
WebsocketWaitMutex sync.Mutex
|
|
|
|
WebsocketWaitGroup sync.WaitGroup
|
|
|
|
|
2022-12-01 17:43:28 +00:00
|
|
|
metricsCache *metricscache.Cache
|
2022-06-04 20:13:37 +00:00
|
|
|
workspaceAgentCache *wsconncache.Cache
|
2022-12-01 17:43:28 +00:00
|
|
|
updateChecker *updatecheck.Checker
|
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 {
|
2022-11-16 22:34:06 +00:00
|
|
|
api.WebsocketWaitMutex.Lock()
|
|
|
|
api.WebsocketWaitGroup.Wait()
|
|
|
|
api.WebsocketWaitMutex.Unlock()
|
2022-06-04 20:13:37 +00:00
|
|
|
|
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()
|
|
|
|
}
|
2022-10-17 13:43:30 +00:00
|
|
|
coordinator := api.TailnetCoordinator.Load()
|
|
|
|
if coordinator != nil {
|
|
|
|
_ = (*coordinator).Close()
|
|
|
|
}
|
2022-06-04 20:13:37 +00:00
|
|
|
return api.workspaceAgentCache.Close()
|
2022-05-26 03:14:08 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 18:14:22 +00:00
|
|
|
func compressHandler(h http.Handler) http.Handler {
|
|
|
|
cmp := middleware.NewCompressor(5,
|
|
|
|
"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.
|
2022-11-16 22:34:06 +00:00
|
|
|
func (api *API) CreateInMemoryProvisionerDaemon(ctx context.Context, debounce time.Duration) (client proto.DRPCProvisionerDaemonClient, err error) {
|
2022-11-22 18:19:32 +00:00
|
|
|
clientSession, serverSession := provisionersdk.MemTransportPipe()
|
2022-11-16 22:34:06 +00:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
_ = clientSession.Close()
|
|
|
|
_ = serverSession.Close()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
name := namesgenerator.GetRandomName(1)
|
|
|
|
daemon, err := api.Database.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{
|
|
|
|
ID: uuid.New(),
|
|
|
|
CreatedAt: database.Now(),
|
|
|
|
Name: name,
|
|
|
|
Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho, database.ProvisionerTypeTerraform},
|
|
|
|
Tags: dbtype.StringMap{
|
|
|
|
provisionerdserver.TagScope: provisionerdserver.ScopeOrganization,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("insert provisioner daemon %q: %w", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tags, err := json.Marshal(daemon.Tags)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("marshal tags: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mux := drpcmux.New()
|
|
|
|
err = proto.DRPCRegisterProvisionerDaemon(mux, &provisionerdserver.Server{
|
|
|
|
AccessURL: api.AccessURL,
|
|
|
|
ID: daemon.ID,
|
|
|
|
Database: api.Database,
|
|
|
|
Pubsub: api.Pubsub,
|
|
|
|
Provisioners: daemon.Provisioners,
|
|
|
|
Telemetry: api.Telemetry,
|
|
|
|
Tags: tags,
|
|
|
|
QuotaCommitter: &api.QuotaCommitter,
|
2022-11-22 18:22:56 +00:00
|
|
|
Auditor: &api.Auditor,
|
2022-11-16 22:34:06 +00:00
|
|
|
AcquireJobDebounce: debounce,
|
|
|
|
Logger: api.Logger.Named(fmt.Sprintf("provisionerd-%s", daemon.Name)),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
server := drpcserver.NewWithOptions(mux, drpcserver.Options{
|
|
|
|
Log: func(err error) {
|
|
|
|
if xerrors.Is(err, io.EOF) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
api.Logger.Debug(ctx, "drpc server error", slog.Error(err))
|
|
|
|
},
|
|
|
|
})
|
|
|
|
go func() {
|
|
|
|
err := server.Serve(ctx, serverSession)
|
|
|
|
if err != nil && !xerrors.Is(err, io.EOF) {
|
|
|
|
api.Logger.Debug(ctx, "provisioner daemon disconnected", slog.Error(err))
|
|
|
|
}
|
|
|
|
// close the sessions so we don't leak goroutines serving them.
|
|
|
|
_ = 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
|
|
|
}
|