diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index c5e2a6041526f..f4cddf15cf21b 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5696,62 +5696,6 @@ const docTemplate = `{ } } }, - "/workspaceagents/me/app-health": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Submit workspace agent application health", - "operationId": "submit-workspace-agent-application-health", - "parameters": [ - { - "description": "Application health request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostAppHealthsRequest" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/workspaceagents/me/coordinate": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "description": "It accepts a WebSocket connection to an agent that listens to\nincoming connections and publishes node updates.", - "tags": [ - "Agents" - ], - "summary": "Coordinate workspace agent via Tailnet", - "operationId": "coordinate-workspace-agent-via-tailnet", - "responses": { - "101": { - "description": "Switching Protocols" - } - } - } - }, "/workspaceagents/me/external-auth": { "get": { "security": [ @@ -5949,287 +5893,25 @@ const docTemplate = `{ } } }, - "/workspaceagents/me/manifest": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Get authorized workspace agent manifest", - "operationId": "get-authorized-workspace-agent-manifest", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/agentsdk.Manifest" - } - } - } - } - }, - "/workspaceagents/me/metadata": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Submit workspace agent metadata", - "operationId": "submit-workspace-agent-metadata", - "parameters": [ - { - "description": "Workspace agent metadata request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.PostMetadataRequest" - } - } - } - ], - "responses": { - "204": { - "description": "Success" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/metadata/{key}": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Removed: Submit workspace agent metadata", - "operationId": "removed-submit-workspace-agent-metadata", - "parameters": [ - { - "description": "Workspace agent metadata request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostMetadataRequestDeprecated" - } - }, - { - "type": "string", - "format": "string", - "description": "metadata key", - "name": "key", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "Success" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/report-lifecycle": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Submit workspace agent lifecycle state", - "operationId": "submit-workspace-agent-lifecycle-state", - "parameters": [ - { - "description": "Workspace agent lifecycle request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostLifecycleRequest" - } - } - ], - "responses": { - "204": { - "description": "Success" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/report-stats": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Submit workspace agent stats", - "operationId": "submit-workspace-agent-stats", - "deprecated": true, - "parameters": [ - { - "description": "Stats request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.Stats" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/agentsdk.StatsResponse" - } - } - } - } - }, "/workspaceagents/me/rpc": { "get": { "security": [ { - "CoderSessionToken": [] - } - ], - "tags": [ - "Agents" - ], - "summary": "Workspace agent RPC API", - "operationId": "workspace-agent-rpc-api", - "responses": { - "101": { - "description": "Switching Protocols" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/startup": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Submit workspace agent startup", - "operationId": "submit-workspace-agent-startup", - "parameters": [ - { - "description": "Startup request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostStartupRequest" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/startup-logs": { - "patch": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Agents" - ], - "summary": "Removed: Patch workspace agent logs", - "operationId": "removed-patch-workspace-agent-logs", - "parameters": [ - { - "description": "logs", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PatchLogs" - } + "CoderSessionToken": [] } ], + "tags": [ + "Agents" + ], + "summary": "Workspace agent RPC API", + "operationId": "workspace-agent-rpc-api", "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.Response" - } + "101": { + "description": "Switching Protocols" } + }, + "x-apidocgen": { + "skip": true } } }, @@ -7882,65 +7564,6 @@ const docTemplate = `{ } } }, - "agentsdk.AgentMetric": { - "type": "object", - "required": [ - "name", - "type", - "value" - ], - "properties": { - "labels": { - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.AgentMetricLabel" - } - }, - "name": { - "type": "string" - }, - "type": { - "enum": [ - "counter", - "gauge" - ], - "allOf": [ - { - "$ref": "#/definitions/agentsdk.AgentMetricType" - } - ] - }, - "value": { - "type": "number" - } - } - }, - "agentsdk.AgentMetricLabel": { - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "agentsdk.AgentMetricType": { - "type": "string", - "enum": [ - "counter", - "gauge" - ], - "x-enum-varnames": [ - "AgentMetricTypeCounter", - "AgentMetricTypeGauge" - ] - }, "agentsdk.AuthenticateResponse": { "type": "object", "properties": { @@ -8025,95 +7648,6 @@ const docTemplate = `{ } } }, - "agentsdk.Manifest": { - "type": "object", - "properties": { - "agent_id": { - "type": "string" - }, - "agent_name": { - "type": "string" - }, - "apps": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.WorkspaceApp" - } - }, - "derp_force_websockets": { - "type": "boolean" - }, - "derpmap": { - "$ref": "#/definitions/tailcfg.DERPMap" - }, - "directory": { - "type": "string" - }, - "disable_direct_connections": { - "type": "boolean" - }, - "environment_variables": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "git_auth_configs": { - "description": "GitAuthConfigs stores the number of Git configurations\nthe Coder deployment has. If this number is \u003e0, we\nset up special configuration in the workspace.", - "type": "integer" - }, - "metadata": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataDescription" - } - }, - "motd_file": { - "type": "string" - }, - "owner_name": { - "description": "OwnerName and WorkspaceID are used by an open-source user to identify the workspace.\nWe do not provide insurance that this will not be removed in the future,\nbut if it's easy to persist lets keep it around.", - "type": "string" - }, - "scripts": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.WorkspaceAgentScript" - } - }, - "vscode_port_proxy_uri": { - "type": "string" - }, - "workspace_id": { - "type": "string" - }, - "workspace_name": { - "type": "string" - } - } - }, - "agentsdk.Metadata": { - "type": "object", - "properties": { - "age": { - "description": "Age is the number of seconds since the metadata was collected.\nIt is provided in addition to CollectedAt to protect against clock skew.", - "type": "integer" - }, - "collected_at": { - "type": "string", - "format": "date-time" - }, - "error": { - "type": "string" - }, - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "agentsdk.PatchLogs": { "type": "object", "properties": { @@ -8128,29 +7662,6 @@ const docTemplate = `{ } } }, - "agentsdk.PostAppHealthsRequest": { - "type": "object", - "properties": { - "healths": { - "description": "Healths is a map of the workspace app name and the health of the app.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/codersdk.WorkspaceAppHealth" - } - } - } - }, - "agentsdk.PostLifecycleRequest": { - "type": "object", - "properties": { - "changed_at": { - "type": "string" - }, - "state": { - "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" - } - } - }, "agentsdk.PostLogSourceRequest": { "type": "object", "properties": { @@ -8166,121 +7677,6 @@ const docTemplate = `{ } } }, - "agentsdk.PostMetadataRequest": { - "type": "object", - "properties": { - "metadata": { - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.Metadata" - } - } - } - }, - "agentsdk.PostMetadataRequestDeprecated": { - "type": "object", - "properties": { - "age": { - "description": "Age is the number of seconds since the metadata was collected.\nIt is provided in addition to CollectedAt to protect against clock skew.", - "type": "integer" - }, - "collected_at": { - "type": "string", - "format": "date-time" - }, - "error": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "agentsdk.PostStartupRequest": { - "type": "object", - "properties": { - "expanded_directory": { - "type": "string" - }, - "subsystems": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.AgentSubsystem" - } - }, - "version": { - "type": "string" - } - } - }, - "agentsdk.Stats": { - "type": "object", - "properties": { - "connection_count": { - "description": "ConnectionCount is the number of connections received by an agent.", - "type": "integer" - }, - "connection_median_latency_ms": { - "description": "ConnectionMedianLatencyMS is the median latency of all connections in milliseconds.", - "type": "number" - }, - "connections_by_proto": { - "description": "ConnectionsByProto is a count of connections by protocol.", - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "metrics": { - "description": "Metrics collected by the agent", - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.AgentMetric" - } - }, - "rx_bytes": { - "description": "RxBytes is the number of received bytes.", - "type": "integer" - }, - "rx_packets": { - "description": "RxPackets is the number of received packets.", - "type": "integer" - }, - "session_count_jetbrains": { - "description": "SessionCountJetBrains is the number of connections received by an agent\nthat are from our JetBrains extension.", - "type": "integer" - }, - "session_count_reconnecting_pty": { - "description": "SessionCountReconnectingPTY is the number of connections received by an agent\nthat are from the reconnecting web terminal.", - "type": "integer" - }, - "session_count_ssh": { - "description": "SessionCountSSH is the number of connections received by an agent\nthat are normal, non-tagged SSH sessions.", - "type": "integer" - }, - "session_count_vscode": { - "description": "SessionCountVSCode is the number of connections received by an agent\nthat are from our VS Code extension.", - "type": "integer" - }, - "tx_bytes": { - "description": "TxBytes is the number of transmitted bytes.", - "type": "integer" - }, - "tx_packets": { - "description": "TxPackets is the number of transmitted bytes.", - "type": "integer" - } - } - }, - "agentsdk.StatsResponse": { - "type": "object", - "properties": { - "report_interval": { - "description": "ReportInterval is the duration after which the agent should send stats\nagain.", - "type": "integer" - } - } - }, "coderd.SCIMUser": { "type": "object", "properties": { @@ -13141,26 +12537,6 @@ const docTemplate = `{ } } }, - "codersdk.WorkspaceAgentMetadataDescription": { - "type": "object", - "properties": { - "display_name": { - "type": "string" - }, - "interval": { - "type": "integer" - }, - "key": { - "type": "string" - }, - "script": { - "type": "string" - }, - "timeout": { - "type": "integer" - } - } - }, "codersdk.WorkspaceAgentPortShare": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 66afad1f041f0..fe98dc5c6b304 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5024,54 +5024,6 @@ } } }, - "/workspaceagents/me/app-health": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Agents"], - "summary": "Submit workspace agent application health", - "operationId": "submit-workspace-agent-application-health", - "parameters": [ - { - "description": "Application health request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostAppHealthsRequest" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/workspaceagents/me/coordinate": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "description": "It accepts a WebSocket connection to an agent that listens to\nincoming connections and publishes node updates.", - "tags": ["Agents"], - "summary": "Coordinate workspace agent via Tailnet", - "operationId": "coordinate-workspace-agent-via-tailnet", - "responses": { - "101": { - "description": "Switching Protocols" - } - } - } - }, "/workspaceagents/me/external-auth": { "get": { "security": [ @@ -5245,168 +5197,6 @@ } } }, - "/workspaceagents/me/manifest": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": ["application/json"], - "tags": ["Agents"], - "summary": "Get authorized workspace agent manifest", - "operationId": "get-authorized-workspace-agent-manifest", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/agentsdk.Manifest" - } - } - } - } - }, - "/workspaceagents/me/metadata": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "tags": ["Agents"], - "summary": "Submit workspace agent metadata", - "operationId": "submit-workspace-agent-metadata", - "parameters": [ - { - "description": "Workspace agent metadata request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.PostMetadataRequest" - } - } - } - ], - "responses": { - "204": { - "description": "Success" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/metadata/{key}": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "tags": ["Agents"], - "summary": "Removed: Submit workspace agent metadata", - "operationId": "removed-submit-workspace-agent-metadata", - "parameters": [ - { - "description": "Workspace agent metadata request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostMetadataRequestDeprecated" - } - }, - { - "type": "string", - "format": "string", - "description": "metadata key", - "name": "key", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "Success" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/report-lifecycle": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "tags": ["Agents"], - "summary": "Submit workspace agent lifecycle state", - "operationId": "submit-workspace-agent-lifecycle-state", - "parameters": [ - { - "description": "Workspace agent lifecycle request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostLifecycleRequest" - } - } - ], - "responses": { - "204": { - "description": "Success" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/report-stats": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Agents"], - "summary": "Submit workspace agent stats", - "operationId": "submit-workspace-agent-stats", - "deprecated": true, - "parameters": [ - { - "description": "Stats request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.Stats" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/agentsdk.StatsResponse" - } - } - } - } - }, "/workspaceagents/me/rpc": { "get": { "security": [ @@ -5427,72 +5217,6 @@ } } }, - "/workspaceagents/me/startup": { - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Agents"], - "summary": "Submit workspace agent startup", - "operationId": "submit-workspace-agent-startup", - "parameters": [ - { - "description": "Startup request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PostStartupRequest" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - }, - "x-apidocgen": { - "skip": true - } - } - }, - "/workspaceagents/me/startup-logs": { - "patch": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Agents"], - "summary": "Removed: Patch workspace agent logs", - "operationId": "removed-patch-workspace-agent-logs", - "parameters": [ - { - "description": "logs", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agentsdk.PatchLogs" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.Response" - } - } - } - } - }, "/workspaceagents/{workspaceagent}": { "get": { "security": [ @@ -6969,49 +6693,6 @@ } } }, - "agentsdk.AgentMetric": { - "type": "object", - "required": ["name", "type", "value"], - "properties": { - "labels": { - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.AgentMetricLabel" - } - }, - "name": { - "type": "string" - }, - "type": { - "enum": ["counter", "gauge"], - "allOf": [ - { - "$ref": "#/definitions/agentsdk.AgentMetricType" - } - ] - }, - "value": { - "type": "number" - } - } - }, - "agentsdk.AgentMetricLabel": { - "type": "object", - "required": ["name", "value"], - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "agentsdk.AgentMetricType": { - "type": "string", - "enum": ["counter", "gauge"], - "x-enum-varnames": ["AgentMetricTypeCounter", "AgentMetricTypeGauge"] - }, "agentsdk.AuthenticateResponse": { "type": "object", "properties": { @@ -7091,95 +6772,6 @@ } } }, - "agentsdk.Manifest": { - "type": "object", - "properties": { - "agent_id": { - "type": "string" - }, - "agent_name": { - "type": "string" - }, - "apps": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.WorkspaceApp" - } - }, - "derp_force_websockets": { - "type": "boolean" - }, - "derpmap": { - "$ref": "#/definitions/tailcfg.DERPMap" - }, - "directory": { - "type": "string" - }, - "disable_direct_connections": { - "type": "boolean" - }, - "environment_variables": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "git_auth_configs": { - "description": "GitAuthConfigs stores the number of Git configurations\nthe Coder deployment has. If this number is \u003e0, we\nset up special configuration in the workspace.", - "type": "integer" - }, - "metadata": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataDescription" - } - }, - "motd_file": { - "type": "string" - }, - "owner_name": { - "description": "OwnerName and WorkspaceID are used by an open-source user to identify the workspace.\nWe do not provide insurance that this will not be removed in the future,\nbut if it's easy to persist lets keep it around.", - "type": "string" - }, - "scripts": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.WorkspaceAgentScript" - } - }, - "vscode_port_proxy_uri": { - "type": "string" - }, - "workspace_id": { - "type": "string" - }, - "workspace_name": { - "type": "string" - } - } - }, - "agentsdk.Metadata": { - "type": "object", - "properties": { - "age": { - "description": "Age is the number of seconds since the metadata was collected.\nIt is provided in addition to CollectedAt to protect against clock skew.", - "type": "integer" - }, - "collected_at": { - "type": "string", - "format": "date-time" - }, - "error": { - "type": "string" - }, - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "agentsdk.PatchLogs": { "type": "object", "properties": { @@ -7194,29 +6786,6 @@ } } }, - "agentsdk.PostAppHealthsRequest": { - "type": "object", - "properties": { - "healths": { - "description": "Healths is a map of the workspace app name and the health of the app.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/codersdk.WorkspaceAppHealth" - } - } - } - }, - "agentsdk.PostLifecycleRequest": { - "type": "object", - "properties": { - "changed_at": { - "type": "string" - }, - "state": { - "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" - } - } - }, "agentsdk.PostLogSourceRequest": { "type": "object", "properties": { @@ -7232,121 +6801,6 @@ } } }, - "agentsdk.PostMetadataRequest": { - "type": "object", - "properties": { - "metadata": { - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.Metadata" - } - } - } - }, - "agentsdk.PostMetadataRequestDeprecated": { - "type": "object", - "properties": { - "age": { - "description": "Age is the number of seconds since the metadata was collected.\nIt is provided in addition to CollectedAt to protect against clock skew.", - "type": "integer" - }, - "collected_at": { - "type": "string", - "format": "date-time" - }, - "error": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "agentsdk.PostStartupRequest": { - "type": "object", - "properties": { - "expanded_directory": { - "type": "string" - }, - "subsystems": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.AgentSubsystem" - } - }, - "version": { - "type": "string" - } - } - }, - "agentsdk.Stats": { - "type": "object", - "properties": { - "connection_count": { - "description": "ConnectionCount is the number of connections received by an agent.", - "type": "integer" - }, - "connection_median_latency_ms": { - "description": "ConnectionMedianLatencyMS is the median latency of all connections in milliseconds.", - "type": "number" - }, - "connections_by_proto": { - "description": "ConnectionsByProto is a count of connections by protocol.", - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "metrics": { - "description": "Metrics collected by the agent", - "type": "array", - "items": { - "$ref": "#/definitions/agentsdk.AgentMetric" - } - }, - "rx_bytes": { - "description": "RxBytes is the number of received bytes.", - "type": "integer" - }, - "rx_packets": { - "description": "RxPackets is the number of received packets.", - "type": "integer" - }, - "session_count_jetbrains": { - "description": "SessionCountJetBrains is the number of connections received by an agent\nthat are from our JetBrains extension.", - "type": "integer" - }, - "session_count_reconnecting_pty": { - "description": "SessionCountReconnectingPTY is the number of connections received by an agent\nthat are from the reconnecting web terminal.", - "type": "integer" - }, - "session_count_ssh": { - "description": "SessionCountSSH is the number of connections received by an agent\nthat are normal, non-tagged SSH sessions.", - "type": "integer" - }, - "session_count_vscode": { - "description": "SessionCountVSCode is the number of connections received by an agent\nthat are from our VS Code extension.", - "type": "integer" - }, - "tx_bytes": { - "description": "TxBytes is the number of transmitted bytes.", - "type": "integer" - }, - "tx_packets": { - "description": "TxPackets is the number of transmitted bytes.", - "type": "integer" - } - } - }, - "agentsdk.StatsResponse": { - "type": "object", - "properties": { - "report_interval": { - "description": "ReportInterval is the duration after which the agent should send stats\nagain.", - "type": "integer" - } - } - }, "coderd.SCIMUser": { "type": "object", "properties": { @@ -11943,26 +11397,6 @@ } } }, - "codersdk.WorkspaceAgentMetadataDescription": { - "type": "object", - "properties": { - "display_name": { - "type": "string" - }, - "interval": { - "type": "integer" - }, - "key": { - "type": "string" - }, - "script": { - "type": "string" - }, - "timeout": { - "type": "integer" - } - } - }, "codersdk.WorkspaceAgentPortShare": { "type": "object", "properties": { diff --git a/coderd/coderd.go b/coderd/coderd.go index 60bfe9813c559..bbcc080326ba5 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1004,23 +1004,11 @@ func New(options *Options) *API { Optional: false, })) r.Get("/rpc", api.workspaceAgentRPC) - 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) - r.Post("/startup", api.postWorkspaceAgentStartup) - r.Patch("/startup-logs", api.patchWorkspaceAgentLogsDeprecated) r.Patch("/logs", api.patchWorkspaceAgentLogs) - r.Post("/app-health", api.postWorkspaceAppHealth) // Deprecated: Required to support legacy agents r.Get("/gitauth", api.workspaceAgentsGitAuth) r.Get("/external-auth", api.workspaceAgentsExternalAuth) r.Get("/gitsshkey", api.agentGitSSHKey) - r.Get("/coordinate", api.workspaceAgentCoordinate) - r.Post("/report-stats", api.workspaceAgentReportStats) - r.Post("/report-lifecycle", api.workspaceAgentReportLifecycle) - r.Post("/metadata", api.workspaceAgentPostMetadata) - r.Post("/metadata/{key}", api.workspaceAgentPostMetadataDeprecated) r.Post("/log-source", api.workspaceAgentPostLogSource) }) r.Route("/{workspaceagent}", func(r chi.Router) { diff --git a/coderd/deprecated.go b/coderd/deprecated.go index 762b5bc931e38..6dc03e540ce33 100644 --- a/coderd/deprecated.go +++ b/coderd/deprecated.go @@ -3,13 +3,9 @@ package coderd import ( "net/http" - "github.com/go-chi/chi/v5" - - "cdr.dev/slog" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/codersdk/agentsdk" ) // @Summary Removed: Get parameters by template version @@ -34,19 +30,6 @@ func templateVersionSchemaDeprecated(rw http.ResponseWriter, r *http.Request) { httpapi.Write(r.Context(), rw, http.StatusOK, []struct{}{}) } -// @Summary Removed: Patch workspace agent logs -// @ID removed-patch-workspace-agent-logs -// @Security CoderSessionToken -// @Accept json -// @Produce json -// @Tags Agents -// @Param request body agentsdk.PatchLogs true "logs" -// @Success 200 {object} codersdk.Response -// @Router /workspaceagents/me/startup-logs [patch] -func (api *API) patchWorkspaceAgentLogsDeprecated(rw http.ResponseWriter, r *http.Request) { - api.patchWorkspaceAgentLogs(rw, r) -} - // @Summary Removed: Get logs by workspace agent // @ID removed-get-logs-by-workspace-agent // @Security CoderSessionToken @@ -77,45 +60,6 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) api.workspaceAgentsExternalAuth(rw, r) } -// @Summary Removed: Submit workspace agent metadata -// @ID removed-submit-workspace-agent-metadata -// @Security CoderSessionToken -// @Accept json -// @Tags Agents -// @Param request body agentsdk.PostMetadataRequestDeprecated true "Workspace agent metadata request" -// @Param key path string true "metadata key" format(string) -// @Success 204 "Success" -// @Router /workspaceagents/me/metadata/{key} [post] -// @x-apidocgen {"skip": true} -func (api *API) workspaceAgentPostMetadataDeprecated(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var req agentsdk.PostMetadataRequestDeprecated - if !httpapi.Read(ctx, rw, r, &req) { - return - } - - workspaceAgent := httpmw.WorkspaceAgent(r) - - key := chi.URLParam(r, "key") - - err := api.workspaceAgentUpdateMetadata(ctx, workspaceAgent, agentsdk.PostMetadataRequest{ - Metadata: []agentsdk.Metadata{ - { - Key: key, - WorkspaceAgentMetadataResult: req, - }, - }, - }) - if err != nil { - api.Logger.Error(ctx, "failed to handle metadata request", slog.Error(err)) - httpapi.InternalServerError(rw, err) - return - } - - httpapi.Write(ctx, rw, http.StatusNoContent, nil) -} - // @Summary Removed: Get workspace resources for workspace build // @ID removed-get-workspace-resources-for-workspace-build // @Security CoderSessionToken diff --git a/coderd/prometheusmetrics/insights/metricscollector_test.go b/coderd/prometheusmetrics/insights/metricscollector_test.go index 91ef3c7ee88fa..9179c9896235d 100644 --- a/coderd/prometheusmetrics/insights/metricscollector_test.go +++ b/coderd/prometheusmetrics/insights/metricscollector_test.go @@ -18,6 +18,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" + agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" @@ -87,25 +88,37 @@ func TestCollectInsights(t *testing.T) { ) // Start an agent so that we can generate stats. - var agentClients []*agentsdk.Client + var agentClients []agentproto.DRPCAgentClient for i, agent := range []database.WorkspaceAgent{agent1, agent2} { agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(agent.AuthToken.String()) agentClient.SDK.SetLogger(logger.Leveled(slog.LevelDebug).Named(fmt.Sprintf("agent%d", i+1))) - agentClients = append(agentClients, agentClient) + conn, err := agentClient.ConnectRPC(context.Background()) + require.NoError(t, err) + agentAPI := agentproto.NewDRPCAgentClient(conn) + agentClients = append(agentClients, agentAPI) } + defer func() { + for a := range agentClients { + err := agentClients[a].DRPCConn().Close() + require.NoError(t, err) + } + }() + // Fake app stats - _, err = agentClients[0].PostStats(context.Background(), &agentsdk.Stats{ - // ConnectionCount must be positive as database query ignores stats with no active connections at the time frame - ConnectionsByProto: map[string]int64{"TCP": 1}, - ConnectionCount: 1, - ConnectionMedianLatencyMS: 15, - // Session counts must be positive, but the exact value is ignored. - // Database query approximates it to 60s of usage. - SessionCountSSH: 99, - SessionCountJetBrains: 47, - SessionCountVSCode: 34, + _, err = agentClients[0].UpdateStats(context.Background(), &agentproto.UpdateStatsRequest{ + Stats: &agentproto.Stats{ + // ConnectionCount must be positive as database query ignores stats with no active connections at the time frame + ConnectionsByProto: map[string]int64{"TCP": 1}, + ConnectionCount: 1, + ConnectionMedianLatencyMs: 15, + // Session counts must be positive, but the exact value is ignored. + // Database query approximates it to 60s of usage. + SessionCountSsh: 99, + SessionCountJetbrains: 47, + SessionCountVscode: 34, + }, }) require.NoError(t, err, "unable to post fake stats") diff --git a/coderd/prometheusmetrics/prometheusmetrics_test.go b/coderd/prometheusmetrics/prometheusmetrics_test.go index 9c4c9fca0b66f..e76383b8b3fa3 100644 --- a/coderd/prometheusmetrics/prometheusmetrics_test.go +++ b/coderd/prometheusmetrics/prometheusmetrics_test.go @@ -20,6 +20,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" + agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/agentmetrics" "github.com/coder/coder/v2/coderd/batchstats" "github.com/coder/coder/v2/coderd/coderdtest" @@ -415,36 +416,45 @@ func TestAgentStats(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) - agent1 := prepareWorkspaceAndAgent(t, client, user, 1) - agent2 := prepareWorkspaceAndAgent(t, client, user, 2) - agent3 := prepareWorkspaceAndAgent(t, client, user, 3) + agent1 := prepareWorkspaceAndAgent(ctx, t, client, user, 1) + agent2 := prepareWorkspaceAndAgent(ctx, t, client, user, 2) + agent3 := prepareWorkspaceAndAgent(ctx, t, client, user, 3) + defer agent1.DRPCConn().Close() + defer agent2.DRPCConn().Close() + defer agent3.DRPCConn().Close() registry := prometheus.NewRegistry() // given var i int64 for i = 0; i < 3; i++ { - _, err = agent1.PostStats(ctx, &agentsdk.Stats{ - TxBytes: 1 + i, RxBytes: 2 + i, - SessionCountVSCode: 3 + i, SessionCountJetBrains: 4 + i, SessionCountReconnectingPTY: 5 + i, SessionCountSSH: 6 + i, - ConnectionCount: 7 + i, ConnectionMedianLatencyMS: 8000, - ConnectionsByProto: map[string]int64{"TCP": 1}, + _, err = agent1.UpdateStats(ctx, &agentproto.UpdateStatsRequest{ + Stats: &agentproto.Stats{ + TxBytes: 1 + i, RxBytes: 2 + i, + SessionCountVscode: 3 + i, SessionCountJetbrains: 4 + i, SessionCountReconnectingPty: 5 + i, SessionCountSsh: 6 + i, + ConnectionCount: 7 + i, ConnectionMedianLatencyMs: 8000, + ConnectionsByProto: map[string]int64{"TCP": 1}, + }, }) require.NoError(t, err) - _, err = agent2.PostStats(ctx, &agentsdk.Stats{ - TxBytes: 2 + i, RxBytes: 4 + i, - SessionCountVSCode: 6 + i, SessionCountJetBrains: 8 + i, SessionCountReconnectingPTY: 10 + i, SessionCountSSH: 12 + i, - ConnectionCount: 8 + i, ConnectionMedianLatencyMS: 10000, - ConnectionsByProto: map[string]int64{"TCP": 1}, + _, err = agent2.UpdateStats(ctx, &agentproto.UpdateStatsRequest{ + Stats: &agentproto.Stats{ + TxBytes: 2 + i, RxBytes: 4 + i, + SessionCountVscode: 6 + i, SessionCountJetbrains: 8 + i, SessionCountReconnectingPty: 10 + i, SessionCountSsh: 12 + i, + ConnectionCount: 8 + i, ConnectionMedianLatencyMs: 10000, + ConnectionsByProto: map[string]int64{"TCP": 1}, + }, }) require.NoError(t, err) - _, err = agent3.PostStats(ctx, &agentsdk.Stats{ - TxBytes: 3 + i, RxBytes: 6 + i, - SessionCountVSCode: 12 + i, SessionCountJetBrains: 14 + i, SessionCountReconnectingPTY: 16 + i, SessionCountSSH: 18 + i, - ConnectionCount: 9 + i, ConnectionMedianLatencyMS: 12000, - ConnectionsByProto: map[string]int64{"TCP": 1}, + _, err = agent3.UpdateStats(ctx, &agentproto.UpdateStatsRequest{ + Stats: &agentproto.Stats{ + TxBytes: 3 + i, RxBytes: 6 + i, + SessionCountVscode: 12 + i, SessionCountJetbrains: 14 + i, SessionCountReconnectingPty: 16 + i, SessionCountSsh: 18 + i, + ConnectionCount: 9 + i, ConnectionMedianLatencyMs: 12000, + ConnectionsByProto: map[string]int64{"TCP": 1}, + }, }) require.NoError(t, err) } @@ -596,7 +606,7 @@ func TestExperimentsMetric(t *testing.T) { } } -func prepareWorkspaceAndAgent(t *testing.T, client *codersdk.Client, user codersdk.CreateFirstUserResponse, workspaceNum int) *agentsdk.Client { +func prepareWorkspaceAndAgent(ctx context.Context, t *testing.T, client *codersdk.Client, user codersdk.CreateFirstUserResponse, workspaceNum int) agentproto.DRPCAgentClient { authToken := uuid.NewString() version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ @@ -611,9 +621,12 @@ func prepareWorkspaceAndAgent(t *testing.T, client *codersdk.Client, user coders }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) - agentClient := agentsdk.New(client.URL) - agentClient.SetSessionToken(authToken) - return agentClient + ac := agentsdk.New(client.URL) + ac.SetSessionToken(authToken) + conn, err := ac.ConnectRPC(ctx) + require.NoError(t, err) + agentAPI := agentproto.NewDRPCAgentClient(conn) + return agentAPI } var ( diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index c45fae8726480..e9e2ab18027d9 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -18,14 +18,12 @@ import ( "github.com/sqlc-dev/pqtype" "golang.org/x/exp/maps" "golang.org/x/exp/slices" - "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "nhooyr.io/websocket" "tailscale.com/tailcfg" "cdr.dev/slog" - agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/agentapi" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" @@ -136,144 +134,8 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, apiAgent) } -// @Summary Get authorized workspace agent manifest -// @ID get-authorized-workspace-agent-manifest -// @Security CoderSessionToken -// @Produce json -// @Tags Agents -// @Success 200 {object} agentsdk.Manifest -// @Router /workspaceagents/me/manifest [get] -func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - workspaceAgent := httpmw.WorkspaceAgent(r) - - // As this API becomes deprecated, use the new protobuf API and convert the - // types back to the SDK types. - manifestAPI := &agentapi.ManifestAPI{ - AccessURL: api.AccessURL, - AppHostname: api.AppHostname, - ExternalAuthConfigs: api.ExternalAuthConfigs, - DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(), - DerpForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), - - AgentFn: func(_ context.Context) (database.WorkspaceAgent, error) { return workspaceAgent, nil }, - WorkspaceIDFn: func(ctx context.Context, wa *database.WorkspaceAgent) (uuid.UUID, error) { - // Sadly this results in a double query, but it's only temporary for - // now. - ws, err := api.Database.GetWorkspaceByAgentID(ctx, wa.ID) - if err != nil { - return uuid.Nil, err - } - return ws.Workspace.ID, nil - }, - Database: api.Database, - DerpMapFn: api.DERPMap, - } - manifest, err := manifestAPI.GetManifest(ctx, &agentproto.GetManifestRequest{}) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace agent manifest.", - Detail: err.Error(), - }) - return - } - sdkManifest, err := agentsdk.ManifestFromProto(manifest) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error converting manifest.", - Detail: err.Error(), - }) - return - } - - httpapi.Write(ctx, rw, http.StatusOK, sdkManifest) -} - const AgentAPIVersionREST = "1.0" -// @Summary Submit workspace agent startup -// @ID submit-workspace-agent-startup -// @Security CoderSessionToken -// @Accept json -// @Produce json -// @Tags Agents -// @Param request body agentsdk.PostStartupRequest true "Startup request" -// @Success 200 -// @Router /workspaceagents/me/startup [post] -// @x-apidocgen {"skip": true} -func (api *API) postWorkspaceAgentStartup(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := db2sdk.WorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, api.AgentInactiveDisconnectTimeout, - api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), - ) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error reading workspace agent.", - Detail: err.Error(), - }) - return - } - - var req agentsdk.PostStartupRequest - if !httpapi.Read(ctx, rw, r, &req) { - return - } - - api.Logger.Debug( - ctx, - "post workspace agent version", - slog.F("agent_id", apiAgent.ID), - slog.F("agent_version", req.Version), - slog.F("remote_addr", r.RemoteAddr), - ) - - if !semver.IsValid(req.Version) { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid workspace agent version provided.", - Detail: fmt.Sprintf("invalid semver version: %q", req.Version), - }) - return - } - - // Validate subsystems. - seen := make(map[codersdk.AgentSubsystem]bool) - for _, s := range req.Subsystems { - if !s.Valid() { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid workspace agent subsystem provided.", - Detail: fmt.Sprintf("invalid subsystem: %q", s), - }) - return - } - if seen[s] { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid workspace agent subsystem provided.", - Detail: fmt.Sprintf("duplicate subsystem: %q", s), - }) - return - } - seen[s] = true - } - - if err := api.Database.UpdateWorkspaceAgentStartupByID(ctx, database.UpdateWorkspaceAgentStartupByIDParams{ - ID: apiAgent.ID, - Version: req.Version, - ExpandedDirectory: req.ExpandedDirectory, - Subsystems: convertWorkspaceAgentSubsystems(req.Subsystems), - APIVersion: AgentAPIVersionREST, - }); err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Error setting agent version", - Detail: err.Error(), - }) - return - } - - httpapi.Write(ctx, rw, http.StatusOK, nil) -} - // @Summary Patch workspace agent logs // @ID patch-workspace-agent-logs // @Security CoderSessionToken @@ -938,79 +800,6 @@ func (api *API) derpMapUpdates(rw http.ResponseWriter, r *http.Request) { } } -// @Summary Coordinate workspace agent via Tailnet -// @Description It accepts a WebSocket connection to an agent that listens to -// @Description incoming connections and publishes node updates. -// @ID coordinate-workspace-agent-via-tailnet -// @Security CoderSessionToken -// @Tags Agents -// @Success 101 -// @Router /workspaceagents/me/coordinate [get] -func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - api.WebsocketWaitMutex.Lock() - api.WebsocketWaitGroup.Add(1) - api.WebsocketWaitMutex.Unlock() - defer api.WebsocketWaitGroup.Done() - // The middleware only accept agents for resources on the latest build. - workspaceAgent := httpmw.WorkspaceAgent(r) - build := httpmw.LatestBuild(r) - - workspace, err := api.Database.GetWorkspaceByID(ctx, build.WorkspaceID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Internal error fetching workspace.", - Detail: err.Error(), - }) - return - } - - owner, err := api.Database.GetUserByID(ctx, workspace.OwnerID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Internal error fetching user.", - Detail: err.Error(), - }) - return - } - - conn, err := websocket.Accept(rw, r, nil) - if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to accept websocket.", - Detail: err.Error(), - }) - return - } - - ctx, wsNetConn := codersdk.WebsocketNetConn(ctx, conn, websocket.MessageBinary) - defer wsNetConn.Close() - - closeCtx, closeCtxCancel := context.WithCancel(ctx) - defer closeCtxCancel() - monitor := api.startAgentWebsocketMonitor(closeCtx, workspaceAgent, build, conn) - defer monitor.close() - - api.Logger.Debug(ctx, "accepting agent", - slog.F("owner", owner.Username), - slog.F("workspace", workspace.Name), - slog.F("name", workspaceAgent.Name), - ) - api.Logger.Debug(ctx, "accepting agent details", slog.F("agent", workspaceAgent)) - - defer conn.Close(websocket.StatusNormalClosure, "") - - err = (*api.TailnetCoordinator.Load()).ServeAgent(wsNetConn, workspaceAgent.ID, - fmt.Sprintf("%s-%s-%s", owner.Username, workspace.Name, workspaceAgent.Name), - ) - if err != nil { - api.Logger.Warn(ctx, "tailnet coordinator agent error", slog.Error(err)) - _ = conn.Close(websocket.StatusInternalError, err.Error()) - return - } -} - // workspaceAgentClientCoordinate accepts a WebSocket that reads node network updates. // After accept a PubSub starts listening for new connection node updates // which are written to the WebSocket. @@ -1171,214 +960,6 @@ func convertScripts(dbScripts []database.WorkspaceAgentScript) []codersdk.Worksp return scripts } -// @Summary Submit workspace agent stats -// @ID submit-workspace-agent-stats -// @Security CoderSessionToken -// @Accept json -// @Produce json -// @Tags Agents -// @Param request body agentsdk.Stats true "Stats request" -// @Success 200 {object} agentsdk.StatsResponse -// @Router /workspaceagents/me/report-stats [post] -// @Deprecated Uses agent API v2 endpoint instead. -func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - workspaceAgent := httpmw.WorkspaceAgent(r) - row, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to get workspace.", - Detail: err.Error(), - }) - return - } - workspace := row.Workspace - - var req agentsdk.Stats - if !httpapi.Read(ctx, rw, r, &req) { - return - } - - // An empty stat means it's just looking for the report interval. - if req.ConnectionsByProto == nil { - httpapi.Write(ctx, rw, http.StatusOK, agentsdk.StatsResponse{ - ReportInterval: api.AgentStatsRefreshInterval, - }) - return - } - - api.Logger.Debug(ctx, "read stats report", - slog.F("interval", api.AgentStatsRefreshInterval), - slog.F("workspace_agent_id", workspaceAgent.ID), - slog.F("workspace_id", workspace.ID), - slog.F("payload", req), - ) - - protoStats := &agentproto.Stats{ - ConnectionsByProto: req.ConnectionsByProto, - ConnectionCount: req.ConnectionCount, - ConnectionMedianLatencyMs: req.ConnectionMedianLatencyMS, - RxPackets: req.RxPackets, - RxBytes: req.RxBytes, - TxPackets: req.TxPackets, - TxBytes: req.TxBytes, - SessionCountVscode: req.SessionCountVSCode, - SessionCountJetbrains: req.SessionCountJetBrains, - SessionCountReconnectingPty: req.SessionCountReconnectingPTY, - SessionCountSsh: req.SessionCountSSH, - Metrics: make([]*agentproto.Stats_Metric, len(req.Metrics)), - } - for i, metric := range req.Metrics { - metricType := agentproto.Stats_Metric_TYPE_UNSPECIFIED - switch metric.Type { - case agentsdk.AgentMetricTypeCounter: - metricType = agentproto.Stats_Metric_COUNTER - case agentsdk.AgentMetricTypeGauge: - metricType = agentproto.Stats_Metric_GAUGE - } - - protoStats.Metrics[i] = &agentproto.Stats_Metric{ - Name: metric.Name, - Type: metricType, - Value: metric.Value, - Labels: make([]*agentproto.Stats_Metric_Label, len(metric.Labels)), - } - for j, label := range metric.Labels { - protoStats.Metrics[i].Labels[j] = &agentproto.Stats_Metric_Label{ - Name: label.Name, - Value: label.Value, - } - } - } - err = api.statsReporter.ReportAgentStats( - ctx, - dbtime.Now(), - workspace, - workspaceAgent, - row.TemplateName, - protoStats, - ) - if err != nil { - httpapi.InternalServerError(rw, err) - return - } - - httpapi.Write(ctx, rw, http.StatusOK, agentsdk.StatsResponse{ - ReportInterval: api.AgentStatsRefreshInterval, - }) -} - -func ellipse(v string, n int) string { - if len(v) > n { - return v[:n] + "..." - } - return v -} - -// @Summary Submit workspace agent metadata -// @ID submit-workspace-agent-metadata -// @Security CoderSessionToken -// @Accept json -// @Tags Agents -// @Param request body []agentsdk.PostMetadataRequest true "Workspace agent metadata request" -// @Success 204 "Success" -// @Router /workspaceagents/me/metadata [post] -// @x-apidocgen {"skip": true} -func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var req agentsdk.PostMetadataRequest - if !httpapi.Read(ctx, rw, r, &req) { - return - } - - workspaceAgent := httpmw.WorkspaceAgent(r) - - // Split into function to allow call by deprecated handler. - err := api.workspaceAgentUpdateMetadata(ctx, workspaceAgent, req) - if err != nil { - api.Logger.Error(ctx, "failed to handle metadata request", slog.Error(err)) - httpapi.InternalServerError(rw, err) - return - } - - httpapi.Write(ctx, rw, http.StatusNoContent, nil) -} - -func (api *API) workspaceAgentUpdateMetadata(ctx context.Context, workspaceAgent database.WorkspaceAgent, req agentsdk.PostMetadataRequest) error { - const ( - // maxValueLen is set to 2048 to stay under the 8000 byte Postgres - // NOTIFY limit. Since both value and error can be set, the real - // payload limit is 2 * 2048 * 4/3 = 5461 bytes + a few hundred bytes for JSON - // syntax, key names, and metadata. - maxValueLen = 2048 - maxErrorLen = maxValueLen - ) - - collectedAt := time.Now() - - datum := database.UpdateWorkspaceAgentMetadataParams{ - WorkspaceAgentID: workspaceAgent.ID, - Key: make([]string, 0, len(req.Metadata)), - Value: make([]string, 0, len(req.Metadata)), - Error: make([]string, 0, len(req.Metadata)), - CollectedAt: make([]time.Time, 0, len(req.Metadata)), - } - - for _, md := range req.Metadata { - metadataError := md.Error - - // We overwrite the error if the provided payload is too long. - if len(md.Value) > maxValueLen { - metadataError = fmt.Sprintf("value of %d bytes exceeded %d bytes", len(md.Value), maxValueLen) - md.Value = md.Value[:maxValueLen] - } - - if len(md.Error) > maxErrorLen { - metadataError = fmt.Sprintf("error of %d bytes exceeded %d bytes", len(md.Error), maxErrorLen) - md.Error = md.Error[:maxErrorLen] - } - - // We don't want a misconfigured agent to fill the database. - datum.Key = append(datum.Key, md.Key) - datum.Value = append(datum.Value, md.Value) - datum.Error = append(datum.Error, metadataError) - // We ignore the CollectedAt from the agent to avoid bugs caused by - // clock skew. - datum.CollectedAt = append(datum.CollectedAt, collectedAt) - - api.Logger.Debug( - ctx, "accepted metadata report", - slog.F("workspace_agent_id", workspaceAgent.ID), - slog.F("collected_at", collectedAt), - slog.F("original_collected_at", md.CollectedAt), - slog.F("key", md.Key), - slog.F("value", ellipse(md.Value, 16)), - ) - } - - payload, err := json.Marshal(agentapi.WorkspaceAgentMetadataChannelPayload{ - CollectedAt: collectedAt, - Keys: datum.Key, - }) - if err != nil { - return err - } - - err = api.Database.UpdateWorkspaceAgentMetadata(ctx, datum) - if err != nil { - return err - } - - err = api.Pubsub.Publish(agentapi.WatchWorkspaceAgentMetadataChannel(workspaceAgent.ID), payload) - if err != nil { - return err - } - - return nil -} - // @Summary Watch for workspace agent metadata updates // @ID watch-for-workspace-agent-metadata-updates // @Security CoderSessionToken @@ -1612,211 +1193,6 @@ func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []code return result } -// @Summary Submit workspace agent lifecycle state -// @ID submit-workspace-agent-lifecycle-state -// @Security CoderSessionToken -// @Accept json -// @Tags Agents -// @Param request body agentsdk.PostLifecycleRequest true "Workspace agent lifecycle request" -// @Success 204 "Success" -// @Router /workspaceagents/me/report-lifecycle [post] -// @x-apidocgen {"skip": true} -func (api *API) workspaceAgentReportLifecycle(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - workspaceAgent := httpmw.WorkspaceAgent(r) - row, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to get workspace.", - Detail: err.Error(), - }) - return - } - workspace := row.Workspace - - var req agentsdk.PostLifecycleRequest - if !httpapi.Read(ctx, rw, r, &req) { - return - } - - logger := api.Logger.With( - slog.F("workspace_agent_id", workspaceAgent.ID), - slog.F("workspace_id", workspace.ID), - slog.F("payload", req), - ) - logger.Debug(ctx, "workspace agent state report") - - lifecycleState := req.State - dbLifecycleState := database.WorkspaceAgentLifecycleState(lifecycleState) - if !dbLifecycleState.Valid() { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid lifecycle state.", - Detail: fmt.Sprintf("Invalid lifecycle state %q, must be be one of %q.", lifecycleState, database.AllWorkspaceAgentLifecycleStateValues()), - }) - return - } - - if req.ChangedAt.IsZero() { - // Backwards compatibility with older agents. - req.ChangedAt = dbtime.Now() - } - changedAt := sql.NullTime{Time: req.ChangedAt, Valid: true} - - startedAt := workspaceAgent.StartedAt - readyAt := workspaceAgent.ReadyAt - switch lifecycleState { - case codersdk.WorkspaceAgentLifecycleStarting: - startedAt = changedAt - readyAt.Valid = false // This agent is re-starting, so it's not ready yet. - case codersdk.WorkspaceAgentLifecycleReady, codersdk.WorkspaceAgentLifecycleStartError: - readyAt = changedAt - } - - err = api.Database.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{ - ID: workspaceAgent.ID, - LifecycleState: dbLifecycleState, - StartedAt: startedAt, - ReadyAt: readyAt, - }) - if err != nil { - if !xerrors.Is(err, context.Canceled) { - // not an error if we are canceled - logger.Error(ctx, "failed to update lifecycle state", slog.Error(err)) - } - httpapi.InternalServerError(rw, err) - return - } - - api.publishWorkspaceUpdate(ctx, workspace.ID) - - httpapi.Write(ctx, rw, http.StatusNoContent, nil) -} - -// @Summary Submit workspace agent application health -// @ID submit-workspace-agent-application-health -// @Security CoderSessionToken -// @Accept json -// @Produce json -// @Tags Agents -// @Param request body agentsdk.PostAppHealthsRequest true "Application health request" -// @Success 200 -// @Router /workspaceagents/me/app-health [post] -func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - workspaceAgent := httpmw.WorkspaceAgent(r) - var req agentsdk.PostAppHealthsRequest - if !httpapi.Read(ctx, rw, r, &req) { - return - } - - if req.Healths == nil || len(req.Healths) == 0 { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Health field is empty", - }) - return - } - - apps, err := api.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Error getting agent apps", - Detail: err.Error(), - }) - return - } - - var newApps []database.WorkspaceApp - for id, newHealth := range req.Healths { - old := func() *database.WorkspaceApp { - for _, app := range apps { - if app.ID == id { - return &app - } - } - - return nil - }() - if old == nil { - httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: xerrors.Errorf("workspace app name %s not found", id).Error(), - }) - return - } - - if old.HealthcheckUrl == "" { - httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: xerrors.Errorf("health checking is disabled for workspace app %s", id).Error(), - }) - return - } - - switch newHealth { - case codersdk.WorkspaceAppHealthInitializing: - case codersdk.WorkspaceAppHealthHealthy: - case codersdk.WorkspaceAppHealthUnhealthy: - default: - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: xerrors.Errorf("workspace app health %s is not a valid value", newHealth).Error(), - }) - return - } - - // don't save if the value hasn't changed - if old.Health == database.WorkspaceAppHealth(newHealth) { - continue - } - old.Health = database.WorkspaceAppHealth(newHealth) - - newApps = append(newApps, *old) - } - - for _, app := range newApps { - err = api.Database.UpdateWorkspaceAppHealthByID(ctx, database.UpdateWorkspaceAppHealthByIDParams{ - ID: app.ID, - Health: app.Health, - }) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: err.Error(), - }) - return - } - } - - resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace resource.", - Detail: err.Error(), - }) - return - } - job, err := api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace build.", - Detail: err.Error(), - }) - return - } - workspace, err := api.Database.GetWorkspaceByID(ctx, job.WorkspaceID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace.", - Detail: err.Error(), - }) - return - } - api.publishWorkspaceUpdate(ctx, workspace.ID) - - httpapi.Write(ctx, rw, http.StatusOK, nil) -} - // workspaceAgentsExternalAuth returns an access token for a given URL // or finds a provider by ID. // @@ -2117,24 +1493,3 @@ func convertWorkspaceAgentLog(logEntry database.WorkspaceAgentLog) codersdk.Work SourceID: logEntry.LogSourceID, } } - -func convertWorkspaceAgentSubsystems(ss []codersdk.AgentSubsystem) []database.WorkspaceAgentSubsystem { - out := make([]database.WorkspaceAgentSubsystem, 0, len(ss)) - for _, s := range ss { - switch s { - case codersdk.AgentSubsystemEnvbox: - out = append(out, database.WorkspaceAgentSubsystemEnvbox) - case codersdk.AgentSubsystemEnvbuilder: - out = append(out, database.WorkspaceAgentSubsystemEnvbuilder) - case codersdk.AgentSubsystemExectrace: - out = append(out, database.WorkspaceAgentSubsystemExectrace) - default: - // Invalid, drop it. - } - } - - sort.Slice(out, func(i, j int) bool { - return out[i] < out[j] - }) - return out -} diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 7052d59144e1b..f50a886205cdf 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/timestamppb" "tailscale.com/tailcfg" "cdr.dev/slog" @@ -34,7 +35,6 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/externalauth" - "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/workspacesdk" @@ -963,110 +963,12 @@ func TestWorkspaceAgentPostLogSource(t *testing.T) { }) } -// TestWorkspaceAgentReportStats tests the legacy (agent API v1) report stats endpoint. -func TestWorkspaceAgentReportStats(t *testing.T) { - t.Parallel() - - t.Run("OK", func(t *testing.T) { - t.Parallel() - - client, db := coderdtest.NewWithDatabase(t, nil) - user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ - OrganizationID: user.OrganizationID, - OwnerID: user.UserID, - }).WithAgent().Do() - - agentClient := agentsdk.New(client.URL) - agentClient.SetSessionToken(r.AgentToken) - - _, err := agentClient.PostStats(context.Background(), &agentsdk.Stats{ - ConnectionsByProto: map[string]int64{"TCP": 1}, - ConnectionCount: 1, - RxPackets: 1, - RxBytes: 1, - TxPackets: 1, - TxBytes: 1, - SessionCountVSCode: 1, - SessionCountJetBrains: 0, - SessionCountReconnectingPTY: 0, - SessionCountSSH: 0, - ConnectionMedianLatencyMS: 10, - }) - require.NoError(t, err) - - newWorkspace, err := client.Workspace(context.Background(), r.Workspace.ID) - require.NoError(t, err) - - assert.True(t, - newWorkspace.LastUsedAt.After(r.Workspace.LastUsedAt), - "%s is not after %s", newWorkspace.LastUsedAt, r.Workspace.LastUsedAt, - ) - }) - - t.Run("FailDeleted", func(t *testing.T) { - t.Parallel() - - owner, db := coderdtest.NewWithDatabase(t, nil) - ownerUser := coderdtest.CreateFirstUser(t, owner) - client, admin := coderdtest.CreateAnotherUser(t, owner, ownerUser.OrganizationID, rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin()) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ - OrganizationID: admin.OrganizationIDs[0], - OwnerID: admin.ID, - }).WithAgent().Do() - - agentClient := agentsdk.New(client.URL) - agentClient.SetSessionToken(r.AgentToken) - - _, err := agentClient.PostStats(context.Background(), &agentsdk.Stats{ - ConnectionsByProto: map[string]int64{"TCP": 1}, - ConnectionCount: 1, - RxPackets: 1, - RxBytes: 1, - TxPackets: 1, - TxBytes: 1, - SessionCountVSCode: 0, - SessionCountJetBrains: 0, - SessionCountReconnectingPTY: 0, - SessionCountSSH: 0, - ConnectionMedianLatencyMS: 10, - }) - require.NoError(t, err) - - newWorkspace, err := client.Workspace(context.Background(), r.Workspace.ID) - require.NoError(t, err) - - // nolint:gocritic // using db directly over creating a delete job - err = db.UpdateWorkspaceDeletedByID(dbauthz.As(context.Background(), - coderdtest.AuthzUserSubject(admin, ownerUser.OrganizationID)), - database.UpdateWorkspaceDeletedByIDParams{ - ID: newWorkspace.ID, - Deleted: true, - }) - require.NoError(t, err) - - _, err = agentClient.PostStats(context.Background(), &agentsdk.Stats{ - ConnectionsByProto: map[string]int64{"TCP": 1}, - ConnectionCount: 1, - RxPackets: 1, - RxBytes: 1, - TxPackets: 1, - TxBytes: 1, - SessionCountVSCode: 1, - SessionCountJetBrains: 0, - SessionCountReconnectingPTY: 0, - SessionCountSSH: 0, - ConnectionMedianLatencyMS: 10, - }) - require.ErrorContains(t, err, "agent is invalid") - }) -} - func TestWorkspaceAgent_LifecycleState(t *testing.T) { t.Parallel() t.Run("Set", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitLong) client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) @@ -1082,8 +984,15 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) { } } - agentClient := agentsdk.New(client.URL) - agentClient.SetSessionToken(r.AgentToken) + ac := agentsdk.New(client.URL) + ac.SetSessionToken(r.AgentToken) + conn, err := ac.ConnectRPC(ctx) + require.NoError(t, err) + defer func() { + cErr := conn.Close() + require.NoError(t, cErr) + }() + agentAPI := agentproto.NewDRPCAgentClient(conn) tests := []struct { state codersdk.WorkspaceAgentLifecycle @@ -1105,16 +1014,17 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) { for _, tt := range tests { tt := tt t.Run(string(tt.state), func(t *testing.T) { - ctx := testutil.Context(t, testutil.WaitLong) - - err := agentClient.PostLifecycle(ctx, agentsdk.PostLifecycleRequest{ - State: tt.state, - ChangedAt: time.Now(), - }) + state, err := agentsdk.ProtoFromLifecycleState(tt.state) if tt.wantErr { require.Error(t, err) return } + _, err = agentAPI.UpdateLifecycle(ctx, &agentproto.UpdateLifecycleRequest{ + Lifecycle: &agentproto.Lifecycle{ + State: state, + ChangedAt: timestamppb.Now(), + }, + }) require.NoError(t, err, "post lifecycle state %q", tt.state) workspace, err = client.Workspace(ctx, workspace.ID) @@ -1197,11 +1107,11 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.EqualValues(t, 3, manifest.Metadata[0].Timeout) post := func(ctx context.Context, key string, mr codersdk.WorkspaceAgentMetadataResult) { - err := agentClient.PostMetadata(ctx, agentsdk.PostMetadataRequest{ - Metadata: []agentsdk.Metadata{ + _, err := aAPI.BatchUpdateMetadata(ctx, &agentproto.BatchUpdateMetadataRequest{ + Metadata: []*agentproto.Metadata{ { - Key: key, - WorkspaceAgentMetadataResult: mr, + Key: key, + Result: agentsdk.ProtoFromMetadataResult(mr), }, }, }) @@ -1452,17 +1362,18 @@ func TestWorkspaceAgent_Metadata_CatchMemoryLeak(t *testing.T) { manifest := requireGetManifest(ctx, t, aAPI) post := func(ctx context.Context, key, value string) error { - return agentClient.PostMetadata(ctx, agentsdk.PostMetadataRequest{ - Metadata: []agentsdk.Metadata{ + _, err := aAPI.BatchUpdateMetadata(ctx, &agentproto.BatchUpdateMetadataRequest{ + Metadata: []*agentproto.Metadata{ { Key: key, - WorkspaceAgentMetadataResult: codersdk.WorkspaceAgentMetadataResult{ + Result: agentsdk.ProtoFromMetadataResult(codersdk.WorkspaceAgentMetadataResult{ CollectedAt: time.Now(), Value: value, - }, + }), }, }, }) + return err } workspace, err = client.Workspace(ctx, workspace.ID) diff --git a/coderd/workspaceagentsrpc.go b/coderd/workspaceagentsrpc.go index 24b6088ddd8f2..ec8dcd8a0e3fc 100644 --- a/coderd/workspaceagentsrpc.go +++ b/coderd/workspaceagentsrpc.go @@ -164,31 +164,6 @@ func (api *API) workspaceAgentRPC(rw http.ResponseWriter, r *http.Request) { } } -func (api *API) startAgentWebsocketMonitor(ctx context.Context, - workspaceAgent database.WorkspaceAgent, workspaceBuild database.WorkspaceBuild, - conn *websocket.Conn, -) *agentConnectionMonitor { - monitor := &agentConnectionMonitor{ - apiCtx: api.ctx, - workspaceAgent: workspaceAgent, - workspaceBuild: workspaceBuild, - conn: conn, - pingPeriod: api.AgentConnectionUpdateFrequency, - db: api.Database, - replicaID: api.ID, - updater: api, - disconnectTimeout: api.AgentInactiveDisconnectTimeout, - logger: api.Logger.With( - slog.F("workspace_id", workspaceBuild.WorkspaceID), - slog.F("agent_id", workspaceAgent.ID), - ), - } - monitor.init() - monitor.start(ctx) - - return monitor -} - type yamuxPingerCloser struct { mux *yamux.Session } diff --git a/coderd/workspaceagentsrpc_test.go b/coderd/workspaceagentsrpc_test.go index a92fbdcd1ca1a..ca8f334d4e766 100644 --- a/coderd/workspaceagentsrpc_test.go +++ b/coderd/workspaceagentsrpc_test.go @@ -1,8 +1,10 @@ package coderd_test import ( + "context" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" agentproto "github.com/coder/coder/v2/agent/proto" @@ -14,6 +16,52 @@ import ( "github.com/coder/coder/v2/testutil" ) +// Ported to RPC API from coderd/workspaceagents_test.go +func TestWorkspaceAgentReportStats(t *testing.T) { + t.Parallel() + + client, db := coderdtest.NewWithDatabase(t, nil) + user := coderdtest.CreateFirstUser(t, client) + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OrganizationID: user.OrganizationID, + OwnerID: user.UserID, + }).WithAgent().Do() + + ac := agentsdk.New(client.URL) + ac.SetSessionToken(r.AgentToken) + conn, err := ac.ConnectRPC(context.Background()) + require.NoError(t, err) + defer func() { + _ = conn.Close() + }() + agentAPI := agentproto.NewDRPCAgentClient(conn) + + _, err = agentAPI.UpdateStats(context.Background(), &agentproto.UpdateStatsRequest{ + Stats: &agentproto.Stats{ + ConnectionsByProto: map[string]int64{"TCP": 1}, + ConnectionCount: 1, + RxPackets: 1, + RxBytes: 1, + TxPackets: 1, + TxBytes: 1, + SessionCountVscode: 1, + SessionCountJetbrains: 0, + SessionCountReconnectingPty: 0, + SessionCountSsh: 0, + ConnectionMedianLatencyMs: 10, + }, + }) + require.NoError(t, err) + + newWorkspace, err := client.Workspace(context.Background(), r.Workspace.ID) + require.NoError(t, err) + + assert.True(t, + newWorkspace.LastUsedAt.After(r.Workspace.LastUsedAt), + "%s is not after %s", newWorkspace.LastUsedAt, r.Workspace.LastUsedAt, + ) +} + func TestAgentAPI_LargeManifest(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) diff --git a/coderd/workspacebuilds_test.go b/coderd/workspacebuilds_test.go index eb76239b84658..5d99e56820aa1 100644 --- a/coderd/workspacebuilds_test.go +++ b/coderd/workspacebuilds_test.go @@ -728,7 +728,7 @@ func TestWorkspaceDeleteSuspendedUser(t *testing.T) { validateCalls++ if userSuspended { // Simulate the user being suspended from the IDP too. - return "", http.StatusForbidden, fmt.Errorf("user is suspended") + return "", http.StatusForbidden, xerrors.New("user is suspended") } return "OK", 0, nil }, diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index f3a09c5357711..32222479b37ee 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -84,23 +84,6 @@ type PostMetadataRequest struct { // performance. type PostMetadataRequestDeprecated = codersdk.WorkspaceAgentMetadataResult -// PostMetadata posts agent metadata to the Coder server. -// -// Deprecated: use BatchUpdateMetadata on the agent dRPC API instead -func (c *Client) PostMetadata(ctx context.Context, req PostMetadataRequest) error { - res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/metadata", req) - if err != nil { - return xerrors.Errorf("execute request: %w", err) - } - defer res.Body.Close() - - if res.StatusCode != http.StatusNoContent { - return codersdk.ReadBodyAsError(res) - } - - return nil -} - type Manifest struct { AgentID uuid.UUID `json:"agent_id"` AgentName string `json:"agent_name"` @@ -457,49 +440,11 @@ type StatsResponse struct { ReportInterval time.Duration `json:"report_interval"` } -// PostStats sends agent stats to the coder server -// -// Deprecated: uses agent API v1 endpoint -func (c *Client) PostStats(ctx context.Context, stats *Stats) (StatsResponse, error) { - res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/report-stats", stats) - if err != nil { - return StatsResponse{}, xerrors.Errorf("send request: %w", err) - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return StatsResponse{}, codersdk.ReadBodyAsError(res) - } - - var interval StatsResponse - err = json.NewDecoder(res.Body).Decode(&interval) - if err != nil { - return StatsResponse{}, xerrors.Errorf("decode stats response: %w", err) - } - - return interval, nil -} - type PostLifecycleRequest struct { State codersdk.WorkspaceAgentLifecycle `json:"state"` ChangedAt time.Time `json:"changed_at"` } -// PostLifecycle posts the agent's lifecycle to the Coder server. -// -// Deprecated: Use UpdateLifecycle on the dRPC API instead -func (c *Client) PostLifecycle(ctx context.Context, req PostLifecycleRequest) error { - res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/report-lifecycle", req) - if err != nil { - return xerrors.Errorf("agent state post request: %w", err) - } - defer res.Body.Close() - if res.StatusCode != http.StatusNoContent { - return codersdk.ReadBodyAsError(res) - } - - return nil -} - type PostStartupRequest struct { Version string `json:"version"` ExpandedDirectory string `json:"expanded_directory"` diff --git a/codersdk/agentsdk/convert.go b/codersdk/agentsdk/convert.go index adfabd1510768..45ea9532c4c45 100644 --- a/codersdk/agentsdk/convert.go +++ b/codersdk/agentsdk/convert.go @@ -371,3 +371,11 @@ func LifecycleStateFromProto(s proto.Lifecycle_State) (codersdk.WorkspaceAgentLi } return codersdk.WorkspaceAgentLifecycle(strings.ToLower(caps)), nil } + +func ProtoFromLifecycleState(s codersdk.WorkspaceAgentLifecycle) (proto.Lifecycle_State, error) { + caps, ok := proto.Lifecycle_State_value[strings.ToUpper(string(s))] + if !ok { + return 0, xerrors.Errorf("unknown lifecycle state: %s", s) + } + return proto.Lifecycle_State(caps), nil +} diff --git a/docs/api/agents.md b/docs/api/agents.md index 13e5c38590d5c..e32fb0ac10f7a 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -160,67 +160,6 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/google-instance-ide To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Submit workspace agent application health - -### Code samples - -```shell -# Example request using curl -curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/app-health \ - -H 'Content-Type: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`POST /workspaceagents/me/app-health` - -> Body parameter - -```json -{ - "healths": { - "property1": "disabled", - "property2": "disabled" - } -} -``` - -### Parameters - -| Name | In | Type | Required | Description | -| ------ | ---- | -------------------------------------------------------------------------- | -------- | -------------------------- | -| `body` | body | [agentsdk.PostAppHealthsRequest](schemas.md#agentsdkpostapphealthsrequest) | true | Application health request | - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - -## Coordinate workspace agent via Tailnet - -### Code samples - -```shell -# Example request using curl -curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/coordinate \ - -H 'Coder-Session-Token: API_KEY' -``` - -`GET /workspaceagents/me/coordinate` - -It accepts a WebSocket connection to an agent that listens to -incoming connections and publishes node updates. - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------------------------ | ------------------- | ------ | -| 101 | [Switching Protocols](https://tools.ietf.org/html/rfc7231#section-6.2.2) | Switching Protocols | | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - ## Get workspace agent external auth ### Code samples @@ -453,283 +392,6 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/logs \ To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get authorized workspace agent manifest - -### Code samples - -```shell -# Example request using curl -curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/manifest \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`GET /workspaceagents/me/manifest` - -### Example responses - -> 200 Response - -```json -{ - "agent_id": "string", - "agent_name": "string", - "apps": [ - { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": { - "interval": 0, - "threshold": 0, - "url": "string" - }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "string" - } - ], - "derp_force_websockets": true, - "derpmap": { - "homeParams": { - "regionScore": { - "property1": 0, - "property2": 0 - } - }, - "omitDefaultRegions": true, - "regions": { - "property1": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "property2": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - } - } - }, - "directory": "string", - "disable_direct_connections": true, - "environment_variables": { - "property1": "string", - "property2": "string" - }, - "git_auth_configs": 0, - "metadata": [ - { - "display_name": "string", - "interval": 0, - "key": "string", - "script": "string", - "timeout": 0 - } - ], - "motd_file": "string", - "owner_name": "string", - "scripts": [ - { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 - } - ], - "vscode_port_proxy_uri": "string", - "workspace_id": "string", - "workspace_name": "string" -} -``` - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [agentsdk.Manifest](schemas.md#agentsdkmanifest) | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - -## Submit workspace agent stats - -### Code samples - -```shell -# Example request using curl -curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/report-stats \ - -H 'Content-Type: application/json' \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`POST /workspaceagents/me/report-stats` - -> Body parameter - -```json -{ - "connection_count": 0, - "connection_median_latency_ms": 0, - "connections_by_proto": { - "property1": 0, - "property2": 0 - }, - "metrics": [ - { - "labels": [ - { - "name": "string", - "value": "string" - } - ], - "name": "string", - "type": "counter", - "value": 0 - } - ], - "rx_bytes": 0, - "rx_packets": 0, - "session_count_jetbrains": 0, - "session_count_reconnecting_pty": 0, - "session_count_ssh": 0, - "session_count_vscode": 0, - "tx_bytes": 0, - "tx_packets": 0 -} -``` - -### Parameters - -| Name | In | Type | Required | Description | -| ------ | ---- | ------------------------------------------ | -------- | ------------- | -| `body` | body | [agentsdk.Stats](schemas.md#agentsdkstats) | true | Stats request | - -### Example responses - -> 200 Response - -```json -{ - "report_interval": 0 -} -``` - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [agentsdk.StatsResponse](schemas.md#agentsdkstatsresponse) | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - -## Removed: Patch workspace agent logs - -### Code samples - -```shell -# Example request using curl -curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/startup-logs \ - -H 'Content-Type: application/json' \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`PATCH /workspaceagents/me/startup-logs` - -> Body parameter - -```json -{ - "log_source_id": "string", - "logs": [ - { - "created_at": "string", - "level": "trace", - "output": "string" - } - ] -} -``` - -### Parameters - -| Name | In | Type | Required | Description | -| ------ | ---- | -------------------------------------------------- | -------- | ----------- | -| `body` | body | [agentsdk.PatchLogs](schemas.md#agentsdkpatchlogs) | true | logs | - -### Example responses - -> 200 Response - -```json -{ - "detail": "string", - "message": "string", - "validations": [ - { - "detail": "string", - "field": "string" - } - ] -} -``` - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - ## Get workspace agent by ID ### Code samples diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 7770b091878bd..9a5f0d9e3c94a 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -16,69 +16,6 @@ | `document` | string | true | | | | `signature` | string | true | | | -## agentsdk.AgentMetric - -```json -{ - "labels": [ - { - "name": "string", - "value": "string" - } - ], - "name": "string", - "type": "counter", - "value": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------- | --------------------------------------------------------------- | -------- | ------------ | ----------- | -| `labels` | array of [agentsdk.AgentMetricLabel](#agentsdkagentmetriclabel) | false | | | -| `name` | string | true | | | -| `type` | [agentsdk.AgentMetricType](#agentsdkagentmetrictype) | true | | | -| `value` | number | true | | | - -#### Enumerated Values - -| Property | Value | -| -------- | --------- | -| `type` | `counter` | -| `type` | `gauge` | - -## agentsdk.AgentMetricLabel - -```json -{ - "name": "string", - "value": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------- | ------ | -------- | ------------ | ----------- | -| `name` | string | true | | | -| `value` | string | true | | | - -## agentsdk.AgentMetricType - -```json -"counter" -``` - -### Properties - -#### Enumerated Values - -| Value | -| --------- | -| `counter` | -| `gauge` | - ## agentsdk.AuthenticateResponse ```json @@ -181,172 +118,6 @@ | `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | | `output` | string | false | | | -## agentsdk.Manifest - -```json -{ - "agent_id": "string", - "agent_name": "string", - "apps": [ - { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": { - "interval": 0, - "threshold": 0, - "url": "string" - }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "string" - } - ], - "derp_force_websockets": true, - "derpmap": { - "homeParams": { - "regionScore": { - "property1": 0, - "property2": 0 - } - }, - "omitDefaultRegions": true, - "regions": { - "property1": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "property2": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - } - } - }, - "directory": "string", - "disable_direct_connections": true, - "environment_variables": { - "property1": "string", - "property2": "string" - }, - "git_auth_configs": 0, - "metadata": [ - { - "display_name": "string", - "interval": 0, - "key": "string", - "script": "string", - "timeout": 0 - } - ], - "motd_file": "string", - "owner_name": "string", - "scripts": [ - { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 - } - ], - "vscode_port_proxy_uri": "string", - "workspace_id": "string", - "workspace_name": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ---------------------------- | ------------------------------------------------------------------------------------------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `agent_id` | string | false | | | -| `agent_name` | string | false | | | -| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | -| `derp_force_websockets` | boolean | false | | | -| `derpmap` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | | -| `directory` | string | false | | | -| `disable_direct_connections` | boolean | false | | | -| `environment_variables` | object | false | | | -| » `[any property]` | string | false | | | -| `git_auth_configs` | integer | false | | Git auth configs stores the number of Git configurations the Coder deployment has. If this number is >0, we set up special configuration in the workspace. | -| `metadata` | array of [codersdk.WorkspaceAgentMetadataDescription](#codersdkworkspaceagentmetadatadescription) | false | | | -| `motd_file` | string | false | | | -| `owner_name` | string | false | | Owner name and WorkspaceID are used by an open-source user to identify the workspace. We do not provide insurance that this will not be removed in the future, but if it's easy to persist lets keep it around. | -| `scripts` | array of [codersdk.WorkspaceAgentScript](#codersdkworkspaceagentscript) | false | | | -| `vscode_port_proxy_uri` | string | false | | | -| `workspace_id` | string | false | | | -| `workspace_name` | string | false | | | - -## agentsdk.Metadata - -```json -{ - "age": 0, - "collected_at": "2019-08-24T14:15:22Z", - "error": "string", - "key": "string", - "value": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------- | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -| `age` | integer | false | | Age is the number of seconds since the metadata was collected. It is provided in addition to CollectedAt to protect against clock skew. | -| `collected_at` | string | false | | | -| `error` | string | false | | | -| `key` | string | false | | | -| `value` | string | false | | | - ## agentsdk.PatchLogs ```json @@ -369,40 +140,6 @@ | `log_source_id` | string | false | | | | `logs` | array of [agentsdk.Log](#agentsdklog) | false | | | -## agentsdk.PostAppHealthsRequest - -```json -{ - "healths": { - "property1": "disabled", - "property2": "disabled" - } -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------ | ---------------------------------------------------------- | -------- | ------------ | --------------------------------------------------------------------- | -| `healths` | object | false | | Healths is a map of the workspace app name and the health of the app. | -| » `[any property]` | [codersdk.WorkspaceAppHealth](#codersdkworkspaceapphealth) | false | | | - -## agentsdk.PostLifecycleRequest - -```json -{ - "changed_at": "string", - "state": "created" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | -------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `changed_at` | string | false | | | -| `state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | - ## agentsdk.PostLogSourceRequest ```json @@ -421,132 +158,6 @@ | `icon` | string | false | | | | `id` | string | false | | ID is a unique identifier for the log source. It is scoped to a workspace agent, and can be statically defined inside code to prevent duplicate sources from being created for the same agent. | -## agentsdk.PostMetadataRequest - -```json -{ - "metadata": [ - { - "age": 0, - "collected_at": "2019-08-24T14:15:22Z", - "error": "string", - "key": "string", - "value": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ---------- | ----------------------------------------------- | -------- | ------------ | ----------- | -| `metadata` | array of [agentsdk.Metadata](#agentsdkmetadata) | false | | | - -## agentsdk.PostMetadataRequestDeprecated - -```json -{ - "age": 0, - "collected_at": "2019-08-24T14:15:22Z", - "error": "string", - "value": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------- | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -| `age` | integer | false | | Age is the number of seconds since the metadata was collected. It is provided in addition to CollectedAt to protect against clock skew. | -| `collected_at` | string | false | | | -| `error` | string | false | | | -| `value` | string | false | | | - -## agentsdk.PostStartupRequest - -```json -{ - "expanded_directory": "string", - "subsystems": ["envbox"], - "version": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------------- | ----------------------------------------------------------- | -------- | ------------ | ----------- | -| `expanded_directory` | string | false | | | -| `subsystems` | array of [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | | -| `version` | string | false | | | - -## agentsdk.Stats - -```json -{ - "connection_count": 0, - "connection_median_latency_ms": 0, - "connections_by_proto": { - "property1": 0, - "property2": 0 - }, - "metrics": [ - { - "labels": [ - { - "name": "string", - "value": "string" - } - ], - "name": "string", - "type": "counter", - "value": 0 - } - ], - "rx_bytes": 0, - "rx_packets": 0, - "session_count_jetbrains": 0, - "session_count_reconnecting_pty": 0, - "session_count_ssh": 0, - "session_count_vscode": 0, - "tx_bytes": 0, - "tx_packets": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------------------------- | ----------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | -| `connection_count` | integer | false | | Connection count is the number of connections received by an agent. | -| `connection_median_latency_ms` | number | false | | Connection median latency ms is the median latency of all connections in milliseconds. | -| `connections_by_proto` | object | false | | Connections by proto is a count of connections by protocol. | -| » `[any property]` | integer | false | | | -| `metrics` | array of [agentsdk.AgentMetric](#agentsdkagentmetric) | false | | Metrics collected by the agent | -| `rx_bytes` | integer | false | | Rx bytes is the number of received bytes. | -| `rx_packets` | integer | false | | Rx packets is the number of received packets. | -| `session_count_jetbrains` | integer | false | | Session count jetbrains is the number of connections received by an agent that are from our JetBrains extension. | -| `session_count_reconnecting_pty` | integer | false | | Session count reconnecting pty is the number of connections received by an agent that are from the reconnecting web terminal. | -| `session_count_ssh` | integer | false | | Session count ssh is the number of connections received by an agent that are normal, non-tagged SSH sessions. | -| `session_count_vscode` | integer | false | | Session count vscode is the number of connections received by an agent that are from our VS Code extension. | -| `tx_bytes` | integer | false | | Tx bytes is the number of transmitted bytes. | -| `tx_packets` | integer | false | | Tx packets is the number of transmitted bytes. | - -## agentsdk.StatsResponse - -```json -{ - "report_interval": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ----------------- | ------- | -------- | ------------ | ------------------------------------------------------------------------------ | -| `report_interval` | integer | false | | Report interval is the duration after which the agent should send stats again. | - ## coderd.SCIMUser ```json @@ -6408,28 +6019,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `id` | string | false | | | | `workspace_agent_id` | string | false | | | -## codersdk.WorkspaceAgentMetadataDescription - -```json -{ - "display_name": "string", - "interval": 0, - "key": "string", - "script": "string", - "timeout": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------- | ------- | -------- | ------------ | ----------- | -| `display_name` | string | false | | | -| `interval` | integer | false | | | -| `key` | string | false | | | -| `script` | string | false | | | -| `timeout` | integer | false | | | - ## codersdk.WorkspaceAgentPortShare ```json diff --git a/site/e2e/tests/outdatedAgent.spec.ts b/site/e2e/tests/outdatedAgent.spec.ts index c6bccba658be4..48393c63f7d0e 100644 --- a/site/e2e/tests/outdatedAgent.spec.ts +++ b/site/e2e/tests/outdatedAgent.spec.ts @@ -11,8 +11,8 @@ import { } from "../helpers"; import { beforeCoderTest } from "../hooks"; -// we no longer support versions prior to single tailnet: https://github.com/coder/coder/commit/d7cbdbd9c64ad26821e6b35834c59ecf85dcd9d4 -const agentVersion = "v0.27.0"; +// we no longer support versions w/o DRPC +const agentVersion = "v2.12.1"; test.beforeEach(({ page }) => beforeCoderTest(page));