chore!: remove deprecated agent v1 routes (#13486)

This commit is contained in:
Ethan
2024-06-11 12:22:59 +10:00
committed by GitHub
parent e7bea17e70
commit dd243686e4
15 changed files with 145 additions and 2884 deletions
-624
View File
@@ -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,190 +5893,6 @@ 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": [
@@ -6155,84 +5915,6 @@ const docTemplate = `{
}
}
},
"/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": [
@@ -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": {
@@ -13166,26 +12562,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": {
-566
View File
@@ -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": {
@@ -11976,26 +11430,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": {
-12
View File
@@ -1002,23 +1002,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) {
-56
View File
@@ -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
@@ -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")
@@ -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/coderdtest"
"github.com/coder/coder/v2/coderd/database"
@@ -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 (
-645
View File
@@ -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 <base64 expansion> = 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
}
+27 -116
View File
@@ -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)
-25
View File
@@ -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
}
+48
View File
@@ -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)
-55
View File
@@ -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"`
+8
View File
@@ -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
}
-338
View File
@@ -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
-411
View File
@@ -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
@@ -6433,28 +6044,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
+2 -2
View File
@@ -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));