mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
@@ -167,6 +167,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
)
|
||||
r.Get("/coordinate", api.workspaceProxyCoordinate)
|
||||
r.Post("/issue-signed-app-token", api.workspaceProxyIssueSignedAppToken)
|
||||
r.Post("/app-stats", api.workspaceProxyReportAppStats)
|
||||
r.Post("/register", api.workspaceProxyRegister)
|
||||
r.Post("/deregister", api.workspaceProxyDeregister)
|
||||
})
|
||||
|
||||
@@ -110,6 +110,10 @@ func NewWorkspaceProxy(t *testing.T, coderdAPI *coderd.API, owner *codersdk.Clie
|
||||
})
|
||||
require.NoError(t, err, "failed to create workspace proxy")
|
||||
|
||||
// Inherit collector options from coderd, but keep the wsproxy reporter.
|
||||
statsCollectorOptions := coderdAPI.Options.WorkspaceAppsStatsCollectorOptions
|
||||
statsCollectorOptions.Reporter = nil
|
||||
|
||||
wssrv, err := wsproxy.New(ctx, &wsproxy.Options{
|
||||
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
Experiments: options.Experiments,
|
||||
@@ -129,6 +133,7 @@ func NewWorkspaceProxy(t *testing.T, coderdAPI *coderd.API, owner *codersdk.Clie
|
||||
DERPEnabled: !options.DerpDisabled,
|
||||
DERPOnly: options.DerpOnly,
|
||||
DERPServerRelayAddress: accessURL.String(),
|
||||
StatsCollectorOptions: statsCollectorOptions,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
|
||||
@@ -497,6 +497,36 @@ func (api *API) workspaceProxyIssueSignedAppToken(rw http.ResponseWriter, r *htt
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Report workspace app stats
|
||||
// @ID report-workspace-app-stats
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Tags Enterprise
|
||||
// @Param request body wsproxysdk.ReportAppStatsRequest true "Report app stats request"
|
||||
// @Success 204
|
||||
// @Router /workspaceproxies/me/app-stats [post]
|
||||
// @x-apidocgen {"skip": true}
|
||||
func (api *API) workspaceProxyReportAppStats(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
_ = httpmw.WorkspaceProxy(r) // Ensure the proxy is authenticated.
|
||||
|
||||
var req wsproxysdk.ReportAppStatsRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
api.Logger.Debug(ctx, "report app stats", slog.F("stats", req.Stats))
|
||||
|
||||
reporter := api.WorkspaceAppsStatsCollectorOptions.Reporter
|
||||
if err := reporter.Report(ctx, req.Stats); err != nil {
|
||||
api.Logger.Error(ctx, "report app stats failed", slog.Error(err))
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
// workspaceProxyRegister is used to register a new workspace proxy. When a proxy
|
||||
// comes online, it will announce itself to this endpoint. This updates its values
|
||||
// in the database and returns a signed token that can be used to authenticate
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package wsproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/coder/coder/coderd/workspaceapps"
|
||||
"github.com/coder/coder/enterprise/wsproxy/wsproxysdk"
|
||||
)
|
||||
|
||||
var _ workspaceapps.StatsReporter = (*appStatsReporter)(nil)
|
||||
|
||||
type appStatsReporter struct {
|
||||
Client *wsproxysdk.Client
|
||||
}
|
||||
|
||||
func (r *appStatsReporter) Report(ctx context.Context, stats []workspaceapps.StatsReport) error {
|
||||
err := r.Client.ReportAppStats(ctx, wsproxysdk.ReportAppStatsRequest{
|
||||
Stats: stats,
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -79,6 +79,8 @@ type Options struct {
|
||||
// By default, CORs is set to accept external requests
|
||||
// from the dashboardURL. This should only be used in development.
|
||||
AllowAllCors bool
|
||||
|
||||
StatsCollectorOptions workspaceapps.StatsCollectorOptions
|
||||
}
|
||||
|
||||
func (o *Options) Validate() error {
|
||||
@@ -262,8 +264,17 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
|
||||
}
|
||||
}
|
||||
|
||||
workspaceAppsLogger := opts.Logger.Named("workspaceapps")
|
||||
if opts.StatsCollectorOptions.Logger == nil {
|
||||
named := workspaceAppsLogger.Named("stats_collector")
|
||||
opts.StatsCollectorOptions.Logger = &named
|
||||
}
|
||||
if opts.StatsCollectorOptions.Reporter == nil {
|
||||
opts.StatsCollectorOptions.Reporter = &appStatsReporter{Client: client}
|
||||
}
|
||||
|
||||
s.AppServer = &workspaceapps.Server{
|
||||
Logger: opts.Logger.Named("workspaceapps"),
|
||||
Logger: workspaceAppsLogger,
|
||||
DashboardURL: opts.DashboardURL,
|
||||
AccessURL: opts.AccessURL,
|
||||
Hostname: opts.AppHostname,
|
||||
@@ -279,9 +290,11 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
|
||||
},
|
||||
AppSecurityKey: secKey,
|
||||
|
||||
AgentProvider: agentProvider,
|
||||
DisablePathApps: opts.DisablePathApps,
|
||||
SecureAuthCookie: opts.SecureAuthCookie,
|
||||
|
||||
AgentProvider: agentProvider,
|
||||
StatsCollector: workspaceapps.NewStatsCollector(opts.StatsCollectorOptions),
|
||||
}
|
||||
|
||||
derpHandler := derphttp.Handler(derpServer)
|
||||
|
||||
@@ -478,6 +478,7 @@ func TestWorkspaceProxyWorkspaceApps_Wsconncache(t *testing.T) {
|
||||
"CF-Connecting-IP",
|
||||
},
|
||||
},
|
||||
WorkspaceAppsStatsCollectorOptions: opts.StatsCollectorOptions,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
@@ -536,6 +537,7 @@ func TestWorkspaceProxyWorkspaceApps_SingleTailnet(t *testing.T) {
|
||||
"CF-Connecting-IP",
|
||||
},
|
||||
},
|
||||
WorkspaceAppsStatsCollectorOptions: opts.StatsCollectorOptions,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
|
||||
@@ -152,6 +152,25 @@ func (c *Client) IssueSignedAppTokenHTML(ctx context.Context, rw http.ResponseWr
|
||||
return res, true
|
||||
}
|
||||
|
||||
type ReportAppStatsRequest struct {
|
||||
Stats []workspaceapps.StatsReport `json:"stats"`
|
||||
}
|
||||
|
||||
// ReportAppStats reports the given app stats to the primary coder server.
|
||||
func (c *Client) ReportAppStats(ctx context.Context, req ReportAppStatsRequest) error {
|
||||
resp, err := c.Request(ctx, http.MethodPost, "/api/v2/workspaceproxies/me/app-stats", req)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return codersdk.ReadBodyAsError(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RegisterWorkspaceProxyRequest struct {
|
||||
// AccessURL that hits the workspace proxy api.
|
||||
AccessURL string `json:"access_url"`
|
||||
|
||||
Reference in New Issue
Block a user