mirror of
https://github.com/coder/coder.git
synced 2026-09-21 12:44:32 +08:00
chore: accept payload on workspace usage route (#13544)
This commit is contained in:
Generated
+44
-3
@@ -7527,6 +7527,9 @@ const docTemplate = `{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Workspaces"
|
||||
],
|
||||
@@ -7540,6 +7543,14 @@ const docTemplate = `{
|
||||
"name": "workspace",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Post workspace usage request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.PostWorkspaceUsageRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -9240,19 +9251,22 @@ const docTemplate = `{
|
||||
"example",
|
||||
"auto-fill-parameters",
|
||||
"multi-organization",
|
||||
"custom-roles"
|
||||
"custom-roles",
|
||||
"workspace-usage"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
||||
"ExperimentCustomRoles": "Allows creating runtime custom roles",
|
||||
"ExperimentExample": "This isn't used for anything.",
|
||||
"ExperimentMultiOrganization": "Requires organization context for interactions, default org is assumed."
|
||||
"ExperimentMultiOrganization": "Requires organization context for interactions, default org is assumed.",
|
||||
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking"
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"ExperimentExample",
|
||||
"ExperimentAutoFillParameters",
|
||||
"ExperimentMultiOrganization",
|
||||
"ExperimentCustomRoles"
|
||||
"ExperimentCustomRoles",
|
||||
"ExperimentWorkspaceUsage"
|
||||
]
|
||||
},
|
||||
"codersdk.ExternalAuth": {
|
||||
@@ -10223,6 +10237,18 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PostWorkspaceUsageRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"app_name": {
|
||||
"$ref": "#/definitions/codersdk.UsageAppName"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PprofConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11978,6 +12004,21 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UsageAppName": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"vscode",
|
||||
"jetbrains",
|
||||
"reconnecting-pty",
|
||||
"ssh"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"UsageAppNameVscode",
|
||||
"UsageAppNameJetbrains",
|
||||
"UsageAppNameReconnectingPty",
|
||||
"UsageAppNameSSH"
|
||||
]
|
||||
},
|
||||
"codersdk.User": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
Generated
+37
-3
@@ -6661,6 +6661,7 @@
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"tags": ["Workspaces"],
|
||||
"summary": "Post Workspace Usage by ID",
|
||||
"operationId": "post-workspace-usage-by-id",
|
||||
@@ -6672,6 +6673,14 @@
|
||||
"name": "workspace",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Post workspace usage request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.PostWorkspaceUsageRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -8272,19 +8281,22 @@
|
||||
"example",
|
||||
"auto-fill-parameters",
|
||||
"multi-organization",
|
||||
"custom-roles"
|
||||
"custom-roles",
|
||||
"workspace-usage"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
||||
"ExperimentCustomRoles": "Allows creating runtime custom roles",
|
||||
"ExperimentExample": "This isn't used for anything.",
|
||||
"ExperimentMultiOrganization": "Requires organization context for interactions, default org is assumed."
|
||||
"ExperimentMultiOrganization": "Requires organization context for interactions, default org is assumed.",
|
||||
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking"
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"ExperimentExample",
|
||||
"ExperimentAutoFillParameters",
|
||||
"ExperimentMultiOrganization",
|
||||
"ExperimentCustomRoles"
|
||||
"ExperimentCustomRoles",
|
||||
"ExperimentWorkspaceUsage"
|
||||
]
|
||||
},
|
||||
"codersdk.ExternalAuth": {
|
||||
@@ -9200,6 +9212,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PostWorkspaceUsageRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"app_name": {
|
||||
"$ref": "#/definitions/codersdk.UsageAppName"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PprofConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -10863,6 +10887,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UsageAppName": {
|
||||
"type": "string",
|
||||
"enum": ["vscode", "jetbrains", "reconnecting-pty", "ssh"],
|
||||
"x-enum-varnames": [
|
||||
"UsageAppNameVscode",
|
||||
"UsageAppNameJetbrains",
|
||||
"UsageAppNameReconnectingPty",
|
||||
"UsageAppNameSSH"
|
||||
]
|
||||
},
|
||||
"codersdk.User": {
|
||||
"type": "object",
|
||||
"required": ["created_at", "email", "id", "username"],
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/audit"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
@@ -1105,7 +1107,9 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||
// @ID post-workspace-usage-by-id
|
||||
// @Security CoderSessionToken
|
||||
// @Tags Workspaces
|
||||
// @Accept json
|
||||
// @Param workspace path string true "Workspace ID" format(uuid)
|
||||
// @Param request body codersdk.PostWorkspaceUsageRequest false "Post workspace usage request"
|
||||
// @Success 204
|
||||
// @Router /workspaces/{workspace}/usage [post]
|
||||
func (api *API) postWorkspaceUsage(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -1116,6 +1120,102 @@ func (api *API) postWorkspaceUsage(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
api.statsReporter.TrackUsage(workspace.ID)
|
||||
|
||||
if !api.Experiments.Enabled(codersdk.ExperimentWorkspaceUsage) {
|
||||
// Continue previous behavior if the experiment is not enabled.
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Body == http.NoBody {
|
||||
// Continue previous behavior if no body is present.
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
var req codersdk.PostWorkspaceUsageRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.AgentID == uuid.Nil && req.AppName == "" {
|
||||
// Continue previous behavior if body is empty.
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
if req.AgentID == uuid.Nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid request",
|
||||
Validations: []codersdk.ValidationError{{
|
||||
Field: "agent_id",
|
||||
Detail: "must be set when app_name is set",
|
||||
}},
|
||||
})
|
||||
return
|
||||
}
|
||||
if req.AppName == "" {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid request",
|
||||
Validations: []codersdk.ValidationError{{
|
||||
Field: "app_name",
|
||||
Detail: "must be set when agent_id is set",
|
||||
}},
|
||||
})
|
||||
return
|
||||
}
|
||||
if !slices.Contains(codersdk.AllowedAppNames, req.AppName) {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid request",
|
||||
Validations: []codersdk.ValidationError{{
|
||||
Field: "app_name",
|
||||
Detail: fmt.Sprintf("must be one of %v", codersdk.AllowedAppNames),
|
||||
}},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
stat := &proto.Stats{
|
||||
ConnectionCount: 1,
|
||||
}
|
||||
switch req.AppName {
|
||||
case codersdk.UsageAppNameVscode:
|
||||
stat.SessionCountVscode = 1
|
||||
case codersdk.UsageAppNameJetbrains:
|
||||
stat.SessionCountJetbrains = 1
|
||||
case codersdk.UsageAppNameReconnectingPty:
|
||||
stat.SessionCountReconnectingPty = 1
|
||||
case codersdk.UsageAppNameSSH:
|
||||
stat.SessionCountSsh = 1
|
||||
default:
|
||||
// This means the app_name is in the codersdk.AllowedAppNames but not being
|
||||
// handled by this switch statement.
|
||||
httpapi.InternalServerError(rw, xerrors.Errorf("unknown app_name %q", req.AppName))
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := api.Database.GetWorkspaceAgentByID(ctx, req.AgentID)
|
||||
if err != nil {
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
template, err := api.Database.GetTemplateByID(ctx, workspace.TemplateID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.statsReporter.ReportAgentStats(ctx, dbtime.Now(), workspace, agent, template.Name, stat)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -3371,3 +3371,127 @@ func TestWorkspaceFavoriteUnfavorite(t *testing.T) {
|
||||
require.ErrorAs(t, err, &sdkErr)
|
||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||
}
|
||||
|
||||
func TestWorkspaceUsageTracking(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("NoExperiment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
tmpDir := t.TempDir()
|
||||
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
|
||||
OrganizationID: user.OrganizationID,
|
||||
OwnerID: user.UserID,
|
||||
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
|
||||
agents[0].Directory = tmpDir
|
||||
return agents
|
||||
}).Do()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
||||
defer cancel()
|
||||
|
||||
// continue legacy behavior
|
||||
err := client.PostWorkspaceUsage(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("Experiment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
||||
defer cancel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceUsage)}
|
||||
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
tmpDir := t.TempDir()
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.UserID,
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
templateVersion := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.UserID,
|
||||
})
|
||||
template := dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: org.ID,
|
||||
ActiveVersionID: templateVersion.ID,
|
||||
CreatedBy: user.UserID,
|
||||
DefaultTTL: int64(8 * time.Hour),
|
||||
})
|
||||
_, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
ActivityBumpMillis: 8 * time.Hour.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
|
||||
OrganizationID: user.OrganizationID,
|
||||
OwnerID: user.UserID,
|
||||
TemplateID: template.ID,
|
||||
Ttl: sql.NullInt64{Valid: true, Int64: int64(8 * time.Hour)},
|
||||
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
|
||||
agents[0].Directory = tmpDir
|
||||
return agents
|
||||
}).Do()
|
||||
|
||||
// continue legacy behavior
|
||||
err = client.PostWorkspaceUsage(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
workspace, err := client.Workspace(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// only agent id fails
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{
|
||||
AgentID: workspace.LatestBuild.Resources[0].Agents[0].ID,
|
||||
})
|
||||
require.ErrorContains(t, err, "agent_id")
|
||||
// only app name fails
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{
|
||||
AppName: "ssh",
|
||||
})
|
||||
require.ErrorContains(t, err, "app_name")
|
||||
// unknown app name fails
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{
|
||||
AgentID: workspace.LatestBuild.Resources[0].Agents[0].ID,
|
||||
AppName: "unknown",
|
||||
})
|
||||
require.ErrorContains(t, err, "app_name")
|
||||
|
||||
// vscode works
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{
|
||||
AgentID: workspace.LatestBuild.Resources[0].Agents[0].ID,
|
||||
AppName: "vscode",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// jetbrains works
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{
|
||||
AgentID: workspace.LatestBuild.Resources[0].Agents[0].ID,
|
||||
AppName: "jetbrains",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// reconnecting-pty works
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{
|
||||
AgentID: workspace.LatestBuild.Resources[0].Agents[0].ID,
|
||||
AppName: "reconnecting-pty",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// ssh works
|
||||
err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{
|
||||
AgentID: workspace.LatestBuild.Resources[0].Agents[0].ID,
|
||||
AppName: "ssh",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// ensure deadline has been bumped
|
||||
newWorkspace, err := client.Workspace(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, workspace.LatestBuild.Deadline.Valid)
|
||||
require.True(t, newWorkspace.LatestBuild.Deadline.Valid)
|
||||
require.Greater(t, newWorkspace.LatestBuild.Deadline.Time, workspace.LatestBuild.Deadline.Time)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2233,6 +2233,7 @@ const (
|
||||
ExperimentAutoFillParameters Experiment = "auto-fill-parameters" // This should not be taken out of experiments until we have redesigned the feature.
|
||||
ExperimentMultiOrganization Experiment = "multi-organization" // Requires organization context for interactions, default org is assumed.
|
||||
ExperimentCustomRoles Experiment = "custom-roles" // Allows creating runtime custom roles
|
||||
ExperimentWorkspaceUsage Experiment = "workspace-usage" // Enables the new workspace usage tracking
|
||||
)
|
||||
|
||||
// ExperimentsAll should include all experiments that are safe for
|
||||
|
||||
+80
-5
@@ -316,7 +316,43 @@ func (c *Client) PutExtendWorkspace(ctx context.Context, id uuid.UUID, req PutEx
|
||||
return nil
|
||||
}
|
||||
|
||||
type PostWorkspaceUsageRequest struct {
|
||||
AgentID uuid.UUID `json:"agent_id" format:"uuid"`
|
||||
AppName UsageAppName `json:"app_name"`
|
||||
}
|
||||
|
||||
type UsageAppName string
|
||||
|
||||
const (
|
||||
UsageAppNameVscode UsageAppName = "vscode"
|
||||
UsageAppNameJetbrains UsageAppName = "jetbrains"
|
||||
UsageAppNameReconnectingPty UsageAppName = "reconnecting-pty"
|
||||
UsageAppNameSSH UsageAppName = "ssh"
|
||||
)
|
||||
|
||||
var AllowedAppNames = []UsageAppName{
|
||||
UsageAppNameVscode,
|
||||
UsageAppNameJetbrains,
|
||||
UsageAppNameReconnectingPty,
|
||||
UsageAppNameSSH,
|
||||
}
|
||||
|
||||
// PostWorkspaceUsage marks the workspace as having been used recently and records an app stat.
|
||||
func (c *Client) PostWorkspaceUsageWithBody(ctx context.Context, id uuid.UUID, req PostWorkspaceUsageRequest) error {
|
||||
path := fmt.Sprintf("/api/v2/workspaces/%s/usage", id.String())
|
||||
res, err := c.Request(ctx, http.MethodPost, path, req)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("post workspace usage: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostWorkspaceUsage marks the workspace as having been used recently.
|
||||
// Deprecated: use PostWorkspaceUsageWithBody instead
|
||||
func (c *Client) PostWorkspaceUsage(ctx context.Context, id uuid.UUID) error {
|
||||
path := fmt.Sprintf("/api/v2/workspaces/%s/usage", id.String())
|
||||
res, err := c.Request(ctx, http.MethodPost, path, nil)
|
||||
@@ -330,14 +366,15 @@ func (c *Client) PostWorkspaceUsage(ctx context.Context, id uuid.UUID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateWorkspaceUsageContext periodically posts workspace usage for the workspace
|
||||
// with the given id in the background.
|
||||
// UpdateWorkspaceUsageWithBodyContext periodically posts workspace usage for the workspace
|
||||
// with the given id and app name in the background.
|
||||
// The caller is responsible for calling the returned function to stop the background
|
||||
// process.
|
||||
func (c *Client) UpdateWorkspaceUsageContext(ctx context.Context, id uuid.UUID) func() {
|
||||
func (c *Client) UpdateWorkspaceUsageWithBodyContext(ctx context.Context, workspaceID uuid.UUID, req PostWorkspaceUsageRequest) func() {
|
||||
hbCtx, hbCancel := context.WithCancel(ctx)
|
||||
// Perform one initial update
|
||||
if err := c.PostWorkspaceUsage(hbCtx, id); err != nil {
|
||||
err := c.PostWorkspaceUsageWithBody(hbCtx, workspaceID, req)
|
||||
if err != nil {
|
||||
c.logger.Warn(ctx, "failed to post workspace usage", slog.Error(err))
|
||||
}
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
@@ -350,7 +387,45 @@ func (c *Client) UpdateWorkspaceUsageContext(ctx context.Context, id uuid.UUID)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := c.PostWorkspaceUsage(hbCtx, id); err != nil {
|
||||
err := c.PostWorkspaceUsageWithBody(hbCtx, workspaceID, req)
|
||||
if err != nil {
|
||||
c.logger.Warn(ctx, "failed to post workspace usage in background", slog.Error(err))
|
||||
}
|
||||
case <-hbCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return func() {
|
||||
hbCancel()
|
||||
<-doneCh
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateWorkspaceUsageContext periodically posts workspace usage for the workspace
|
||||
// with the given id in the background.
|
||||
// The caller is responsible for calling the returned function to stop the background
|
||||
// process.
|
||||
// Deprecated: use UpdateWorkspaceUsageContextWithBody instead
|
||||
func (c *Client) UpdateWorkspaceUsageContext(ctx context.Context, workspaceID uuid.UUID) func() {
|
||||
hbCtx, hbCancel := context.WithCancel(ctx)
|
||||
// Perform one initial update
|
||||
err := c.PostWorkspaceUsage(hbCtx, workspaceID)
|
||||
if err != nil {
|
||||
c.logger.Warn(ctx, "failed to post workspace usage", slog.Error(err))
|
||||
}
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
close(doneCh)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
err := c.PostWorkspaceUsage(hbCtx, workspaceID)
|
||||
if err != nil {
|
||||
c.logger.Warn(ctx, "failed to post workspace usage in background", slog.Error(err))
|
||||
}
|
||||
case <-hbCtx.Done():
|
||||
|
||||
Generated
+34
@@ -2362,6 +2362,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `auto-fill-parameters` |
|
||||
| `multi-organization` |
|
||||
| `custom-roles` |
|
||||
| `workspace-usage` |
|
||||
|
||||
## codersdk.ExternalAuth
|
||||
|
||||
@@ -3393,6 +3394,22 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `icon` | string | false | | |
|
||||
| `name` | string | true | | |
|
||||
|
||||
## codersdk.PostWorkspaceUsageRequest
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
|
||||
"app_name": "vscode"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------- | ---------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `agent_id` | string | false | | |
|
||||
| `app_name` | [codersdk.UsageAppName](#codersdkusageappname) | false | | |
|
||||
|
||||
## codersdk.PprofConfig
|
||||
|
||||
```json
|
||||
@@ -5263,6 +5280,23 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
| `share_level` | `authenticated` |
|
||||
| `share_level` | `public` |
|
||||
|
||||
## codersdk.UsageAppName
|
||||
|
||||
```json
|
||||
"vscode"
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value |
|
||||
| ------------------ |
|
||||
| `vscode` |
|
||||
| `jetbrains` |
|
||||
| `reconnecting-pty` |
|
||||
| `ssh` |
|
||||
|
||||
## codersdk.User
|
||||
|
||||
```json
|
||||
|
||||
Generated
+14
-3
@@ -1397,16 +1397,27 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/usage \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`POST /workspaces/{workspace}/usage`
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
|
||||
"app_name": "vscode"
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ----------- | ---- | ------------ | -------- | ------------ |
|
||||
| `workspace` | path | string(uuid) | true | Workspace ID |
|
||||
| Name | In | Type | Required | Description |
|
||||
| ----------- | ---- | ---------------------------------------------------------------------------------- | -------- | ---------------------------- |
|
||||
| `workspace` | path | string(uuid) | true | Workspace ID |
|
||||
| `body` | body | [codersdk.PostWorkspaceUsageRequest](schemas.md#codersdkpostworkspaceusagerequest) | false | Post workspace usage request |
|
||||
|
||||
### Responses
|
||||
|
||||
|
||||
Generated
+18
-1
@@ -848,6 +848,12 @@ export interface PostOAuth2ProviderAppRequest {
|
||||
readonly icon: string;
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
export interface PostWorkspaceUsageRequest {
|
||||
readonly agent_id: string;
|
||||
readonly app_name: UsageAppName;
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface PprofConfig {
|
||||
readonly enable: boolean;
|
||||
@@ -1951,12 +1957,14 @@ export type Experiment =
|
||||
| "auto-fill-parameters"
|
||||
| "custom-roles"
|
||||
| "example"
|
||||
| "multi-organization";
|
||||
| "multi-organization"
|
||||
| "workspace-usage";
|
||||
export const Experiments: Experiment[] = [
|
||||
"auto-fill-parameters",
|
||||
"custom-roles",
|
||||
"example",
|
||||
"multi-organization",
|
||||
"workspace-usage",
|
||||
];
|
||||
|
||||
// From codersdk/deployment.go
|
||||
@@ -2251,6 +2259,15 @@ export const TemplateVersionWarnings: TemplateVersionWarning[] = [
|
||||
"UNSUPPORTED_WORKSPACES",
|
||||
];
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
export type UsageAppName = "jetbrains" | "reconnecting-pty" | "ssh" | "vscode";
|
||||
export const UsageAppNames: UsageAppName[] = [
|
||||
"jetbrains",
|
||||
"reconnecting-pty",
|
||||
"ssh",
|
||||
"vscode",
|
||||
];
|
||||
|
||||
// From codersdk/users.go
|
||||
export type UserStatus = "active" | "dormant" | "suspended";
|
||||
export const UserStatuses: UserStatus[] = ["active", "dormant", "suspended"];
|
||||
|
||||
Reference in New Issue
Block a user