From ca8177f65c2db8b24ab00c8dc1d7402c4d40ae87 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 14 Mar 2023 23:48:31 +0000 Subject: [PATCH 01/73] Start writing docs --- docs/templates/resource-metadata.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/templates/resource-metadata.md b/docs/templates/resource-metadata.md index a5870a9925734..ea511055c3330 100644 --- a/docs/templates/resource-metadata.md +++ b/docs/templates/resource-metadata.md @@ -97,6 +97,26 @@ To make easier for you to customize your resource we added some built-in icons: We also have other icons related to the IDEs. You can see all the icons [here](https://github.com/coder/coder/tree/main/site/static/icon). +## Agent Metadata + +In cases where you want to present automatically updating, dynamic values. You +can use the `metadata` block in the `coder_agent` resource. For example: + +```hcl +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" + metadata { + name = "Process Count" + cmd = "ps aux | wc -l" + refresh = 5s + # Any data above this width will require scrolling. + width = 5 + } +} +``` + ## Up next - Learn about [secrets](../secrets.md) From a4bbb6e7abf0217f04e2e6c7e8413cde56d134a3 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 15 Mar 2023 00:25:12 +0000 Subject: [PATCH 02/73] regenerate testdata --- provisioner/terraform/resources.go | 24 +- .../calling-module/calling-module.tfplan.json | 2 +- .../calling-module.tfstate.json | 10 +- .../chaining-resources.tfplan.json | 2 +- .../chaining-resources.tfstate.json | 10 +- .../conflicting-resources.tfplan.json | 2 +- .../conflicting-resources.tfstate.json | 10 +- .../git-auth-providers.tfstate.json | 6 +- .../instance-id/instance-id.tfplan.json | 2 +- .../instance-id/instance-id.tfstate.json | 12 +- .../mapped-apps/mapped-apps.tfstate.json | 14 +- .../multiple-agents.tfstate.json | 14 +- .../multiple-apps/multiple-apps.tfplan.json | 2 +- .../multiple-apps/multiple-apps.tfstate.json | 20 +- .../resource-metadata/resource-metadata.tf | 11 +- .../resource-metadata.tfplan.json | 58 +- .../resource-metadata.tfstate.json | 31 +- .../rich-parameters.tfplan.json | 6 +- .../rich-parameters.tfstate.json | 10 +- provisionersdk/proto/provisioner.pb.go | 615 ++++++++++-------- provisionersdk/proto/provisioner.proto | 6 + 21 files changed, 524 insertions(+), 343 deletions(-) diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index f32456ebc4fd9..2c49df75821c2 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -14,6 +14,13 @@ import ( "github.com/coder/coder/provisionersdk/proto" ) +type agentMetadata struct { + Key string `mapstructure:"key"` + DisplayName string `mapstructure:"display_name"` + Cmd string `mapstructure:"cmd"` + Interval string `mapstructure:"interval"` +} + // A mapping of attributes on the "coder_agent" resource. type agentAttributes struct { Auth string `mapstructure:"auth"` @@ -31,6 +38,7 @@ type agentAttributes struct { StartupScriptTimeoutSeconds int32 `mapstructure:"startup_script_timeout"` ShutdownScript string `mapstructure:"shutdown_script"` ShutdownScriptTimeoutSeconds int32 `mapstructure:"shutdown_script_timeout"` + Metadata []agentMetadata `mapstructure:"metadata"` } // A mapping of attributes on the "coder_app" resource. @@ -59,15 +67,15 @@ type appHealthcheckAttributes struct { } // A mapping of attributes on the "coder_metadata" resource. -type metadataAttributes struct { - ResourceID string `mapstructure:"resource_id"` - Hide bool `mapstructure:"hide"` - Icon string `mapstructure:"icon"` - DailyCost int32 `mapstructure:"daily_cost"` - Items []metadataItem `mapstructure:"item"` +type resourceMetadataAttributes struct { + ResourceID string `mapstructure:"resource_id"` + Hide bool `mapstructure:"hide"` + Icon string `mapstructure:"icon"` + DailyCost int32 `mapstructure:"daily_cost"` + Items []resourceMetadataItem `mapstructure:"item"` } -type metadataItem struct { +type resourceMetadataItem struct { Key string `mapstructure:"key"` Value string `mapstructure:"value"` Sensitive bool `mapstructure:"sensitive"` @@ -348,7 +356,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error continue } - var attrs metadataAttributes + var attrs resourceMetadataAttributes err = mapstructure.Decode(resource.AttributeValues, &attrs) if err != nil { return nil, xerrors.Errorf("decode metadata attributes: %w", err) diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json b/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json index 363b4f52fde5e..be7a0aaaca9da 100644 --- a/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json +++ b/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json index fc8751b6e6724..7815da0645ead 100644 --- a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json +++ b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "411bdd93-0ea4-4376-a032-52b1fbf44ca5", + "id": "ec3bc71d-2877-4868-9ed4-18044a80566d", "init_script": "", "os": "linux", "startup_script": null, - "token": "eeac85aa-19f9-4a50-8002-dfd11556081b", + "token": "e379aab7-c6db-4087-a5b5-2fdde66e612a", "troubleshooting_url": null }, "sensitive_values": {} @@ -46,7 +46,7 @@ "outputs": { "script": "" }, - "random": "5816533441722838433" + "random": "7942145452449303137" }, "sensitive_values": { "inputs": {}, @@ -61,7 +61,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5594550025354402054", + "id": "8322217898682923972", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json index e3e1fe4440fb9..9097d4a673473 100644 --- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json +++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json index a55aa267bf5e9..6a04c8e178980 100644 --- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json +++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "4dc52ff5-b270-47a2-8b6a-695b4872f07b", + "id": "e8d81d35-3d5a-4f44-af85-204477f01d00", "init_script": "", "os": "linux", "startup_script": null, - "token": "c5c8378e-66df-4f3f-94a2-84bff1dc6fc9", + "token": "f89a57d3-a998-4c9d-a356-6ce552f428cc", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "7372487656283423086", + "id": "878432931862797828", "triggers": null }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2553224683756509362", + "id": "4711735818561655282", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json index 89793191c802a..ff45f741cfeaa 100644 --- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json +++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json index e696c33fea0a9..15ebad5c76794 100644 --- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json +++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "3cd9cbba-31f7-482c-a8a0-bf39dfe42dc2", + "id": "8dcb6e1e-256a-429a-b0d7-42f5f1c4353a", "init_script": "", "os": "linux", "startup_script": null, - "token": "8b063f22-9e66-4dbf-9f13-7b09ac2a470f", + "token": "19fa0650-d189-4fef-bd77-6f2694ea8e4b", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "3370347998754925285", + "id": "5708883680778586149", "triggers": null }, "sensitive_values": {}, @@ -50,7 +50,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "4707694957868093590", + "id": "8587806737557769172", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json index 268ed84b45ae3..8be42e4dd4ad5 100644 --- a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json +++ b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "78b29f93-097d-403b-ab56-0bc943d427cc", + "id": "26744910-6b47-422e-a820-ca5414fa4917", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "a57838e5-355c-471a-9a85-f81314fbaec6", + "token": "142b7536-e9e7-4b96-93eb-382eadc53341", "troubleshooting_url": null }, "sensitive_values": {} @@ -65,7 +65,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "1416347524569828366", + "id": "2396000761970773941", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json b/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json index 6bafb713b386a..f54a0d003e4c8 100644 --- a/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json +++ b/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json index 0344e88948fff..83268219c62db 100644 --- a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json +++ b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.3.6", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "36189f12-6eed-4094-9179-6584a8659219", + "id": "d87543f4-5188-49f9-9a94-6be9fd66388e", "init_script": "", "os": "linux", "startup_script": null, - "token": "907fa482-fd3b-44be-8cfb-4515e3122e78", + "token": "ef54867c-ea2a-483b-a260-0d3354b48355", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,8 +34,8 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "36189f12-6eed-4094-9179-6584a8659219", - "id": "c9bd849e-ac37-440b-9c5b-a288344be41c", + "agent_id": "d87543f4-5188-49f9-9a94-6be9fd66388e", + "id": "437f796e-2577-448c-b08c-1fe147762524", "instance_id": "example" }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "4399071137990404376", + "id": "3203243228823326192", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json index a43e7923880f4..61ad97e826952 100644 --- a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json +++ b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "5cb3c105-1a70-4c60-bd32-b75d9a60a98d", + "id": "5bf03483-cab9-4e49-a361-cd039b4148be", "init_script": "", "os": "linux", "startup_script": null, - "token": "b477690f-0a2d-4d9b-818e-7b60c845c44f", + "token": "1e352211-5c3d-4da4-a910-6aa3683ad634", "troubleshooting_url": null }, "sensitive_values": {} @@ -35,12 +35,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "5cb3c105-1a70-4c60-bd32-b75d9a60a98d", + "agent_id": "5bf03483-cab9-4e49-a361-cd039b4148be", "command": null, "display_name": "app1", "healthcheck": [], "icon": null, - "id": "72d41f36-d775-424c-9a05-b633da43cd58", + "id": "9c1cf913-cd47-4f7f-aea9-b5952d3c2711", "name": null, "relative_path": null, "share": "owner", @@ -64,12 +64,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "5cb3c105-1a70-4c60-bd32-b75d9a60a98d", + "agent_id": "5bf03483-cab9-4e49-a361-cd039b4148be", "command": null, "display_name": "app2", "healthcheck": [], "icon": null, - "id": "810dbeda-3041-403d-86e8-146354ad2657", + "id": "2fb5ce5a-8952-47dd-af12-243fec98ab96", "name": null, "relative_path": null, "share": "owner", @@ -92,7 +92,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "1955786418433284816", + "id": "1023901799100321145", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json index f4b5ff036e511..215a32a185ed8 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "6b912abe-50d4-48b2-be7c-1464ca69b5b9", + "id": "925e7f6c-d872-485c-a0d5-7a53197f864d", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "d296a9cd-6f7c-4c6b-b2f3-7a647512efe8", + "token": "d57bd6dd-f104-4a0f-80c3-db081834b5f1", "troubleshooting_url": null }, "sensitive_values": {} @@ -44,7 +44,7 @@ "connection_timeout": 1, "dir": null, "env": null, - "id": "8a2956f7-d37b-441e-bf62-bd9a45316f6a", + "id": "e1157d02-3938-47ab-95ca-a0613751a5f5", "init_script": "", "login_before_ready": true, "motd_file": "/etc/motd", @@ -53,7 +53,7 @@ "shutdown_script_timeout": 30, "startup_script": null, "startup_script_timeout": 30, - "token": "b1e0fba4-5bba-439f-b3ea-3f6a8ba4d301", + "token": "b4216cb4-a4ea-4af0-9a12-66005eb23294", "troubleshooting_url": null }, "sensitive_values": {} @@ -71,7 +71,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "819b1b19-a709-463e-9aeb-5e1321b7af23", + "id": "633ecc95-445e-40ef-be92-91fed80a1c34", "init_script": "", "login_before_ready": false, "motd_file": null, @@ -80,7 +80,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "238ff017-12ae-403f-b3f8-4dea4dc87a7d", + "token": "6f5f34fd-6160-4a9c-ad5a-8b0e64d401c7", "troubleshooting_url": "https://coder.com/troubleshoot" }, "sensitive_values": {} @@ -93,7 +93,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5288433022262248914", + "id": "2126183902282042292", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json index 04c77276e3669..9b10a6a2d50d7 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.3.3", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json index 07c2f8ce17eed..ad3456982321b 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.3.3", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "f911bd98-54fc-476a-aec1-df6e525630a9", + "id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", "init_script": "", "os": "linux", "startup_script": null, - "token": "fa05ad9c-2062-4707-a27f-12364c89641e", + "token": "027fa8cd-c2fe-468f-9f5f-77a8ccf07acf", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,12 +34,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "f911bd98-54fc-476a-aec1-df6e525630a9", + "agent_id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "038d0f6c-90b7-465b-915a-8a9f0cf21757", + "id": "9b59a585-b78a-4dae-87ff-ec9541101efb", "name": null, "relative_path": null, "share": "owner", @@ -62,7 +62,7 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "f911bd98-54fc-476a-aec1-df6e525630a9", + "agent_id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", "command": null, "display_name": null, "healthcheck": [ @@ -73,7 +73,7 @@ } ], "icon": null, - "id": "c00ec121-a167-4418-8c4e-2ccae0a0cd6e", + "id": "bcd08d27-a4bd-42ce-aa39-746470988d06", "name": null, "relative_path": null, "share": "owner", @@ -98,12 +98,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "f911bd98-54fc-476a-aec1-df6e525630a9", + "agent_id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "e9226aa6-a1a6-42a7-8557-64620cbf3dc2", + "id": "4e2a6993-61d5-4d32-bb8b-ede27607a9ef", "name": null, "relative_path": null, "share": "owner", @@ -126,7 +126,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5577006791947779410", + "id": "8089099893012814532", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf index 07dcdffdac150..38bf8fba08c0a 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf @@ -1,8 +1,9 @@ terraform { required_providers { coder = { - source = "coder/coder" - version = "0.6.3" + source = "coder/coder" + // TODO: update terraform-provider-coder before merge. + version = "= 0.6.18-rc" } } } @@ -10,6 +11,12 @@ terraform { resource "coder_agent" "main" { os = "linux" arch = "amd64" + metadata { + key = "process_count" + display_name = "Process Count" + cmd = "ps -ef | wc -l" + interval = "1s" + } } resource "null_resource" "about" {} diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json index 6fc6591b820a6..25352c64d89a3 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.3.3", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ @@ -17,11 +17,28 @@ "connection_timeout": 120, "dir": null, "env": null, + "login_before_ready": true, + "metadata": [ + { + "cmd": "ps -ef | wc -l", + "display_name": "Process Count", + "interval": "1s", + "key": "process_count" + } + ], + "motd_file": null, "os": "linux", + "shutdown_script": null, + "shutdown_script_timeout": 300, "startup_script": null, + "startup_script_timeout": 300, "troubleshooting_url": null }, - "sensitive_values": {} + "sensitive_values": { + "metadata": [ + {} + ] + } }, { "address": "coder_metadata.about_info", @@ -99,17 +116,36 @@ "connection_timeout": 120, "dir": null, "env": null, + "login_before_ready": true, + "metadata": [ + { + "cmd": "ps -ef | wc -l", + "display_name": "Process Count", + "interval": "1s", + "key": "process_count" + } + ], + "motd_file": null, "os": "linux", + "shutdown_script": null, + "shutdown_script_timeout": 300, "startup_script": null, + "startup_script_timeout": 300, "troubleshooting_url": null }, "after_unknown": { "id": true, "init_script": true, + "metadata": [ + {} + ], "token": true }, "before_sensitive": false, "after_sensitive": { + "metadata": [ + {} + ], "token": true } } @@ -208,7 +244,7 @@ "coder": { "name": "coder", "full_name": "registry.terraform.io/coder/coder", - "version_constraint": "0.6.3" + "version_constraint": "0.6.18-rc" }, "null": { "name": "null", @@ -227,6 +263,22 @@ "arch": { "constant_value": "amd64" }, + "metadata": [ + { + "cmd": { + "constant_value": "ps -ef | wc -l" + }, + "display_name": { + "constant_value": "Process Count" + }, + "interval": { + "constant_value": "1s" + }, + "key": { + "constant_value": "process_count" + } + } + ], "os": { "constant_value": "linux" } diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json index 0b0e51e5286a4..7d78864320abb 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.3.3", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -17,14 +17,31 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "7766b2a9-c00f-4cde-9acc-1fc05651dbdf", + "id": "6631d9f2-358e-4f96-9aa8-dfbcdf333c7f", "init_script": "", + "login_before_ready": true, + "metadata": [ + { + "cmd": "ps -ef | wc -l", + "display_name": "Process Count", + "interval": "1s", + "key": "process_count" + } + ], + "motd_file": null, "os": "linux", + "shutdown_script": null, + "shutdown_script_timeout": 300, "startup_script": null, - "token": "5e54c173-a813-4df0-b87d-0617082769dc", + "startup_script_timeout": 300, + "token": "61f86056-f729-47d5-ad21-83e631c6f421", "troubleshooting_url": null }, - "sensitive_values": {} + "sensitive_values": { + "metadata": [ + {} + ] + } }, { "address": "coder_metadata.about_info", @@ -37,7 +54,7 @@ "daily_cost": 29, "hide": true, "icon": "/icon/server.svg", - "id": "e43f1cd6-5dbb-4d6b-8942-37f914b37be5", + "id": "1b9c1e16-c37f-437a-b848-d150a4dcbc05", "item": [ { "is_null": false, @@ -64,7 +81,7 @@ "value": "squirrel" } ], - "resource_id": "5577006791947779410" + "resource_id": "8825461314403469687" }, "sensitive_values": { "item": [ @@ -86,7 +103,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5577006791947779410", + "id": "8825461314403469687", "triggers": null }, "sensitive_values": {} diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json index 532b51221508b..a3f9b28f48784 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.2.6", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ @@ -99,7 +99,7 @@ ], "prior_state": { "format_version": "1.0", - "terraform_version": "1.2.6", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -114,7 +114,7 @@ "default": null, "description": null, "icon": null, - "id": "80782610-71f2-45a2-97ea-6029ddbf7ae8", + "id": "8f1725f6-1823-466a-831a-3039d7ee0ffa", "mutable": false, "name": "Example", "option": [ diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json index 10fc1376bc82a..d7086fce55c24 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.2.6", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -17,13 +17,13 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "c816f258-9140-44e9-8f9c-c67b6561106c", + "id": "6c94779a-16c7-489c-8a02-fd4858172973", "init_script": "", "motd_file": null, "os": "windows", "shutdown_script": null, "startup_script": null, - "token": "d8353b78-99d1-4ae0-b895-3dbf08df2a9a", + "token": "b6f1e2a6-f671-4e9e-b860-dff843493752", "troubleshooting_url": null }, "sensitive_values": {} @@ -39,7 +39,7 @@ "default": null, "description": null, "icon": null, - "id": "7f0e325e-0016-4213-8239-c52c678a6a3c", + "id": "1a685633-58f5-4e88-9cfc-2244167322d1", "mutable": false, "name": "Example", "option": [ @@ -75,7 +75,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8669777619875370025", + "id": "1445908128647274077", "triggers": null }, "sensitive_values": {}, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 466ef41592e3b..d4effc2a16520 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1797,6 +1797,77 @@ func (*Provision) Descriptor() ([]byte, []int) { return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{18} } +type Agent_Metadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Cmd string `protobuf:"bytes,3,opt,name=cmd,proto3" json:"cmd,omitempty"` + Interval string `protobuf:"bytes,4,opt,name=interval,proto3" json:"interval,omitempty"` +} + +func (x *Agent_Metadata) Reset() { + *x = Agent_Metadata{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Agent_Metadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Agent_Metadata) ProtoMessage() {} + +func (x *Agent_Metadata) ProtoReflect() protoreflect.Message { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Agent_Metadata.ProtoReflect.Descriptor instead. +func (*Agent_Metadata) Descriptor() ([]byte, []int) { + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{13, 0} +} + +func (x *Agent_Metadata) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Agent_Metadata) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *Agent_Metadata) GetCmd() string { + if x != nil { + return x.Cmd + } + return "" +} + +func (x *Agent_Metadata) GetInterval() string { + if x != nil { + return x.Interval + } + return "" +} + type Resource_Metadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1811,7 +1882,7 @@ type Resource_Metadata struct { func (x *Resource_Metadata) Reset() { *x = Resource_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1824,7 +1895,7 @@ func (x *Resource_Metadata) String() string { func (*Resource_Metadata) ProtoMessage() {} func (x *Resource_Metadata) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1879,7 +1950,7 @@ type Parse_Request struct { func (x *Parse_Request) Reset() { *x = Parse_Request{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1892,7 +1963,7 @@ func (x *Parse_Request) String() string { func (*Parse_Request) ProtoMessage() {} func (x *Parse_Request) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1927,7 +1998,7 @@ type Parse_Complete struct { func (x *Parse_Complete) Reset() { *x = Parse_Complete{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1940,7 +2011,7 @@ func (x *Parse_Complete) String() string { func (*Parse_Complete) ProtoMessage() {} func (x *Parse_Complete) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1985,7 +2056,7 @@ type Parse_Response struct { func (x *Parse_Response) Reset() { *x = Parse_Response{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1998,7 +2069,7 @@ func (x *Parse_Response) String() string { func (*Parse_Response) ProtoMessage() {} func (x *Parse_Response) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2070,7 +2141,7 @@ type Provision_Metadata struct { func (x *Provision_Metadata) Reset() { *x = Provision_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2083,7 +2154,7 @@ func (x *Provision_Metadata) String() string { func (*Provision_Metadata) ProtoMessage() {} func (x *Provision_Metadata) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2177,7 +2248,7 @@ type Provision_Config struct { func (x *Provision_Config) Reset() { *x = Provision_Config{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2190,7 +2261,7 @@ func (x *Provision_Config) String() string { func (*Provision_Config) ProtoMessage() {} func (x *Provision_Config) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2242,7 +2313,7 @@ type Provision_Plan struct { func (x *Provision_Plan) Reset() { *x = Provision_Plan{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2255,7 +2326,7 @@ func (x *Provision_Plan) String() string { func (*Provision_Plan) ProtoMessage() {} func (x *Provision_Plan) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2318,7 +2389,7 @@ type Provision_Apply struct { func (x *Provision_Apply) Reset() { *x = Provision_Apply{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2331,7 +2402,7 @@ func (x *Provision_Apply) String() string { func (*Provision_Apply) ProtoMessage() {} func (x *Provision_Apply) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2370,7 +2441,7 @@ type Provision_Cancel struct { func (x *Provision_Cancel) Reset() { *x = Provision_Cancel{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2383,7 +2454,7 @@ func (x *Provision_Cancel) String() string { func (*Provision_Cancel) ProtoMessage() {} func (x *Provision_Cancel) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2415,7 +2486,7 @@ type Provision_Request struct { func (x *Provision_Request) Reset() { *x = Provision_Request{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2428,7 +2499,7 @@ func (x *Provision_Request) String() string { func (*Provision_Request) ProtoMessage() {} func (x *Provision_Request) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2510,7 +2581,7 @@ type Provision_Complete struct { func (x *Provision_Complete) Reset() { *x = Provision_Complete{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2523,7 +2594,7 @@ func (x *Provision_Complete) String() string { func (*Provision_Complete) ProtoMessage() {} func (x *Provision_Complete) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2596,7 +2667,7 @@ type Provision_Response struct { func (x *Provision_Response) Reset() { *x = Provision_Response{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2609,7 +2680,7 @@ func (x *Provision_Response) String() string { func (*Provision_Response) ProtoMessage() {} func (x *Provision_Response) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2811,7 +2882,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0xfe, 0x05, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x22, 0xed, 0x06, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, @@ -2855,202 +2926,209 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1c, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x73, 0x1a, 0x6d, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, - 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, - 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, - 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, - 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, - 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, - 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, - 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, - 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, - 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, - 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, - 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, - 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, - 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08, - 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, - 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, - 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, - 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x91, 0x0c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xa1, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, - 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, - 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, - 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x79, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x6d, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x63, 0x6d, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, + 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, + 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, + 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, + 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, + 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, + 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, + 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, + 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, + 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, + 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, + 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, + 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x91, 0x0c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x1a, 0xa1, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, + 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, + 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, + 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x79, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x1a, 0xeb, 0x02, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, + 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, + 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, + 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, + 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, + 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0xb3, + 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, 0x6c, + 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x1a, 0xeb, 0x02, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, + 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, 0x0a, + 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, - 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, - 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, - 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, - 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, - 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, - 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, - 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, - 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, - 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, - 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, - 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, - 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, - 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, - 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, - 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, - 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, - 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, - 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, - 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, - 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, - 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, - 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, - 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, - 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, - 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, + 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, + 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, + 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, + 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, + 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, + 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, + 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, + 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, + 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, + 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, + 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, + 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, + 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, + 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, + 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, + 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3066,7 +3144,7 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte { } var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 33) var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: provisioner.LogLevel (AppSharingLevel)(0), // 1: provisioner.AppSharingLevel @@ -3093,19 +3171,20 @@ var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (*Resource)(nil), // 22: provisioner.Resource (*Parse)(nil), // 23: provisioner.Parse (*Provision)(nil), // 24: provisioner.Provision - nil, // 25: provisioner.Agent.EnvEntry - (*Resource_Metadata)(nil), // 26: provisioner.Resource.Metadata - (*Parse_Request)(nil), // 27: provisioner.Parse.Request - (*Parse_Complete)(nil), // 28: provisioner.Parse.Complete - (*Parse_Response)(nil), // 29: provisioner.Parse.Response - (*Provision_Metadata)(nil), // 30: provisioner.Provision.Metadata - (*Provision_Config)(nil), // 31: provisioner.Provision.Config - (*Provision_Plan)(nil), // 32: provisioner.Provision.Plan - (*Provision_Apply)(nil), // 33: provisioner.Provision.Apply - (*Provision_Cancel)(nil), // 34: provisioner.Provision.Cancel - (*Provision_Request)(nil), // 35: provisioner.Provision.Request - (*Provision_Complete)(nil), // 36: provisioner.Provision.Complete - (*Provision_Response)(nil), // 37: provisioner.Provision.Response + (*Agent_Metadata)(nil), // 25: provisioner.Agent.Metadata + nil, // 26: provisioner.Agent.EnvEntry + (*Resource_Metadata)(nil), // 27: provisioner.Resource.Metadata + (*Parse_Request)(nil), // 28: provisioner.Parse.Request + (*Parse_Complete)(nil), // 29: provisioner.Parse.Complete + (*Parse_Response)(nil), // 30: provisioner.Parse.Response + (*Provision_Metadata)(nil), // 31: provisioner.Provision.Metadata + (*Provision_Config)(nil), // 32: provisioner.Provision.Config + (*Provision_Plan)(nil), // 33: provisioner.Provision.Plan + (*Provision_Apply)(nil), // 34: provisioner.Provision.Apply + (*Provision_Cancel)(nil), // 35: provisioner.Provision.Cancel + (*Provision_Request)(nil), // 36: provisioner.Provision.Request + (*Provision_Complete)(nil), // 37: provisioner.Provision.Complete + (*Provision_Response)(nil), // 38: provisioner.Provision.Response } var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ 3, // 0: provisioner.ParameterSource.scheme:type_name -> provisioner.ParameterSource.Scheme @@ -3116,35 +3195,35 @@ var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ 5, // 5: provisioner.ParameterSchema.validation_type_system:type_name -> provisioner.ParameterSchema.TypeSystem 12, // 6: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption 0, // 7: provisioner.Log.level:type_name -> provisioner.LogLevel - 25, // 8: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry + 26, // 8: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry 20, // 9: provisioner.Agent.apps:type_name -> provisioner.App 21, // 10: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck 1, // 11: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel 19, // 12: provisioner.Resource.agents:type_name -> provisioner.Agent - 26, // 13: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata + 27, // 13: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata 11, // 14: provisioner.Parse.Complete.template_variables:type_name -> provisioner.TemplateVariable 10, // 15: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema 16, // 16: provisioner.Parse.Response.log:type_name -> provisioner.Log - 28, // 17: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete + 29, // 17: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete 2, // 18: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition - 30, // 19: provisioner.Provision.Config.metadata:type_name -> provisioner.Provision.Metadata - 31, // 20: provisioner.Provision.Plan.config:type_name -> provisioner.Provision.Config + 31, // 19: provisioner.Provision.Config.metadata:type_name -> provisioner.Provision.Metadata + 32, // 20: provisioner.Provision.Plan.config:type_name -> provisioner.Provision.Config 9, // 21: provisioner.Provision.Plan.parameter_values:type_name -> provisioner.ParameterValue 14, // 22: provisioner.Provision.Plan.rich_parameter_values:type_name -> provisioner.RichParameterValue 15, // 23: provisioner.Provision.Plan.variable_values:type_name -> provisioner.VariableValue 18, // 24: provisioner.Provision.Plan.git_auth_providers:type_name -> provisioner.GitAuthProvider - 31, // 25: provisioner.Provision.Apply.config:type_name -> provisioner.Provision.Config - 32, // 26: provisioner.Provision.Request.plan:type_name -> provisioner.Provision.Plan - 33, // 27: provisioner.Provision.Request.apply:type_name -> provisioner.Provision.Apply - 34, // 28: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel + 32, // 25: provisioner.Provision.Apply.config:type_name -> provisioner.Provision.Config + 33, // 26: provisioner.Provision.Request.plan:type_name -> provisioner.Provision.Plan + 34, // 27: provisioner.Provision.Request.apply:type_name -> provisioner.Provision.Apply + 35, // 28: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel 22, // 29: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource 13, // 30: provisioner.Provision.Complete.parameters:type_name -> provisioner.RichParameter 16, // 31: provisioner.Provision.Response.log:type_name -> provisioner.Log - 36, // 32: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete - 27, // 33: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request - 35, // 34: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request - 29, // 35: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response - 37, // 36: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response + 37, // 32: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete + 28, // 33: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request + 36, // 34: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request + 30, // 35: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response + 38, // 36: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response 35, // [35:37] is the sub-list for method output_type 33, // [33:35] is the sub-list for method input_type 33, // [33:33] is the sub-list for extension type_name @@ -3386,8 +3465,8 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Resource_Metadata); i { + file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Agent_Metadata); i { case 0: return &v.state case 1: @@ -3399,7 +3478,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Parse_Request); i { + switch v := v.(*Resource_Metadata); i { case 0: return &v.state case 1: @@ -3411,7 +3490,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Parse_Complete); i { + switch v := v.(*Parse_Request); i { case 0: return &v.state case 1: @@ -3423,7 +3502,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Parse_Response); i { + switch v := v.(*Parse_Complete); i { case 0: return &v.state case 1: @@ -3435,7 +3514,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provision_Metadata); i { + switch v := v.(*Parse_Response); i { case 0: return &v.state case 1: @@ -3447,7 +3526,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provision_Config); i { + switch v := v.(*Provision_Metadata); i { case 0: return &v.state case 1: @@ -3459,7 +3538,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provision_Plan); i { + switch v := v.(*Provision_Config); i { case 0: return &v.state case 1: @@ -3471,7 +3550,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provision_Apply); i { + switch v := v.(*Provision_Plan); i { case 0: return &v.state case 1: @@ -3483,7 +3562,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provision_Cancel); i { + switch v := v.(*Provision_Apply); i { case 0: return &v.state case 1: @@ -3495,7 +3574,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provision_Request); i { + switch v := v.(*Provision_Cancel); i { case 0: return &v.state case 1: @@ -3507,7 +3586,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Provision_Complete); i { + switch v := v.(*Provision_Request); i { case 0: return &v.state case 1: @@ -3519,6 +3598,18 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Provision_Complete); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionersdk_proto_provisioner_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision_Response); i { case 0: return &v.state @@ -3535,16 +3626,16 @@ func file_provisionersdk_proto_provisioner_proto_init() { (*Agent_Token)(nil), (*Agent_InstanceId)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[23].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[24].OneofWrappers = []interface{}{ (*Parse_Response_Log)(nil), (*Parse_Response_Complete)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[29].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[30].OneofWrappers = []interface{}{ (*Provision_Request_Plan)(nil), (*Provision_Request_Apply)(nil), (*Provision_Request_Cancel)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[31].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[32].OneofWrappers = []interface{}{ (*Provision_Response_Log)(nil), (*Provision_Response_Complete)(nil), } @@ -3554,7 +3645,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc, NumEnums: 6, - NumMessages: 32, + NumMessages: 33, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 5ab7e4c0173bd..168087d3388d5 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -127,6 +127,12 @@ message GitAuthProvider { // Agent represents a running agent on the workspace. message Agent { + message Metadata { + string key = 1; + string display_name = 2; + string cmd = 3; + string interval = 4; + } string id = 1; string name = 2; map env = 3; From 3cb3b74f3a79e6b5a63b6204855a0a0e3cae5135 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 15 Mar 2023 01:09:39 +0000 Subject: [PATCH 03/73] Fixup provisioner --- provisioner/terraform/resources.go | 13 +- provisioner/terraform/resources_test.go | 16 + .../calling-module.tfstate.json | 8 +- .../chaining-resources.tfstate.json | 8 +- .../conflicting-resources.tfstate.json | 8 +- .../git-auth-providers.tfstate.json | 6 +- .../instance-id/instance-id.tfstate.json | 10 +- .../mapped-apps/mapped-apps.tfstate.json | 14 +- .../multiple-agents.tfstate.json | 14 +- .../multiple-apps/multiple-apps.tfstate.json | 18 +- .../resource-metadata/resource-metadata.tf | 10 +- .../resource-metadata.tfplan.dot | 3 +- .../resource-metadata.tfplan.json | 13 +- .../resource-metadata.tfstate.dot | 3 +- .../resource-metadata.tfstate.json | 18 +- .../rich-parameters.tfplan.json | 2 +- .../rich-parameters.tfstate.json | 8 +- provisionersdk/proto/provisioner.pb.go | 505 +++++++++--------- provisionersdk/proto/provisioner.proto | 3 +- 19 files changed, 365 insertions(+), 315 deletions(-) diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 2c49df75821c2..61065117a29f9 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -18,7 +18,7 @@ type agentMetadata struct { Key string `mapstructure:"key"` DisplayName string `mapstructure:"display_name"` Cmd string `mapstructure:"cmd"` - Interval string `mapstructure:"interval"` + Interval int64 `mapstructure:"interval"` } // A mapping of attributes on the "coder_agent" resource. @@ -148,6 +148,16 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error loginBeforeReady = attrs.LoginBeforeReady } + var metadata []*proto.Agent_Metadata + for _, item := range attrs.Metadata { + metadata = append(metadata, &proto.Agent_Metadata{ + Key: item.Key, + DisplayName: item.DisplayName, + Cmd: item.Cmd, + Interval: item.Interval, + }) + } + agent := &proto.Agent{ Name: tfResource.Name, Id: attrs.ID, @@ -163,6 +173,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error StartupScriptTimeoutSeconds: attrs.StartupScriptTimeoutSeconds, ShutdownScript: attrs.ShutdownScript, ShutdownScriptTimeoutSeconds: attrs.ShutdownScriptTimeoutSeconds, + Metadata: metadata, } switch attrs.Auth { case "token": diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index 415650bbaceb3..a7fc18ad19db0 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -220,6 +220,22 @@ func TestConvertResources(t *testing.T) { Value: "squirrel", Sensitive: true, }}, + Agents: []*proto.Agent{{ + Name: "main", + Auth: &proto.Agent_Token{}, + OperatingSystem: "linux", + Architecture: "amd64", + Metadata: []*proto.Agent_Metadata{{ + Key: "process_count", + DisplayName: "Process Count", + Cmd: "ps -ef | wc -l", + Interval: 1, + }}, + ShutdownScriptTimeoutSeconds: 300, + StartupScriptTimeoutSeconds: 300, + LoginBeforeReady: true, + ConnectionTimeoutSeconds: 120, + }}, }}, }, // Tests that resources with the same id correctly get metadata applied diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json index 7815da0645ead..91804c5220bd6 100644 --- a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json +++ b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "ec3bc71d-2877-4868-9ed4-18044a80566d", + "id": "388003b2-8710-4b51-a56d-c022eb3b836b", "init_script": "", "os": "linux", "startup_script": null, - "token": "e379aab7-c6db-4087-a5b5-2fdde66e612a", + "token": "720f05f1-3152-4c28-8957-20071b7a5b3e", "troubleshooting_url": null }, "sensitive_values": {} @@ -46,7 +46,7 @@ "outputs": { "script": "" }, - "random": "7942145452449303137" + "random": "4256784547276174255" }, "sensitive_values": { "inputs": {}, @@ -61,7 +61,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8322217898682923972", + "id": "8146628865354704855", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json index 6a04c8e178980..96680c929ceda 100644 --- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json +++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "e8d81d35-3d5a-4f44-af85-204477f01d00", + "id": "8d5431ee-123f-413d-b540-38dafd072070", "init_script": "", "os": "linux", "startup_script": null, - "token": "f89a57d3-a998-4c9d-a356-6ce552f428cc", + "token": "4234aa4b-f7c3-4367-89ad-7901c3d9868e", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "878432931862797828", + "id": "413851319920758839", "triggers": null }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "4711735818561655282", + "id": "1236629432916328127", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json index 15ebad5c76794..1becf93570fcc 100644 --- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json +++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "8dcb6e1e-256a-429a-b0d7-42f5f1c4353a", + "id": "0150cd90-4051-4bd6-9fa9-b0faebfa2188", "init_script": "", "os": "linux", "startup_script": null, - "token": "19fa0650-d189-4fef-bd77-6f2694ea8e4b", + "token": "b6119d01-b1a8-4353-a3eb-ca4416730dc5", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5708883680778586149", + "id": "2011143373046435058", "triggers": null }, "sensitive_values": {}, @@ -50,7 +50,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8587806737557769172", + "id": "8488040712554307223", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json index 8be42e4dd4ad5..ec1525ad7806a 100644 --- a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json +++ b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "26744910-6b47-422e-a820-ca5414fa4917", + "id": "d643602d-615c-4671-ba5b-88bad6c96dc0", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "142b7536-e9e7-4b96-93eb-382eadc53341", + "token": "7ceffeb0-3841-433b-a06d-ae782c8fa255", "troubleshooting_url": null }, "sensitive_values": {} @@ -65,7 +65,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2396000761970773941", + "id": "7899939329384955011", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json index 83268219c62db..1c03ef1f887f8 100644 --- a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json +++ b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "d87543f4-5188-49f9-9a94-6be9fd66388e", + "id": "7425fb13-1661-4eaa-ad7d-6bc3388e49a5", "init_script": "", "os": "linux", "startup_script": null, - "token": "ef54867c-ea2a-483b-a260-0d3354b48355", + "token": "0c2e5244-ff09-4bf9-98c3-74a8243ca45f", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,8 +34,8 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "d87543f4-5188-49f9-9a94-6be9fd66388e", - "id": "437f796e-2577-448c-b08c-1fe147762524", + "agent_id": "7425fb13-1661-4eaa-ad7d-6bc3388e49a5", + "id": "64410592-8c91-4922-b25f-d531ed975794", "instance_id": "example" }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "3203243228823326192", + "id": "8961076348162067126", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json index 61ad97e826952..2963d23b0965e 100644 --- a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json +++ b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "5bf03483-cab9-4e49-a361-cd039b4148be", + "id": "cb0d2ad4-b777-40f9-9df6-e965bfdfda62", "init_script": "", "os": "linux", "startup_script": null, - "token": "1e352211-5c3d-4da4-a910-6aa3683ad634", + "token": "6615b629-8418-42c9-a52d-e9d83d353eec", "troubleshooting_url": null }, "sensitive_values": {} @@ -35,12 +35,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "5bf03483-cab9-4e49-a361-cd039b4148be", + "agent_id": "cb0d2ad4-b777-40f9-9df6-e965bfdfda62", "command": null, "display_name": "app1", "healthcheck": [], "icon": null, - "id": "9c1cf913-cd47-4f7f-aea9-b5952d3c2711", + "id": "f802f3d1-e742-4dff-a644-a99035903320", "name": null, "relative_path": null, "share": "owner", @@ -64,12 +64,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "5bf03483-cab9-4e49-a361-cd039b4148be", + "agent_id": "cb0d2ad4-b777-40f9-9df6-e965bfdfda62", "command": null, "display_name": "app2", "healthcheck": [], "icon": null, - "id": "2fb5ce5a-8952-47dd-af12-243fec98ab96", + "id": "e62cefdb-f595-4f56-ac6d-482f6d952d5b", "name": null, "relative_path": null, "share": "owner", @@ -92,7 +92,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "1023901799100321145", + "id": "588892076664802665", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json index 215a32a185ed8..c2dcf193168e7 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "925e7f6c-d872-485c-a0d5-7a53197f864d", + "id": "20fbe3f3-1ba8-452d-bfa3-d2595dd69979", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "d57bd6dd-f104-4a0f-80c3-db081834b5f1", + "token": "44f0149d-92a8-45f7-9603-5a2712f21852", "troubleshooting_url": null }, "sensitive_values": {} @@ -44,7 +44,7 @@ "connection_timeout": 1, "dir": null, "env": null, - "id": "e1157d02-3938-47ab-95ca-a0613751a5f5", + "id": "2b52cde2-e77e-47b4-8d8b-8e8819faa3de", "init_script": "", "login_before_ready": true, "motd_file": "/etc/motd", @@ -53,7 +53,7 @@ "shutdown_script_timeout": 30, "startup_script": null, "startup_script_timeout": 30, - "token": "b4216cb4-a4ea-4af0-9a12-66005eb23294", + "token": "19dd2a91-4c3f-428c-8f75-3b9fa74bbb1b", "troubleshooting_url": null }, "sensitive_values": {} @@ -71,7 +71,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "633ecc95-445e-40ef-be92-91fed80a1c34", + "id": "4654241a-5fb8-41a2-b785-80524525c5ec", "init_script": "", "login_before_ready": false, "motd_file": null, @@ -80,7 +80,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "6f5f34fd-6160-4a9c-ad5a-8b0e64d401c7", + "token": "30bd6e5d-8664-47e4-9560-f6db43d3e5e2", "troubleshooting_url": "https://coder.com/troubleshoot" }, "sensitive_values": {} @@ -93,7 +93,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2126183902282042292", + "id": "7653227335898253433", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json index ad3456982321b..77f1a6d8f1008 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", + "id": "76a93c83-0b4b-4ff4-9819-3993354fe788", "init_script": "", "os": "linux", "startup_script": null, - "token": "027fa8cd-c2fe-468f-9f5f-77a8ccf07acf", + "token": "cdd4febe-9dbc-4f60-b200-d5bc2e0e91a4", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,12 +34,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", + "agent_id": "76a93c83-0b4b-4ff4-9819-3993354fe788", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "9b59a585-b78a-4dae-87ff-ec9541101efb", + "id": "f9215fe7-7d58-4c48-8575-6fff2496b26f", "name": null, "relative_path": null, "share": "owner", @@ -62,7 +62,7 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", + "agent_id": "76a93c83-0b4b-4ff4-9819-3993354fe788", "command": null, "display_name": null, "healthcheck": [ @@ -73,7 +73,7 @@ } ], "icon": null, - "id": "bcd08d27-a4bd-42ce-aa39-746470988d06", + "id": "91b5ea13-5cb5-4b59-b151-92a762df2d75", "name": null, "relative_path": null, "share": "owner", @@ -98,12 +98,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "b260e3d7-4f27-44c9-bf16-2f1ac1b8fdd4", + "agent_id": "76a93c83-0b4b-4ff4-9819-3993354fe788", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "4e2a6993-61d5-4d32-bb8b-ede27607a9ef", + "id": "c40cfde6-de6d-4c9e-a1cf-96011adef93f", "name": null, "relative_path": null, "share": "owner", @@ -126,7 +126,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8089099893012814532", + "id": "8674861375979341492", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf index 38bf8fba08c0a..8a25be0dd3a65 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf @@ -3,7 +3,7 @@ terraform { coder = { source = "coder/coder" // TODO: update terraform-provider-coder before merge. - version = "= 0.6.18-rc" + version = "= 0.6.18-rc1" } } } @@ -15,11 +15,15 @@ resource "coder_agent" "main" { key = "process_count" display_name = "Process Count" cmd = "ps -ef | wc -l" - interval = "1s" + interval = 1 } } -resource "null_resource" "about" {} +resource "null_resource" "about" { + depends_on = [ + coder_agent.main, + ] +} resource "coder_metadata" "about_info" { resource_id = null_resource.about.id diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot index 4bac498b3516c..041734ac4bbc4 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot @@ -9,9 +9,8 @@ digraph { "[root] provider[\"registry.terraform.io/hashicorp/null\"]" [label = "provider[\"registry.terraform.io/hashicorp/null\"]", shape = "diamond"] "[root] coder_agent.main (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]" "[root] coder_metadata.about_info (expand)" -> "[root] null_resource.about (expand)" - "[root] coder_metadata.about_info (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]" + "[root] null_resource.about (expand)" -> "[root] coder_agent.main (expand)" "[root] null_resource.about (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/null\"]" - "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_agent.main (expand)" "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_metadata.about_info (expand)" "[root] provider[\"registry.terraform.io/hashicorp/null\"] (close)" -> "[root] null_resource.about (expand)" "[root] root" -> "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json index 25352c64d89a3..1a954004b7049 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json @@ -22,7 +22,7 @@ { "cmd": "ps -ef | wc -l", "display_name": "Process Count", - "interval": "1s", + "interval": 1, "key": "process_count" } ], @@ -121,7 +121,7 @@ { "cmd": "ps -ef | wc -l", "display_name": "Process Count", - "interval": "1s", + "interval": 1, "key": "process_count" } ], @@ -244,7 +244,7 @@ "coder": { "name": "coder", "full_name": "registry.terraform.io/coder/coder", - "version_constraint": "0.6.18-rc" + "version_constraint": "0.6.18-rc1" }, "null": { "name": "null", @@ -272,7 +272,7 @@ "constant_value": "Process Count" }, "interval": { - "constant_value": "1s" + "constant_value": 1 }, "key": { "constant_value": "process_count" @@ -350,7 +350,10 @@ "type": "null_resource", "name": "about", "provider_config_key": "null", - "schema_version": 0 + "schema_version": 0, + "depends_on": [ + "coder_agent.main" + ] } ] } diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot index 4bac498b3516c..041734ac4bbc4 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot @@ -9,9 +9,8 @@ digraph { "[root] provider[\"registry.terraform.io/hashicorp/null\"]" [label = "provider[\"registry.terraform.io/hashicorp/null\"]", shape = "diamond"] "[root] coder_agent.main (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]" "[root] coder_metadata.about_info (expand)" -> "[root] null_resource.about (expand)" - "[root] coder_metadata.about_info (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]" + "[root] null_resource.about (expand)" -> "[root] coder_agent.main (expand)" "[root] null_resource.about (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/null\"]" - "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_agent.main (expand)" "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_metadata.about_info (expand)" "[root] provider[\"registry.terraform.io/hashicorp/null\"] (close)" -> "[root] null_resource.about (expand)" "[root] root" -> "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json index 7d78864320abb..3dbd633f4d95d 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json @@ -17,14 +17,14 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "6631d9f2-358e-4f96-9aa8-dfbcdf333c7f", + "id": "0a9c67ad-4af5-40d9-b5e4-59dbfd27a390", "init_script": "", "login_before_ready": true, "metadata": [ { "cmd": "ps -ef | wc -l", "display_name": "Process Count", - "interval": "1s", + "interval": 1, "key": "process_count" } ], @@ -34,7 +34,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "61f86056-f729-47d5-ad21-83e631c6f421", + "token": "fba4b5d7-be79-4af2-b678-87d2239b887c", "troubleshooting_url": null }, "sensitive_values": { @@ -54,7 +54,7 @@ "daily_cost": 29, "hide": true, "icon": "/icon/server.svg", - "id": "1b9c1e16-c37f-437a-b848-d150a4dcbc05", + "id": "84c8eb80-572c-41ef-a811-16c31381587d", "item": [ { "is_null": false, @@ -81,7 +81,7 @@ "value": "squirrel" } ], - "resource_id": "8825461314403469687" + "resource_id": "5419686285324437053" }, "sensitive_values": { "item": [ @@ -92,6 +92,7 @@ ] }, "depends_on": [ + "coder_agent.main", "null_resource.about" ] }, @@ -103,10 +104,13 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8825461314403469687", + "id": "5419686285324437053", "triggers": null }, - "sensitive_values": {} + "sensitive_values": {}, + "depends_on": [ + "coder_agent.main" + ] } ] } diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json index a3f9b28f48784..6b0dffa4d4add 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json @@ -114,7 +114,7 @@ "default": null, "description": null, "icon": null, - "id": "8f1725f6-1823-466a-831a-3039d7ee0ffa", + "id": "712597e4-da74-47b8-a094-faf95041f0fc", "mutable": false, "name": "Example", "option": [ diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json index d7086fce55c24..708cb792c1150 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json @@ -17,13 +17,13 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "6c94779a-16c7-489c-8a02-fd4858172973", + "id": "148d8738-b782-4397-98ab-2989443e25c6", "init_script": "", "motd_file": null, "os": "windows", "shutdown_script": null, "startup_script": null, - "token": "b6f1e2a6-f671-4e9e-b860-dff843493752", + "token": "54d03ecf-ed7c-4bf6-b1fb-88f1fcaa4097", "troubleshooting_url": null }, "sensitive_values": {} @@ -39,7 +39,7 @@ "default": null, "description": null, "icon": null, - "id": "1a685633-58f5-4e88-9cfc-2244167322d1", + "id": "840786b9-8cab-45b2-8413-db0b0214ba34", "mutable": false, "name": "Example", "option": [ @@ -75,7 +75,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "1445908128647274077", + "id": "6158472040912045176", "triggers": null }, "sensitive_values": {}, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index d4effc2a16520..70ee65cd1095f 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1252,14 +1252,15 @@ type Agent struct { // // *Agent_Token // *Agent_InstanceId - Auth isAgent_Auth `protobuf_oneof:"auth"` - ConnectionTimeoutSeconds int32 `protobuf:"varint,11,opt,name=connection_timeout_seconds,json=connectionTimeoutSeconds,proto3" json:"connection_timeout_seconds,omitempty"` - TroubleshootingUrl string `protobuf:"bytes,12,opt,name=troubleshooting_url,json=troubleshootingUrl,proto3" json:"troubleshooting_url,omitempty"` - MotdFile string `protobuf:"bytes,13,opt,name=motd_file,json=motdFile,proto3" json:"motd_file,omitempty"` - LoginBeforeReady bool `protobuf:"varint,14,opt,name=login_before_ready,json=loginBeforeReady,proto3" json:"login_before_ready,omitempty"` - StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"` - ShutdownScript string `protobuf:"bytes,16,opt,name=shutdown_script,json=shutdownScript,proto3" json:"shutdown_script,omitempty"` - ShutdownScriptTimeoutSeconds int32 `protobuf:"varint,17,opt,name=shutdown_script_timeout_seconds,json=shutdownScriptTimeoutSeconds,proto3" json:"shutdown_script_timeout_seconds,omitempty"` + Auth isAgent_Auth `protobuf_oneof:"auth"` + ConnectionTimeoutSeconds int32 `protobuf:"varint,11,opt,name=connection_timeout_seconds,json=connectionTimeoutSeconds,proto3" json:"connection_timeout_seconds,omitempty"` + TroubleshootingUrl string `protobuf:"bytes,12,opt,name=troubleshooting_url,json=troubleshootingUrl,proto3" json:"troubleshooting_url,omitempty"` + MotdFile string `protobuf:"bytes,13,opt,name=motd_file,json=motdFile,proto3" json:"motd_file,omitempty"` + LoginBeforeReady bool `protobuf:"varint,14,opt,name=login_before_ready,json=loginBeforeReady,proto3" json:"login_before_ready,omitempty"` + StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"` + ShutdownScript string `protobuf:"bytes,16,opt,name=shutdown_script,json=shutdownScript,proto3" json:"shutdown_script,omitempty"` + ShutdownScriptTimeoutSeconds int32 `protobuf:"varint,17,opt,name=shutdown_script_timeout_seconds,json=shutdownScriptTimeoutSeconds,proto3" json:"shutdown_script_timeout_seconds,omitempty"` + Metadata []*Agent_Metadata `protobuf:"bytes,18,rep,name=metadata,proto3" json:"metadata,omitempty"` } func (x *Agent) Reset() { @@ -1420,6 +1421,13 @@ func (x *Agent) GetShutdownScriptTimeoutSeconds() int32 { return 0 } +func (x *Agent) GetMetadata() []*Agent_Metadata { + if x != nil { + return x.Metadata + } + return nil +} + type isAgent_Auth interface { isAgent_Auth() } @@ -1805,7 +1813,7 @@ type Agent_Metadata struct { Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` Cmd string `protobuf:"bytes,3,opt,name=cmd,proto3" json:"cmd,omitempty"` - Interval string `protobuf:"bytes,4,opt,name=interval,proto3" json:"interval,omitempty"` + Interval int64 `protobuf:"varint,4,opt,name=interval,proto3" json:"interval,omitempty"` } func (x *Agent_Metadata) Reset() { @@ -1861,11 +1869,11 @@ func (x *Agent_Metadata) GetCmd() string { return "" } -func (x *Agent_Metadata) GetInterval() string { +func (x *Agent_Metadata) GetInterval() int64 { if x != nil { return x.Interval } - return "" + return 0 } type Resource_Metadata struct { @@ -2882,7 +2890,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0xed, 0x06, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x22, 0xa6, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, @@ -2926,209 +2934,213 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1c, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x1a, 0x6d, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x6d, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x63, 0x6d, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, - 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, - 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, - 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, - 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, - 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, - 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, - 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, - 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, - 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, - 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, - 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, - 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, - 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, - 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08, 0x43, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, - 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, - 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, - 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, - 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, - 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, - 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x91, 0x0c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x1a, 0xa1, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, - 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, - 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, - 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x79, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x1a, 0xeb, 0x02, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, - 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, - 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, - 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, - 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, - 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, - 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0xb3, - 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, 0x6c, - 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, 0x0a, - 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, + 0x73, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x6d, 0x0a, 0x08, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, + 0x6d, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x6d, 0x64, 0x12, 0x1a, 0x0a, + 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, + 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, + 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, + 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, + 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, + 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, + 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, + 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, + 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, + 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, + 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, + 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x91, + 0x0c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xa1, 0x03, 0x0a, + 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, + 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, + 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x1a, 0x79, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, + 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xeb, 0x02, 0x0a, 0x04, + 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, + 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, + 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, + 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, + 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, + 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, - 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, - 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, - 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, - 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, - 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, - 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, - 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, - 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, - 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, - 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, - 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, - 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, - 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, - 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, - 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, - 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, - 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, + 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, + 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, + 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, + 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, + 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, + 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, + 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, + 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, + 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, + 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, + 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, + 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, + 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, + 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3197,38 +3209,39 @@ var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ 0, // 7: provisioner.Log.level:type_name -> provisioner.LogLevel 26, // 8: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry 20, // 9: provisioner.Agent.apps:type_name -> provisioner.App - 21, // 10: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck - 1, // 11: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel - 19, // 12: provisioner.Resource.agents:type_name -> provisioner.Agent - 27, // 13: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata - 11, // 14: provisioner.Parse.Complete.template_variables:type_name -> provisioner.TemplateVariable - 10, // 15: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema - 16, // 16: provisioner.Parse.Response.log:type_name -> provisioner.Log - 29, // 17: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete - 2, // 18: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition - 31, // 19: provisioner.Provision.Config.metadata:type_name -> provisioner.Provision.Metadata - 32, // 20: provisioner.Provision.Plan.config:type_name -> provisioner.Provision.Config - 9, // 21: provisioner.Provision.Plan.parameter_values:type_name -> provisioner.ParameterValue - 14, // 22: provisioner.Provision.Plan.rich_parameter_values:type_name -> provisioner.RichParameterValue - 15, // 23: provisioner.Provision.Plan.variable_values:type_name -> provisioner.VariableValue - 18, // 24: provisioner.Provision.Plan.git_auth_providers:type_name -> provisioner.GitAuthProvider - 32, // 25: provisioner.Provision.Apply.config:type_name -> provisioner.Provision.Config - 33, // 26: provisioner.Provision.Request.plan:type_name -> provisioner.Provision.Plan - 34, // 27: provisioner.Provision.Request.apply:type_name -> provisioner.Provision.Apply - 35, // 28: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel - 22, // 29: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource - 13, // 30: provisioner.Provision.Complete.parameters:type_name -> provisioner.RichParameter - 16, // 31: provisioner.Provision.Response.log:type_name -> provisioner.Log - 37, // 32: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete - 28, // 33: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request - 36, // 34: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request - 30, // 35: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response - 38, // 36: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response - 35, // [35:37] is the sub-list for method output_type - 33, // [33:35] is the sub-list for method input_type - 33, // [33:33] is the sub-list for extension type_name - 33, // [33:33] is the sub-list for extension extendee - 0, // [0:33] is the sub-list for field type_name + 25, // 10: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata + 21, // 11: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck + 1, // 12: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel + 19, // 13: provisioner.Resource.agents:type_name -> provisioner.Agent + 27, // 14: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata + 11, // 15: provisioner.Parse.Complete.template_variables:type_name -> provisioner.TemplateVariable + 10, // 16: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema + 16, // 17: provisioner.Parse.Response.log:type_name -> provisioner.Log + 29, // 18: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete + 2, // 19: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition + 31, // 20: provisioner.Provision.Config.metadata:type_name -> provisioner.Provision.Metadata + 32, // 21: provisioner.Provision.Plan.config:type_name -> provisioner.Provision.Config + 9, // 22: provisioner.Provision.Plan.parameter_values:type_name -> provisioner.ParameterValue + 14, // 23: provisioner.Provision.Plan.rich_parameter_values:type_name -> provisioner.RichParameterValue + 15, // 24: provisioner.Provision.Plan.variable_values:type_name -> provisioner.VariableValue + 18, // 25: provisioner.Provision.Plan.git_auth_providers:type_name -> provisioner.GitAuthProvider + 32, // 26: provisioner.Provision.Apply.config:type_name -> provisioner.Provision.Config + 33, // 27: provisioner.Provision.Request.plan:type_name -> provisioner.Provision.Plan + 34, // 28: provisioner.Provision.Request.apply:type_name -> provisioner.Provision.Apply + 35, // 29: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel + 22, // 30: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource + 13, // 31: provisioner.Provision.Complete.parameters:type_name -> provisioner.RichParameter + 16, // 32: provisioner.Provision.Response.log:type_name -> provisioner.Log + 37, // 33: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete + 28, // 34: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request + 36, // 35: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request + 30, // 36: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response + 38, // 37: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response + 36, // [36:38] is the sub-list for method output_type + 34, // [34:36] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name } func init() { file_provisionersdk_proto_provisioner_proto_init() } diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 168087d3388d5..e991eb1c19d30 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -131,7 +131,7 @@ message Agent { string key = 1; string display_name = 2; string cmd = 3; - string interval = 4; + int64 interval = 4; } string id = 1; string name = 2; @@ -152,6 +152,7 @@ message Agent { int32 startup_script_timeout_seconds = 15; string shutdown_script = 16; int32 shutdown_script_timeout_seconds = 17; + repeated Metadata metadata = 18; } enum AppSharingLevel { From 826cca375a8ca1b52053e2b074ab4c59b62fe820 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 15 Mar 2023 16:12:03 +0000 Subject: [PATCH 04/73] Rename Agent Metadata to Agent Manifest --- agent/agent.go | 10 ++-- agent/agent_test.go | 78 +++++++++++++------------- coderd/coderd.go | 2 +- coderd/workspaceagents.go | 12 ++-- coderd/workspaceagents_test.go | 22 ++++---- coderd/workspaceapps_test.go | 4 +- coderd/wsconncache/wsconncache_test.go | 18 +++--- codersdk/agentsdk/agentsdk.go | 28 ++++++--- codersdk/workspaceagents_test.go | 6 +- docs/templates/resource-metadata.md | 2 +- 10 files changed, 96 insertions(+), 86 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 29c56a9e43f38..d938b05d84720 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -81,7 +81,7 @@ type Options struct { } type Client interface { - Metadata(ctx context.Context) (agentsdk.Metadata, error) + Manifest(ctx context.Context) (agentsdk.Manifest, error) Listen(ctx context.Context) (net.Conn, error) ReportStats(ctx context.Context, log slog.Logger, statsChan <-chan *agentsdk.Stats, setInterval func(time.Duration)) (io.Closer, error) PostLifecycle(ctx context.Context, state agentsdk.PostLifecycleRequest) error @@ -274,7 +274,7 @@ func (a *agent) run(ctx context.Context) error { } a.sessionToken.Store(&sessionToken) - metadata, err := a.client.Metadata(ctx) + metadata, err := a.client.Manifest(ctx) if err != nil { return xerrors.Errorf("fetch metadata: %w", err) } @@ -804,7 +804,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri if rawMetadata == nil { return nil, xerrors.Errorf("no metadata was provided") } - metadata, valid := rawMetadata.(agentsdk.Metadata) + metadata, valid := rawMetadata.(agentsdk.Manifest) if !valid { return nil, xerrors.Errorf("metadata is the wrong type: %T", metadata) } @@ -940,7 +940,7 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) { session.DisablePTYEmulation() if !isQuietLogin(session.RawCommand()) { - metadata, ok := a.metadata.Load().(agentsdk.Metadata) + metadata, ok := a.metadata.Load().(agentsdk.Manifest) if ok { err = showMOTD(session, metadata.MOTDFile) if err != nil { @@ -1330,7 +1330,7 @@ func (a *agent) Close() error { a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleShuttingDown) lifecycleState := codersdk.WorkspaceAgentLifecycleOff - if metadata, ok := a.metadata.Load().(agentsdk.Metadata); ok && metadata.ShutdownScript != "" { + if metadata, ok := a.metadata.Load().(agentsdk.Manifest); ok && metadata.ShutdownScript != "" { scriptDone := make(chan error, 1) scriptStart := time.Now() go func() { diff --git a/agent/agent_test.go b/agent/agent_test.go index 9e2ff33d2d5c2..fac6403eda78b 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -60,7 +60,7 @@ func TestAgent_Stats_SSH(t *testing.T) { defer cancel() //nolint:dogsled - conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) @@ -93,7 +93,7 @@ func TestAgent_Stats_ReconnectingPTY(t *testing.T) { defer cancel() //nolint:dogsled - conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) ptyConn, err := conn.ReconnectingPTY(ctx, uuid.New(), 128, 128, "/bin/bash") require.NoError(t, err) @@ -123,7 +123,7 @@ func TestAgent_Stats_Magic(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() @@ -150,7 +150,7 @@ func TestAgent_Stats_Magic(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() //nolint:dogsled - conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() @@ -185,7 +185,7 @@ func TestAgent_Stats_Magic(t *testing.T) { func TestAgent_SessionExec(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agentsdk.Metadata{}) + session := setupSSHSession(t, agentsdk.Manifest{}) command := "echo test" if runtime.GOOS == "windows" { @@ -198,7 +198,7 @@ func TestAgent_SessionExec(t *testing.T) { func TestAgent_GitSSH(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agentsdk.Metadata{}) + session := setupSSHSession(t, agentsdk.Manifest{}) command := "sh -c 'echo $GIT_SSH_COMMAND'" if runtime.GOOS == "windows" { command = "cmd.exe /c echo %GIT_SSH_COMMAND%" @@ -218,7 +218,7 @@ func TestAgent_SessionTTYShell(t *testing.T) { // it seems like it could be either. t.Skip("ConPTY appears to be inconsistent on Windows.") } - session := setupSSHSession(t, agentsdk.Metadata{}) + session := setupSSHSession(t, agentsdk.Manifest{}) command := "sh" if runtime.GOOS == "windows" { command = "cmd.exe" @@ -241,7 +241,7 @@ func TestAgent_SessionTTYShell(t *testing.T) { func TestAgent_SessionTTYExitCode(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agentsdk.Metadata{}) + session := setupSSHSession(t, agentsdk.Manifest{}) command := "areallynotrealcommand" err := session.RequestPty("xterm", 128, 128, ssh.TerminalModes{}) require.NoError(t, err) @@ -280,7 +280,7 @@ func TestAgent_Session_TTY_MOTD(t *testing.T) { // Set HOME so we can ensure no ~/.hushlogin is present. t.Setenv("HOME", tmpdir) - session := setupSSHSession(t, agentsdk.Metadata{ + session := setupSSHSession(t, agentsdk.Manifest{ MOTDFile: name, }) err = session.RequestPty("xterm", 128, 128, ssh.TerminalModes{}) @@ -326,7 +326,7 @@ func TestAgent_Session_TTY_Hushlogin(t *testing.T) { // Set HOME so we can ensure ~/.hushlogin is present. t.Setenv("HOME", tmpdir) - session := setupSSHSession(t, agentsdk.Metadata{ + session := setupSSHSession(t, agentsdk.Manifest{ MOTDFile: name, }) err = session.RequestPty("xterm", 128, 128, ssh.TerminalModes{}) @@ -607,7 +607,7 @@ func TestAgent_SFTP(t *testing.T) { home = "/" + strings.ReplaceAll(home, "\\", "/") } //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() @@ -639,7 +639,7 @@ func TestAgent_SCP(t *testing.T) { defer cancel() //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() @@ -658,7 +658,7 @@ func TestAgent_EnvironmentVariables(t *testing.T) { t.Parallel() key := "EXAMPLE" value := "value" - session := setupSSHSession(t, agentsdk.Metadata{ + session := setupSSHSession(t, agentsdk.Manifest{ EnvironmentVariables: map[string]string{ key: value, }, @@ -675,7 +675,7 @@ func TestAgent_EnvironmentVariables(t *testing.T) { func TestAgent_EnvironmentVariableExpansion(t *testing.T) { t.Parallel() key := "EXAMPLE" - session := setupSSHSession(t, agentsdk.Metadata{ + session := setupSSHSession(t, agentsdk.Manifest{ EnvironmentVariables: map[string]string{ key: "$SOMETHINGNOTSET", }, @@ -702,7 +702,7 @@ func TestAgent_CoderEnvVars(t *testing.T) { t.Run(key, func(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agentsdk.Metadata{}) + session := setupSSHSession(t, agentsdk.Manifest{}) command := "sh -c 'echo $" + key + "'" if runtime.GOOS == "windows" { command = "cmd.exe /c echo %" + key + "%" @@ -725,7 +725,7 @@ func TestAgent_SSHConnectionEnvVars(t *testing.T) { t.Run(key, func(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agentsdk.Metadata{}) + session := setupSSHSession(t, agentsdk.Manifest{}) command := "sh -c 'echo $" + key + "'" if runtime.GOOS == "windows" { command = "cmd.exe /c echo %" + key + "%" @@ -744,7 +744,7 @@ func TestAgent_StartupScript(t *testing.T) { } content := "output" //nolint:dogsled - _, _, _, fs, _ := setupAgent(t, agentsdk.Metadata{ + _, _, _, fs, _ := setupAgent(t, agentsdk.Manifest{ StartupScript: "echo " + content, }, 0) var gotContent string @@ -778,7 +778,7 @@ func TestAgent_Lifecycle(t *testing.T) { t.Run("StartTimeout", func(t *testing.T) { t.Parallel() - _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ StartupScript: "sleep 5", StartupScriptTimeout: time.Nanosecond, }, 0) @@ -807,7 +807,7 @@ func TestAgent_Lifecycle(t *testing.T) { t.Run("StartError", func(t *testing.T) { t.Parallel() - _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ StartupScript: "false", StartupScriptTimeout: 30 * time.Second, }, 0) @@ -836,7 +836,7 @@ func TestAgent_Lifecycle(t *testing.T) { t.Run("Ready", func(t *testing.T) { t.Parallel() - _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ StartupScript: "true", StartupScriptTimeout: 30 * time.Second, }, 0) @@ -865,7 +865,7 @@ func TestAgent_Lifecycle(t *testing.T) { t.Run("ShuttingDown", func(t *testing.T) { t.Parallel() - _, client, _, _, closer := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{ ShutdownScript: "sleep 5", StartupScriptTimeout: 30 * time.Second, }, 0) @@ -903,7 +903,7 @@ func TestAgent_Lifecycle(t *testing.T) { t.Run("ShutdownTimeout", func(t *testing.T) { t.Parallel() - _, client, _, _, closer := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{ ShutdownScript: "sleep 5", ShutdownScriptTimeout: time.Nanosecond, }, 0) @@ -950,7 +950,7 @@ func TestAgent_Lifecycle(t *testing.T) { t.Run("ShutdownError", func(t *testing.T) { t.Parallel() - _, client, _, _, closer := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{ ShutdownScript: "false", ShutdownScriptTimeout: 30 * time.Second, }, 0) @@ -1001,7 +1001,7 @@ func TestAgent_Lifecycle(t *testing.T) { client := &client{ t: t, agentID: uuid.New(), - metadata: agentsdk.Metadata{ + manifest: agentsdk.Manifest{ DERPMap: tailnettest.RunDERPAndSTUN(t), StartupScript: "echo 1", ShutdownScript: "echo " + expected, @@ -1054,7 +1054,7 @@ func TestAgent_Startup(t *testing.T) { t.Run("EmptyDirectory", func(t *testing.T) { t.Parallel() - _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ StartupScript: "true", StartupScriptTimeout: 30 * time.Second, Directory: "", @@ -1068,7 +1068,7 @@ func TestAgent_Startup(t *testing.T) { t.Run("HomeDirectory", func(t *testing.T) { t.Parallel() - _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ StartupScript: "true", StartupScriptTimeout: 30 * time.Second, Directory: "~", @@ -1084,7 +1084,7 @@ func TestAgent_Startup(t *testing.T) { t.Run("HomeEnvironmentVariable", func(t *testing.T) { t.Parallel() - _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{ + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ StartupScript: "true", StartupScriptTimeout: 30 * time.Second, Directory: "$HOME", @@ -1111,7 +1111,7 @@ func TestAgent_ReconnectingPTY(t *testing.T) { defer cancel() //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) id := uuid.New() netConn, err := conn.ReconnectingPTY(ctx, id, 100, 100, "/bin/bash") require.NoError(t, err) @@ -1213,7 +1213,7 @@ func TestAgent_Dial(t *testing.T) { }() //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) require.True(t, conn.AwaitReachable(context.Background())) conn1, err := conn.DialContext(context.Background(), l.Addr().Network(), l.Addr().String()) require.NoError(t, err) @@ -1235,7 +1235,7 @@ func TestAgent_Speedtest(t *testing.T) { defer cancel() derpMap := tailnettest.RunDERPAndSTUN(t) //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{ + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{ DERPMap: derpMap, }, 0) defer conn.Close() @@ -1257,7 +1257,7 @@ func TestAgent_Reconnect(t *testing.T) { client := &client{ t: t, agentID: agentID, - metadata: agentsdk.Metadata{ + manifest: agentsdk.Manifest{ DERPMap: derpMap, }, statsChan: statsCh, @@ -1292,7 +1292,7 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) { client := &client{ t: t, agentID: uuid.New(), - metadata: agentsdk.Metadata{ + manifest: agentsdk.Manifest{ GitAuthConfigs: 1, DERPMap: &tailcfg.DERPMap{}, }, @@ -1321,7 +1321,7 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) { func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd { //nolint:dogsled - agentConn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + agentConn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) waitGroup := sync.WaitGroup{} @@ -1364,7 +1364,7 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe return exec.Command("ssh", args...) } -func setupSSHSession(t *testing.T, options agentsdk.Metadata) *ssh.Session { +func setupSSHSession(t *testing.T, options agentsdk.Manifest) *ssh.Session { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() //nolint:dogsled @@ -1388,7 +1388,7 @@ func (c closeFunc) Close() error { return c() } -func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Duration) ( +func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration) ( *codersdk.WorkspaceAgentConn, *client, <-chan *agentsdk.Stats, @@ -1408,7 +1408,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati c := &client{ t: t, agentID: agentID, - metadata: metadata, + manifest: metadata, statsChan: statsCh, coordinator: coordinator, } @@ -1491,7 +1491,7 @@ func assertWritePayload(t *testing.T, w io.Writer, payload []byte) { type client struct { t *testing.T agentID uuid.UUID - metadata agentsdk.Metadata + manifest agentsdk.Manifest statsChan chan *agentsdk.Stats coordinator tailnet.Coordinator lastWorkspaceAgent func() @@ -1501,8 +1501,8 @@ type client struct { startup agentsdk.PostStartupRequest } -func (c *client) Metadata(_ context.Context) (agentsdk.Metadata, error) { - return c.metadata, nil +func (c *client) Manifest(_ context.Context) (agentsdk.Manifest, error) { + return c.manifest, nil } func (c *client) Listen(_ context.Context) (net.Conn, error) { diff --git a/coderd/coderd.go b/coderd/coderd.go index ed53b095751e5..b26986fe06222 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -590,7 +590,7 @@ func New(options *Options) *API { r.Post("/google-instance-identity", api.postWorkspaceAuthGoogleInstanceIdentity) r.Route("/me", func(r chi.Router) { r.Use(httpmw.ExtractWorkspaceAgent(options.Database)) - r.Get("/metadata", api.workspaceAgentMetadata) + r.Get("/manifest", api.workspaceAgentManifest) r.Post("/startup", api.postWorkspaceAgentStartup) r.Post("/app-health", api.postWorkspaceAppHealth) r.Get("/gitauth", api.workspaceAgentsGitAuth) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 098f83b5358ec..488c0dbdc58dc 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -79,14 +79,14 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, apiAgent) } -// @Summary Get authorized workspace agent metadata -// @ID get-authorized-workspace-agent-metadata +// @Summary Get authorized workspace agent manifest +// @ID get-authorized-workspace-agent-manifest // @Security CoderSessionToken // @Produce json // @Tags Agents -// @Success 200 {object} agentsdk.Metadata -// @Router /workspaceagents/me/metadata [get] -func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { +// @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) apiAgent, err := convertWorkspaceAgent( @@ -152,7 +152,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) vscodeProxyURI += fmt.Sprintf(":%s", api.AccessURL.Port()) } - httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Metadata{ + httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Manifest{ Apps: convertApps(dbApps), DERPMap: api.DERPMap, GitAuthConfigs: len(api.GitAuthConfigs), diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index e4937143b4291..9bf5d87d2de08 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -768,10 +768,10 @@ func TestWorkspaceAgentAppHealth(t *testing.T) { agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) - metadata, err := agentClient.Metadata(ctx) + manifest, err := agentClient.Manifest(ctx) require.NoError(t, err) - require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, metadata.Apps[0].Health) - require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, metadata.Apps[1].Health) + require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, manifest.Apps[0].Health) + require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, manifest.Apps[1].Health) err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{}) require.Error(t, err) // empty @@ -780,37 +780,37 @@ func TestWorkspaceAgentAppHealth(t *testing.T) { // healthcheck disabled err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{ Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{ - metadata.Apps[0].ID: codersdk.WorkspaceAppHealthInitializing, + manifest.Apps[0].ID: codersdk.WorkspaceAppHealthInitializing, }, }) require.Error(t, err) // invalid value err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{ Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{ - metadata.Apps[1].ID: codersdk.WorkspaceAppHealth("bad-value"), + manifest.Apps[1].ID: codersdk.WorkspaceAppHealth("bad-value"), }, }) require.Error(t, err) // update to healthy err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{ Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{ - metadata.Apps[1].ID: codersdk.WorkspaceAppHealthHealthy, + manifest.Apps[1].ID: codersdk.WorkspaceAppHealthHealthy, }, }) require.NoError(t, err) - metadata, err = agentClient.Metadata(ctx) + manifest, err = agentClient.Manifest(ctx) require.NoError(t, err) - require.EqualValues(t, codersdk.WorkspaceAppHealthHealthy, metadata.Apps[1].Health) + require.EqualValues(t, codersdk.WorkspaceAppHealthHealthy, manifest.Apps[1].Health) // update to unhealthy err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{ Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{ - metadata.Apps[1].ID: codersdk.WorkspaceAppHealthUnhealthy, + manifest.Apps[1].ID: codersdk.WorkspaceAppHealthUnhealthy, }, }) require.NoError(t, err) - metadata, err = agentClient.Metadata(ctx) + manifest, err = agentClient.Manifest(ctx) require.NoError(t, err) - require.EqualValues(t, codersdk.WorkspaceAppHealthUnhealthy, metadata.Apps[1].Health) + require.EqualValues(t, codersdk.WorkspaceAppHealthUnhealthy, manifest.Apps[1].Health) } // nolint:bodyclose diff --git a/coderd/workspaceapps_test.go b/coderd/workspaceapps_test.go index 690f8ba35bb26..62a5acaa532b8 100644 --- a/coderd/workspaceapps_test.go +++ b/coderd/workspaceapps_test.go @@ -275,7 +275,7 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) if appHost != "" { - metadata, err := agentClient.Metadata(context.Background()) + manifest, err := agentClient.Manifest(context.Background()) require.NoError(t, err) proxyURL := fmt.Sprintf( "http://{{port}}--%s--%s--%s%s", @@ -287,7 +287,7 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U if client.URL.Port() != "" { proxyURL += fmt.Sprintf(":%s", client.URL.Port()) } - require.Equal(t, proxyURL, metadata.VSCodePortProxyURI) + require.Equal(t, proxyURL, manifest.VSCodePortProxyURI) } agentCloser := agent.New(agent.Options{ Client: agentClient, diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index 6abc5609ec6dc..be9665c783d4b 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -41,7 +41,7 @@ func TestCache(t *testing.T) { t.Run("Same", func(t *testing.T) { t.Parallel() cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) { - return setupAgent(t, agentsdk.Metadata{}, 0), nil + return setupAgent(t, agentsdk.Manifest{}, 0), nil }, 0) defer func() { _ = cache.Close() @@ -57,7 +57,7 @@ func TestCache(t *testing.T) { called := atomic.NewInt32(0) cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) { called.Add(1) - return setupAgent(t, agentsdk.Metadata{}, 0), nil + return setupAgent(t, agentsdk.Manifest{}, 0), nil }, time.Microsecond) defer func() { _ = cache.Close() @@ -75,7 +75,7 @@ func TestCache(t *testing.T) { t.Run("NoExpireWhenLocked", func(t *testing.T) { t.Parallel() cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) { - return setupAgent(t, agentsdk.Metadata{}, 0), nil + return setupAgent(t, agentsdk.Manifest{}, 0), nil }, time.Microsecond) defer func() { _ = cache.Close() @@ -108,7 +108,7 @@ func TestCache(t *testing.T) { go server.Serve(random) cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) { - return setupAgent(t, agentsdk.Metadata{}, 0), nil + return setupAgent(t, agentsdk.Manifest{}, 0), nil }, time.Microsecond) defer func() { _ = cache.Close() @@ -154,7 +154,7 @@ func TestCache(t *testing.T) { }) } -func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn { +func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn { t.Helper() metadata.DERPMap = tailnettest.RunDERPAndSTUN(t) @@ -168,7 +168,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati Client: &client{ t: t, agentID: agentID, - metadata: metadata, + manifest: metadata, coordinator: coordinator, }, Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo), @@ -211,12 +211,12 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati type client struct { t *testing.T agentID uuid.UUID - metadata agentsdk.Metadata + manifest agentsdk.Manifest coordinator tailnet.Coordinator } -func (c *client) Metadata(_ context.Context) (agentsdk.Metadata, error) { - return c.metadata, nil +func (c *client) Manifest(_ context.Context) (agentsdk.Manifest, error) { + return c.manifest, nil } func (c *client) Listen(_ context.Context) (net.Conn, error) { diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 7035fbefcdbd9..ff2c10754f5a5 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -65,7 +65,16 @@ func (c *Client) GitSSHKey(ctx context.Context) (GitSSHKey, error) { return gitSSHKey, json.NewDecoder(res.Body).Decode(&gitSSHKey) } +// Metadata is a description of dynamic metadata the agent should report +// back to coderd. It is provided via the `metadata` list in the `coder_agent` +// block. type Metadata struct { + Key string + Cmd []string + Interval time.Duration +} + +type Manifest struct { // GitAuthConfigs stores the number of Git configurations // the Coder deployment has. If this number is >0, we // set up special configuration in the workspace. @@ -80,22 +89,23 @@ type Metadata struct { MOTDFile string `json:"motd_file"` ShutdownScript string `json:"shutdown_script"` ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"` + Metadata []Metadata `json:"dynamic_metadata"` } -// Metadata fetches metadata for the currently authenticated workspace agent. -func (c *Client) Metadata(ctx context.Context) (Metadata, error) { - res, err := c.SDK.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/metadata", nil) +// Manifest fetches manifest for the currently authenticated workspace agent. +func (c *Client) Manifest(ctx context.Context) (Manifest, error) { + res, err := c.SDK.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/manifest", nil) if err != nil { - return Metadata{}, err + return Manifest{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return Metadata{}, codersdk.ReadBodyAsError(res) + return Manifest{}, codersdk.ReadBodyAsError(res) } - var agentMeta Metadata + var agentMeta Manifest err = json.NewDecoder(res.Body).Decode(&agentMeta) if err != nil { - return Metadata{}, err + return Manifest{}, err } accessingPort := c.SDK.URL.Port() if accessingPort == "" { @@ -106,14 +116,14 @@ func (c *Client) Metadata(ctx context.Context) (Metadata, error) { } accessPort, err := strconv.Atoi(accessingPort) if err != nil { - return Metadata{}, xerrors.Errorf("convert accessing port %q: %w", accessingPort, err) + return Manifest{}, xerrors.Errorf("convert accessing port %q: %w", accessingPort, err) } // Agents can provide an arbitrary access URL that may be different // that the globally configured one. This breaks the built-in DERP, // which would continue to reference the global access URL. // // This converts all built-in DERPs to use the access URL that the - // metadata request was performed with. + // manifest request was performed with. for _, region := range agentMeta.DERPMap.Regions { if !region.EmbeddedRelay { continue diff --git a/codersdk/workspaceagents_test.go b/codersdk/workspaceagents_test.go index a5b20d886acdc..6373160c299e1 100644 --- a/codersdk/workspaceagents_test.go +++ b/codersdk/workspaceagents_test.go @@ -25,7 +25,7 @@ func TestWorkspaceAgentMetadata(t *testing.T) { // This test ensures that the DERP map returned properly // mutates built-in DERPs with the client access URL. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.Metadata{ + httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.Manifest{ DERPMap: &tailcfg.DERPMap{ Regions: map[int]*tailcfg.DERPRegion{ 1: { @@ -43,9 +43,9 @@ func TestWorkspaceAgentMetadata(t *testing.T) { parsed, err := url.Parse(srv.URL) require.NoError(t, err) client := agentsdk.New(parsed) - metadata, err := client.Metadata(context.Background()) + manifest, err := client.Manifest(context.Background()) require.NoError(t, err) - region := metadata.DERPMap.Regions[1] + region := manifest.DERPMap.Regions[1] require.True(t, region.EmbeddedRelay) require.Len(t, region.Nodes, 1) node := region.Nodes[0] diff --git a/docs/templates/resource-metadata.md b/docs/templates/resource-metadata.md index ea511055c3330..05775440fede4 100644 --- a/docs/templates/resource-metadata.md +++ b/docs/templates/resource-metadata.md @@ -109,7 +109,7 @@ resource "coder_agent" "dev" { dir = "/workspace" metadata { name = "Process Count" - cmd = "ps aux | wc -l" + cmd = ["sh", "-c", "ps aux | wc -l"] refresh = 5s # Any data above this width will require scrolling. width = 5 From c30f1c68ea36ed4faf49a443cad7f9979f54d250 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 15 Mar 2023 17:25:11 +0000 Subject: [PATCH 05/73] =?UTF-8?q?WIP=20=E2=80=94=20agent=20report=20metada?= =?UTF-8?q?ta=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent/agent.go | 87 ++++++++++++++++++++++------------- agent/agent_test.go | 45 ++++++++++++++++++ codersdk/agentsdk/agentsdk.go | 22 +++++++++ 3 files changed, 123 insertions(+), 31 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index d938b05d84720..8144176101fc0 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "encoding/json" "errors" + "flag" "fmt" "io" "net" @@ -87,6 +88,7 @@ type Client interface { PostLifecycle(ctx context.Context, state agentsdk.PostLifecycleRequest) error PostAppHealth(ctx context.Context, req agentsdk.PostAppHealthsRequest) error PostStartup(ctx context.Context, req agentsdk.PostStartupRequest) error + PostMetadata(ctx context.Context, req agentsdk.PostMetadataRequest) error } func New(options Options) io.Closer { @@ -152,8 +154,8 @@ type agent struct { closed chan struct{} envVars map[string]string - // metadata is atomic because values can change after reconnection. - metadata atomic.Value + // manifest is atomic because values can change after reconnection. + manifest atomic.Pointer[agentsdk.Manifest] sessionToken atomic.Pointer[string] sshServer *ssh.Server @@ -178,6 +180,7 @@ type agent struct { // failure, you'll want the agent to reconnect. func (a *agent) runLoop(ctx context.Context) { go a.reportLifecycleLoop(ctx) + go a.reportMetadataLoop(ctx) for retrier := retry.New(100*time.Millisecond, 10*time.Second); retrier.Wait(ctx); { a.logger.Info(ctx, "connecting to coderd") @@ -200,6 +203,32 @@ func (a *agent) runLoop(ctx context.Context) { } } +func (a *agent) reportMetadata(ctx context.Context) error { + ma := a.manifest.Load() + tickers := make([]time.Ticker, 0, len(ma.Metadata)) +} + +func (a *agent) reportMetadataLoop(ctx context.Context) { + // In production, the minimum report interval is one second. + ticker := time.Second + if flag.Lookup("test.v") != nil { + ticker = time.Millisecond * 100 + } + baseTicker := time.NewTicker(ticker) + + for { + select { + case <-ctx.Done(): + return + case <-baseTicker.C: + err := a.reportMetadata(ctx) + if err != nil { + a.logger.Error(ctx, "report metadata", slog.Error(err)) + } + } + } +} + // reportLifecycleLoop reports the current lifecycle state once. // Only the latest state is reported, intermediate states may be // lost if the agent can't communicate with the API. @@ -274,30 +303,30 @@ func (a *agent) run(ctx context.Context) error { } a.sessionToken.Store(&sessionToken) - metadata, err := a.client.Manifest(ctx) + manifest, err := a.client.Manifest(ctx) if err != nil { return xerrors.Errorf("fetch metadata: %w", err) } - a.logger.Info(ctx, "fetched metadata", slog.F("metadata", metadata)) + a.logger.Info(ctx, "fetched metadata", slog.F("metadata", manifest)) // Expand the directory and send it back to coderd so external // applications that rely on the directory can use it. // // An example is VS Code Remote, which must know the directory // before initializing a connection. - metadata.Directory, err = expandDirectory(metadata.Directory) + manifest.Directory, err = expandDirectory(manifest.Directory) if err != nil { return xerrors.Errorf("expand directory: %w", err) } err = a.client.PostStartup(ctx, agentsdk.PostStartupRequest{ Version: buildinfo.Version(), - ExpandedDirectory: metadata.Directory, + ExpandedDirectory: manifest.Directory, }) if err != nil { return xerrors.Errorf("update workspace agent version: %w", err) } - oldMetadata := a.metadata.Swap(metadata) + oldMetadata := a.manifest.Swap(&manifest) // The startup script should only execute on the first run! if oldMetadata == nil { @@ -307,7 +336,7 @@ func (a *agent) run(ctx context.Context) error { // connect to a workspace that is not yet ready. We don't run this // concurrently with the startup script to avoid conflicts between // them. - if metadata.GitAuthConfigs > 0 { + if manifest.GitAuthConfigs > 0 { // If this fails, we should consider surfacing the error in the // startup log and setting the lifecycle state to be "start_error" // (after startup script completion), but for now we'll just log it. @@ -322,7 +351,7 @@ func (a *agent) run(ctx context.Context) error { scriptStart := time.Now() err = a.trackConnGoroutine(func() { defer close(scriptDone) - scriptDone <- a.runStartupScript(ctx, metadata.StartupScript) + scriptDone <- a.runStartupScript(ctx, manifest.StartupScript) }) if err != nil { return xerrors.Errorf("track startup script: %w", err) @@ -331,8 +360,8 @@ func (a *agent) run(ctx context.Context) error { var timeout <-chan time.Time // If timeout is zero, an older version of the coder // provider was used. Otherwise a timeout is always > 0. - if metadata.StartupScriptTimeout > 0 { - t := time.NewTimer(metadata.StartupScriptTimeout) + if manifest.StartupScriptTimeout > 0 { + t := time.NewTimer(manifest.StartupScriptTimeout) defer t.Stop() timeout = t.C } @@ -349,7 +378,7 @@ func (a *agent) run(ctx context.Context) error { return } // Only log if there was a startup script. - if metadata.StartupScript != "" { + if manifest.StartupScript != "" { execTime := time.Since(scriptStart) if err != nil { a.logger.Warn(ctx, "startup script failed", slog.F("execution_time", execTime), slog.Error(err)) @@ -366,13 +395,13 @@ func (a *agent) run(ctx context.Context) error { appReporterCtx, appReporterCtxCancel := context.WithCancel(ctx) defer appReporterCtxCancel() go NewWorkspaceAppHealthReporter( - a.logger, metadata.Apps, a.client.PostAppHealth)(appReporterCtx) + a.logger, manifest.Apps, a.client.PostAppHealth)(appReporterCtx) a.closeMutex.Lock() network := a.network a.closeMutex.Unlock() if network == nil { - network, err = a.createTailnet(ctx, metadata.DERPMap) + network, err = a.createTailnet(ctx, manifest.DERPMap) if err != nil { return xerrors.Errorf("create tailnet: %w", err) } @@ -391,7 +420,7 @@ func (a *agent) run(ctx context.Context) error { a.startReportingConnectionStats(ctx) } else { // Update the DERP map! - network.SetDERPMap(metadata.DERPMap) + network.SetDERPMap(manifest.DERPMap) } a.logger.Debug(ctx, "running tailnet connection coordinator") @@ -800,14 +829,10 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri return nil, xerrors.Errorf("get user shell: %w", err) } - rawMetadata := a.metadata.Load() - if rawMetadata == nil { + manifest := a.manifest.Load() + if manifest == nil { return nil, xerrors.Errorf("no metadata was provided") } - metadata, valid := rawMetadata.(agentsdk.Manifest) - if !valid { - return nil, xerrors.Errorf("metadata is the wrong type: %T", metadata) - } // OpenSSH executes all commands with the users current shell. // We replicate that behavior for IDE support. @@ -829,7 +854,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri } cmd := exec.CommandContext(ctx, shell, args...) - cmd.Dir = metadata.Directory + cmd.Dir = manifest.Directory // If the metadata directory doesn't exist, we run the command // in the users home directory. @@ -870,14 +895,14 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri // This adds the ports dialog to code-server that enables // proxying a port dynamically. - cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", metadata.VSCodePortProxyURI)) + cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", manifest.VSCodePortProxyURI)) // Hide Coder message on code-server's "Getting Started" page cmd.Env = append(cmd.Env, "CS_DISABLE_GETTING_STARTED_OVERRIDE=true") // Load environment variables passed via the agent. // These should override all variables we manually specify. - for envKey, value := range metadata.EnvironmentVariables { + for envKey, value := range manifest.EnvironmentVariables { // Expanding environment variables allows for customization // of the $PATH, among other variables. Customers can prepend // or append to the $PATH, so allowing expand is required! @@ -940,9 +965,9 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) { session.DisablePTYEmulation() if !isQuietLogin(session.RawCommand()) { - metadata, ok := a.metadata.Load().(agentsdk.Manifest) - if ok { - err = showMOTD(session, metadata.MOTDFile) + manifest := a.manifest.Load() + if manifest != nil { + err = showMOTD(session, manifest.MOTDFile) if err != nil { a.logger.Error(ctx, "show MOTD", slog.Error(err)) } @@ -1330,19 +1355,19 @@ func (a *agent) Close() error { a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleShuttingDown) lifecycleState := codersdk.WorkspaceAgentLifecycleOff - if metadata, ok := a.metadata.Load().(agentsdk.Manifest); ok && metadata.ShutdownScript != "" { + if manifest := a.manifest.Load(); manifest != nil && manifest.ShutdownScript != "" { scriptDone := make(chan error, 1) scriptStart := time.Now() go func() { defer close(scriptDone) - scriptDone <- a.runShutdownScript(ctx, metadata.ShutdownScript) + scriptDone <- a.runShutdownScript(ctx, manifest.ShutdownScript) }() var timeout <-chan time.Time // If timeout is zero, an older version of the coder // provider was used. Otherwise a timeout is always > 0. - if metadata.ShutdownScriptTimeout > 0 { - t := time.NewTimer(metadata.ShutdownScriptTimeout) + if manifest.ShutdownScriptTimeout > 0 { + t := time.NewTimer(manifest.ShutdownScriptTimeout) defer t.Stop() timeout = t.C } diff --git a/agent/agent_test.go b/agent/agent_test.go index fac6403eda78b..2d0574dc5e9ea 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/goleak" "golang.org/x/crypto/ssh" + "golang.org/x/exp/maps" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" "golang.org/x/xerrors" @@ -772,6 +773,36 @@ func TestAgent_StartupScript(t *testing.T) { require.Equal(t, content, strings.TrimSpace(gotContent)) } +func TestAgent_Metadata(t *testing.T) { + t.Parallel() + + //nolint:dogsled + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ + Metadata: []agentsdk.Metadata{ + { + Key: "greeting", + Interval: time.Millisecond * 100, + Cmd: []string{"echo", "hello"}, + }, + { + Key: "bad", + Interval: time.Millisecond * 100, + Cmd: []string{"sh", "-c", "exit 1"}, + }, + }, + }, 0) + + var gotMd agentsdk.PostMetadataRequest + require.Eventually(t, func() bool { + gotMd = client.getMetadata() + return len(gotMd) == 2 + }, testutil.WaitShort, testutil.IntervalMedium) + + require.Equal(t, "hello", gotMd["greeting"].Value) + require.Empty(t, gotMd["bad"].Value) + require.Equal(t, "exit status 1", gotMd["bad"].Error) +} + func TestAgent_Lifecycle(t *testing.T) { t.Parallel() @@ -1492,6 +1523,7 @@ type client struct { t *testing.T agentID uuid.UUID manifest agentsdk.Manifest + metadata agentsdk.PostMetadataRequest statsChan chan *agentsdk.Stats coordinator tailnet.Coordinator lastWorkspaceAgent func() @@ -1576,6 +1608,19 @@ func (c *client) getStartup() agentsdk.PostStartupRequest { return c.startup } +func (c *client) getMetadata() agentsdk.PostMetadataRequest { + c.mu.Lock() + defer c.mu.Unlock() + return maps.Clone(c.metadata) +} + +func (c *client) PostMetadata(_ context.Context, req agentsdk.PostMetadataRequest) error { + c.mu.Lock() + defer c.mu.Unlock() + c.metadata = req + return nil +} + func (c *client) PostStartup(_ context.Context, startup agentsdk.PostStartupRequest) error { c.mu.Lock() defer c.mu.Unlock() diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index ff2c10754f5a5..1634d3e54916e 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -74,6 +74,28 @@ type Metadata struct { Interval time.Duration } +type MetadataResult struct { + Key string + Value string + Error string +} + +type PostMetadataRequest map[string]MetadataResult + +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.StatusOK { + return codersdk.ReadBodyAsError(res) + } + + return nil +} + type Manifest struct { // GitAuthConfigs stores the number of Git configurations // the Coder deployment has. If this number is >0, we From 592b8a5ea9103b93512bad5db591c3b89a6fea0a Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 15 Mar 2023 18:05:51 +0000 Subject: [PATCH 06/73] Finish godloop --- agent/agent.go | 79 ++++++++++++++++++++++++++++++++--- codersdk/agentsdk/agentsdk.go | 11 +++-- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 772eb2e0290d9..5e26ffbb747ce 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -2,6 +2,7 @@ package agent import ( "bufio" + "bytes" "context" "crypto/rand" "crypto/rsa" @@ -206,9 +207,33 @@ func (a *agent) runLoop(ctx context.Context) { } } -func (a *agent) reportMetadata(ctx context.Context) error { - ma := a.manifest.Load() - tickers := make([]time.Ticker, 0, len(ma.Metadata)) +func (a *agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agentsdk.MetadataResult { + collectedAt := time.Now() + + var out bytes.Buffer + + cmd := exec.CommandContext(ctx, md.Cmd[0], md.Cmd[1:]...) + cmd.Stdout = &out + cmd.Stderr = &out + err := cmd.Run() + if err != nil { + return agentsdk.MetadataResult{ + CollectedAt: collectedAt, + Key: md.Key, + Error: err.Error(), + } + } + + const bufLimit = 10 << 14 + if out.Len() > bufLimit { + out.Truncate(bufLimit) + } + + return agentsdk.MetadataResult{ + CollectedAt: collectedAt, + Key: md.Key, + Value: out.String(), + } } func (a *agent) reportMetadataLoop(ctx context.Context) { @@ -217,17 +242,59 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { if flag.Lookup("test.v") != nil { ticker = time.Millisecond * 100 } - baseTicker := time.NewTicker(ticker) + + var ( + baseTicker = time.NewTicker(ticker) + lastCollectedAts = make(map[string]time.Time) + metadataResults = make(chan agentsdk.MetadataResult, 16) + ) + defer baseTicker.Stop() for { select { case <-ctx.Done(): return - case <-baseTicker.C: - err := a.reportMetadata(ctx) + case mr := <-metadataResults: + lastCollectedAts[mr.Key] = mr.CollectedAt + err := a.client.PostMetadata(ctx, mr) if err != nil { a.logger.Error(ctx, "report metadata", slog.Error(err)) } + case <-baseTicker.C: + manifest := a.manifest.Load() + if manifest == nil { + continue + } + // If the manifest changes (e.g. on agent reconnect) we need to + // purge old values. + for key := range lastCollectedAts { + if slices.IndexFunc(manifest.Metadata, func(md agentsdk.Metadata) bool { + return md.Key == key + }) < 0 { + delete(lastCollectedAts, key) + } + } + + // Spawn a goroutine for each metadata collection, and use channels + // to synchronize the results and avoid messy mutex logic. + for _, md := range manifest.Metadata { + collectedAt, ok := lastCollectedAts[md.Key] + if ok && collectedAt.Add(md.Interval).After(time.Now()) { + continue + } + if len(metadataResults) > cap(metadataResults)/2 { + // If we're backpressured on sending back results, we risk + // runaway goroutine growth. + continue + } + go func(md agentsdk.Metadata) { + select { + case <-ctx.Done(): + return + case metadataResults <- a.collectMetadata(ctx, md): + } + }(md) + } } } } diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 1634d3e54916e..f70a26fa25ce7 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -75,12 +75,15 @@ type Metadata struct { } type MetadataResult struct { - Key string - Value string - Error string + CollectedAt time.Time + Key string + Value string + Error string } -type PostMetadataRequest map[string]MetadataResult +// In the future, we may want to support sending back multiple values for +// performance. +type PostMetadataRequest = MetadataResult func (c *Client) PostMetadata(ctx context.Context, req PostMetadataRequest) error { res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/metadata", req) From 658f5b2b4eb179bdffa38bfeafa259adb9bbb435 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 15 Mar 2023 18:26:48 +0000 Subject: [PATCH 07/73] WIP agent tests --- agent/agent.go | 32 +++++++++----- agent/agent_test.go | 101 +++++++++++++++++++++++++++++++++----------- go.mod | 4 -- go.sum | 20 --------- 4 files changed, 99 insertions(+), 58 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 5e26ffbb747ce..48254a1626a58 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -207,11 +207,15 @@ func (a *agent) runLoop(ctx context.Context) { } } -func (a *agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agentsdk.MetadataResult { +func (*agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agentsdk.MetadataResult { + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(md.Interval)) + defer cancel() + collectedAt := time.Now() var out bytes.Buffer + //nolint:gosec cmd := exec.CommandContext(ctx, md.Cmd[0], md.Cmd[1:]...) cmd.Stdout = &out cmd.Stderr = &out @@ -237,14 +241,15 @@ func (a *agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agent } func (a *agent) reportMetadataLoop(ctx context.Context) { - // In production, the minimum report interval is one second. - ticker := time.Second + // In production, the minimum report interval is one second because + // `coder_agent.metadata` accepts `interval` in integer seconds. + baseInterval := time.Second if flag.Lookup("test.v") != nil { - ticker = time.Millisecond * 100 + baseInterval = time.Millisecond * 100 } var ( - baseTicker = time.NewTicker(ticker) + baseTicker = time.NewTicker(baseInterval) lastCollectedAts = make(map[string]time.Time) metadataResults = make(chan agentsdk.MetadataResult, 16) ) @@ -279,12 +284,19 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { // to synchronize the results and avoid messy mutex logic. for _, md := range manifest.Metadata { collectedAt, ok := lastCollectedAts[md.Key] - if ok && collectedAt.Add(md.Interval).After(time.Now()) { - continue + if ok { + // If the interval is zero, we assume the user just wants + // a single collection at startup, not a spinning loop. + if md.Interval == 0 { + continue + } + if collectedAt.Add(md.Interval).After(time.Now()) { + continue + } } if len(metadataResults) > cap(metadataResults)/2 { // If we're backpressured on sending back results, we risk - // runaway goroutine growth. + // runaway goroutine growth or overloading coderd. continue } go func(md agentsdk.Metadata) { @@ -396,10 +408,10 @@ func (a *agent) run(ctx context.Context) error { return xerrors.Errorf("update workspace agent version: %w", err) } - oldMetadata := a.manifest.Swap(&manifest) + oldManifest := a.manifest.Swap(&manifest) // The startup script should only execute on the first run! - if oldMetadata == nil { + if oldManifest == nil { a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStarting) // Perform overrides early so that Git auth can work even if users diff --git a/agent/agent_test.go b/agent/agent_test.go index 2d0574dc5e9ea..99df66f108b46 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -776,31 +776,81 @@ func TestAgent_StartupScript(t *testing.T) { func TestAgent_Metadata(t *testing.T) { t.Parallel() - //nolint:dogsled - _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - Metadata: []agentsdk.Metadata{ - { - Key: "greeting", - Interval: time.Millisecond * 100, - Cmd: []string{"echo", "hello"}, + t.Run("Basic", func(t *testing.T) { + t.Parallel() + //nolint:dogsled + dir := t.TempDir() + greetingPath := filepath.Join(dir, "greeting") + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ + Metadata: []agentsdk.Metadata{ + { + Key: "greeting", + Interval: time.Millisecond * 100, + Cmd: []string{"sh", "-c", "echo hello | tee " + greetingPath}, + }, + { + Key: "bad", + Interval: time.Millisecond * 100, + Cmd: []string{"sh", "-c", "exit 1"}, + }, }, - { - Key: "bad", - Interval: time.Millisecond * 100, - Cmd: []string{"sh", "-c", "exit 1"}, + }, 0) + + start := time.Now() + + require.Eventually(t, func() bool { + return len(client.getMetadata()) == 2 + }, testutil.WaitShort, testutil.IntervalMedium) + + for start := time.Now(); time.Since(start) < testutil.WaitShort; time.Sleep(testutil.IntervalMedium) { + md := client.getMetadata() + if len(md) != 2 { + panic("unexpected number of metadata entries") + } + + require.Equal(t, "hello", md["greeting"].Value) + require.Equal(t, "exit status 1", md["bad"].Error) + + greetingByt, err := os.ReadFile(greetingPath) + require.NoError(t, err) + + var ( + numGreetings = bytes.Count(greetingByt, []byte("hello")) + idealNumGreetings = time.Since(start) / (time.Millisecond * 100) + ) + + } + }) + + t.Run("CollectOnce", func(t *testing.T) { + t.Parallel() + //nolint:dogsled + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ + Metadata: []agentsdk.Metadata{ + { + Key: "greeting", + Interval: 0, + Cmd: []string{"echo", "-n", "hello"}, + }, }, - }, - }, 0) + }, 0) - var gotMd agentsdk.PostMetadataRequest - require.Eventually(t, func() bool { - gotMd = client.getMetadata() - return len(gotMd) == 2 - }, testutil.WaitShort, testutil.IntervalMedium) + var gotMd map[string]agentsdk.PostMetadataRequest + require.Eventually(t, func() bool { + gotMd = client.getMetadata() + return len(gotMd) == 1 + }, testutil.WaitShort, testutil.IntervalMedium) + + collectedAt := gotMd["greeting"].CollectedAt - require.Equal(t, "hello", gotMd["greeting"].Value) - require.Empty(t, gotMd["bad"].Value) - require.Equal(t, "exit status 1", gotMd["bad"].Error) + require.Never(t, func() bool { + gotMd = client.getMetadata() + if len(gotMd) != 1 { + panic("unexpected number of metadata") + } + return !gotMd["greeting"].CollectedAt.Equal(collectedAt) + }, testutil.WaitShort, testutil.IntervalMedium) + }) } func TestAgent_Lifecycle(t *testing.T) { @@ -1523,7 +1573,7 @@ type client struct { t *testing.T agentID uuid.UUID manifest agentsdk.Manifest - metadata agentsdk.PostMetadataRequest + metadata map[string]agentsdk.PostMetadataRequest statsChan chan *agentsdk.Stats coordinator tailnet.Coordinator lastWorkspaceAgent func() @@ -1608,7 +1658,7 @@ func (c *client) getStartup() agentsdk.PostStartupRequest { return c.startup } -func (c *client) getMetadata() agentsdk.PostMetadataRequest { +func (c *client) getMetadata() map[string]agentsdk.PostMetadataRequest { c.mu.Lock() defer c.mu.Unlock() return maps.Clone(c.metadata) @@ -1617,7 +1667,10 @@ func (c *client) getMetadata() agentsdk.PostMetadataRequest { func (c *client) PostMetadata(_ context.Context, req agentsdk.PostMetadataRequest) error { c.mu.Lock() defer c.mu.Unlock() - c.metadata = req + if c.metadata == nil { + c.metadata = make(map[string]agentsdk.PostMetadataRequest) + } + c.metadata[req.Key] = req return nil } diff --git a/go.mod b/go.mod index 317b857bd2fc3..bb589fb96f103 100644 --- a/go.mod +++ b/go.mod @@ -183,9 +183,6 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/flatbuffers v23.1.21+incompatible // indirect github.com/h2non/filetype v1.1.3 // indirect - github.com/hashicorp/go-plugin v1.4.4 // indirect - github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect - github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/errors v1.0.0 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -193,7 +190,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/oklog/run v1.0.0 // indirect ) require ( diff --git a/go.sum b/go.sum index 3b3f19badf7ce..a22b9dfa998ba 100644 --- a/go.sum +++ b/go.sum @@ -376,22 +376,8 @@ github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d h1:09JG37IgTB6n3ouX9 github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d/go.mod h1:r+1J5i/989wt6CUeNSuvFKKA9hHuKKPMxdzDbTuvwwk= github.com/coder/ssh v0.0.0-20220811105153-fcea99919338 h1:tN5GKFT68YLVzJoA8AHuiMNJ0qlhoD3pGN3JY9gxSko= github.com/coder/ssh v0.0.0-20220811105153-fcea99919338/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= -github.com/coder/tailscale v1.1.1-0.20230307022319-1e5e724a3949 h1:8WfMfRTDaEpnmhCJWfFQ7JHz19GyP+EgFgLGu5ngdek= -github.com/coder/tailscale v1.1.1-0.20230307022319-1e5e724a3949/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= -github.com/coder/tailscale v1.1.1-0.20230313184322-307d4b9ef4e1 h1:kOh+1rBcbahdodSiNBioO4g47m3Ef8CagNEX8U7/RyY= -github.com/coder/tailscale v1.1.1-0.20230313184322-307d4b9ef4e1/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= -github.com/coder/tailscale v1.1.1-0.20230313192237-8d691a009b20 h1:FFPv3xLsAT+n8chkePxB/VwqFwn4NL8GV8Lk97efUP0= -github.com/coder/tailscale v1.1.1-0.20230313192237-8d691a009b20/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= -github.com/coder/tailscale v1.1.1-0.20230313194848-e281f646c460 h1:WYnHzxQ1DsgDYyyS6i8tgdpjiwS1LrXJjwXcOVkU/3g= -github.com/coder/tailscale v1.1.1-0.20230313194848-e281f646c460/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= -github.com/coder/tailscale v1.1.1-0.20230313195101-c6534848756b h1:3xWbBCpQ+CqVP2Myh1YhCw8cJbMi6mjkDl4/x6YkiUw= -github.com/coder/tailscale v1.1.1-0.20230313195101-c6534848756b/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= -github.com/coder/tailscale v1.1.1-0.20230314021518-0cda9db154be h1:b0ZTDPpV/fAdkuS/V59cawnq+5vcXzkvXPMG/eZcRx0= -github.com/coder/tailscale v1.1.1-0.20230314021518-0cda9db154be/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972 h1:193YGsJz8hc4yxqAclE36paKl+9CQ6KGLgdleIguCVE= github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= -github.com/coder/terraform-provider-coder v0.6.15 h1:Llvh4RwxSQ/goy7ToTOeHf3tdEz+79qbyOh61hNnJs0= -github.com/coder/terraform-provider-coder v0.6.15/go.mod h1:UIfU3bYNeSzJJvHyJ30tEKjD6Z9utloI+HUM/7n94CY= github.com/coder/terraform-provider-coder v0.6.17 h1:YvjM5nQx5RO+gXsYIv++CkiWCuJueQdJaPrsjnkZ4XQ= github.com/coder/terraform-provider-coder v0.6.17/go.mod h1:UIfU3bYNeSzJJvHyJ30tEKjD6Z9utloI+HUM/7n94CY= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= @@ -1039,7 +1025,6 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= -github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-reap v0.0.0-20170704170343-bf58d8a43e7b h1:3GrpnZQBxcMj1gCXQLelfjCT1D5MPGTuGMKHVzSIH6A= github.com/hashicorp/go-reap v0.0.0-20170704170343-bf58d8a43e7b/go.mod h1:qIFzeFcJU3OIFk/7JreWXcUjFmcCaeHTH9KoNyHYVCs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -1050,7 +1035,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= @@ -1087,9 +1071,7 @@ github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfD github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 h1:+KxZULPsbjpAVoP0WNj/8aVW6EqpcX5JcUcQ5wl7Da4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0/go.mod h1:DwGJG3KNxIPluVk6hexvDfYR/MS/eKGpiztJoT3Bbbw= github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce h1:7FO+LmZwiG/eDsBWo50ZeqV5PoH0gwiM1mxFajXAkas= github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= @@ -1513,7 +1495,6 @@ github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -2182,7 +2163,6 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= From 465e0d8ed10a0ebf82c3bd1ef2c6f7fc9f6d4449 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 15 Mar 2023 20:55:54 +0000 Subject: [PATCH 08/73] Terraform tests pass! --- agent/agent.go | 41 +- agent/agent_test.go | 33 +- coderd/apidoc/docs.go | 36 +- coderd/apidoc/swagger.json | 36 +- coderd/wsconncache/wsconncache_test.go | 12 +- codersdk/agentsdk/agentsdk.go | 7 +- docs/api/agents.md | 16 +- docs/api/schemas.md | 59 ++- docs/manifest.json | 16 - provisioner/terraform/resources.go | 10 +- provisioner/terraform/resources_test.go | 5 +- .../calling-module.tfstate.json | 8 +- .../chaining-resources.tfstate.json | 8 +- .../conflicting-resources.tfstate.json | 8 +- .../git-auth-providers.tfstate.json | 6 +- .../instance-id/instance-id.tfstate.json | 10 +- .../mapped-apps/mapped-apps.tfstate.json | 14 +- .../multiple-agents.tfstate.json | 14 +- .../multiple-apps/multiple-apps.tfstate.json | 18 +- .../resource-metadata/resource-metadata.tf | 7 +- .../resource-metadata.tfplan.json | 59 ++- .../resource-metadata.tfstate.json | 29 +- .../rich-parameters.tfplan.json | 2 +- .../rich-parameters.tfstate.json | 8 +- provisionersdk/proto/provisioner.pb.go | 428 +++++++++--------- provisionersdk/proto/provisioner.proto | 3 +- 26 files changed, 528 insertions(+), 365 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 48254a1626a58..4a68eea161866 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -207,8 +207,12 @@ func (a *agent) runLoop(ctx context.Context) { } } -func (*agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agentsdk.MetadataResult { - ctx, cancel := context.WithDeadline(ctx, time.Now().Add(md.Interval)) +func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) agentsdk.MetadataResult { + timeout := md.Timeout + if timeout == 0 { + timeout = md.Interval + } + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) defer cancel() collectedAt := time.Now() @@ -219,25 +223,28 @@ func (*agent) collectMetadata(ctx context.Context, md agentsdk.Metadata) agentsd cmd := exec.CommandContext(ctx, md.Cmd[0], md.Cmd[1:]...) cmd.Stdout = &out cmd.Stderr = &out + + // The error isn't mutually exclusive with useful output. err := cmd.Run() - if err != nil { - return agentsdk.MetadataResult{ - CollectedAt: collectedAt, - Key: md.Key, - Error: err.Error(), - } - } const bufLimit = 10 << 14 if out.Len() > bufLimit { + err = errors.Join( + err, + xerrors.Errorf("output truncated from %v to %v bytes", out.Len(), bufLimit), + ) out.Truncate(bufLimit) } - return agentsdk.MetadataResult{ + result := agentsdk.MetadataResult{ CollectedAt: collectedAt, Key: md.Key, Value: out.String(), } + if err != nil { + result.Error = err.Error() + } + return result } func (a *agent) reportMetadataLoop(ctx context.Context) { @@ -271,9 +278,10 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { continue } // If the manifest changes (e.g. on agent reconnect) we need to - // purge old values. + // purge old cache values to prevent lastCollectedAt from growing + // boundlessly. for key := range lastCollectedAts { - if slices.IndexFunc(manifest.Metadata, func(md agentsdk.Metadata) bool { + if slices.IndexFunc(manifest.Metadata, func(md agentsdk.MetadataDescription) bool { return md.Key == key }) < 0 { delete(lastCollectedAts, key) @@ -296,14 +304,17 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { } if len(metadataResults) > cap(metadataResults)/2 { // If we're backpressured on sending back results, we risk - // runaway goroutine growth or overloading coderd. + // runaway goroutine growth and/or overloading coderd. So, + // we just skip the collection. Since we never update + // "lastCollectedAt" for this key, we'll retry the collection + // on the next tick. continue } - go func(md agentsdk.Metadata) { + go func(md agentsdk.MetadataDescription) { select { case <-ctx.Done(): return - case metadataResults <- a.collectMetadata(ctx, md): + case metadataResults <- collectMetadata(ctx, md): } }(md) } diff --git a/agent/agent_test.go b/agent/agent_test.go index 99df66f108b46..77ee71a0dc9fd 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -778,26 +778,24 @@ func TestAgent_Metadata(t *testing.T) { t.Run("Basic", func(t *testing.T) { t.Parallel() - //nolint:dogsled dir := t.TempDir() + const reportInterval = time.Millisecond * 200 greetingPath := filepath.Join(dir, "greeting") _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - Metadata: []agentsdk.Metadata{ + Metadata: []agentsdk.MetadataDescription{ { Key: "greeting", - Interval: time.Millisecond * 100, - Cmd: []string{"sh", "-c", "echo hello | tee " + greetingPath}, + Interval: reportInterval, + Cmd: []string{"sh", "-c", "echo hello | tee -a " + greetingPath}, }, { Key: "bad", - Interval: time.Millisecond * 100, + Interval: reportInterval, Cmd: []string{"sh", "-c", "exit 1"}, }, }, }, 0) - start := time.Now() - require.Eventually(t, func() bool { return len(client.getMetadata()) == 2 }, testutil.WaitShort, testutil.IntervalMedium) @@ -808,7 +806,7 @@ func TestAgent_Metadata(t *testing.T) { panic("unexpected number of metadata entries") } - require.Equal(t, "hello", md["greeting"].Value) + require.Equal(t, "hello\n", md["greeting"].Value) require.Equal(t, "exit status 1", md["bad"].Error) greetingByt, err := os.ReadFile(greetingPath) @@ -816,9 +814,24 @@ func TestAgent_Metadata(t *testing.T) { var ( numGreetings = bytes.Count(greetingByt, []byte("hello")) - idealNumGreetings = time.Since(start) / (time.Millisecond * 100) + idealNumGreetings = time.Since(start) / (reportInterval) + upperBound = int(idealNumGreetings) + 1 + lowerBound = (int(idealNumGreetings) / 2) ) + if idealNumGreetings < 5 { + // Not enough time has passed to get a good sample size. + continue + } + + t.Logf("numGreetings: %d, idealNumGreetings: %d", numGreetings, idealNumGreetings) + // The report loop may slow down on load, but it should never, ever + // speed up. + if numGreetings > upperBound { + t.Fatalf("too many greetings: %d > %d", numGreetings, upperBound) + } else if numGreetings < lowerBound { + t.Fatalf("too few greetings: %d < %d", numGreetings, lowerBound) + } } }) @@ -826,7 +839,7 @@ func TestAgent_Metadata(t *testing.T) { t.Parallel() //nolint:dogsled _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - Metadata: []agentsdk.Metadata{ + Metadata: []agentsdk.MetadataDescription{ { Key: "greeting", Interval: 0, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 5785cb57f4ac8..86c69566f1c83 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -4159,7 +4159,7 @@ const docTemplate = `{ } } }, - "/workspaceagents/me/metadata": { + "/workspaceagents/me/manifest": { "get": { "security": [ { @@ -4172,13 +4172,13 @@ const docTemplate = `{ "tags": [ "Agents" ], - "summary": "Get authorized workspace agent metadata", - "operationId": "get-authorized-workspace-agent-metadata", + "summary": "Get authorized workspace agent manifest", + "operationId": "get-authorized-workspace-agent-manifest", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/agentsdk.Metadata" + "$ref": "#/definitions/agentsdk.Manifest" } } } @@ -5195,7 +5195,7 @@ const docTemplate = `{ } } }, - "agentsdk.Metadata": { + "agentsdk.Manifest": { "type": "object", "properties": { "apps": { @@ -5210,6 +5210,12 @@ const docTemplate = `{ "directory": { "type": "string" }, + "dynamic_metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/agentsdk.MetadataDescription" + } + }, "environment_variables": { "type": "object", "additionalProperties": { @@ -5240,6 +5246,26 @@ const docTemplate = `{ } } }, + "agentsdk.MetadataDescription": { + "type": "object", + "properties": { + "cmd": { + "type": "array", + "items": { + "type": "string" + } + }, + "interval": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "timeout": { + "type": "integer" + } + } + }, "agentsdk.PostAppHealthsRequest": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index dc7a9dd435e4a..79924bdd70059 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -3657,7 +3657,7 @@ } } }, - "/workspaceagents/me/metadata": { + "/workspaceagents/me/manifest": { "get": { "security": [ { @@ -3666,13 +3666,13 @@ ], "produces": ["application/json"], "tags": ["Agents"], - "summary": "Get authorized workspace agent metadata", - "operationId": "get-authorized-workspace-agent-metadata", + "summary": "Get authorized workspace agent manifest", + "operationId": "get-authorized-workspace-agent-manifest", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/agentsdk.Metadata" + "$ref": "#/definitions/agentsdk.Manifest" } } } @@ -4580,7 +4580,7 @@ } } }, - "agentsdk.Metadata": { + "agentsdk.Manifest": { "type": "object", "properties": { "apps": { @@ -4595,6 +4595,12 @@ "directory": { "type": "string" }, + "dynamic_metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/agentsdk.MetadataDescription" + } + }, "environment_variables": { "type": "object", "additionalProperties": { @@ -4625,6 +4631,26 @@ } } }, + "agentsdk.MetadataDescription": { + "type": "object", + "properties": { + "cmd": { + "type": "array", + "items": { + "type": "string" + } + }, + "interval": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "timeout": { + "type": "integer" + } + } + }, "agentsdk.PostAppHealthsRequest": { "type": "object", "properties": { diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index be9665c783d4b..8be242bd3b389 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -154,10 +154,10 @@ func TestCache(t *testing.T) { }) } -func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn { +func setupAgent(t *testing.T, manifest agentsdk.Manifest, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn { t.Helper() - metadata.DERPMap = tailnettest.RunDERPAndSTUN(t) + manifest.DERPMap = tailnettest.RunDERPAndSTUN(t) coordinator := tailnet.NewCoordinator() t.Cleanup(func() { @@ -168,7 +168,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati Client: &client{ t: t, agentID: agentID, - manifest: metadata, + manifest: manifest, coordinator: coordinator, }, Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo), @@ -179,7 +179,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati }) conn, err := tailnet.NewConn(&tailnet.Options{ Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)}, - DERPMap: metadata.DERPMap, + DERPMap: manifest.DERPMap, Logger: slogtest.Make(t, nil).Named("tailnet").Leveled(slog.LevelDebug), }) require.NoError(t, err) @@ -246,6 +246,10 @@ func (*client) PostAppHealth(_ context.Context, _ agentsdk.PostAppHealthsRequest return nil } +func (*client) PostMetadata(_ context.Context, _ agentsdk.PostMetadataRequest) error { + return nil +} + func (*client) PostStartup(_ context.Context, _ agentsdk.PostStartupRequest) error { return nil } diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index f70a26fa25ce7..a8a2157618948 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -65,13 +65,14 @@ func (c *Client) GitSSHKey(ctx context.Context) (GitSSHKey, error) { return gitSSHKey, json.NewDecoder(res.Body).Decode(&gitSSHKey) } -// Metadata is a description of dynamic metadata the agent should report +// MetadataDescription is a description of dynamic metadata the agent should report // back to coderd. It is provided via the `metadata` list in the `coder_agent` // block. -type Metadata struct { +type MetadataDescription struct { Key string Cmd []string Interval time.Duration + Timeout time.Duration } type MetadataResult struct { @@ -114,7 +115,7 @@ type Manifest struct { MOTDFile string `json:"motd_file"` ShutdownScript string `json:"shutdown_script"` ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"` - Metadata []Metadata `json:"dynamic_metadata"` + Metadata []MetadataDescription `json:"dynamic_metadata"` } // Manifest fetches manifest for the currently authenticated workspace agent. diff --git a/docs/api/agents.md b/docs/api/agents.md index 6031e8698815b..3a70893dd82a6 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -273,18 +273,18 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/gitsshkey \ To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get authorized workspace agent metadata +## Get authorized workspace agent manifest ### Code samples ```shell # Example request using curl -curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \ +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/metadata` +`GET /workspaceagents/me/manifest` ### Example responses @@ -363,6 +363,14 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \ } }, "directory": "string", + "dynamic_metadata": [ + { + "cmd": ["string"], + "interval": 0, + "key": "string", + "timeout": 0 + } + ], "environment_variables": { "property1": "string", "property2": "string" @@ -381,7 +389,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \ | Status | Meaning | Description | Schema | | ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [agentsdk.Metadata](schemas.md#agentsdkmetadata) | +| 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). diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 7ee906d6195b7..771fdfa3ecc8c 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -94,7 +94,7 @@ | ---------------- | ------ | -------- | ------------ | ----------- | | `json_web_token` | string | true | | | -## agentsdk.Metadata +## agentsdk.Manifest ```json { @@ -169,6 +169,14 @@ } }, "directory": "string", + "dynamic_metadata": [ + { + "cmd": ["string"], + "interval": 0, + "key": "string", + "timeout": 0 + } + ], "environment_variables": { "property1": "string", "property2": "string" @@ -185,20 +193,41 @@ ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------------- | ------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | -| `derpmap` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | | -| `directory` | string | 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. | -| `motd_file` | string | false | | | -| `shutdown_script` | string | false | | | -| `shutdown_script_timeout` | integer | false | | | -| `startup_script` | string | false | | | -| `startup_script_timeout` | integer | false | | | -| `vscode_port_proxy_uri` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------------- | --------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | +| `derpmap` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | | +| `directory` | string | false | | | +| `dynamic_metadata` | array of [agentsdk.MetadataDescription](#agentsdkmetadatadescription) | 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. | +| `motd_file` | string | false | | | +| `shutdown_script` | string | false | | | +| `shutdown_script_timeout` | integer | false | | | +| `startup_script` | string | false | | | +| `startup_script_timeout` | integer | false | | | +| `vscode_port_proxy_uri` | string | false | | | + +## agentsdk.MetadataDescription + +```json +{ + "cmd": ["string"], + "interval": 0, + "key": "string", + "timeout": 0 +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------- | --------------- | -------- | ------------ | ----------- | +| `cmd` | array of string | false | | | +| `interval` | integer | false | | | +| `key` | string | false | | | +| `timeout` | integer | false | | | ## agentsdk.PostAppHealthsRequest diff --git a/docs/manifest.json b/docs/manifest.json index dea571b079cf6..5508ddf016a1d 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -530,22 +530,6 @@ "title": "schedule stop", "path": "./cli/coder_schedule_stop.md" }, - { - "title": "server", - "path": "./cli/coder_server.md" - }, - { - "title": "server create-admin-user", - "path": "./cli/coder_server_create-admin-user.md" - }, - { - "title": "server postgres-builtin-serve", - "path": "./cli/coder_server_postgres-builtin-serve.md" - }, - { - "title": "server postgres-builtin-url", - "path": "./cli/coder_server_postgres-builtin-url.md" - }, { "title": "show", "path": "./cli/coder_show.md" diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 61065117a29f9..4ceb76d0ee9b3 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -15,10 +15,11 @@ import ( ) type agentMetadata struct { - Key string `mapstructure:"key"` - DisplayName string `mapstructure:"display_name"` - Cmd string `mapstructure:"cmd"` - Interval int64 `mapstructure:"interval"` + Key string `mapstructure:"key"` + DisplayName string `mapstructure:"display_name"` + Cmd []string `mapstructure:"cmd"` + Interval int64 `mapstructure:"interval"` + Timeout int64 `mapstructure:"timeout"` } // A mapping of attributes on the "coder_agent" resource. @@ -155,6 +156,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error DisplayName: item.DisplayName, Cmd: item.Cmd, Interval: item.Interval, + Timeout: item.Timeout, }) } diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index a7fc18ad19db0..e5b28a00f35ff 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -228,8 +228,9 @@ func TestConvertResources(t *testing.T) { Metadata: []*proto.Agent_Metadata{{ Key: "process_count", DisplayName: "Process Count", - Cmd: "ps -ef | wc -l", - Interval: 1, + Cmd: []string{"sh", "-c", "ps -ef | wc -l"}, + Interval: 5, + Timeout: 1, }}, ShutdownScriptTimeoutSeconds: 300, StartupScriptTimeoutSeconds: 300, diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json index 91804c5220bd6..548d07460c561 100644 --- a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json +++ b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "388003b2-8710-4b51-a56d-c022eb3b836b", + "id": "0f6bf0dd-8979-4cee-ad93-1d6b296030df", "init_script": "", "os": "linux", "startup_script": null, - "token": "720f05f1-3152-4c28-8957-20071b7a5b3e", + "token": "3b9216a3-5068-4e5e-9c8d-431b6b193e40", "troubleshooting_url": null }, "sensitive_values": {} @@ -46,7 +46,7 @@ "outputs": { "script": "" }, - "random": "4256784547276174255" + "random": "5312277644306944506" }, "sensitive_values": { "inputs": {}, @@ -61,7 +61,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8146628865354704855", + "id": "5731009027150083481", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json index 96680c929ceda..de2939397665b 100644 --- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json +++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "8d5431ee-123f-413d-b540-38dafd072070", + "id": "d7ca77d3-5694-4917-ab95-b0e4d5b863fb", "init_script": "", "os": "linux", "startup_script": null, - "token": "4234aa4b-f7c3-4367-89ad-7901c3d9868e", + "token": "55110d15-f6f2-494d-9d88-d900426452fd", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "413851319920758839", + "id": "6566453986753470457", "triggers": null }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "1236629432916328127", + "id": "1978030318234045706", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json index 1becf93570fcc..31a84588e0dad 100644 --- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json +++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "0150cd90-4051-4bd6-9fa9-b0faebfa2188", + "id": "da98334f-b15d-4c7d-af40-171c9534eda9", "init_script": "", "os": "linux", "startup_script": null, - "token": "b6119d01-b1a8-4353-a3eb-ca4416730dc5", + "token": "f1a9d7a9-56eb-49ea-873c-61e9e95c8f4a", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2011143373046435058", + "id": "5349614300871249481", "triggers": null }, "sensitive_values": {}, @@ -50,7 +50,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8488040712554307223", + "id": "3037219017109852101", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json index ec1525ad7806a..bdcf4201eb35c 100644 --- a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json +++ b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "d643602d-615c-4671-ba5b-88bad6c96dc0", + "id": "45348caf-46c4-4ea0-beab-448d88632870", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "7ceffeb0-3841-433b-a06d-ae782c8fa255", + "token": "2a5b992b-06d6-4ca9-8772-bbb6a5ce2072", "troubleshooting_url": null }, "sensitive_values": {} @@ -65,7 +65,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "7899939329384955011", + "id": "919786932930959182", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json index 1c03ef1f887f8..16c2dc2133821 100644 --- a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json +++ b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "7425fb13-1661-4eaa-ad7d-6bc3388e49a5", + "id": "0317bb92-103a-405a-93a6-3de2aab69ae1", "init_script": "", "os": "linux", "startup_script": null, - "token": "0c2e5244-ff09-4bf9-98c3-74a8243ca45f", + "token": "ec348810-e364-4867-ac34-578d0cdb6677", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,8 +34,8 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "7425fb13-1661-4eaa-ad7d-6bc3388e49a5", - "id": "64410592-8c91-4922-b25f-d531ed975794", + "agent_id": "0317bb92-103a-405a-93a6-3de2aab69ae1", + "id": "6f87d601-d899-491a-b2ce-4b678e767170", "instance_id": "example" }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8961076348162067126", + "id": "1658063359083321737", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json index 2963d23b0965e..1e739c1ad1e94 100644 --- a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json +++ b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "cb0d2ad4-b777-40f9-9df6-e965bfdfda62", + "id": "5add6e45-7a1a-4e21-b4ca-cda7ecd3f787", "init_script": "", "os": "linux", "startup_script": null, - "token": "6615b629-8418-42c9-a52d-e9d83d353eec", + "token": "f0607c56-a8f1-4668-9157-ac4a752ee600", "troubleshooting_url": null }, "sensitive_values": {} @@ -35,12 +35,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "cb0d2ad4-b777-40f9-9df6-e965bfdfda62", + "agent_id": "5add6e45-7a1a-4e21-b4ca-cda7ecd3f787", "command": null, "display_name": "app1", "healthcheck": [], "icon": null, - "id": "f802f3d1-e742-4dff-a644-a99035903320", + "id": "de94512a-e967-47c8-a50d-a2a79c9e4c0f", "name": null, "relative_path": null, "share": "owner", @@ -64,12 +64,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "cb0d2ad4-b777-40f9-9df6-e965bfdfda62", + "agent_id": "5add6e45-7a1a-4e21-b4ca-cda7ecd3f787", "command": null, "display_name": "app2", "healthcheck": [], "icon": null, - "id": "e62cefdb-f595-4f56-ac6d-482f6d952d5b", + "id": "ff5b3cfc-c3ef-4e57-9b9d-7731a52fc6de", "name": null, "relative_path": null, "share": "owner", @@ -92,7 +92,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "588892076664802665", + "id": "7833742822266148121", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json index c2dcf193168e7..740cada116021 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "20fbe3f3-1ba8-452d-bfa3-d2595dd69979", + "id": "92d69f26-3086-4d18-8318-715d13cbd6da", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "44f0149d-92a8-45f7-9603-5a2712f21852", + "token": "e851cebe-7791-461a-9cb2-0cc46cfba171", "troubleshooting_url": null }, "sensitive_values": {} @@ -44,7 +44,7 @@ "connection_timeout": 1, "dir": null, "env": null, - "id": "2b52cde2-e77e-47b4-8d8b-8e8819faa3de", + "id": "fd36fe36-6a3b-4a3a-8fcd-db113555c54c", "init_script": "", "login_before_ready": true, "motd_file": "/etc/motd", @@ -53,7 +53,7 @@ "shutdown_script_timeout": 30, "startup_script": null, "startup_script_timeout": 30, - "token": "19dd2a91-4c3f-428c-8f75-3b9fa74bbb1b", + "token": "aa5aa77f-aaa3-401b-9e9e-344da10eed2f", "troubleshooting_url": null }, "sensitive_values": {} @@ -71,7 +71,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "4654241a-5fb8-41a2-b785-80524525c5ec", + "id": "ced645fb-c1dc-45cf-a734-556ee0c49b4c", "init_script": "", "login_before_ready": false, "motd_file": null, @@ -80,7 +80,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "30bd6e5d-8664-47e4-9560-f6db43d3e5e2", + "token": "d580abf2-6efb-46df-8ceb-cdb2bbb90c0e", "troubleshooting_url": "https://coder.com/troubleshoot" }, "sensitive_values": {} @@ -93,7 +93,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "7653227335898253433", + "id": "8642727725045126860", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json index 77f1a6d8f1008..90753debf456b 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "76a93c83-0b4b-4ff4-9819-3993354fe788", + "id": "3c986469-184b-4e43-9700-e1b9188640f3", "init_script": "", "os": "linux", "startup_script": null, - "token": "cdd4febe-9dbc-4f60-b200-d5bc2e0e91a4", + "token": "0890e63f-f637-4c50-9790-f9edaa939f95", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,12 +34,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "76a93c83-0b4b-4ff4-9819-3993354fe788", + "agent_id": "3c986469-184b-4e43-9700-e1b9188640f3", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "f9215fe7-7d58-4c48-8575-6fff2496b26f", + "id": "3d78476e-9fa0-481c-8820-edd86642efa1", "name": null, "relative_path": null, "share": "owner", @@ -62,7 +62,7 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "76a93c83-0b4b-4ff4-9819-3993354fe788", + "agent_id": "3c986469-184b-4e43-9700-e1b9188640f3", "command": null, "display_name": null, "healthcheck": [ @@ -73,7 +73,7 @@ } ], "icon": null, - "id": "91b5ea13-5cb5-4b59-b151-92a762df2d75", + "id": "944cf3f9-3d02-4e9f-a85e-83bb25dcd3b9", "name": null, "relative_path": null, "share": "owner", @@ -98,12 +98,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "76a93c83-0b4b-4ff4-9819-3993354fe788", + "agent_id": "3c986469-184b-4e43-9700-e1b9188640f3", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "c40cfde6-de6d-4c9e-a1cf-96011adef93f", + "id": "7671da60-cd22-47fd-a83f-0dc44a3e2c1c", "name": null, "relative_path": null, "share": "owner", @@ -126,7 +126,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8674861375979341492", + "id": "5315587693128536668", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf index 8a25be0dd3a65..3bf51cbe423d7 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf @@ -3,7 +3,7 @@ terraform { coder = { source = "coder/coder" // TODO: update terraform-provider-coder before merge. - version = "= 0.6.18-rc1" + version = "= 0.6.18-rc3" } } } @@ -14,8 +14,9 @@ resource "coder_agent" "main" { metadata { key = "process_count" display_name = "Process Count" - cmd = "ps -ef | wc -l" - interval = 1 + cmd = ["sh", "-c", "ps -ef | wc -l"] + interval = 5 + timeout = 1 } } diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json index 1a954004b7049..4f3af1e1518b3 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json @@ -20,10 +20,15 @@ "login_before_ready": true, "metadata": [ { - "cmd": "ps -ef | wc -l", + "cmd": [ + "sh", + "-c", + "ps -ef | wc -l" + ], "display_name": "Process Count", - "interval": 1, - "key": "process_count" + "interval": 5, + "key": "process_count", + "timeout": 1 } ], "motd_file": null, @@ -36,7 +41,13 @@ }, "sensitive_values": { "metadata": [ - {} + { + "cmd": [ + false, + false, + false + ] + } ] } }, @@ -119,10 +130,15 @@ "login_before_ready": true, "metadata": [ { - "cmd": "ps -ef | wc -l", + "cmd": [ + "sh", + "-c", + "ps -ef | wc -l" + ], "display_name": "Process Count", - "interval": 1, - "key": "process_count" + "interval": 5, + "key": "process_count", + "timeout": 1 } ], "motd_file": null, @@ -137,14 +153,26 @@ "id": true, "init_script": true, "metadata": [ - {} + { + "cmd": [ + false, + false, + false + ] + } ], "token": true }, "before_sensitive": false, "after_sensitive": { "metadata": [ - {} + { + "cmd": [ + false, + false, + false + ] + } ], "token": true } @@ -244,7 +272,7 @@ "coder": { "name": "coder", "full_name": "registry.terraform.io/coder/coder", - "version_constraint": "0.6.18-rc1" + "version_constraint": "0.6.18-rc3" }, "null": { "name": "null", @@ -266,16 +294,23 @@ "metadata": [ { "cmd": { - "constant_value": "ps -ef | wc -l" + "constant_value": [ + "sh", + "-c", + "ps -ef | wc -l" + ] }, "display_name": { "constant_value": "Process Count" }, "interval": { - "constant_value": 1 + "constant_value": 5 }, "key": { "constant_value": "process_count" + }, + "timeout": { + "constant_value": 1 } } ], diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json index 3dbd633f4d95d..fc504708c2d3c 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json @@ -17,15 +17,20 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "0a9c67ad-4af5-40d9-b5e4-59dbfd27a390", + "id": "5ddeb3e1-ff58-42a8-b951-aeeb33bb093d", "init_script": "", "login_before_ready": true, "metadata": [ { - "cmd": "ps -ef | wc -l", + "cmd": [ + "sh", + "-c", + "ps -ef | wc -l" + ], "display_name": "Process Count", - "interval": 1, - "key": "process_count" + "interval": 5, + "key": "process_count", + "timeout": 1 } ], "motd_file": null, @@ -34,12 +39,18 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "fba4b5d7-be79-4af2-b678-87d2239b887c", + "token": "051aba07-6d8a-45ad-bb79-9c49b32cbe19", "troubleshooting_url": null }, "sensitive_values": { "metadata": [ - {} + { + "cmd": [ + false, + false, + false + ] + } ] } }, @@ -54,7 +65,7 @@ "daily_cost": 29, "hide": true, "icon": "/icon/server.svg", - "id": "84c8eb80-572c-41ef-a811-16c31381587d", + "id": "f689a177-8bd5-467a-80e8-66ea6c5cfbc1", "item": [ { "is_null": false, @@ -81,7 +92,7 @@ "value": "squirrel" } ], - "resource_id": "5419686285324437053" + "resource_id": "6412674345056346383" }, "sensitive_values": { "item": [ @@ -104,7 +115,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5419686285324437053", + "id": "6412674345056346383", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json index 6b0dffa4d4add..b4c4af35a1fe8 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json @@ -114,7 +114,7 @@ "default": null, "description": null, "icon": null, - "id": "712597e4-da74-47b8-a094-faf95041f0fc", + "id": "4564c362-c121-422e-8cc6-e9f4561df31e", "mutable": false, "name": "Example", "option": [ diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json index 708cb792c1150..2ec3bf0b3ea4b 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json @@ -17,13 +17,13 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "148d8738-b782-4397-98ab-2989443e25c6", + "id": "72a4ca97-32b3-4e34-b8f0-6133270dc29a", "init_script": "", "motd_file": null, "os": "windows", "shutdown_script": null, "startup_script": null, - "token": "54d03ecf-ed7c-4bf6-b1fb-88f1fcaa4097", + "token": "72393c2d-e496-4e30-9bd6-3d5c42d4567a", "troubleshooting_url": null }, "sensitive_values": {} @@ -39,7 +39,7 @@ "default": null, "description": null, "icon": null, - "id": "840786b9-8cab-45b2-8413-db0b0214ba34", + "id": "6cced87d-2b1c-4681-877f-129862b1fe06", "mutable": false, "name": "Example", "option": [ @@ -75,7 +75,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "6158472040912045176", + "id": "8376977411041104706", "triggers": null }, "sensitive_values": {}, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 70ee65cd1095f..4920e57ac724d 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1810,10 +1810,11 @@ type Agent_Metadata struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` - Cmd string `protobuf:"bytes,3,opt,name=cmd,proto3" json:"cmd,omitempty"` - Interval int64 `protobuf:"varint,4,opt,name=interval,proto3" json:"interval,omitempty"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Cmd []string `protobuf:"bytes,3,rep,name=cmd,proto3" json:"cmd,omitempty"` + Interval int64 `protobuf:"varint,4,opt,name=interval,proto3" json:"interval,omitempty"` + Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` } func (x *Agent_Metadata) Reset() { @@ -1862,11 +1863,11 @@ func (x *Agent_Metadata) GetDisplayName() string { return "" } -func (x *Agent_Metadata) GetCmd() string { +func (x *Agent_Metadata) GetCmd() []string { if x != nil { return x.Cmd } - return "" + return nil } func (x *Agent_Metadata) GetInterval() int64 { @@ -1876,6 +1877,13 @@ func (x *Agent_Metadata) GetInterval() int64 { return 0 } +func (x *Agent_Metadata) GetTimeout() int64 { + if x != nil { + return x.Timeout + } + return 0 +} + type Resource_Metadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2890,7 +2898,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0xa6, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x22, 0xc1, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, @@ -2937,210 +2945,212 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x73, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x6d, 0x0a, 0x08, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, - 0x6d, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x6d, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, - 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, - 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, - 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, - 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, - 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, - 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, - 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, - 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, - 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, - 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x87, 0x01, 0x0a, 0x08, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x63, 0x6d, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x63, 0x6d, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, + 0x61, 0x75, 0x74, 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, + 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, + 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, + 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, + 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, + 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, + 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, + 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, + 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, + 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, + 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, + 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x91, 0x0c, 0x0a, 0x09, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xa1, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, + 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x79, 0x0a, 0x06, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, - 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, - 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x91, - 0x0c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xa1, 0x03, 0x0a, - 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, - 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, - 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, - 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x1a, 0x79, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, - 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, - 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xeb, 0x02, 0x0a, 0x04, - 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, - 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, - 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, - 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, - 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, - 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, - 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, - 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, - 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, - 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, - 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xeb, 0x02, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, + 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, + 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, + 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, + 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, - 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, - 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, - 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, - 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, - 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, - 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, - 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, - 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, - 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, - 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, - 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, + 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, + 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, + 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, + 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, + 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, + 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, + 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, + 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, + 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, + 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, + 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, + 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, + 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index e991eb1c19d30..88cabe42da0dd 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -130,8 +130,9 @@ message Agent { message Metadata { string key = 1; string display_name = 2; - string cmd = 3; + repeated string cmd = 3; int64 interval = 4; + int64 timeout = 5; } string id = 1; string name = 2; From d0156b3d12fb85e0ad2d35fe00ce32c4ca9225e1 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 16 Mar 2023 03:51:43 +0000 Subject: [PATCH 09/73] Add Post metadata endpoint to API --- agent/agent.go | 30 ++-- coderd/apidoc/docs.go | 76 ++++++++++ coderd/apidoc/swagger.json | 72 +++++++++ coderd/coderd.go | 1 + coderd/database/dbauthz/querier.go | 14 ++ coderd/database/dbfake/databasefake.go | 25 ++++ coderd/database/dump.sql | 18 +++ .../000110_workspace_agent_metadata.down.sql | 1 + .../000110_workspace_agent_metadata.up.sql | 11 ++ coderd/database/models.go | 9 ++ coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 39 +++++ coderd/database/queries/workspaceagents.sql | 17 +++ coderd/workspaceagents.go | 60 ++++++++ coderd/workspaceagents_test.go | 58 +++++++ codersdk/agentsdk/agentsdk.go | 11 +- codersdk/workspaceagents.go | 14 +- docs/api/agents.md | 8 + docs/api/builds.md | 58 +++++++ docs/api/schemas.md | 141 ++++++++++++++---- docs/api/templates.md | 26 ++++ docs/api/workspaces.md | 32 ++++ docs/manifest.json | 16 ++ site/src/api/typesGenerated.ts | 9 ++ 24 files changed, 694 insertions(+), 53 deletions(-) create mode 100644 coderd/database/migrations/000110_workspace_agent_metadata.down.sql create mode 100644 coderd/database/migrations/000110_workspace_agent_metadata.up.sql diff --git a/agent/agent.go b/agent/agent.go index 4a68eea161866..908cb16507dcf 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -207,7 +207,7 @@ func (a *agent) runLoop(ctx context.Context) { } } -func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) agentsdk.MetadataResult { +func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) codersdk.WorkspaceAgentMetadataResult { timeout := md.Timeout if timeout == 0 { timeout = md.Interval @@ -236,7 +236,7 @@ func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) agent out.Truncate(bufLimit) } - result := agentsdk.MetadataResult{ + result := codersdk.WorkspaceAgentMetadataResult{ CollectedAt: collectedAt, Key: md.Key, Value: out.String(), @@ -250,6 +250,8 @@ func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) agent func (a *agent) reportMetadataLoop(ctx context.Context) { // In production, the minimum report interval is one second because // `coder_agent.metadata` accepts `interval` in integer seconds. + // In tests, it helps to set shorter intervals because engineers are + // expensive. baseInterval := time.Second if flag.Lookup("test.v") != nil { baseInterval = time.Millisecond * 100 @@ -258,7 +260,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { var ( baseTicker = time.NewTicker(baseInterval) lastCollectedAts = make(map[string]time.Time) - metadataResults = make(chan agentsdk.MetadataResult, 16) + metadataResults = make(chan codersdk.WorkspaceAgentMetadataResult, 16) ) defer baseTicker.Stop() @@ -273,6 +275,19 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { a.logger.Error(ctx, "report metadata", slog.Error(err)) } case <-baseTicker.C: + if len(metadataResults) > cap(metadataResults)/2 { + // If we're backpressured on sending back results, we risk + // runaway goroutine growth and/or overloading coderd. So, + // we just skip the collection. Since we never update + // "lastCollectedAt" for this key, we'll retry the collection + // on the next tick. + a.logger.Debug( + ctx, "metadata collection backpressured", + slog.F("queue_len", len(metadataResults)), + ) + continue + } + manifest := a.manifest.Load() if manifest == nil { continue @@ -302,14 +317,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { continue } } - if len(metadataResults) > cap(metadataResults)/2 { - // If we're backpressured on sending back results, we risk - // runaway goroutine growth and/or overloading coderd. So, - // we just skip the collection. Since we never update - // "lastCollectedAt" for this key, we'll retry the collection - // on the next tick. - continue - } + go func(md agentsdk.MetadataDescription) { select { case <-ctx.Done(): diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 86c69566f1c83..32643b7b05f76 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -4184,6 +4184,42 @@ const docTemplate = `{ } } }, + "/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": { + "$ref": "#/definitions/agentsdk.PostMetadataRequest" + } + } + ], + "responses": { + "204": { + "description": "Success" + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceagents/me/report-lifecycle": { "post": { "security": [ @@ -5286,6 +5322,23 @@ const docTemplate = `{ } } }, + "agentsdk.PostMetadataRequest": { + "type": "object", + "properties": { + "collectedAt": { + "type": "string" + }, + "error": { + "type": "string" + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "agentsdk.PostStartupRequest": { "type": "object", "properties": { @@ -8528,6 +8581,12 @@ const docTemplate = `{ "description": "LoginBeforeReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended).", "type": "boolean" }, + "metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataResult" + } + }, "name": { "type": "string" }, @@ -8627,6 +8686,23 @@ const docTemplate = `{ } } }, + "codersdk.WorkspaceAgentMetadataResult": { + "type": "object", + "properties": { + "collectedAt": { + "type": "string" + }, + "error": { + "type": "string" + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "codersdk.WorkspaceAgentStatus": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 79924bdd70059..21012c30ab561 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -3678,6 +3678,38 @@ } } }, + "/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": { + "$ref": "#/definitions/agentsdk.PostMetadataRequest" + } + } + ], + "responses": { + "204": { + "description": "Success" + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceagents/me/report-lifecycle": { "post": { "security": [ @@ -4671,6 +4703,23 @@ } } }, + "agentsdk.PostMetadataRequest": { + "type": "object", + "properties": { + "collectedAt": { + "type": "string" + }, + "error": { + "type": "string" + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "agentsdk.PostStartupRequest": { "type": "object", "properties": { @@ -7685,6 +7734,12 @@ "description": "LoginBeforeReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended).", "type": "boolean" }, + "metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataResult" + } + }, "name": { "type": "string" }, @@ -7784,6 +7839,23 @@ } } }, + "codersdk.WorkspaceAgentMetadataResult": { + "type": "object", + "properties": { + "collectedAt": { + "type": "string" + }, + "error": { + "type": "string" + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "codersdk.WorkspaceAgentStatus": { "type": "string", "enum": ["connecting", "connected", "disconnected", "timeout"], diff --git a/coderd/coderd.go b/coderd/coderd.go index b26986fe06222..201797ade29fe 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -598,6 +598,7 @@ func New(options *Options) *API { r.Get("/coordinate", api.workspaceAgentCoordinate) r.Post("/report-stats", api.workspaceAgentReportStats) r.Post("/report-lifecycle", api.workspaceAgentReportLifecycle) + r.Post("/metadata", api.workspaceAgentPostMetadata) }) r.Route("/{workspaceagent}", func(r chi.Router) { r.Use( diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 4de696222a828..d59682f9b8227 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1468,6 +1468,20 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins return q.db.InsertWorkspaceAgentStat(ctx, arg) } +func (q *querier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg database.InsertOrUpdateWorkspaceAgentMetadataParams) error { + workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceAgentID) + if err != nil { + return err + } + + err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) + if err != nil { + return err + } + + return q.db.InsertOrUpdateWorkspaceAgentMetadata(ctx, arg) +} + func (q *querier) UpdateWorkspaceAppHealthByID(ctx context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error { // TODO: This is a workspace agent operation. Should users be able to query this? workspace, err := q.db.GetWorkspaceByWorkspaceAppID(ctx, arg.ID) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 344eadb67fe1c..a6d47e3080716 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -123,6 +123,7 @@ type data struct { templateVersionVariables []database.TemplateVersionVariable templates []database.Template workspaceAgents []database.WorkspaceAgent + workspaceAgentMetadata []database.WorkspaceAgentMetadatum workspaceApps []database.WorkspaceApp workspaceBuilds []database.WorkspaceBuild workspaceBuildParameters []database.WorkspaceBuildParameter @@ -2666,6 +2667,30 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP return key, nil } +func (q *fakeQuerier) InsertOrUpdateWorkspaceAgentMetadata(_ context.Context, arg database.InsertOrUpdateWorkspaceAgentMetadataParams) error { + q.mutex.Lock() + defer q.mutex.Unlock() + + updated := database.WorkspaceAgentMetadatum{ + WorkspaceID: arg.WorkspaceID, + WorkspaceAgentID: arg.WorkspaceAgentID, + Key: arg.Key, + Value: arg.Value, + Error: arg.Error, + CollectedAt: arg.CollectedAt, + } + + for i, m := range q.workspaceAgentMetadata { + if m.WorkspaceAgentID == arg.WorkspaceAgentID { + q.workspaceAgentMetadata[i] = updated + return nil + } + } + + q.workspaceAgentMetadata = append(q.workspaceAgentMetadata, updated) + return nil +} + func (q *fakeQuerier) InsertFile(_ context.Context, arg database.InsertFileParams) (database.File, error) { if err := validateDatabaseType(arg); err != nil { return database.File{}, err diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 402ef5a43ca1b..d48535e743ad2 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -475,6 +475,15 @@ CREATE TABLE users ( last_seen_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL ); +CREATE TABLE workspace_agent_metadata ( + workspace_id uuid NOT NULL, + workspace_agent_id uuid NOT NULL, + key character varying(128) NOT NULL, + value text NOT NULL, + error text NOT NULL, + collected_at timestamp with time zone NOT NULL +); + CREATE TABLE workspace_agent_stats ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -731,6 +740,9 @@ ALTER TABLE ONLY user_links ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); +ALTER TABLE ONLY workspace_agent_metadata + ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); + ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); @@ -864,6 +876,12 @@ ALTER TABLE ONLY templates ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY workspace_agent_metadata + ADD CONSTRAINT workspace_agent_metadata_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + +ALTER TABLE ONLY workspace_agent_metadata + ADD CONSTRAINT workspace_agent_metadata_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.down.sql b/coderd/database/migrations/000110_workspace_agent_metadata.down.sql new file mode 100644 index 0000000000000..e59fb238447b6 --- /dev/null +++ b/coderd/database/migrations/000110_workspace_agent_metadata.down.sql @@ -0,0 +1 @@ +DROP TABLE workspace_agent_metadata; diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql new file mode 100644 index 0000000000000..48c57f22ec423 --- /dev/null +++ b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql @@ -0,0 +1,11 @@ +CREATE TABLE workspace_agent_metadata ( + workspace_id uuid NOT NULL, + workspace_agent_id uuid NOT NULL, + key character varying(128) NOT NULL, + value text NOT NULL, + error text NOT NULL, + collected_at timestamp with time zone NOT NULL, + PRIMARY KEY (workspace_agent_id, key), + FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE, + FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE +); diff --git a/coderd/database/models.go b/coderd/database/models.go index 3b8cbc2a97126..e09f903edeb2b 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1571,6 +1571,15 @@ type WorkspaceAgent struct { ShutdownScriptTimeoutSeconds int32 `db:"shutdown_script_timeout_seconds" json:"shutdown_script_timeout_seconds"` } +type WorkspaceAgentMetadatum struct { + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + Key string `db:"key" json:"key"` + Value string `db:"value" json:"value"` + Error string `db:"error" json:"error"` + CollectedAt time.Time `db:"collected_at" json:"collected_at"` +} + type WorkspaceAgentStat struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 174e5fcec1575..0dd748427eea2 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -163,6 +163,7 @@ type sqlcQuerier interface { InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error InsertOrUpdateLogoURL(ctx context.Context, value string) error InsertOrUpdateServiceBanner(ctx context.Context, value string) error + InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg InsertOrUpdateWorkspaceAgentMetadataParams) error InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error) InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error) InsertParameterSchema(ctx context.Context, arg InsertParameterSchemaParams) (ParameterSchema, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 7d5539ba5b369..039ffb5f06016 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5308,6 +5308,45 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created return items, nil } +const insertOrUpdateWorkspaceAgentMetadata = `-- name: InsertOrUpdateWorkspaceAgentMetadata :exec +INSERT INTO + workspace_agent_metadata ( + workspace_id, + workspace_agent_id, + key, + value, + error, + collected_at + ) +VALUES + ($1, $2, $3, $4, $5, $6) +ON CONFLICT (workspace_agent_id, key) DO UPDATE SET + value = $4, + error = $5, + collected_at = $6 +` + +type InsertOrUpdateWorkspaceAgentMetadataParams struct { + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + Key string `db:"key" json:"key"` + Value string `db:"value" json:"value"` + Error string `db:"error" json:"error"` + CollectedAt time.Time `db:"collected_at" json:"collected_at"` +} + +func (q *sqlQuerier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg InsertOrUpdateWorkspaceAgentMetadataParams) error { + _, err := q.db.ExecContext(ctx, insertOrUpdateWorkspaceAgentMetadata, + arg.WorkspaceID, + arg.WorkspaceAgentID, + arg.Key, + arg.Value, + arg.Error, + arg.CollectedAt, + ) + return err +} + const insertWorkspaceAgent = `-- name: InsertWorkspaceAgent :one INSERT INTO workspace_agents ( diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 2bc30faf21095..4008807b5f48c 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -93,3 +93,20 @@ SET lifecycle_state = $2 WHERE id = $1; + +-- name: InsertOrUpdateWorkspaceAgentMetadata :exec +INSERT INTO + workspace_agent_metadata ( + workspace_id, + workspace_agent_id, + key, + value, + error, + collected_at + ) +VALUES + ($1, $2, $3, $4, $5, $6) +ON CONFLICT (workspace_agent_id, key) DO UPDATE SET + value = $4, + error = $5, + collected_at = $6; diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 488c0dbdc58dc..e53b18f987980 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1039,6 +1039,66 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques }) } +func ellipse(s string, maxLength int) string { + if len(s) > maxLength { + return s[:maxLength] + "..." + } + return s +} + +// @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() + + workspaceAgent := httpmw.WorkspaceAgent(r) + workspace, 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 + } + + var req agentsdk.PostMetadataRequest + if !httpapi.Read(ctx, rw, r, &req) { + return + } + + datum := database.InsertOrUpdateWorkspaceAgentMetadataParams{ + WorkspaceID: workspace.ID, + WorkspaceAgentID: workspaceAgent.ID, + // We don't want a misconfigured agent to fill the database. + Key: ellipse(req.Key, 128), + Value: ellipse(req.Value, 10<<10), + Error: ellipse(req.Value, 10<<10), + CollectedAt: req.CollectedAt, + } + + err = api.Database.InsertOrUpdateWorkspaceAgentMetadata(ctx, datum) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + api.Logger.Debug( + ctx, "read metadata report", + slog.F("agent", workspaceAgent.ID), + slog.F("workspace", workspace.ID), + slog.F("key", datum.Key), + ) + + httpapi.Write(ctx, rw, http.StatusNoContent, nil) +} + // @Summary Submit workspace agent lifecycle state // @ID submit-workspace-agent-lifecycle-state // @Security CoderSessionToken diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 9bf5d87d2de08..345e82ecbd580 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1290,3 +1290,61 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) { } }) } + +func TestWorkspaceAgent_Metadata(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + authToken := uuid.NewString() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.ProvisionComplete, + ProvisionApply: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "example", + Type: "aws_instance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Auth: &proto.Agent_Token{ + Token: authToken, + }, + }}, + }}, + }, + }, + }}, + }) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + for _, res := range workspace.LatestBuild.Resources { + for _, a := range res.Agents { + require.Equal(t, codersdk.WorkspaceAgentLifecycleCreated, a.LifecycleState) + } + } + + agentClient := agentsdk.New(client.URL) + agentClient.SetSessionToken(authToken) + + ctx, cancel := testutil.Context(t) + defer cancel() + + err := agentClient.PostMetadata(ctx, codersdk.WorkspaceAgentMetadataResult{ + CollectedAt: time.Now(), + Key: "foo", + Value: "bar", + }) + require.NoError(t, err, "post metadata", t) + + workspace, err = client.Workspace(ctx, workspace.ID) + require.NoError(t, err, "get workspace") + + t.Logf("%+v", workspace.LatestBuild.Resources[0]) +} diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index a8a2157618948..e16356700a276 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -75,16 +75,9 @@ type MetadataDescription struct { Timeout time.Duration } -type MetadataResult struct { - CollectedAt time.Time - Key string - Value string - Error string -} - // In the future, we may want to support sending back multiple values for // performance. -type PostMetadataRequest = MetadataResult +type PostMetadataRequest = codersdk.WorkspaceAgentMetadataResult func (c *Client) PostMetadata(ctx context.Context, req PostMetadataRequest) error { res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/metadata", req) @@ -93,7 +86,7 @@ func (c *Client) PostMetadata(ctx context.Context, req PostMetadataRequest) erro } defer res.Body.Close() - if res.StatusCode != http.StatusOK { + if res.StatusCode != http.StatusNoContent { return codersdk.ReadBodyAsError(res) } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 4d953f8e050d5..b71e9d37df3a2 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -73,6 +73,13 @@ var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{ WorkspaceAgentLifecycleOff, } +type WorkspaceAgentMetadataResult struct { + CollectedAt time.Time + Key string + Value string + Error string +} + type WorkspaceAgent struct { ID uuid.UUID `json:"id" format:"uuid"` CreatedAt time.Time `json:"created_at" format:"date-time"` @@ -100,9 +107,10 @@ type WorkspaceAgent struct { // LoginBeforeReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). LoginBeforeReady bool `json:"login_before_ready"` // StartupScriptTimeoutSeconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. - StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"` - ShutdownScript string `json:"shutdown_script,omitempty"` - ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"` + StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"` + ShutdownScript string `json:"shutdown_script,omitempty"` + ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"` + Metadata []WorkspaceAgentMetadataResult `json:"metadata"` } type DERPRegion struct { diff --git a/docs/api/agents.md b/docs/api/agents.md index 3a70893dd82a6..a286d1d84684b 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -522,6 +522,14 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", diff --git a/docs/api/builds.md b/docs/api/builds.md index d46abaada436d..7918208efe108 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -101,6 +101,14 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -251,6 +259,14 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -542,6 +558,14 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -622,6 +646,11 @@ Status Code **200** | `»»»» preferred` | boolean | false | | | | `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»» metadata` | array | false | | | +| `»»» collectedAt` | string | false | | | +| `»»» error` | string | false | | | +| `»»» key` | string | false | | | +| `»»» value` | string | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | | `»» resource_id` | string(uuid) | false | | | @@ -776,6 +805,14 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -931,6 +968,14 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -1045,6 +1090,11 @@ Status Code **200** | `»»»»» preferred` | boolean | false | | | | `»»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»»» login_before_ready` | boolean | false | | »»login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»»» metadata` | array | false | | | +| `»»»» collectedAt` | string | false | | | +| `»»»» error` | string | false | | | +| `»»»» key` | string | false | | | +| `»»»» value` | string | false | | | | `»»» name` | string | false | | | | `»»» operating_system` | string | false | | | | `»»» resource_id` | string(uuid) | false | | | @@ -1261,6 +1311,14 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 771fdfa3ecc8c..7662915370476 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -261,6 +261,26 @@ | ------- | -------------------------------------------------------------------- | -------- | ------------ | ----------- | | `state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | +## agentsdk.PostMetadataRequest + +```json +{ + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | ------ | -------- | ------------ | ----------- | +| `collectedAt` | string | false | | | +| `error` | string | false | | | +| `key` | string | false | | | +| `value` | string | false | | | + ## agentsdk.PostStartupRequest ```json @@ -4265,6 +4285,14 @@ Parameter represents a set value for the scope. }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -4390,6 +4418,14 @@ Parameter represents a set value for the scope. }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -4406,36 +4442,37 @@ Parameter represents a set value for the scope. ### Properties -| Name | Type | Required | Restrictions | Description | -| --------------------------------- | -------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | -| `architecture` | string | false | | | -| `connection_timeout_seconds` | integer | false | | | -| `created_at` | string | false | | | -| `directory` | string | false | | | -| `disconnected_at` | string | false | | | -| `environment_variables` | object | false | | | -| » `[any property]` | string | false | | | -| `expanded_directory` | string | false | | | -| `first_connected_at` | string | false | | | -| `id` | string | false | | | -| `instance_id` | string | false | | | -| `last_connected_at` | string | false | | | -| `latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | -| » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | | -| `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | -| `login_before_ready` | boolean | false | | Login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | -| `name` | string | false | | | -| `operating_system` | string | false | | | -| `resource_id` | string | false | | | -| `shutdown_script` | string | false | | | -| `shutdown_script_timeout_seconds` | integer | false | | | -| `startup_script` | string | false | | | -| `startup_script_timeout_seconds` | integer | false | | Startup script timeout seconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. | -| `status` | [codersdk.WorkspaceAgentStatus](#codersdkworkspaceagentstatus) | false | | | -| `troubleshooting_url` | string | false | | | -| `updated_at` | string | false | | | -| `version` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| --------------------------------- | --------------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | +| `architecture` | string | false | | | +| `connection_timeout_seconds` | integer | false | | | +| `created_at` | string | false | | | +| `directory` | string | false | | | +| `disconnected_at` | string | false | | | +| `environment_variables` | object | false | | | +| » `[any property]` | string | false | | | +| `expanded_directory` | string | false | | | +| `first_connected_at` | string | false | | | +| `id` | string | false | | | +| `instance_id` | string | false | | | +| `last_connected_at` | string | false | | | +| `latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | +| » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | | +| `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | +| `login_before_ready` | boolean | false | | Login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `metadata` | array of [codersdk.WorkspaceAgentMetadataResult](#codersdkworkspaceagentmetadataresult) | false | | | +| `name` | string | false | | | +| `operating_system` | string | false | | | +| `resource_id` | string | false | | | +| `shutdown_script` | string | false | | | +| `shutdown_script_timeout_seconds` | integer | false | | | +| `startup_script` | string | false | | | +| `startup_script_timeout_seconds` | integer | false | | Startup script timeout seconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. | +| `status` | [codersdk.WorkspaceAgentStatus](#codersdkworkspaceagentstatus) | false | | | +| `troubleshooting_url` | string | false | | | +| `updated_at` | string | false | | | +| `version` | string | false | | | ## codersdk.WorkspaceAgentConnectionInfo @@ -4561,6 +4598,26 @@ Parameter represents a set value for the scope. | ------- | ------------------------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ports` | array of [codersdk.WorkspaceAgentListeningPort](#codersdkworkspaceagentlisteningport) | false | | If there are no ports in the list, nothing should be displayed in the UI. There must not be a "no ports available" message or anything similar, as there will always be no ports displayed on platforms where our port detection logic is unsupported. | +## codersdk.WorkspaceAgentMetadataResult + +```json +{ + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | ------ | -------- | ------------ | ----------- | +| `collectedAt` | string | false | | | +| `error` | string | false | | | +| `key` | string | false | | | +| `value` | string | false | | | + ## codersdk.WorkspaceAgentStatus ```json @@ -4735,6 +4792,14 @@ Parameter represents a set value for the scope. }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -4954,6 +5019,14 @@ Parameter represents a set value for the scope. }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -5149,6 +5222,14 @@ Parameter represents a set value for the scope. }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", diff --git a/docs/api/templates.md b/docs/api/templates.md index b0d2b74f9b426..629d1978887d5 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1619,6 +1619,14 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -1699,6 +1707,11 @@ Status Code **200** | `»»»» preferred` | boolean | false | | | | `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»» metadata` | array | false | | | +| `»»» collectedAt` | string | false | | | +| `»»» error` | string | false | | | +| `»»» key` | string | false | | | +| `»»» value` | string | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | | `»» resource_id` | string(uuid) | false | | | @@ -2044,6 +2057,14 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -2124,6 +2145,11 @@ Status Code **200** | `»»»» preferred` | boolean | false | | | | `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»» metadata` | array | false | | | +| `»»» collectedAt` | string | false | | | +| `»»» error` | string | false | | | +| `»»» key` | string | false | | | +| `»»» value` | string | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | | `»» resource_id` | string(uuid) | false | | | diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index ca19ba4f54dcb..5e7191838b6f9 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -133,6 +133,14 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -302,6 +310,14 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -490,6 +506,14 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", @@ -660,6 +684,14 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ }, "lifecycle_state": "created", "login_before_ready": true, + "metadata": [ + { + "collectedAt": "string", + "error": "string", + "key": "string", + "value": "string" + } + ], "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", diff --git a/docs/manifest.json b/docs/manifest.json index 5508ddf016a1d..dea571b079cf6 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -530,6 +530,22 @@ "title": "schedule stop", "path": "./cli/coder_schedule_stop.md" }, + { + "title": "server", + "path": "./cli/coder_server.md" + }, + { + "title": "server create-admin-user", + "path": "./cli/coder_server_create-admin-user.md" + }, + { + "title": "server postgres-builtin-serve", + "path": "./cli/coder_server_postgres-builtin-serve.md" + }, + { + "title": "server postgres-builtin-url", + "path": "./cli/coder_server_postgres-builtin-url.md" + }, { "title": "show", "path": "./cli/coder_show.md" diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 9cb75fd64fa6d..21645773b080f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1020,6 +1020,7 @@ export interface WorkspaceAgent { readonly startup_script_timeout_seconds: number readonly shutdown_script?: string readonly shutdown_script_timeout_seconds: number + readonly metadata: WorkspaceAgentMetadataResult[] } // From codersdk/workspaceagentconn.go @@ -1034,6 +1035,14 @@ export interface WorkspaceAgentListeningPortsResponse { readonly ports: WorkspaceAgentListeningPort[] } +// From codersdk/workspaceagents.go +export interface WorkspaceAgentMetadataResult { + readonly CollectedAt: string + readonly Key: string + readonly Value: string + readonly Error: string +} + // From codersdk/workspaceapps.go export interface WorkspaceApp { readonly id: string From 7a0541cbcab07f80a334badd497572dc5dc8b0d6 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 21 Mar 2023 20:25:53 +0000 Subject: [PATCH 10/73] Test setting metadata --- coderd/database/dbauthz/querier.go | 2 +- coderd/database/dbfake/databasefake.go | 11 ++--------- coderd/workspaceagents.go | 13 +++++++------ codersdk/workspaceagents.go | 7 +++---- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index ff7ed154283bc..d2e6b80596a1a 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1473,7 +1473,7 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins } func (q *querier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg database.InsertOrUpdateWorkspaceAgentMetadataParams) error { - workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceAgentID) + workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) if err != nil { return err } diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 5432c32428681..f4075df95f437 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2674,17 +2674,10 @@ func (q *fakeQuerier) InsertOrUpdateWorkspaceAgentMetadata(_ context.Context, ar q.mutex.Lock() defer q.mutex.Unlock() - updated := database.WorkspaceAgentMetadatum{ - WorkspaceID: arg.WorkspaceID, - WorkspaceAgentID: arg.WorkspaceAgentID, - Key: arg.Key, - Value: arg.Value, - Error: arg.Error, - CollectedAt: arg.CollectedAt, - } + updated := database.WorkspaceAgentMetadatum(arg) for i, m := range q.workspaceAgentMetadata { - if m.WorkspaceAgentID == arg.WorkspaceAgentID { + if m.WorkspaceAgentID == arg.WorkspaceAgentID && m.Key == arg.Key { q.workspaceAgentMetadata[i] = updated return nil } diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 8c2e2dcec06c1..42842344c1a1b 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1047,7 +1047,13 @@ func ellipse(s string, maxLength int) string { 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) + workspace, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ @@ -1057,11 +1063,6 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque return } - var req agentsdk.PostMetadataRequest - if !httpapi.Read(ctx, rw, r, &req) { - return - } - datum := database.InsertOrUpdateWorkspaceAgentMetadataParams{ WorkspaceID: workspace.ID, WorkspaceAgentID: workspaceAgent.ID, @@ -1079,7 +1080,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque } api.Logger.Debug( - ctx, "read metadata report", + ctx, "accepted metadata report", slog.F("agent", workspaceAgent.ID), slog.F("workspace", workspace.ID), slog.F("key", datum.Key), diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index a3ed36d7b44d8..25be1609bdfef 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -107,10 +107,9 @@ type WorkspaceAgent struct { // LoginBeforeReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). LoginBeforeReady bool `json:"login_before_ready"` // StartupScriptTimeoutSeconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. - StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"` - ShutdownScript string `json:"shutdown_script,omitempty"` - ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"` - Metadata []WorkspaceAgentMetadataResult `json:"metadata"` + StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"` + ShutdownScript string `json:"shutdown_script,omitempty"` + ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"` } type DERPRegion struct { From 16cd11eff0abc2459ca1113a9a4c426cd05a6fb0 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 21 Mar 2023 21:29:47 +0000 Subject: [PATCH 11/73] Create watch endpoint --- coderd/coderd.go | 1 + coderd/database/dbauthz/querier.go | 14 +++ coderd/database/dbfake/databasefake.go | 13 +++ .../000110_workspace_agent_metadata.up.sql | 2 + coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 39 ++++++++ coderd/database/queries/workspaceagents.sql | 8 ++ coderd/workspaceagents.go | 89 +++++++++++++++++++ coderd/workspaceagents_test.go | 15 +++- codersdk/workspaceagents.go | 48 ++++++++++ site/src/api/typesGenerated.ts | 1 - 11 files changed, 227 insertions(+), 4 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index c3054c8b30a6b..7a532eff68d9a 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -618,6 +618,7 @@ func New(options *Options) *API { httpmw.ExtractWorkspaceParam(options.Database), ) r.Get("/", api.workspaceAgent) + r.Get("/watch-metadata", api.watchWorkspaceAgentMetadata) r.Get("/pty", api.workspaceAgentPTY) r.Get("/listening-ports", api.workspaceAgentListeningPorts) r.Get("/connection", api.workspaceAgentConnection) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index d2e6b80596a1a..8af3b630e6f9a 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1486,6 +1486,20 @@ func (q *querier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg return q.db.InsertOrUpdateWorkspaceAgentMetadata(ctx, arg) } +func (q *querier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { + workspace, err := q.db.GetWorkspaceByAgentID(ctx, workspaceAgentID) + if err != nil { + return nil, err + } + + err = q.authorizeContext(ctx, rbac.ActionRead, workspace) + if err != nil { + return nil, err + } + + return q.db.GetWorkspaceAgentMetadata(ctx, workspaceAgentID) +} + func (q *querier) UpdateWorkspaceAppHealthByID(ctx context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error { // TODO: This is a workspace agent operation. Should users be able to query this? workspace, err := q.db.GetWorkspaceByWorkspaceAppID(ctx, arg.ID) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index f4075df95f437..83265ea44fcb3 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2687,6 +2687,19 @@ func (q *fakeQuerier) InsertOrUpdateWorkspaceAgentMetadata(_ context.Context, ar return nil } +func (q *fakeQuerier) GetWorkspaceAgentMetadata(_ context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + metadata := make([]database.WorkspaceAgentMetadatum, 0) + for _, m := range q.workspaceAgentMetadata { + if m.WorkspaceAgentID == workspaceAgentID { + metadata = append(metadata, m) + } + } + return metadata, nil +} + func (q *fakeQuerier) InsertFile(_ context.Context, arg database.InsertFileParams) (database.File, error) { if err := validateDatabaseType(arg); err != nil { return database.File{}, err diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql index 48c57f22ec423..274fee1c16637 100644 --- a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql @@ -1,3 +1,5 @@ +-- TODO: Do we need an index for workspace_agent_id or is the multi-column PRIMARY +-- key enough? CREATE TABLE workspace_agent_metadata ( workspace_id uuid NOT NULL, workspace_agent_id uuid NOT NULL, diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 0dd748427eea2..e36c1583f4365 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -121,6 +121,7 @@ type sqlcQuerier interface { GetWorkspaceAgentByAuthToken(ctx context.Context, authToken uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error) + GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error) GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsRow, error) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 039ffb5f06016..7c5c147f47b19 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5191,6 +5191,45 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst return i, err } +const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many +SELECT + workspace_id, workspace_agent_id, key, value, error, collected_at +FROM + workspace_agent_metadata +WHERE + workspace_agent_id = $1 +` + +func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAgentMetadata, workspaceAgentID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentMetadatum + for rows.Next() { + var i WorkspaceAgentMetadatum + if err := rows.Scan( + &i.WorkspaceID, + &i.WorkspaceAgentID, + &i.Key, + &i.Value, + &i.Error, + &i.CollectedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getWorkspaceAgentsByResourceIDs = `-- name: GetWorkspaceAgentsByResourceIDs :many SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 4008807b5f48c..f668e531cb789 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -110,3 +110,11 @@ ON CONFLICT (workspace_agent_id, key) DO UPDATE SET value = $4, error = $5, collected_at = $6; + +-- name: GetWorkspaceAgentMetadata :many +SELECT + * +FROM + workspace_agent_metadata +WHERE + workspace_agent_id = $1; diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 42842344c1a1b..da9da76e31dc4 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1086,9 +1086,98 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque slog.F("key", datum.Key), ) + err = api.Pubsub.Publish(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), []byte(datum.Key)) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + httpapi.Write(ctx, rw, http.StatusNoContent, nil) } +// @Summary Watch for workspace agent metadata updates +// @ID watch-workspace-agent-metadata +// @Security CoderSessionToken +// @Accept json +// @Tags Agents +// @Success 200 "Success" +// @Param workspaceagent path string true "Workspace agent ID" format(uuid) +// @Router /workspaceagents/{workspaceagent}/watch-metadata [get] +// @x-apidocgen {"skip": true} +func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + workspaceAgent = httpmw.WorkspaceAgentParam(r) + ) + + sendEvent, senderClosed, err := httpapi.ServerSentEventSender(rw, r) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error setting up server-sent events.", + Detail: err.Error(), + }) + return + } + // Prevent handler from returning until the sender is closed. + defer func() { + <-senderClosed + }() + + // We don't want this intentionally long request to skew our tracing + // reports. + ctx = trace.ContextWithSpan(ctx, tracing.NoopSpan) + + sendMetadata := func(ctx context.Context) { + data, err := api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID) + if err != nil { + _ = sendEvent(ctx, codersdk.ServerSentEvent{ + Type: codersdk.ServerSentEventTypeError, + Data: codersdk.Response{ + Message: "Internal getting metadata.", + Detail: err.Error(), + }, + }) + return + } + _ = sendEvent(ctx, codersdk.ServerSentEvent{ + Type: codersdk.ServerSentEventTypeData, + Data: convertWorkspaceAgentMetadata(data), + }) + } + + // Send initial metadata. + sendMetadata(ctx) + + // Send metadata on updates. + cancelSub, err := api.Pubsub.Subscribe(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), func(ctx context.Context, _ []byte) { + sendMetadata(ctx) + }) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + defer cancelSub() + + <-senderClosed +} + +func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataResult { + var result []codersdk.WorkspaceAgentMetadataResult + for _, datum := range db { + result = append(result, codersdk.WorkspaceAgentMetadataResult{ + Key: datum.Key, + Value: datum.Value, + Error: datum.Error, + CollectedAt: datum.CollectedAt, + }) + } + return result +} + +func watchWorkspaceAgentMetadataChannel(id uuid.UUID) string { + return "workspace_agent_metadata:" + id.String() +} + // @Summary Submit workspace agent lifecycle state // @ID submit-workspace-agent-lifecycle-state // @Security CoderSessionToken diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 586bfe588668e..70ff9d5c874b3 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1149,6 +1149,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { }) user := coderdtest.CreateFirstUser(t, client) authToken := uuid.NewString() + agentID := uuid.New() version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, ProvisionPlan: echo.ProvisionComplete, @@ -1159,7 +1160,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { Name: "example", Type: "aws_instance", Agents: []*proto.Agent{{ - Id: uuid.NewString(), + Id: agentID.String(), Auth: &proto.Agent_Token{ Token: authToken, }, @@ -1186,15 +1187,23 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { ctx, cancel := testutil.Context(t) defer cancel() - err := agentClient.PostMetadata(ctx, codersdk.WorkspaceAgentMetadataResult{ + wantMetadata := codersdk.WorkspaceAgentMetadataResult{ CollectedAt: time.Now(), Key: "foo", Value: "bar", - }) + } + err := agentClient.PostMetadata(ctx, wantMetadata) require.NoError(t, err, "post metadata", t) workspace, err = client.Workspace(ctx, workspace.ID) require.NoError(t, err, "get workspace") t.Logf("%+v", workspace.LatestBuild.Resources[0]) + + updates, err := client.WatchWorkspaceAgentMetadata(ctx, agentID) + require.NoError(t, err) + + update := <-updates + require.Len(t, update, 1) + require.Equal(t, wantMetadata, update[0]) } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 25be1609bdfef..8e0a1162e316c 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -18,6 +18,7 @@ import ( "tailscale.com/tailcfg" "cdr.dev/slog" + "github.com/coder/coder/coderd/tracing" "github.com/coder/coder/tailnet" "github.com/coder/retry" ) @@ -261,6 +262,53 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti return agentConn, nil } +func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) (<-chan []WorkspaceAgentMetadataResult, error) { + ctx, span := tracing.StartSpan(ctx) + defer span.End() + //nolint:bodyclose + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/watch-metadata", id), nil) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + return nil, ReadBodyAsError(res) + } + nextEvent := ServerSentEventReader(ctx, res.Body) + + ch := make(chan []WorkspaceAgentMetadataResult, 256) + go func() { + defer close(ch) + defer res.Body.Close() + + for { + select { + case <-ctx.Done(): + return + default: + sse, err := nextEvent() + if err != nil { + return + } + if sse.Type != ServerSentEventTypeData { + continue + } + var met []WorkspaceAgentMetadataResult + b, ok := sse.Data.([]byte) + if !ok { + return + } + err = json.Unmarshal(b, &met) + if err != nil { + return + } + ch <- met + } + } + }() + + return ch, nil +} + // WorkspaceAgent returns an agent by ID. func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) { res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s", id), nil) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 5e336df19ce3c..734f632a7944c 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1047,7 +1047,6 @@ export interface WorkspaceAgent { readonly startup_script_timeout_seconds: number readonly shutdown_script?: string readonly shutdown_script_timeout_seconds: number - readonly metadata: WorkspaceAgentMetadataResult[] } // From codersdk/workspaceagentconn.go From c840962cf5d4e4b6f31106278c8e4cfa38aa72a3 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 21 Mar 2023 23:24:57 +0000 Subject: [PATCH 12/73] Watch tests pass!! --- coderd/database/dbfake/databasefake.go | 10 +++- coderd/workspaceagents.go | 19 ++++--- coderd/workspaceagents_test.go | 61 +++++++++++++++++----- codersdk/workspaceagents.go | 70 +++++++++++++++++--------- 4 files changed, 116 insertions(+), 44 deletions(-) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 83265ea44fcb3..265db8c2fbf21 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2674,7 +2674,15 @@ func (q *fakeQuerier) InsertOrUpdateWorkspaceAgentMetadata(_ context.Context, ar q.mutex.Lock() defer q.mutex.Unlock() - updated := database.WorkspaceAgentMetadatum(arg) + //nolint:gosimple + updated := database.WorkspaceAgentMetadatum{ + WorkspaceID: arg.WorkspaceID, + WorkspaceAgentID: arg.WorkspaceAgentID, + Key: arg.Key, + Value: arg.Value, + Error: arg.Error, + CollectedAt: arg.CollectedAt, + } for i, m := range q.workspaceAgentMetadata { if m.WorkspaceAgentID == arg.WorkspaceAgentID && m.Key == arg.Key { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index da9da76e31dc4..60a61ac73297b 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -20,6 +20,7 @@ import ( "github.com/google/uuid" "go.opentelemetry.io/otel/trace" + "golang.org/x/exp/slices" "golang.org/x/mod/semver" "golang.org/x/oauth2" "golang.org/x/xerrors" @@ -1069,7 +1070,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque // We don't want a misconfigured agent to fill the database. Key: ellipse(req.Key, 128), Value: ellipse(req.Value, 10<<10), - Error: ellipse(req.Value, 10<<10), + Error: ellipse(req.Error, 10<<10), CollectedAt: req.CollectedAt, } @@ -1084,6 +1085,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque slog.F("agent", workspaceAgent.ID), slog.F("workspace", workspace.ID), slog.F("key", datum.Key), + slog.F("value", ellipse(datum.Value, 5)), ) err = api.Pubsub.Publish(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), []byte(datum.Key)) @@ -1127,18 +1129,23 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ // reports. ctx = trace.ContextWithSpan(ctx, tracing.NoopSpan) - sendMetadata := func(ctx context.Context) { + sendMetadata := func() { + // We always use the original Request context because it contains + // the RBAC actor. data, err := api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID) if err != nil { _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeError, Data: codersdk.Response{ - Message: "Internal getting metadata.", + Message: "Internal error getting metadata.", Detail: err.Error(), }, }) return } + slices.SortFunc(data, func(i, j database.WorkspaceAgentMetadatum) bool { + return i.Key < j.Key + }) _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeData, Data: convertWorkspaceAgentMetadata(data), @@ -1146,11 +1153,11 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ } // Send initial metadata. - sendMetadata(ctx) + sendMetadata() // Send metadata on updates. - cancelSub, err := api.Pubsub.Subscribe(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), func(ctx context.Context, _ []byte) { - sendMetadata(ctx) + cancelSub, err := api.Pubsub.Subscribe(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), func(_ context.Context, _ []byte) { + sendMetadata() }) if err != nil { httpapi.InternalServerError(rw, err) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 70ff9d5c874b3..e4721f3ba6783 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1149,7 +1149,6 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { }) user := coderdtest.CreateFirstUser(t, client) authToken := uuid.NewString() - agentID := uuid.New() version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, ProvisionPlan: echo.ProvisionComplete, @@ -1160,7 +1159,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { Name: "example", Type: "aws_instance", Agents: []*proto.Agent{{ - Id: agentID.String(), + Id: uuid.NewString(), Auth: &proto.Agent_Token{ Token: authToken, }, @@ -1187,23 +1186,61 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { ctx, cancel := testutil.Context(t) defer cancel() - wantMetadata := codersdk.WorkspaceAgentMetadataResult{ + post := func(mr codersdk.WorkspaceAgentMetadataResult) { + err := agentClient.PostMetadata(ctx, mr) + require.NoError(t, err, "post metadata", t) + } + + workspace, err := client.Workspace(ctx, workspace.ID) + require.NoError(t, err, "get workspace") + + agentID := workspace.LatestBuild.Resources[0].Agents[0].ID + + var update []codersdk.WorkspaceAgentMetadataResult + + check := func(want codersdk.WorkspaceAgentMetadataResult, got codersdk.WorkspaceAgentMetadataResult) { + require.WithinDuration(t, want.CollectedAt, got.CollectedAt, time.Second) + require.Equal(t, want.Key, got.Key) + require.Equal(t, want.Value, got.Value) + require.Equal(t, want.Error, got.Error) + } + + wantMetadata1 := codersdk.WorkspaceAgentMetadataResult{ CollectedAt: time.Now(), Key: "foo", Value: "bar", } - err := agentClient.PostMetadata(ctx, wantMetadata) - require.NoError(t, err, "post metadata", t) - workspace, err = client.Workspace(ctx, workspace.ID) - require.NoError(t, err, "get workspace") + // Initial post must come before the Watch is established. + post(wantMetadata1) - t.Logf("%+v", workspace.LatestBuild.Resources[0]) + updates, errors := client.WatchWorkspaceAgentMetadata(ctx, agentID) - updates, err := client.WatchWorkspaceAgentMetadata(ctx, agentID) - require.NoError(t, err) + recvUpdate := func() []codersdk.WorkspaceAgentMetadataResult { + select { + case err := <-errors: + t.Fatalf("error watching metadata: %v", err) + return nil + case update := <-updates: + return update + } + } - update := <-updates + update = recvUpdate() require.Len(t, update, 1) - require.Equal(t, wantMetadata, update[0]) + check(wantMetadata1, update[0]) + + wantMetadata2 := wantMetadata1 + wantMetadata2.Key = wantMetadata1.Key + "2" + post(wantMetadata2) + update = recvUpdate() + require.Len(t, update, 2) + check(wantMetadata1, update[0]) + check(wantMetadata2, update[1]) + + wantMetadata1.Error = "error" + post(wantMetadata1) + update = recvUpdate() + require.Len(t, update, 2) + check(wantMetadata1, update[0]) } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 8e0a1162e316c..bc55f3ed4d6ab 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -262,51 +262,71 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti return agentConn, nil } -func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) (<-chan []WorkspaceAgentMetadataResult, error) { +// WatchWorkspaceAgentMetadata watches the metadata of a workspace agent. +// The returned channel will be closed when the context is canceled. Exactly +// one error will be sent on the error channel. The metadata channel is never closed. +func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) (<-chan []WorkspaceAgentMetadataResult, <-chan error) { ctx, span := tracing.StartSpan(ctx) defer span.End() - //nolint:bodyclose - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/watch-metadata", id), nil) - if err != nil { - return nil, err - } - if res.StatusCode != http.StatusOK { - return nil, ReadBodyAsError(res) - } - nextEvent := ServerSentEventReader(ctx, res.Body) - ch := make(chan []WorkspaceAgentMetadataResult, 256) - go func() { - defer close(ch) + metadataChan := make(chan []WorkspaceAgentMetadataResult, 256) + + watch := func() error { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/watch-metadata", id), nil) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return ReadBodyAsError(res) + } + + nextEvent := ServerSentEventReader(ctx, res.Body) defer res.Body.Close() for { select { case <-ctx.Done(): - return + return ctx.Err() default: sse, err := nextEvent() if err != nil { - return - } - if sse.Type != ServerSentEventTypeData { - continue + return err } - var met []WorkspaceAgentMetadataResult + b, ok := sse.Data.([]byte) if !ok { - return + return xerrors.Errorf("unexpected data type: %T", sse.Data) } - err = json.Unmarshal(b, &met) - if err != nil { - return + + switch sse.Type { + case ServerSentEventTypeData: + var met []WorkspaceAgentMetadataResult + err = json.Unmarshal(b, &met) + if err != nil { + return xerrors.Errorf("unmarshal metadata: %w", err) + } + metadataChan <- met + case ServerSentEventTypeError: + var r Response + err = json.Unmarshal(b, &r) + if err != nil { + return xerrors.Errorf("unmarshal error: %w", err) + } + return xerrors.Errorf("%+v", r) + default: + return xerrors.Errorf("unexpected event type: %s", sse.Type) } - ch <- met } } + } + + errorChan := make(chan error, 1) + go func() { + defer close(errorChan) + errorChan <- watch() }() - return ch, nil + return metadataChan, errorChan } // WorkspaceAgent returns an agent by ID. From e4a5dd18bd0af3e8654b0ad9de6b9620b3c0cf66 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 22 Mar 2023 16:22:14 +0000 Subject: [PATCH 13/73] WIP DB refinement --- agent/agent.go | 2 +- coderd/database/dbfake/databasefake.go | 2 ++ coderd/database/dump.sql | 2 ++ .../000110_workspace_agent_metadata.up.sql | 2 ++ coderd/database/models.go | 2 ++ coderd/database/queries.sql.go | 18 ++++++++++++++---- coderd/database/queries/workspaceagents.sql | 9 ++++++--- coderd/workspaceagents.go | 3 +-- coderd/workspaceagents_test.go | 9 +++++++++ codersdk/agentsdk/agentsdk.go | 2 +- 10 files changed, 40 insertions(+), 11 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 908cb16507dcf..14b916d79bd55 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -408,7 +408,7 @@ func (a *agent) run(ctx context.Context) error { if err != nil { return xerrors.Errorf("fetch metadata: %w", err) } - a.logger.Info(ctx, "fetched metadata", slog.F("metadata", manifest)) + a.logger.Info(ctx, "fetched manifest", slog.F("manifest", manifest)) // Expand the directory and send it back to coderd so external // applications that rely on the directory can use it. diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 265db8c2fbf21..0eb14ea8c7ef1 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2682,6 +2682,8 @@ func (q *fakeQuerier) InsertOrUpdateWorkspaceAgentMetadata(_ context.Context, ar Value: arg.Value, Error: arg.Error, CollectedAt: arg.CollectedAt, + Timeout: arg.Timeout, + Interval: arg.Interval, } for i, m := range q.workspaceAgentMetadata { diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index d48535e743ad2..f8c29efb87387 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -481,6 +481,8 @@ CREATE TABLE workspace_agent_metadata ( key character varying(128) NOT NULL, value text NOT NULL, error text NOT NULL, + timeout bigint NOT NULL, + "interval" bigint NOT NULL, collected_at timestamp with time zone NOT NULL ); diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql index 274fee1c16637..df7a3ba207ef2 100644 --- a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql @@ -6,6 +6,8 @@ CREATE TABLE workspace_agent_metadata ( key character varying(128) NOT NULL, value text NOT NULL, error text NOT NULL, + timeout bigint NOT NULL, + interval bigint NOT NULL, collected_at timestamp with time zone NOT NULL, PRIMARY KEY (workspace_agent_id, key), FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE, diff --git a/coderd/database/models.go b/coderd/database/models.go index e09f903edeb2b..0fa23716dfbf2 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1577,6 +1577,8 @@ type WorkspaceAgentMetadatum struct { Key string `db:"key" json:"key"` Value string `db:"value" json:"value"` Error string `db:"error" json:"error"` + Timeout int64 `db:"timeout" json:"timeout"` + Interval int64 `db:"interval" json:"interval"` CollectedAt time.Time `db:"collected_at" json:"collected_at"` } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 7c5c147f47b19..efed1963774a9 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5193,7 +5193,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many SELECT - workspace_id, workspace_agent_id, key, value, error, collected_at + workspace_id, workspace_agent_id, key, value, error, timeout, interval, collected_at FROM workspace_agent_metadata WHERE @@ -5215,6 +5215,8 @@ func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAge &i.Key, &i.Value, &i.Error, + &i.Timeout, + &i.Interval, &i.CollectedAt, ); err != nil { return nil, err @@ -5355,14 +5357,18 @@ INSERT INTO key, value, error, - collected_at + collected_at, + timeout, + interval ) VALUES - ($1, $2, $3, $4, $5, $6) + ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (workspace_agent_id, key) DO UPDATE SET value = $4, error = $5, - collected_at = $6 + collected_at = $6, + timeout = $7, + interval = $8 ` type InsertOrUpdateWorkspaceAgentMetadataParams struct { @@ -5372,6 +5378,8 @@ type InsertOrUpdateWorkspaceAgentMetadataParams struct { Value string `db:"value" json:"value"` Error string `db:"error" json:"error"` CollectedAt time.Time `db:"collected_at" json:"collected_at"` + Timeout int64 `db:"timeout" json:"timeout"` + Interval int64 `db:"interval" json:"interval"` } func (q *sqlQuerier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg InsertOrUpdateWorkspaceAgentMetadataParams) error { @@ -5382,6 +5390,8 @@ func (q *sqlQuerier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, a arg.Value, arg.Error, arg.CollectedAt, + arg.Timeout, + arg.Interval, ) return err } diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index f668e531cb789..06a37b914e624 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -102,14 +102,17 @@ INSERT INTO key, value, error, - collected_at + collected_at, + timeout, + interval ) VALUES - ($1, $2, $3, $4, $5, $6) + ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (workspace_agent_id, key) DO UPDATE SET value = $4, error = $5, - collected_at = $6; + collected_at = $6, + -- interval and timeout are set once -- name: GetWorkspaceAgentMetadata :many SELECT diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 60a61ac73297b..d27e502f477bc 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1098,9 +1098,8 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque } // @Summary Watch for workspace agent metadata updates -// @ID watch-workspace-agent-metadata +// @ID watch-for-workspace-agent-metadata-updates // @Security CoderSessionToken -// @Accept json // @Tags Agents // @Success 200 "Success" // @Param workspaceagent path string true "Workspace agent ID" format(uuid) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index e4721f3ba6783..cdb64205d02e1 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1159,6 +1159,15 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { Name: "example", Type: "aws_instance", Agents: []*proto.Agent{{ + Metadata: []*proto.Agent_Metadata{ + { + DisplayName: "First Meta", + Key: "m1", + Cmd: []string{"echo", "hi"}, + Interval: 10, + Timeout: 3, + }, + }, Id: uuid.NewString(), Auth: &proto.Agent_Token{ Token: authToken, diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index e16356700a276..d3342a30165c1 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -108,7 +108,7 @@ type Manifest struct { MOTDFile string `json:"motd_file"` ShutdownScript string `json:"shutdown_script"` ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"` - Metadata []MetadataDescription `json:"dynamic_metadata"` + Metadata []MetadataDescription `json:"metadata"` } // Manifest fetches manifest for the currently authenticated workspace agent. From 5ef56711643cb5e4ed58e36a9fbf73323117ec79 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 22 Mar 2023 20:55:24 +0000 Subject: [PATCH 14/73] Upsert --- coderd/database/dbauthz/querier.go | 12 +++++----- coderd/database/dbauthz/querier_test.go | 8 +++---- coderd/database/dbauthz/system.go | 4 ++-- coderd/database/dbauthz/system_test.go | 4 ++-- coderd/database/dbfake/databasefake.go | 8 +++---- coderd/database/querier.go | 8 +++---- coderd/database/queries.sql.go | 26 ++++++++++----------- coderd/database/queries/siteconfig.sql | 6 ++--- coderd/database/queries/workspaceagents.sql | 2 +- coderd/updatecheck/updatecheck.go | 2 +- coderd/workspaceagents.go | 4 ++-- enterprise/coderd/appearance.go | 4 ++-- 12 files changed, 44 insertions(+), 44 deletions(-) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 8af3b630e6f9a..5b5b0fa15e0b4 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -286,18 +286,18 @@ func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseP return q.db.InsertLicense(ctx, arg) } -func (q *querier) InsertOrUpdateLogoURL(ctx context.Context, value string) error { +func (q *querier) UpsertLogoURL(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } - return q.db.InsertOrUpdateLogoURL(ctx, value) + return q.db.UpsertLogoURL(ctx, value) } -func (q *querier) InsertOrUpdateServiceBanner(ctx context.Context, value string) error { +func (q *querier) UpsertServiceBanner(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } - return q.db.InsertOrUpdateServiceBanner(ctx, value) + return q.db.UpsertServiceBanner(ctx, value) } func (q *querier) GetLicenseByID(ctx context.Context, id int32) (database.License, error) { @@ -1472,7 +1472,7 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins return q.db.InsertWorkspaceAgentStat(ctx, arg) } -func (q *querier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg database.InsertOrUpdateWorkspaceAgentMetadataParams) error { +func (q *querier) UpsertWorkspaceAgentMetadata(ctx context.Context, arg database.UpsertWorkspaceAgentMetadataParams) error { workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) if err != nil { return err @@ -1483,7 +1483,7 @@ func (q *querier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg return err } - return q.db.InsertOrUpdateWorkspaceAgentMetadata(ctx, arg) + return q.db.UpsertWorkspaceAgentMetadata(ctx, arg) } func (q *querier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { diff --git a/coderd/database/dbauthz/querier_test.go b/coderd/database/dbauthz/querier_test.go index cb746cb81d514..42fe652a917bf 100644 --- a/coderd/database/dbauthz/querier_test.go +++ b/coderd/database/dbauthz/querier_test.go @@ -307,10 +307,10 @@ func (s *MethodTestSuite) TestLicense() { check.Args(database.InsertLicenseParams{}). Asserts(rbac.ResourceLicense, rbac.ActionCreate) })) - s.Run("InsertOrUpdateLogoURL", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpsertLogoURL", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) - s.Run("InsertOrUpdateServiceBanner", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpsertServiceBanner", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) s.Run("GetLicenseByID", s.Subtest(func(db database.Store, check *expects) { @@ -331,12 +331,12 @@ func (s *MethodTestSuite) TestLicense() { check.Args().Asserts().Returns("") })) s.Run("GetLogoURL", s.Subtest(func(db database.Store, check *expects) { - err := db.InsertOrUpdateLogoURL(context.Background(), "value") + err := db.UpsertLogoURL(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts().Returns("value") })) s.Run("GetServiceBanner", s.Subtest(func(db database.Store, check *expects) { - err := db.InsertOrUpdateServiceBanner(context.Background(), "value") + err := db.UpsertServiceBanner(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts().Returns("value") })) diff --git a/coderd/database/dbauthz/system.go b/coderd/database/dbauthz/system.go index d46aff267dc75..645e1826be013 100644 --- a/coderd/database/dbauthz/system.go +++ b/coderd/database/dbauthz/system.go @@ -221,11 +221,11 @@ func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database return q.db.UpdateWorkspaceBuildCostByID(ctx, arg) } -func (q *querier) InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error { +func (q *querier) UpsertLastUpdateCheck(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil { return err } - return q.db.InsertOrUpdateLastUpdateCheck(ctx, value) + return q.db.UpsertLastUpdateCheck(ctx, value) } func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) { diff --git a/coderd/database/dbauthz/system_test.go b/coderd/database/dbauthz/system_test.go index 4ce8d6a8a1066..65cf9d2743605 100644 --- a/coderd/database/dbauthz/system_test.go +++ b/coderd/database/dbauthz/system_test.go @@ -102,11 +102,11 @@ func (s *MethodTestSuite) TestSystemFunctions() { DailyCost: 10, }).Asserts(rbac.ResourceSystem, rbac.ActionUpdate).Returns(o) })) - s.Run("InsertOrUpdateLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpsertLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionUpdate) })) s.Run("GetLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { - err := db.InsertOrUpdateLastUpdateCheck(context.Background(), "value") + err := db.UpsertLastUpdateCheck(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead) })) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 0eb14ea8c7ef1..101b556594a8e 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2670,7 +2670,7 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP return key, nil } -func (q *fakeQuerier) InsertOrUpdateWorkspaceAgentMetadata(_ context.Context, arg database.InsertOrUpdateWorkspaceAgentMetadataParams) error { +func (q *fakeQuerier) UpsertWorkspaceAgentMetadata(_ context.Context, arg database.UpsertWorkspaceAgentMetadataParams) error { q.mutex.Lock() defer q.mutex.Unlock() @@ -4223,7 +4223,7 @@ func (q *fakeQuerier) GetDERPMeshKey(_ context.Context) (string, error) { return q.derpMeshKey, nil } -func (q *fakeQuerier) InsertOrUpdateLastUpdateCheck(_ context.Context, data string) error { +func (q *fakeQuerier) UpsertLastUpdateCheck(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() @@ -4241,7 +4241,7 @@ func (q *fakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) { return string(q.lastUpdateCheck), nil } -func (q *fakeQuerier) InsertOrUpdateServiceBanner(_ context.Context, data string) error { +func (q *fakeQuerier) UpsertServiceBanner(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() @@ -4260,7 +4260,7 @@ func (q *fakeQuerier) GetServiceBanner(_ context.Context) (string, error) { return string(q.serviceBanner), nil } -func (q *fakeQuerier) InsertOrUpdateLogoURL(_ context.Context, data string) error { +func (q *fakeQuerier) UpsertLogoURL(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index e36c1583f4365..71385e98bfffc 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -161,10 +161,10 @@ type sqlcQuerier interface { InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error) InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error InsertLicense(ctx context.Context, arg InsertLicenseParams) (License, error) - InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error - InsertOrUpdateLogoURL(ctx context.Context, value string) error - InsertOrUpdateServiceBanner(ctx context.Context, value string) error - InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg InsertOrUpdateWorkspaceAgentMetadataParams) error + UpsertLastUpdateCheck(ctx context.Context, value string) error + UpsertLogoURL(ctx context.Context, value string) error + UpsertServiceBanner(ctx context.Context, value string) error + UpsertWorkspaceAgentMetadata(ctx context.Context, arg UpsertWorkspaceAgentMetadataParams) error InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error) InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error) InsertParameterSchema(ctx context.Context, arg InsertParameterSchemaParams) (ParameterSchema, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index efed1963774a9..0fe31334cd449 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3036,33 +3036,33 @@ func (q *sqlQuerier) InsertDeploymentID(ctx context.Context, value string) error return err } -const insertOrUpdateLastUpdateCheck = `-- name: InsertOrUpdateLastUpdateCheck :exec +const UpsertLastUpdateCheck = `-- name: UpsertLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check' ` -func (q *sqlQuerier) InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, insertOrUpdateLastUpdateCheck, value) +func (q *sqlQuerier) UpsertLastUpdateCheck(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, UpsertLastUpdateCheck, value) return err } -const insertOrUpdateLogoURL = `-- name: InsertOrUpdateLogoURL :exec +const UpsertLogoURL = `-- name: UpsertLogoURL :exec INSERT INTO site_configs (key, value) VALUES ('logo_url', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url' ` -func (q *sqlQuerier) InsertOrUpdateLogoURL(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, insertOrUpdateLogoURL, value) +func (q *sqlQuerier) UpsertLogoURL(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, UpsertLogoURL, value) return err } -const insertOrUpdateServiceBanner = `-- name: InsertOrUpdateServiceBanner :exec +const UpsertServiceBanner = `-- name: UpsertServiceBanner :exec INSERT INTO site_configs (key, value) VALUES ('service_banner', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner' ` -func (q *sqlQuerier) InsertOrUpdateServiceBanner(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, insertOrUpdateServiceBanner, value) +func (q *sqlQuerier) UpsertServiceBanner(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, UpsertServiceBanner, value) return err } @@ -5349,7 +5349,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created return items, nil } -const insertOrUpdateWorkspaceAgentMetadata = `-- name: InsertOrUpdateWorkspaceAgentMetadata :exec +const UpsertWorkspaceAgentMetadata = `-- name: UpsertWorkspaceAgentMetadata :exec INSERT INTO workspace_agent_metadata ( workspace_id, @@ -5371,7 +5371,7 @@ ON CONFLICT (workspace_agent_id, key) DO UPDATE SET interval = $8 ` -type InsertOrUpdateWorkspaceAgentMetadataParams struct { +type UpsertWorkspaceAgentMetadataParams struct { WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` Key string `db:"key" json:"key"` @@ -5382,8 +5382,8 @@ type InsertOrUpdateWorkspaceAgentMetadataParams struct { Interval int64 `db:"interval" json:"interval"` } -func (q *sqlQuerier) InsertOrUpdateWorkspaceAgentMetadata(ctx context.Context, arg InsertOrUpdateWorkspaceAgentMetadataParams) error { - _, err := q.db.ExecContext(ctx, insertOrUpdateWorkspaceAgentMetadata, +func (q *sqlQuerier) UpsertWorkspaceAgentMetadata(ctx context.Context, arg UpsertWorkspaceAgentMetadataParams) error { + _, err := q.db.ExecContext(ctx, UpsertWorkspaceAgentMetadata, arg.WorkspaceID, arg.WorkspaceAgentID, arg.Key, diff --git a/coderd/database/queries/siteconfig.sql b/coderd/database/queries/siteconfig.sql index a2b5427f70ad1..9f2e5042abdf3 100644 --- a/coderd/database/queries/siteconfig.sql +++ b/coderd/database/queries/siteconfig.sql @@ -10,21 +10,21 @@ INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1); -- name: GetDERPMeshKey :one SELECT value FROM site_configs WHERE key = 'derp_mesh_key'; --- name: InsertOrUpdateLastUpdateCheck :exec +-- name: UpsertLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check'; -- name: GetLastUpdateCheck :one SELECT value FROM site_configs WHERE key = 'last_update_check'; --- name: InsertOrUpdateServiceBanner :exec +-- name: UpsertServiceBanner :exec INSERT INTO site_configs (key, value) VALUES ('service_banner', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner'; -- name: GetServiceBanner :one SELECT value FROM site_configs WHERE key = 'service_banner'; --- name: InsertOrUpdateLogoURL :exec +-- name: UpsertLogoURL :exec INSERT INTO site_configs (key, value) VALUES ('logo_url', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url'; diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 06a37b914e624..f8d0b6209b690 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -94,7 +94,7 @@ SET WHERE id = $1; --- name: InsertOrUpdateWorkspaceAgentMetadata :exec +-- name: UpsertWorkspaceAgentMetadata :exec INSERT INTO workspace_agent_metadata ( workspace_id, diff --git a/coderd/updatecheck/updatecheck.go b/coderd/updatecheck/updatecheck.go index 0f5d2ce913d36..e18bc5e5ccd77 100644 --- a/coderd/updatecheck/updatecheck.go +++ b/coderd/updatecheck/updatecheck.go @@ -211,7 +211,7 @@ func (c *Checker) update() (r Result, err error) { } // nolint:gocritic // Inserting the last update check is a system function. - err = c.db.InsertOrUpdateLastUpdateCheck(dbauthz.AsSystemRestricted(ctx), string(b)) + err = c.db.UpsertLastUpdateCheck(dbauthz.AsSystemRestricted(ctx), string(b)) if err != nil { return r, err } diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index d27e502f477bc..5002d43b33b8c 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1064,7 +1064,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque return } - datum := database.InsertOrUpdateWorkspaceAgentMetadataParams{ + datum := database.UpsertWorkspaceAgentMetadataParams{ WorkspaceID: workspace.ID, WorkspaceAgentID: workspaceAgent.ID, // We don't want a misconfigured agent to fill the database. @@ -1074,7 +1074,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque CollectedAt: req.CollectedAt, } - err = api.Database.InsertOrUpdateWorkspaceAgentMetadata(ctx, datum) + err = api.Database.UpsertWorkspaceAgentMetadata(ctx, datum) if err != nil { httpapi.InternalServerError(rw, err) return diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go index a784b678ea2d8..6f8ef8500e21f 100644 --- a/enterprise/coderd/appearance.go +++ b/enterprise/coderd/appearance.go @@ -148,7 +148,7 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.InsertOrUpdateServiceBanner(ctx, string(serviceBannerJSON)) + err = api.Database.UpsertServiceBanner(ctx, string(serviceBannerJSON)) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("database error: %+v", err), @@ -156,7 +156,7 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.InsertOrUpdateLogoURL(ctx, appearance.LogoURL) + err = api.Database.UpsertLogoURL(ctx, appearance.LogoURL) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("database error: %+v", err), From 34935c504355899cadccb86f72699d451261c470 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 22 Mar 2023 21:18:06 +0000 Subject: [PATCH 15/73] Correctly insert metadata into db --- coderd/database/dbauthz/querier.go | 14 +- coderd/database/dbfake/databasefake.go | 22 +++- coderd/database/dump.sql | 4 - .../000110_workspace_agent_metadata.up.sql | 4 +- coderd/database/models.go | 1 - coderd/database/querier.go | 9 +- coderd/database/queries.sql.go | 122 ++++++++++-------- coderd/database/queries/workspaceagents.sql | 24 ++-- .../provisionerdserver/provisionerdserver.go | 12 ++ coderd/workspaceagents.go | 5 +- coderd/workspaceagents_test.go | 22 +++- 11 files changed, 146 insertions(+), 93 deletions(-) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 5b5b0fa15e0b4..f6c4a6ac56d70 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1472,18 +1472,26 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins return q.db.InsertWorkspaceAgentStat(ctx, arg) } -func (q *querier) UpsertWorkspaceAgentMetadata(ctx context.Context, arg database.UpsertWorkspaceAgentMetadataParams) error { - workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) +func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { + workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) + + err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) if err != nil { return err } + return q.db.InsertWorkspaceAgentMetadata(ctx, arg) +} + +func (q *querier) UpdateWorkspaceAgentMetadata(ctx context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { + workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) + err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) if err != nil { return err } - return q.db.UpsertWorkspaceAgentMetadata(ctx, arg) + return q.db.UpdateWorkspaceAgentMetadata(ctx, arg) } func (q *querier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 101b556594a8e..5d023c01f5490 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2670,20 +2670,17 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP return key, nil } -func (q *fakeQuerier) UpsertWorkspaceAgentMetadata(_ context.Context, arg database.UpsertWorkspaceAgentMetadataParams) error { +func (q *fakeQuerier) UpdateWorkspaceAgentMetadata(_ context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { q.mutex.Lock() defer q.mutex.Unlock() //nolint:gosimple updated := database.WorkspaceAgentMetadatum{ - WorkspaceID: arg.WorkspaceID, WorkspaceAgentID: arg.WorkspaceAgentID, Key: arg.Key, Value: arg.Value, Error: arg.Error, CollectedAt: arg.CollectedAt, - Timeout: arg.Timeout, - Interval: arg.Interval, } for i, m := range q.workspaceAgentMetadata { @@ -2693,7 +2690,22 @@ func (q *fakeQuerier) UpsertWorkspaceAgentMetadata(_ context.Context, arg databa } } - q.workspaceAgentMetadata = append(q.workspaceAgentMetadata, updated) + return sql.ErrNoRows +} + +func (q *fakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { + q.mutex.Lock() + defer q.mutex.Unlock() + + //nolint:gosimple + metadatum := database.WorkspaceAgentMetadatum{ + WorkspaceAgentID: arg.WorkspaceAgentID, + Key: arg.Key, + Timeout: arg.Timeout, + Interval: arg.Interval, + } + + q.workspaceAgentMetadata = append(q.workspaceAgentMetadata, metadatum) return nil } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f8c29efb87387..4baa3fd2176f9 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -476,7 +476,6 @@ CREATE TABLE users ( ); CREATE TABLE workspace_agent_metadata ( - workspace_id uuid NOT NULL, workspace_agent_id uuid NOT NULL, key character varying(128) NOT NULL, value text NOT NULL, @@ -881,9 +880,6 @@ ALTER TABLE ONLY user_links ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; -ALTER TABLE ONLY workspace_agent_metadata - ADD CONSTRAINT workspace_agent_metadata_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE; - ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql index df7a3ba207ef2..a39d7bfc594aa 100644 --- a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql @@ -1,7 +1,6 @@ -- TODO: Do we need an index for workspace_agent_id or is the multi-column PRIMARY -- key enough? CREATE TABLE workspace_agent_metadata ( - workspace_id uuid NOT NULL, workspace_agent_id uuid NOT NULL, key character varying(128) NOT NULL, value text NOT NULL, @@ -10,6 +9,5 @@ CREATE TABLE workspace_agent_metadata ( interval bigint NOT NULL, collected_at timestamp with time zone NOT NULL, PRIMARY KEY (workspace_agent_id, key), - FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE, - FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE + FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE ); diff --git a/coderd/database/models.go b/coderd/database/models.go index 0fa23716dfbf2..3dfabacd8d8d6 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1572,7 +1572,6 @@ type WorkspaceAgent struct { } type WorkspaceAgentMetadatum struct { - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` Key string `db:"key" json:"key"` Value string `db:"value" json:"value"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 71385e98bfffc..428e6b62ff301 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -161,10 +161,6 @@ type sqlcQuerier interface { InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error) InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error InsertLicense(ctx context.Context, arg InsertLicenseParams) (License, error) - UpsertLastUpdateCheck(ctx context.Context, value string) error - UpsertLogoURL(ctx context.Context, value string) error - UpsertServiceBanner(ctx context.Context, value string) error - UpsertWorkspaceAgentMetadata(ctx context.Context, arg UpsertWorkspaceAgentMetadataParams) error InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error) InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error) InsertParameterSchema(ctx context.Context, arg InsertParameterSchemaParams) (ParameterSchema, error) @@ -183,6 +179,7 @@ type sqlcQuerier interface { InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) + InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error InsertWorkspaceAgentStat(ctx context.Context, arg InsertWorkspaceAgentStatParams) (WorkspaceAgentStat, error) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuild, error) @@ -226,6 +223,7 @@ type sqlcQuerier interface { UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error + UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error @@ -235,6 +233,9 @@ type sqlcQuerier interface { UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error UpdateWorkspaceTTLToBeWithinTemplateMax(ctx context.Context, arg UpdateWorkspaceTTLToBeWithinTemplateMaxParams) error + UpsertLastUpdateCheck(ctx context.Context, value string) error + UpsertLogoURL(ctx context.Context, value string) error + UpsertServiceBanner(ctx context.Context, value string) error } var _ sqlcQuerier = (*sqlQuerier)(nil) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 0fe31334cd449..a0831768203c1 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3036,33 +3036,33 @@ func (q *sqlQuerier) InsertDeploymentID(ctx context.Context, value string) error return err } -const UpsertLastUpdateCheck = `-- name: UpsertLastUpdateCheck :exec +const upsertLastUpdateCheck = `-- name: UpsertLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check' ` func (q *sqlQuerier) UpsertLastUpdateCheck(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, UpsertLastUpdateCheck, value) + _, err := q.db.ExecContext(ctx, upsertLastUpdateCheck, value) return err } -const UpsertLogoURL = `-- name: UpsertLogoURL :exec +const upsertLogoURL = `-- name: UpsertLogoURL :exec INSERT INTO site_configs (key, value) VALUES ('logo_url', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url' ` func (q *sqlQuerier) UpsertLogoURL(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, UpsertLogoURL, value) + _, err := q.db.ExecContext(ctx, upsertLogoURL, value) return err } -const UpsertServiceBanner = `-- name: UpsertServiceBanner :exec +const upsertServiceBanner = `-- name: UpsertServiceBanner :exec INSERT INTO site_configs (key, value) VALUES ('service_banner', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner' ` func (q *sqlQuerier) UpsertServiceBanner(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, UpsertServiceBanner, value) + _, err := q.db.ExecContext(ctx, upsertServiceBanner, value) return err } @@ -5193,7 +5193,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many SELECT - workspace_id, workspace_agent_id, key, value, error, timeout, interval, collected_at + workspace_agent_id, key, value, error, timeout, interval, collected_at FROM workspace_agent_metadata WHERE @@ -5210,7 +5210,6 @@ func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAge for rows.Next() { var i WorkspaceAgentMetadatum if err := rows.Scan( - &i.WorkspaceID, &i.WorkspaceAgentID, &i.Key, &i.Value, @@ -5349,53 +5348,6 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created return items, nil } -const UpsertWorkspaceAgentMetadata = `-- name: UpsertWorkspaceAgentMetadata :exec -INSERT INTO - workspace_agent_metadata ( - workspace_id, - workspace_agent_id, - key, - value, - error, - collected_at, - timeout, - interval - ) -VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) -ON CONFLICT (workspace_agent_id, key) DO UPDATE SET - value = $4, - error = $5, - collected_at = $6, - timeout = $7, - interval = $8 -` - -type UpsertWorkspaceAgentMetadataParams struct { - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` - WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` - Key string `db:"key" json:"key"` - Value string `db:"value" json:"value"` - Error string `db:"error" json:"error"` - CollectedAt time.Time `db:"collected_at" json:"collected_at"` - Timeout int64 `db:"timeout" json:"timeout"` - Interval int64 `db:"interval" json:"interval"` -} - -func (q *sqlQuerier) UpsertWorkspaceAgentMetadata(ctx context.Context, arg UpsertWorkspaceAgentMetadataParams) error { - _, err := q.db.ExecContext(ctx, UpsertWorkspaceAgentMetadata, - arg.WorkspaceID, - arg.WorkspaceAgentID, - arg.Key, - arg.Value, - arg.Error, - arg.CollectedAt, - arg.Timeout, - arg.Interval, - ) - return err -} - const insertWorkspaceAgent = `-- name: InsertWorkspaceAgent :one INSERT INTO workspace_agents ( @@ -5507,6 +5459,35 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa return i, err } +const insertWorkspaceAgentMetadata = `-- name: InsertWorkspaceAgentMetadata :exec +INSERT INTO + workspace_agent_metadata ( + workspace_agent_id, + key, + timeout, + interval + ) +VALUES + ($1, $2, $3, $4) +` + +type InsertWorkspaceAgentMetadataParams struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + Key string `db:"key" json:"key"` + Timeout int64 `db:"timeout" json:"timeout"` + Interval int64 `db:"interval" json:"interval"` +} + +func (q *sqlQuerier) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error { + _, err := q.db.ExecContext(ctx, insertWorkspaceAgentMetadata, + arg.WorkspaceAgentID, + arg.Key, + arg.Timeout, + arg.Interval, + ) + return err +} + const updateWorkspaceAgentConnectionByID = `-- name: UpdateWorkspaceAgentConnectionByID :exec UPDATE workspace_agents @@ -5560,6 +5541,37 @@ func (q *sqlQuerier) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, return err } +const updateWorkspaceAgentMetadata = `-- name: UpdateWorkspaceAgentMetadata :exec +UPDATE + workspace_agent_metadata +SET + value = $3, + error = $4, + collected_at = $5 +WHERE + workspace_agent_id = $1 + AND key = $2 +` + +type UpdateWorkspaceAgentMetadataParams struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + Key string `db:"key" json:"key"` + Value string `db:"value" json:"value"` + Error string `db:"error" json:"error"` + CollectedAt time.Time `db:"collected_at" json:"collected_at"` +} + +func (q *sqlQuerier) UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error { + _, err := q.db.ExecContext(ctx, updateWorkspaceAgentMetadata, + arg.WorkspaceAgentID, + arg.Key, + arg.Value, + arg.Error, + arg.CollectedAt, + ) + return err +} + const updateWorkspaceAgentStartupByID = `-- name: UpdateWorkspaceAgentStartupByID :exec UPDATE workspace_agents diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index f8d0b6209b690..198a10a165ce7 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -94,25 +94,27 @@ SET WHERE id = $1; --- name: UpsertWorkspaceAgentMetadata :exec +-- name: InsertWorkspaceAgentMetadata :exec INSERT INTO workspace_agent_metadata ( - workspace_id, workspace_agent_id, key, - value, - error, - collected_at, timeout, interval ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) -ON CONFLICT (workspace_agent_id, key) DO UPDATE SET - value = $4, - error = $5, - collected_at = $6, - -- interval and timeout are set once + ($1, $2, $3, $4); + +-- name: UpdateWorkspaceAgentMetadata :exec +UPDATE + workspace_agent_metadata +SET + value = $3, + error = $4, + collected_at = $5 +WHERE + workspace_agent_id = $1 + AND key = $2; -- name: GetWorkspaceAgentMetadata :many SELECT diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index cd8c9005065b6..eb8d8e68f29dd 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1226,6 +1226,18 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. } snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent)) + for _, md := range prAgent.Metadata { + err := db.InsertWorkspaceAgentMetadata(ctx, database.InsertWorkspaceAgentMetadataParams{ + WorkspaceAgentID: agentID, + Key: md.Key, + Timeout: md.Timeout, + Interval: md.Interval, + }) + if err != nil { + return xerrors.Errorf("insert agent metadata: %w", err) + } + } + for _, app := range prAgent.Apps { slug := app.Slug if slug == "" { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 5002d43b33b8c..4f1c4d8810a36 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1064,8 +1064,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque return } - datum := database.UpsertWorkspaceAgentMetadataParams{ - WorkspaceID: workspace.ID, + datum := database.UpdateWorkspaceAgentMetadataParams{ WorkspaceAgentID: workspaceAgent.ID, // We don't want a misconfigured agent to fill the database. Key: ellipse(req.Key, 128), @@ -1074,7 +1073,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque CollectedAt: req.CollectedAt, } - err = api.Database.UpsertWorkspaceAgentMetadata(ctx, datum) + err = api.Database.UpdateWorkspaceAgentMetadata(ctx, datum) if err != nil { httpapi.InternalServerError(rw, err) return diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index cdb64205d02e1..ea9f556d53fbe 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1162,7 +1162,14 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { Metadata: []*proto.Agent_Metadata{ { DisplayName: "First Meta", - Key: "m1", + Key: "foo1", + Cmd: []string{"echo", "hi"}, + Interval: 10, + Timeout: 3, + }, + { + DisplayName: "Second Meta", + Key: "foo2", Cmd: []string{"echo", "hi"}, Interval: 10, Timeout: 3, @@ -1216,7 +1223,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { wantMetadata1 := codersdk.WorkspaceAgentMetadataResult{ CollectedAt: time.Now(), - Key: "foo", + Key: "foo1", Value: "bar", } @@ -1236,11 +1243,13 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { } update = recvUpdate() - require.Len(t, update, 1) + require.Len(t, update, 2) check(wantMetadata1, update[0]) + // The second metadata result is not yet posted. + require.Zero(t, update[1].CollectedAt) wantMetadata2 := wantMetadata1 - wantMetadata2.Key = wantMetadata1.Key + "2" + wantMetadata2.Key = "foo2" post(wantMetadata2) update = recvUpdate() require.Len(t, update, 2) @@ -1252,4 +1261,9 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { update = recvUpdate() require.Len(t, update, 2) check(wantMetadata1, update[0]) + + badMetadata := wantMetadata1 + badMetadata.Key = "unknown" + err = agentClient.PostMetadata(ctx, badMetadata) + require.Error(t, err) } From 555ee669a98d83fb11d7c91594fef72690f9a86f Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 22 Mar 2023 21:30:13 +0000 Subject: [PATCH 16/73] Return complete manifest --- coderd/database/dbfake/databasefake.go | 2 ++ coderd/database/dump.sql | 2 ++ .../000110_workspace_agent_metadata.up.sql | 2 ++ coderd/database/models.go | 2 ++ coderd/database/queries.sql.go | 12 ++++++-- coderd/database/queries/workspaceagents.sql | 4 ++- .../provisionerdserver/provisionerdserver.go | 2 ++ coderd/workspaceagents.go | 29 +++++++++++++++++-- coderd/workspaceagents_test.go | 16 ++++++++-- codersdk/agentsdk/agentsdk.go | 9 +++--- 10 files changed, 69 insertions(+), 11 deletions(-) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 5d023c01f5490..bba4515aafff9 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2700,6 +2700,8 @@ func (q *fakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg databa //nolint:gosimple metadatum := database.WorkspaceAgentMetadatum{ WorkspaceAgentID: arg.WorkspaceAgentID, + Cmd: arg.Cmd, + DisplayName: arg.DisplayName, Key: arg.Key, Timeout: arg.Timeout, Interval: arg.Interval, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 4baa3fd2176f9..a45d8d915f530 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -477,7 +477,9 @@ CREATE TABLE users ( CREATE TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, + display_name text NOT NULL, key character varying(128) NOT NULL, + cmd text[] NOT NULL, value text NOT NULL, error text NOT NULL, timeout bigint NOT NULL, diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql index a39d7bfc594aa..eb6abe253a5bd 100644 --- a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql @@ -2,7 +2,9 @@ -- key enough? CREATE TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, + display_name text NOT NULL, key character varying(128) NOT NULL, + cmd text[] NOT NULL, value text NOT NULL, error text NOT NULL, timeout bigint NOT NULL, diff --git a/coderd/database/models.go b/coderd/database/models.go index 3dfabacd8d8d6..fb1dd3c6505ab 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1573,7 +1573,9 @@ type WorkspaceAgent struct { type WorkspaceAgentMetadatum struct { WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + DisplayName string `db:"display_name" json:"display_name"` Key string `db:"key" json:"key"` + Cmd []string `db:"cmd" json:"cmd"` Value string `db:"value" json:"value"` Error string `db:"error" json:"error"` Timeout int64 `db:"timeout" json:"timeout"` diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index a0831768203c1..337d6ea719769 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5193,7 +5193,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many SELECT - workspace_agent_id, key, value, error, timeout, interval, collected_at + workspace_agent_id, display_name, key, cmd, value, error, timeout, interval, collected_at FROM workspace_agent_metadata WHERE @@ -5211,7 +5211,9 @@ func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAge var i WorkspaceAgentMetadatum if err := rows.Scan( &i.WorkspaceAgentID, + &i.DisplayName, &i.Key, + pq.Array(&i.Cmd), &i.Value, &i.Error, &i.Timeout, @@ -5463,17 +5465,21 @@ const insertWorkspaceAgentMetadata = `-- name: InsertWorkspaceAgentMetadata :exe INSERT INTO workspace_agent_metadata ( workspace_agent_id, + display_name, key, + cmd, timeout, interval ) VALUES - ($1, $2, $3, $4) + ($1, $2, $3, $4, $5, $6) ` type InsertWorkspaceAgentMetadataParams struct { WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + DisplayName string `db:"display_name" json:"display_name"` Key string `db:"key" json:"key"` + Cmd []string `db:"cmd" json:"cmd"` Timeout int64 `db:"timeout" json:"timeout"` Interval int64 `db:"interval" json:"interval"` } @@ -5481,7 +5487,9 @@ type InsertWorkspaceAgentMetadataParams struct { func (q *sqlQuerier) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error { _, err := q.db.ExecContext(ctx, insertWorkspaceAgentMetadata, arg.WorkspaceAgentID, + arg.DisplayName, arg.Key, + pq.Array(arg.Cmd), arg.Timeout, arg.Interval, ) diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 198a10a165ce7..27e6f1c60547b 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -98,12 +98,14 @@ WHERE INSERT INTO workspace_agent_metadata ( workspace_agent_id, + display_name, key, + cmd, timeout, interval ) VALUES - ($1, $2, $3, $4); + ($1, $2, $3, $4, $5, $6); -- name: UpdateWorkspaceAgentMetadata :exec UPDATE diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index eb8d8e68f29dd..86461e25bd0f8 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1229,6 +1229,8 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. for _, md := range prAgent.Metadata { err := db.InsertWorkspaceAgentMetadata(ctx, database.InsertWorkspaceAgentMetadataParams{ WorkspaceAgentID: agentID, + DisplayName: md.DisplayName, + Cmd: md.Cmd, Key: md.Key, Timeout: md.Timeout, Interval: md.Interval, diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 4f1c4d8810a36..d47b6654eb4b8 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -105,6 +105,16 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) }) return } + + metadata, err := api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace agent metadata.", + Detail: err.Error(), + }) + return + } + resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ @@ -161,6 +171,7 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) StartupScriptTimeout: time.Duration(apiAgent.StartupScriptTimeoutSeconds) * time.Second, ShutdownScript: apiAgent.ShutdownScript, ShutdownScriptTimeout: time.Duration(apiAgent.ShutdownScriptTimeoutSeconds) * time.Second, + Metadata: convertWorkspaceAgentMetadataDesc(metadata), }) } @@ -832,6 +843,20 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { return apps } +func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) []agentsdk.MetadataDescription { + metadata := make([]agentsdk.MetadataDescription, 0) + for _, datum := range mds { + metadata = append(metadata, agentsdk.MetadataDescription{ + DisplayName: datum.DisplayName, + Key: datum.Key, + Cmd: datum.Cmd, + Interval: time.Duration(datum.Interval) * time.Second, + Timeout: time.Duration(datum.Timeout) * time.Second, + }) + } + return metadata +} + func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator, dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string) (codersdk.WorkspaceAgent, error) { var envs map[string]string if dbAgent.EnvironmentVariables.Valid { @@ -1146,7 +1171,7 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ }) _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeData, - Data: convertWorkspaceAgentMetadata(data), + Data: convertWorkspaceAgentMetadataResult(data), }) } @@ -1166,7 +1191,7 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ <-senderClosed } -func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataResult { +func convertWorkspaceAgentMetadataResult(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataResult { var result []codersdk.WorkspaceAgentMetadataResult for _, datum := range db { result = append(result, codersdk.WorkspaceAgentMetadataResult{ diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index ea9f556d53fbe..5b911a67b0ec8 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1170,7 +1170,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { { DisplayName: "Second Meta", Key: "foo2", - Cmd: []string{"echo", "hi"}, + Cmd: []string{"echo", "howdy"}, Interval: 10, Timeout: 3, }, @@ -1199,6 +1199,18 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) + ctx, _ := testutil.Context(t) + + manifest, err := agentClient.Manifest(ctx) + require.NoError(t, err) + + // Verify manifest API response. + require.Equal(t, "First Meta", manifest.Metadata[0].DisplayName) + require.Equal(t, "foo1", manifest.Metadata[0].Key) + require.Equal(t, []string{"echo", "hi"}, manifest.Metadata[0].Cmd) + require.Equal(t, time.Second*(10), manifest.Metadata[0].Interval) + require.Equal(t, time.Second*3, manifest.Metadata[0].Timeout) + ctx, cancel := testutil.Context(t) defer cancel() @@ -1207,7 +1219,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.NoError(t, err, "post metadata", t) } - workspace, err := client.Workspace(ctx, workspace.ID) + workspace, err = client.Workspace(ctx, workspace.ID) require.NoError(t, err, "get workspace") agentID := workspace.LatestBuild.Resources[0].Agents[0].ID diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index d3342a30165c1..30084b35138b5 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -69,10 +69,11 @@ func (c *Client) GitSSHKey(ctx context.Context) (GitSSHKey, error) { // back to coderd. It is provided via the `metadata` list in the `coder_agent` // block. type MetadataDescription struct { - Key string - Cmd []string - Interval time.Duration - Timeout time.Duration + DisplayName string `json:"display_name,omitempty"` + Key string `json:"key,omitempty"` + Cmd []string `json:"cmd,omitempty"` + Interval time.Duration `json:"interval,omitempty"` + Timeout time.Duration `json:"timeout,omitempty"` } // In the future, we may want to support sending back multiple values for From 14f898f104a16bbdc2f87cbfe28ad6c9de93b8d4 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 22 Mar 2023 21:45:16 +0000 Subject: [PATCH 17/73] Manually verified value in DB is getting updated --- coderd/database/dump.sql | 6 +++--- .../migrations/000110_workspace_agent_metadata.up.sql | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index a45d8d915f530..2524b201b2326 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -480,11 +480,11 @@ CREATE TABLE workspace_agent_metadata ( display_name text NOT NULL, key character varying(128) NOT NULL, cmd text[] NOT NULL, - value text NOT NULL, - error text NOT NULL, + value text DEFAULT ''::text NOT NULL, + error text DEFAULT ''::text NOT NULL, timeout bigint NOT NULL, "interval" bigint NOT NULL, - collected_at timestamp with time zone NOT NULL + collected_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL ); CREATE TABLE workspace_agent_stats ( diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql index eb6abe253a5bd..06075299c9fe5 100644 --- a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql @@ -5,11 +5,11 @@ CREATE TABLE workspace_agent_metadata ( display_name text NOT NULL, key character varying(128) NOT NULL, cmd text[] NOT NULL, - value text NOT NULL, - error text NOT NULL, + value text NOT NULL DEFAULT '', + error text NOT NULL DEFAULT '', timeout bigint NOT NULL, interval bigint NOT NULL, - collected_at timestamp with time zone NOT NULL, + collected_at timestamp with time zone NOT NULL DEFAULT '0001-01-01 00:00:00+00', PRIMARY KEY (workspace_agent_id, key), FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE ); From 0834cc66bc5dad76e7e595f8383e84878f564ee0 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 22 Mar 2023 23:28:34 +0000 Subject: [PATCH 18/73] It shows and it glows --- agent/agent.go | 25 ++++++-- agent/agent_test.go | 8 +-- coderd/apidoc/docs.go | 4 +- coderd/apidoc/swagger.json | 4 +- coderd/workspaceagents.go | 36 +++++++---- coderd/workspaceagents_test.go | 20 +++--- codersdk/agentsdk/agentsdk.go | 35 ++++------- codersdk/workspaceagents.go | 30 ++++++--- site/src/api/api.ts | 12 ++++ site/src/api/typesGenerated.ts | 23 +++++-- .../components/Resources/AgentMetadata.tsx | 62 +++++++++++++++++++ site/src/components/Resources/AgentRow.tsx | 2 + 12 files changed, 191 insertions(+), 70 deletions(-) create mode 100644 site/src/components/Resources/AgentMetadata.tsx diff --git a/agent/agent.go b/agent/agent.go index 14b916d79bd55..94e1bfcc274f8 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -207,12 +207,15 @@ func (a *agent) runLoop(ctx context.Context) { } } -func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) codersdk.WorkspaceAgentMetadataResult { +func collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentMetadataDescription) codersdk.WorkspaceAgentMetadataResult { timeout := md.Timeout if timeout == 0 { timeout = md.Interval } - ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) + ctx, cancel := context.WithDeadline(ctx, time.Now().Add( + time.Duration(timeout)*time.Second, + ), + ) defer cancel() collectedAt := time.Now() @@ -247,6 +250,16 @@ func collectMetadata(ctx context.Context, md agentsdk.MetadataDescription) coder return result } +func convertInterval(i int64) time.Duration { + // In tests we want to set shorter intervals because engineers are + // impatient. + base := time.Second + if flag.Lookup("test.v") != nil { + base = time.Millisecond + } + return time.Duration(i) * base +} + func (a *agent) reportMetadataLoop(ctx context.Context) { // In production, the minimum report interval is one second because // `coder_agent.metadata` accepts `interval` in integer seconds. @@ -296,7 +309,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { // purge old cache values to prevent lastCollectedAt from growing // boundlessly. for key := range lastCollectedAts { - if slices.IndexFunc(manifest.Metadata, func(md agentsdk.MetadataDescription) bool { + if slices.IndexFunc(manifest.Metadata, func(md codersdk.WorkspaceAgentMetadataDescription) bool { return md.Key == key }) < 0 { delete(lastCollectedAts, key) @@ -313,12 +326,14 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { if md.Interval == 0 { continue } - if collectedAt.Add(md.Interval).After(time.Now()) { + if collectedAt.Add( + convertInterval(md.Interval), + ).After(time.Now()) { continue } } - go func(md agentsdk.MetadataDescription) { + go func(md codersdk.WorkspaceAgentMetadataDescription) { select { case <-ctx.Done(): return diff --git a/agent/agent_test.go b/agent/agent_test.go index 77ee71a0dc9fd..858b2dc3d25ea 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -779,10 +779,10 @@ func TestAgent_Metadata(t *testing.T) { t.Run("Basic", func(t *testing.T) { t.Parallel() dir := t.TempDir() - const reportInterval = time.Millisecond * 200 + const reportInterval = 200 greetingPath := filepath.Join(dir, "greeting") _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - Metadata: []agentsdk.MetadataDescription{ + Metadata: []codersdk.WorkspaceAgentMetadataDescription{ { Key: "greeting", Interval: reportInterval, @@ -814,7 +814,7 @@ func TestAgent_Metadata(t *testing.T) { var ( numGreetings = bytes.Count(greetingByt, []byte("hello")) - idealNumGreetings = time.Since(start) / (reportInterval) + idealNumGreetings = time.Since(start) / (reportInterval * time.Millisecond) upperBound = int(idealNumGreetings) + 1 lowerBound = (int(idealNumGreetings) / 2) ) @@ -839,7 +839,7 @@ func TestAgent_Metadata(t *testing.T) { t.Parallel() //nolint:dogsled _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - Metadata: []agentsdk.MetadataDescription{ + Metadata: []codersdk.WorkspaceAgentMetadataDescription{ { Key: "greeting", Interval: 0, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index d114fa695c808..0c5b8ecf96e48 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5308,7 +5308,7 @@ const docTemplate = `{ "dynamic_metadata": { "type": "array", "items": { - "$ref": "#/definitions/agentsdk.MetadataDescription" + "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataDescription" } }, "environment_variables": { @@ -5341,7 +5341,7 @@ const docTemplate = `{ } } }, - "agentsdk.MetadataDescription": { + "codersdk.WorkspaceAgentMetadataDescription": { "type": "object", "properties": { "cmd": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 9a52d1cf717f8..80520c4c1dae7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4681,7 +4681,7 @@ "dynamic_metadata": { "type": "array", "items": { - "$ref": "#/definitions/agentsdk.MetadataDescription" + "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataDescription" } }, "environment_variables": { @@ -4714,7 +4714,7 @@ } } }, - "agentsdk.MetadataDescription": { + "codersdk.WorkspaceAgentMetadataDescription": { "type": "object", "properties": { "cmd": { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index d47b6654eb4b8..648fd4acb542a 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -843,15 +843,15 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { return apps } -func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) []agentsdk.MetadataDescription { - metadata := make([]agentsdk.MetadataDescription, 0) +func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataDescription { + metadata := make([]codersdk.WorkspaceAgentMetadataDescription, 0) for _, datum := range mds { - metadata = append(metadata, agentsdk.MetadataDescription{ + metadata = append(metadata, codersdk.WorkspaceAgentMetadataDescription{ DisplayName: datum.DisplayName, Key: datum.Key, Cmd: datum.Cmd, - Interval: time.Duration(datum.Interval) * time.Second, - Timeout: time.Duration(datum.Timeout) * time.Second, + Interval: datum.Interval, + Timeout: datum.Timeout, }) } return metadata @@ -1108,6 +1108,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque ctx, "accepted metadata report", slog.F("agent", workspaceAgent.ID), slog.F("workspace", workspace.ID), + slog.F("collected_at", datum.CollectedAt), slog.F("key", datum.Key), slog.F("value", ellipse(datum.Value, 5)), ) @@ -1171,7 +1172,7 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ }) _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeData, - Data: convertWorkspaceAgentMetadataResult(data), + Data: convertWorkspaceAgentMetadata(data), }) } @@ -1191,14 +1192,23 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ <-senderClosed } -func convertWorkspaceAgentMetadataResult(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataResult { - var result []codersdk.WorkspaceAgentMetadataResult +func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadata { + var result []codersdk.WorkspaceAgentMetadata for _, datum := range db { - result = append(result, codersdk.WorkspaceAgentMetadataResult{ - Key: datum.Key, - Value: datum.Value, - Error: datum.Error, - CollectedAt: datum.CollectedAt, + result = append(result, codersdk.WorkspaceAgentMetadata{ + Result: codersdk.WorkspaceAgentMetadataResult{ + Key: datum.Key, + Value: datum.Value, + Error: datum.Error, + CollectedAt: datum.CollectedAt, + }, + Description: codersdk.WorkspaceAgentMetadataDescription{ + DisplayName: datum.DisplayName, + Key: datum.Key, + Cmd: datum.Cmd, + Interval: datum.Interval, + Timeout: datum.Timeout, + }, }) } return result diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 5b911a67b0ec8..8fa8496329c1f 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1208,8 +1208,8 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.Equal(t, "First Meta", manifest.Metadata[0].DisplayName) require.Equal(t, "foo1", manifest.Metadata[0].Key) require.Equal(t, []string{"echo", "hi"}, manifest.Metadata[0].Cmd) - require.Equal(t, time.Second*(10), manifest.Metadata[0].Interval) - require.Equal(t, time.Second*3, manifest.Metadata[0].Timeout) + require.EqualValues(t, 10, manifest.Metadata[0].Interval) + require.EqualValues(t, 3, manifest.Metadata[0].Timeout) ctx, cancel := testutil.Context(t) defer cancel() @@ -1224,13 +1224,13 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { agentID := workspace.LatestBuild.Resources[0].Agents[0].ID - var update []codersdk.WorkspaceAgentMetadataResult + var update []codersdk.WorkspaceAgentMetadata - check := func(want codersdk.WorkspaceAgentMetadataResult, got codersdk.WorkspaceAgentMetadataResult) { - require.WithinDuration(t, want.CollectedAt, got.CollectedAt, time.Second) - require.Equal(t, want.Key, got.Key) - require.Equal(t, want.Value, got.Value) - require.Equal(t, want.Error, got.Error) + check := func(want codersdk.WorkspaceAgentMetadataResult, got codersdk.WorkspaceAgentMetadata) { + require.WithinDuration(t, want.CollectedAt, got.Result.CollectedAt, time.Second) + require.Equal(t, want.Key, got.Result.Key) + require.Equal(t, want.Value, got.Result.Value) + require.Equal(t, want.Error, got.Result.Error) } wantMetadata1 := codersdk.WorkspaceAgentMetadataResult{ @@ -1244,7 +1244,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { updates, errors := client.WatchWorkspaceAgentMetadata(ctx, agentID) - recvUpdate := func() []codersdk.WorkspaceAgentMetadataResult { + recvUpdate := func() []codersdk.WorkspaceAgentMetadata { select { case err := <-errors: t.Fatalf("error watching metadata: %v", err) @@ -1258,7 +1258,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.Len(t, update, 2) check(wantMetadata1, update[0]) // The second metadata result is not yet posted. - require.Zero(t, update[1].CollectedAt) + require.Zero(t, update[1].Result.CollectedAt) wantMetadata2 := wantMetadata1 wantMetadata2.Key = "foo2" diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 30084b35138b5..1a59ba735c07c 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -65,17 +65,6 @@ func (c *Client) GitSSHKey(ctx context.Context) (GitSSHKey, error) { return gitSSHKey, json.NewDecoder(res.Body).Decode(&gitSSHKey) } -// MetadataDescription is a description of dynamic metadata the agent should report -// back to coderd. It is provided via the `metadata` list in the `coder_agent` -// block. -type MetadataDescription struct { - DisplayName string `json:"display_name,omitempty"` - Key string `json:"key,omitempty"` - Cmd []string `json:"cmd,omitempty"` - Interval time.Duration `json:"interval,omitempty"` - Timeout time.Duration `json:"timeout,omitempty"` -} - // In the future, we may want to support sending back multiple values for // performance. type PostMetadataRequest = codersdk.WorkspaceAgentMetadataResult @@ -98,18 +87,18 @@ type Manifest struct { // GitAuthConfigs stores the number of Git configurations // the Coder deployment has. If this number is >0, we // set up special configuration in the workspace. - GitAuthConfigs int `json:"git_auth_configs"` - VSCodePortProxyURI string `json:"vscode_port_proxy_uri"` - Apps []codersdk.WorkspaceApp `json:"apps"` - DERPMap *tailcfg.DERPMap `json:"derpmap"` - EnvironmentVariables map[string]string `json:"environment_variables"` - StartupScript string `json:"startup_script"` - StartupScriptTimeout time.Duration `json:"startup_script_timeout"` - Directory string `json:"directory"` - MOTDFile string `json:"motd_file"` - ShutdownScript string `json:"shutdown_script"` - ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"` - Metadata []MetadataDescription `json:"metadata"` + GitAuthConfigs int `json:"git_auth_configs"` + VSCodePortProxyURI string `json:"vscode_port_proxy_uri"` + Apps []codersdk.WorkspaceApp `json:"apps"` + DERPMap *tailcfg.DERPMap `json:"derpmap"` + EnvironmentVariables map[string]string `json:"environment_variables"` + StartupScript string `json:"startup_script"` + StartupScriptTimeout time.Duration `json:"startup_script_timeout"` + Directory string `json:"directory"` + MOTDFile string `json:"motd_file"` + ShutdownScript string `json:"shutdown_script"` + ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"` + Metadata []codersdk.WorkspaceAgentMetadataDescription `json:"metadata"` } // Manifest fetches manifest for the currently authenticated workspace agent. diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index bc55f3ed4d6ab..eb4b35fa221e5 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -75,10 +75,26 @@ var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{ } type WorkspaceAgentMetadataResult struct { - CollectedAt time.Time - Key string - Value string - Error string + CollectedAt time.Time `json:"collected_at,omitempty"` + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + Error string `json:"error,omitempty"` +} + +// WorkspaceAgentMetadataDescription is a description of dynamic metadata the agent should report +// back to coderd. It is provided via the `metadata` list in the `coder_agent` +// block. +type WorkspaceAgentMetadataDescription struct { + DisplayName string `json:"display_name,omitempty"` + Key string `json:"key,omitempty"` + Cmd []string `json:"cmd,omitempty"` + Interval int64 `json:"interval,omitempty"` + Timeout int64 `json:"timeout,omitempty"` +} + +type WorkspaceAgentMetadata struct { + Result WorkspaceAgentMetadataResult `json:"result,omitempty"` + Description WorkspaceAgentMetadataDescription `json:"description,omitempty"` } type WorkspaceAgent struct { @@ -265,11 +281,11 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti // WatchWorkspaceAgentMetadata watches the metadata of a workspace agent. // The returned channel will be closed when the context is canceled. Exactly // one error will be sent on the error channel. The metadata channel is never closed. -func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) (<-chan []WorkspaceAgentMetadataResult, <-chan error) { +func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) (<-chan []WorkspaceAgentMetadata, <-chan error) { ctx, span := tracing.StartSpan(ctx) defer span.End() - metadataChan := make(chan []WorkspaceAgentMetadataResult, 256) + metadataChan := make(chan []WorkspaceAgentMetadata, 256) watch := func() error { res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/watch-metadata", id), nil) @@ -300,7 +316,7 @@ func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) switch sse.Type { case ServerSentEventTypeData: - var met []WorkspaceAgentMetadataResult + var met []WorkspaceAgentMetadata err = json.Unmarshal(b, &met) if err != nil { return xerrors.Errorf("unmarshal metadata: %w", err) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 210db89a29608..70089d85b56f2 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1001,3 +1001,15 @@ const getMissingParameters = ( return missingParameters } + +/** + * + * @param agentId + * @returns An EventSource that emits agent metadata event objects (ServerSentEvent) + */ +export const watchAgentMetadata = (agentId: string): EventSource => { + return new EventSource( + `${location.protocol}//${location.host}/api/v2/workspaceagents/${agentId}/watch-metadata`, + { withCredentials: true }, + ) +} diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 734f632a7944c..357d5f2148822 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1061,12 +1061,27 @@ export interface WorkspaceAgentListeningPortsResponse { readonly ports: WorkspaceAgentListeningPort[] } +// From codersdk/workspaceagents.go +export interface WorkspaceAgentMetadata { + readonly result?: WorkspaceAgentMetadataResult + readonly description?: WorkspaceAgentMetadataDescription +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentMetadataDescription { + readonly display_name?: string + readonly key?: string + readonly cmd?: string[] + readonly interval?: number + readonly timeout?: number +} + // From codersdk/workspaceagents.go export interface WorkspaceAgentMetadataResult { - readonly CollectedAt: string - readonly Key: string - readonly Value: string - readonly Error: string + readonly collected_at?: string + readonly key?: string + readonly value?: string + readonly error?: string } // From codersdk/workspaceapps.go diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx new file mode 100644 index 0000000000000..3da68d76ed55e --- /dev/null +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -0,0 +1,62 @@ +import CircularProgress from "@material-ui/core/CircularProgress" +import { watchAgentMetadata } from "api/api" +import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated" +import dayjs from "dayjs" +import { FC, useEffect, useState } from "react" + +const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { + if (item.result === undefined) { + throw new Error("Metadata item result is undefined") + } + if (item.description === undefined) { + throw new Error("Metadata item description is undefined") + } + + if (dayjs(item.result.collected_at).year() === 0) { + // Still loading. + return ( +
+ {item.description.display_name}: +
+ ) + } + return ( +
+ {item.description.display_name}: {item.result.value} +
+ ) +} + +export const AgentMetadata: FC<{ agent: WorkspaceAgent }> = ({ agent }) => { + const [metadata, setMetadata] = useState< + WorkspaceAgentMetadata[] | undefined + >(undefined) + + useEffect(() => { + const source = watchAgentMetadata(agent.id) + source.onerror = (e) => { + console.error(e) + } + source.addEventListener("data", (e) => { + const data = JSON.parse(e.data) + setMetadata(data) + }) + return () => { + source.close() + } + }, [agent.id]) + + if (metadata === undefined) { + return + } + return ( +
+ {metadata.map((m) => { + if (m.description === undefined) { + throw new Error("Metadata item description is undefined") + } + return + })} +
+ ) +} diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index d72a14b21ebbf..a7a030dc95095 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -14,6 +14,7 @@ import { AgentStatus } from "./AgentStatus" import { AppLinkSkeleton } from "components/AppLink/AppLinkSkeleton" import { useTranslation } from "react-i18next" import { VSCodeDesktopButton } from "components/VSCodeDesktopButton/VSCodeDesktopButton" +import { AgentMetadata } from "./AgentMetadata" export interface AgentRowProps { agent: WorkspaceAgent @@ -81,6 +82,7 @@ export const AgentRow: FC = ({ {t("unableToConnect")} + From f62578331f56e89299de753508fa71758657e0c7 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 01:07:32 +0000 Subject: [PATCH 19/73] Don't show stale data --- codersdk/workspaceagents.go | 22 +-- site/src/api/typesGenerated.ts | 10 +- .../components/Resources/AgentMetadata.tsx | 95 +++++++-- site/src/components/Resources/AgentRow.tsx | 183 +++++++++--------- 4 files changed, 188 insertions(+), 122 deletions(-) diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index eb4b35fa221e5..c6e11b2d02150 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -75,26 +75,26 @@ var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{ } type WorkspaceAgentMetadataResult struct { - CollectedAt time.Time `json:"collected_at,omitempty"` - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` - Error string `json:"error,omitempty"` + CollectedAt time.Time `json:"collected_at"` + Key string `json:"key"` + Value string `json:"value"` + Error string `json:"error"` } // WorkspaceAgentMetadataDescription is a description of dynamic metadata the agent should report // back to coderd. It is provided via the `metadata` list in the `coder_agent` // block. type WorkspaceAgentMetadataDescription struct { - DisplayName string `json:"display_name,omitempty"` - Key string `json:"key,omitempty"` - Cmd []string `json:"cmd,omitempty"` - Interval int64 `json:"interval,omitempty"` - Timeout int64 `json:"timeout,omitempty"` + DisplayName string `json:"display_name"` + Key string `json:"key"` + Cmd []string `json:"cmd"` + Interval int64 `json:"interval"` + Timeout int64 `json:"timeout"` } type WorkspaceAgentMetadata struct { - Result WorkspaceAgentMetadataResult `json:"result,omitempty"` - Description WorkspaceAgentMetadataDescription `json:"description,omitempty"` + Result WorkspaceAgentMetadataResult `json:"result"` + Description WorkspaceAgentMetadataDescription `json:"description"` } type WorkspaceAgent struct { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 357d5f2148822..fbc34487d1d94 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1069,11 +1069,11 @@ export interface WorkspaceAgentMetadata { // From codersdk/workspaceagents.go export interface WorkspaceAgentMetadataDescription { - readonly display_name?: string - readonly key?: string - readonly cmd?: string[] - readonly interval?: number - readonly timeout?: number + readonly display_name: string + readonly key: string + readonly cmd: string[] + readonly interval: number + readonly timeout: number } // From codersdk/workspaceagents.go diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 3da68d76ed55e..98db1c7493108 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -1,10 +1,14 @@ import CircularProgress from "@material-ui/core/CircularProgress" +import makeStyles from "@material-ui/core/styles/makeStyles" import { watchAgentMetadata } from "api/api" import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated" +import { Stack } from "components/Stack/Stack" import dayjs from "dayjs" import { FC, useEffect, useState } from "react" const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { + const styles = useStyles() + if (item.result === undefined) { throw new Error("Metadata item result is undefined") } @@ -12,17 +16,31 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { throw new Error("Metadata item description is undefined") } - if (dayjs(item.result.collected_at).year() === 0) { - // Still loading. - return ( -
- {item.description.display_name}: -
+ const secondsSinceLastCollected = dayjs().diff( + dayjs(item.result.collected_at), + "seconds", + ) + const staleThreshold = Math.max( + item.description.interval + item.description.timeout * 2, + 5, + ) + + // Stale data is as good as no data. Plus, we want to build confidence in our + // users that what's shown is real. If times aren't correctly synced this + // could lead to issues. + const value = + secondsSinceLastCollected < staleThreshold ? ( +
{item.result.value}
+ ) : ( + ) - } + return ( -
- {item.description.display_name}: {item.result.value} +
+
+ {item.description.display_name} +
+ {value}
) } @@ -46,17 +64,60 @@ export const AgentMetadata: FC<{ agent: WorkspaceAgent }> = ({ agent }) => { } }, [agent.id]) + const styles = useStyles() if (metadata === undefined) { return } + if (metadata.length === 0) { + return <> + } return ( -
- {metadata.map((m) => { - if (m.description === undefined) { - throw new Error("Metadata item description is undefined") - } - return - })} -
+ +
+ {metadata.map((m) => { + if (m.description === undefined) { + throw new Error("Metadata item description is undefined") + } + return + })} +
+
) } + +// These are more or less copied from +// site/src/components/Resources/ResourceCard.tsx +const useStyles = makeStyles((theme) => ({ + metadataHeader: { + display: "grid", + gridTemplateColumns: "repeat(4, minmax(0, 1fr))", + gap: theme.spacing(5), + rowGap: theme.spacing(3), + // background: theme.palette.background.paper, + // padding: 10, + // marginTop: 10, + + // border: `1px solid ${colors.gray[11]}`, + // borderRadius: theme.shape.borderRadius, + }, + + metadata: { + fontSize: 16, + }, + + metadataLabel: { + fontSize: 12, + color: theme.palette.text.secondary, + textOverflow: "ellipsis", + overflow: "hidden", + whiteSpace: "nowrap", + fontWeight: "bold", + }, + + metadataValue: { + textOverflow: "ellipsis", + overflow: "hidden", + color: theme.palette.success.light, + whiteSpace: "nowrap", + }, +})) diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index a7a030dc95095..54df49a77aa8e 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -42,105 +42,110 @@ export const AgentRow: FC = ({ return ( - -
- -
-
-
{agent.name}
+
+ +
+ + + + +
+
{agent.name}
+ + {agent.operating_system} + + + + + + + + + + + + + + {t("unableToConnect")} + + +
+
+ - {agent.operating_system} - - - - - - - - - - - - - - {t("unableToConnect")} - - - -
-
+ {showApps && agent.status === "connected" && ( + <> + {agent.apps.map((app) => ( + + ))} - - {showApps && agent.status === "connected" && ( - <> - {agent.apps.map((app) => ( - - ))} - - - {!hideSSHButton && ( - + + {!hideSSHButton && ( + + )} + {!hideVSCodeDesktopButton && ( + + )} + {applicationsHost !== undefined && applicationsHost !== "" && ( + + )} + )} - {!hideVSCodeDesktopButton && ( - + {showApps && agent.status === "connecting" && ( + <> + + + )} - {applicationsHost !== undefined && applicationsHost !== "" && ( - - )} - - )} - {showApps && agent.status === "connecting" && ( - <> - - - - )} + +
+ + ) From 00cca25a6a27b6e95ef9a1220d5a97ac22f80a6b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 01:36:39 +0000 Subject: [PATCH 20/73] The frontend lays out nicely thanks kyle --- site/src/components/Resources/AgentMetadata.tsx | 2 +- site/src/components/Resources/AgentRow.tsx | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 98db1c7493108..1c5cc888ccce6 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -27,7 +27,7 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { // Stale data is as good as no data. Plus, we want to build confidence in our // users that what's shown is real. If times aren't correctly synced this - // could lead to issues. + // could be buggy. But, how common is that anyways? const value = secondsSinceLastCollected < staleThreshold ? (
{item.result.value}
diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index 54df49a77aa8e..cea4c4fee6a0e 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -52,8 +52,21 @@ export const AgentRow: FC = ({
- - + +
{agent.name}
From 74eb373cc7be567bccc35c6785a3929cfdabe432 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 01:58:35 +0000 Subject: [PATCH 21/73] Add provisioner/terraform --- agent/agent.go | 36 +- agent/agent_test.go | 6 +- coderd/database/dbfake/databasefake.go | 2 +- coderd/database/dump.sql | 2 +- .../000110_workspace_agent_metadata.up.sql | 2 +- coderd/database/models.go | 2 +- coderd/database/queries.sql.go | 10 +- coderd/database/queries/workspaceagents.sql | 2 +- .../provisionerdserver/provisionerdserver.go | 2 +- coderd/workspaceagents.go | 4 +- coderd/workspaceagents_test.go | 6 +- codersdk/workspaceagents.go | 10 +- provisioner/terraform/resources.go | 12 +- provisioner/terraform/resources_test.go | 2 +- .../calling-module.tfstate.json | 8 +- .../chaining-resources.tfstate.json | 8 +- .../conflicting-resources.tfstate.json | 8 +- .../git-auth-providers.tfstate.json | 6 +- .../instance-id/instance-id.tfstate.json | 10 +- .../mapped-apps/mapped-apps.tfstate.json | 14 +- .../multiple-agents.tfstate.json | 14 +- .../multiple-apps/multiple-apps.tfstate.json | 18 +- .../resource-metadata/resource-metadata.tf | 4 +- .../resource-metadata.tfplan.json | 48 +- .../resource-metadata.tfstate.json | 24 +- .../rich-parameters.tfplan.json | 4 +- .../rich-parameters.tfstate.json | 10 +- provisionersdk/proto/provisioner.pb.go | 427 +++++++++--------- provisionersdk/proto/provisioner.proto | 2 +- site/src/api/typesGenerated.ts | 12 +- 30 files changed, 340 insertions(+), 375 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 94e1bfcc274f8..26df710395d13 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -207,7 +207,7 @@ func (a *agent) runLoop(ctx context.Context) { } } -func collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentMetadataDescription) codersdk.WorkspaceAgentMetadataResult { +func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentMetadataDescription) *codersdk.WorkspaceAgentMetadataResult { timeout := md.Timeout if timeout == 0 { timeout = md.Interval @@ -222,13 +222,21 @@ func collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentMetadataDesc var out bytes.Buffer - //nolint:gosec - cmd := exec.CommandContext(ctx, md.Cmd[0], md.Cmd[1:]...) + result := &codersdk.WorkspaceAgentMetadataResult{ + CollectedAt: collectedAt, + Key: md.Key, + } + cmd, err := a.createCommand(ctx, md.Script, nil) + if err != nil { + result.Error = err.Error() + return result + } + cmd.Stdout = &out cmd.Stderr = &out // The error isn't mutually exclusive with useful output. - err := cmd.Run() + err = cmd.Run() const bufLimit = 10 << 14 if out.Len() > bufLimit { @@ -239,14 +247,10 @@ func collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentMetadataDesc out.Truncate(bufLimit) } - result := codersdk.WorkspaceAgentMetadataResult{ - CollectedAt: collectedAt, - Key: md.Key, - Value: out.String(), - } if err != nil { result.Error = err.Error() } + result.Value = out.String() return result } @@ -273,7 +277,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { var ( baseTicker = time.NewTicker(baseInterval) lastCollectedAts = make(map[string]time.Time) - metadataResults = make(chan codersdk.WorkspaceAgentMetadataResult, 16) + metadataResults = make(chan *codersdk.WorkspaceAgentMetadataResult, 16) ) defer baseTicker.Stop() @@ -283,7 +287,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { return case mr := <-metadataResults: lastCollectedAts[mr.Key] = mr.CollectedAt - err := a.client.PostMetadata(ctx, mr) + err := a.client.PostMetadata(ctx, *mr) if err != nil { a.logger.Error(ctx, "report metadata", slog.Error(err)) } @@ -337,7 +341,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { select { case <-ctx.Done(): return - case metadataResults <- collectMetadata(ctx, md): + case metadataResults <- a.collectMetadata(ctx, md): } }(md) } @@ -932,9 +936,9 @@ func (a *agent) init(ctx context.Context) { } // createCommand processes raw command input with OpenSSH-like behavior. -// If the rawCommand provided is empty, it will default to the users shell. +// If the rawScript provided is empty, it will default to the users shell. // This injects environment variables specified by the user at launch too. -func (a *agent) createCommand(ctx context.Context, rawCommand string, env []string) (*exec.Cmd, error) { +func (a *agent) createCommand(ctx context.Context, script string, env []string) (*exec.Cmd, error) { currentUser, err := user.Current() if err != nil { return nil, xerrors.Errorf("get current user: %w", err) @@ -957,11 +961,11 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri if runtime.GOOS == "windows" { caller = "/c" } - args := []string{caller, rawCommand} + args := []string{caller, script} // gliderlabs/ssh returns a command slice of zero // when a shell is requested. - if len(rawCommand) == 0 { + if len(script) == 0 { args = []string{} if runtime.GOOS != "windows" { // On Linux and macOS, we should start a login diff --git a/agent/agent_test.go b/agent/agent_test.go index 858b2dc3d25ea..7573acb3eac27 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -786,12 +786,12 @@ func TestAgent_Metadata(t *testing.T) { { Key: "greeting", Interval: reportInterval, - Cmd: []string{"sh", "-c", "echo hello | tee -a " + greetingPath}, + Script: "echo hello | tee -a " + greetingPath, }, { Key: "bad", Interval: reportInterval, - Cmd: []string{"sh", "-c", "exit 1"}, + Script: "exit 1", }, }, }, 0) @@ -843,7 +843,7 @@ func TestAgent_Metadata(t *testing.T) { { Key: "greeting", Interval: 0, - Cmd: []string{"echo", "-n", "hello"}, + Script: "echo -n hello", }, }, }, 0) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index bba4515aafff9..b3980d322e41c 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2700,7 +2700,7 @@ func (q *fakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg databa //nolint:gosimple metadatum := database.WorkspaceAgentMetadatum{ WorkspaceAgentID: arg.WorkspaceAgentID, - Cmd: arg.Cmd, + Script: arg.Script, DisplayName: arg.DisplayName, Key: arg.Key, Timeout: arg.Timeout, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 2524b201b2326..74c431917e2db 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -479,7 +479,7 @@ CREATE TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, display_name text NOT NULL, key character varying(128) NOT NULL, - cmd text[] NOT NULL, + script text NOT NULL, value text DEFAULT ''::text NOT NULL, error text DEFAULT ''::text NOT NULL, timeout bigint NOT NULL, diff --git a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql index 06075299c9fe5..0f9a526b1e5de 100644 --- a/coderd/database/migrations/000110_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000110_workspace_agent_metadata.up.sql @@ -4,7 +4,7 @@ CREATE TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, display_name text NOT NULL, key character varying(128) NOT NULL, - cmd text[] NOT NULL, + script text NOT NULL, value text NOT NULL DEFAULT '', error text NOT NULL DEFAULT '', timeout bigint NOT NULL, diff --git a/coderd/database/models.go b/coderd/database/models.go index fb1dd3c6505ab..f4412495dc701 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1575,7 +1575,7 @@ type WorkspaceAgentMetadatum struct { WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` DisplayName string `db:"display_name" json:"display_name"` Key string `db:"key" json:"key"` - Cmd []string `db:"cmd" json:"cmd"` + Script string `db:"script" json:"script"` Value string `db:"value" json:"value"` Error string `db:"error" json:"error"` Timeout int64 `db:"timeout" json:"timeout"` diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 337d6ea719769..4c04603b171d2 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5193,7 +5193,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many SELECT - workspace_agent_id, display_name, key, cmd, value, error, timeout, interval, collected_at + workspace_agent_id, display_name, key, script, value, error, timeout, interval, collected_at FROM workspace_agent_metadata WHERE @@ -5213,7 +5213,7 @@ func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAge &i.WorkspaceAgentID, &i.DisplayName, &i.Key, - pq.Array(&i.Cmd), + &i.Script, &i.Value, &i.Error, &i.Timeout, @@ -5467,7 +5467,7 @@ INSERT INTO workspace_agent_id, display_name, key, - cmd, + script, timeout, interval ) @@ -5479,7 +5479,7 @@ type InsertWorkspaceAgentMetadataParams struct { WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` DisplayName string `db:"display_name" json:"display_name"` Key string `db:"key" json:"key"` - Cmd []string `db:"cmd" json:"cmd"` + Script string `db:"script" json:"script"` Timeout int64 `db:"timeout" json:"timeout"` Interval int64 `db:"interval" json:"interval"` } @@ -5489,7 +5489,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentMetadata(ctx context.Context, arg Inser arg.WorkspaceAgentID, arg.DisplayName, arg.Key, - pq.Array(arg.Cmd), + arg.Script, arg.Timeout, arg.Interval, ) diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 27e6f1c60547b..fdfc3f292f2ed 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -100,7 +100,7 @@ INSERT INTO workspace_agent_id, display_name, key, - cmd, + script, timeout, interval ) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 86461e25bd0f8..38e5a478bad50 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1230,7 +1230,7 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. err := db.InsertWorkspaceAgentMetadata(ctx, database.InsertWorkspaceAgentMetadataParams{ WorkspaceAgentID: agentID, DisplayName: md.DisplayName, - Cmd: md.Cmd, + Script: md.Script, Key: md.Key, Timeout: md.Timeout, Interval: md.Interval, diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 648fd4acb542a..930338991d5a8 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -849,7 +849,7 @@ func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) [ metadata = append(metadata, codersdk.WorkspaceAgentMetadataDescription{ DisplayName: datum.DisplayName, Key: datum.Key, - Cmd: datum.Cmd, + Script: datum.Script, Interval: datum.Interval, Timeout: datum.Timeout, }) @@ -1205,7 +1205,7 @@ func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []code Description: codersdk.WorkspaceAgentMetadataDescription{ DisplayName: datum.DisplayName, Key: datum.Key, - Cmd: datum.Cmd, + Script: datum.Script, Interval: datum.Interval, Timeout: datum.Timeout, }, diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 8fa8496329c1f..9d7e952f18336 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1163,14 +1163,14 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { { DisplayName: "First Meta", Key: "foo1", - Cmd: []string{"echo", "hi"}, + Script: "echo hi", Interval: 10, Timeout: 3, }, { DisplayName: "Second Meta", Key: "foo2", - Cmd: []string{"echo", "howdy"}, + Script: "echo howdy", Interval: 10, Timeout: 3, }, @@ -1207,7 +1207,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { // Verify manifest API response. require.Equal(t, "First Meta", manifest.Metadata[0].DisplayName) require.Equal(t, "foo1", manifest.Metadata[0].Key) - require.Equal(t, []string{"echo", "hi"}, manifest.Metadata[0].Cmd) + require.Equal(t, "echo hi", manifest.Metadata[0].Script) require.EqualValues(t, 10, manifest.Metadata[0].Interval) require.EqualValues(t, 3, manifest.Metadata[0].Timeout) diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index c6e11b2d02150..d9b8002d2b42f 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -85,11 +85,11 @@ type WorkspaceAgentMetadataResult struct { // back to coderd. It is provided via the `metadata` list in the `coder_agent` // block. type WorkspaceAgentMetadataDescription struct { - DisplayName string `json:"display_name"` - Key string `json:"key"` - Cmd []string `json:"cmd"` - Interval int64 `json:"interval"` - Timeout int64 `json:"timeout"` + DisplayName string `json:"display_name"` + Key string `json:"key"` + Script string `json:"script"` + Interval int64 `json:"interval"` + Timeout int64 `json:"timeout"` } type WorkspaceAgentMetadata struct { diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 4a8ce50bd98ce..d49fcb1191afb 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -15,11 +15,11 @@ import ( ) type agentMetadata struct { - Key string `mapstructure:"key"` - DisplayName string `mapstructure:"display_name"` - Cmd []string `mapstructure:"cmd"` - Interval int64 `mapstructure:"interval"` - Timeout int64 `mapstructure:"timeout"` + Key string `mapstructure:"key"` + DisplayName string `mapstructure:"display_name"` + Script string `mapstructure:"script"` + Interval int64 `mapstructure:"interval"` + Timeout int64 `mapstructure:"timeout"` } // A mapping of attributes on the "coder_agent" resource. @@ -162,7 +162,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error metadata = append(metadata, &proto.Agent_Metadata{ Key: item.Key, DisplayName: item.DisplayName, - Cmd: item.Cmd, + Script: item.Script, Interval: item.Interval, Timeout: item.Timeout, }) diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index 1dff1b885cfc2..2fd96cb0e75e9 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -228,7 +228,7 @@ func TestConvertResources(t *testing.T) { Metadata: []*proto.Agent_Metadata{{ Key: "process_count", DisplayName: "Process Count", - Cmd: []string{"sh", "-c", "ps -ef | wc -l"}, + Script: "ps -ef | wc -l", Interval: 5, Timeout: 1, }}, diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json index 9a97f3320ae6d..eb44278efaacd 100644 --- a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json +++ b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "d39e2778-6841-4fc1-a76f-f3ee5ebad787", + "id": "2d84db09-c3c3-4272-8119-1847e68e2dbe", "init_script": "", "os": "linux", "startup_script": null, - "token": "8c4a0dc5-9c8e-4207-a6c0-2a007dc4b88d", + "token": "d5d46d6a-5e4b-493f-9b68-dd8c78727cac", "troubleshooting_url": null }, "sensitive_values": {} @@ -46,7 +46,7 @@ "outputs": { "script": "" }, - "random": "8032327410784151578" + "random": "6336115403873146745" }, "sensitive_values": { "inputs": {}, @@ -61,7 +61,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2102103241591180892", + "id": "3671991160538447687", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json index 862638f2c2762..75f44e52662c9 100644 --- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json +++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "d79a51cb-8ee5-4d1f-9f58-21998d207048", + "id": "36c5629c-8ac4-4e6b-ae6e-94b8f1bbb0bc", "init_script": "", "os": "linux", "startup_script": null, - "token": "23da23c4-671a-4dde-a781-caff4a421012", + "token": "9ed706cc-8b11-4b05-b6c3-ea943af2c60c", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8714181027325910132", + "id": "4876594853562919335", "triggers": null }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "9058553509629519317", + "id": "3012307200839131913", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json index 790ea0567f20e..ef172db9b63b6 100644 --- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json +++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "1bfaa458-eb18-42b4-8b48-02abdbf2e2bb", + "id": "a79d53f2-f616-4be2-b1bf-6f6ce7409e23", "init_script": "", "os": "linux", "startup_script": null, - "token": "e2fe36ec-39a3-4fa7-a880-a29d836046a0", + "token": "bb333041-5225-490d-86a0-69d1b5afd0db", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,7 +34,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "1838978580367705292", + "id": "3651093174168180448", "triggers": null }, "sensitive_values": {}, @@ -50,7 +50,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5806053018469594590", + "id": "3820948743929828676", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json index 748728aae4d8a..6116c9d09377d 100644 --- a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json +++ b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "8003d1c7-dfbc-4001-87c3-18c7043959c5", + "id": "7060319c-a8bf-44b7-8fb9-5a4dc3bd35fe", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "6bbfb898-d9f3-49a6-ac82-9523388ed921", + "token": "9ea68982-fd51-4510-9c23-9e9ad1ce1ef7", "troubleshooting_url": null }, "sensitive_values": {} @@ -65,7 +65,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "7768158795926184857", + "id": "4918728983070449527", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json index d2920ab640a76..cc8870100a9b9 100644 --- a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json +++ b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "dfd121c6-792b-42f7-ac8d-73242108f35c", + "id": "facfb2ad-417d-412c-8304-ce50ff8a886e", "init_script": "", "os": "linux", "startup_script": null, - "token": "3aa299b8-1388-45ba-aed1-b9ebfce289d7", + "token": "f1b3312a-af1c-45a8-9695-23d92c4cb68d", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,8 +34,8 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "dfd121c6-792b-42f7-ac8d-73242108f35c", - "id": "23b61e1a-40e9-4c69-82bd-0a011a9f921a", + "agent_id": "facfb2ad-417d-412c-8304-ce50ff8a886e", + "id": "6ce4c6f4-1b93-4b22-9b17-1dcb50a36e77", "instance_id": "example" }, "sensitive_values": {}, @@ -51,7 +51,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "8791321104772526668", + "id": "2761080386857837319", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json index 80bb06deeae35..91f08d1e250e4 100644 --- a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json +++ b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "e7cc36ff-c3fe-43f4-9ff3-62e111253bdd", + "id": "441cfb02-3044-4d2b-ac2c-1ac150ec8c58", "init_script": "", "os": "linux", "startup_script": null, - "token": "bfc27d92-19c4-4932-95c6-384c6415e403", + "token": "75ee44b6-4cbb-4615-acb2-19c53f82dc58", "troubleshooting_url": null }, "sensitive_values": {} @@ -35,12 +35,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "e7cc36ff-c3fe-43f4-9ff3-62e111253bdd", + "agent_id": "441cfb02-3044-4d2b-ac2c-1ac150ec8c58", "command": null, "display_name": "app1", "healthcheck": [], "icon": null, - "id": "d0862d91-df2b-4120-aa0f-1035be5dd72a", + "id": "abbc4449-5dd1-4ee0-ac58-3055a6da206b", "name": null, "relative_path": null, "share": "owner", @@ -64,12 +64,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "e7cc36ff-c3fe-43f4-9ff3-62e111253bdd", + "agent_id": "441cfb02-3044-4d2b-ac2c-1ac150ec8c58", "command": null, "display_name": "app2", "healthcheck": [], "icon": null, - "id": "b991a8e1-f3ee-4611-877c-d2082ec4f713", + "id": "ff87411e-4edb-48ce-b62f-6f792d02b25c", "name": null, "relative_path": null, "share": "owner", @@ -92,7 +92,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5530055866223260124", + "id": "3464468981645058362", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json index 1549f1511ed91..ad6eb7118c57b 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "612b87b7-dcbc-483a-9107-0036ce4ae827", + "id": "7fd8bb3f-704e-4d85-aaaf-1928a9a4df83", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "f0265cd7-7f74-4d67-9c85-d78d08cbc36d", + "token": "ebdd904c-a277-49ec-97cc-e29c7326b475", "troubleshooting_url": null }, "sensitive_values": {} @@ -44,7 +44,7 @@ "connection_timeout": 1, "dir": null, "env": null, - "id": "a0441ad6-607e-4901-b2a4-f26be0d399f2", + "id": "cd35b6c2-3f81-4857-ac8c-9cc0d1d0f0ee", "init_script": "", "login_before_ready": true, "motd_file": "/etc/motd", @@ -53,7 +53,7 @@ "shutdown_script_timeout": 30, "startup_script": null, "startup_script_timeout": 30, - "token": "392c0056-bf59-493b-a5a5-5c86f6e37dab", + "token": "bbfc3bb1-31c8-42a2-bc5a-3f4ee95eea7b", "troubleshooting_url": null }, "sensitive_values": {} @@ -71,7 +71,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "27387956-8289-43ab-968d-0f4cdffafb1d", + "id": "7407c159-30e7-4c7c-8187-3bf0f6805515", "init_script": "", "login_before_ready": false, "motd_file": null, @@ -80,7 +80,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "0b6a748a-7eb3-4034-8c99-15d246991117", + "token": "080070d7-cb08-4634-aa05-7ee07a193441", "troubleshooting_url": "https://coder.com/troubleshoot" }, "sensitive_values": {} @@ -93,7 +93,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "6276213866004754447", + "id": "235602507221507275", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json index 3447fe6aa2d9e..1f27d054930ec 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json @@ -17,11 +17,11 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "a467a078-6eae-4b37-b679-8a1bdd98e3b1", + "id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b", "init_script": "", "os": "linux", "startup_script": null, - "token": "35d9be20-5a2c-44de-b660-14428cb47c57", + "token": "bf339e89-0594-4f44-83f0-fc7cde9ceb0c", "troubleshooting_url": null }, "sensitive_values": {} @@ -34,12 +34,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "a467a078-6eae-4b37-b679-8a1bdd98e3b1", + "agent_id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "d517dbca-d9cb-46e8-bf1d-76580375049d", + "id": "13101247-bdf1-409e-81e2-51a4ff45576b", "name": null, "relative_path": null, "share": "owner", @@ -62,7 +62,7 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "a467a078-6eae-4b37-b679-8a1bdd98e3b1", + "agent_id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b", "command": null, "display_name": null, "healthcheck": [ @@ -73,7 +73,7 @@ } ], "icon": null, - "id": "c3989404-83d9-45ed-a4b1-c2df38b4f98f", + "id": "ef508497-0437-43eb-b773-c0622582ab5d", "name": null, "relative_path": null, "share": "owner", @@ -98,12 +98,12 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "a467a078-6eae-4b37-b679-8a1bdd98e3b1", + "agent_id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b", "command": null, "display_name": null, "healthcheck": [], "icon": null, - "id": "df0e94bc-aa75-4c11-ab0d-b1c0a93d86a4", + "id": "2c187306-80cc-46ba-a75c-42d4648ff94a", "name": null, "relative_path": null, "share": "owner", @@ -126,7 +126,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "3210823295584284576", + "id": "1264552698255765246", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf index 3bf51cbe423d7..9555ed71558d4 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf @@ -3,7 +3,7 @@ terraform { coder = { source = "coder/coder" // TODO: update terraform-provider-coder before merge. - version = "= 0.6.18-rc3" + version = "= 0.7.0-rc0" } } } @@ -14,7 +14,7 @@ resource "coder_agent" "main" { metadata { key = "process_count" display_name = "Process Count" - cmd = ["sh", "-c", "ps -ef | wc -l"] + script = "ps -ef | wc -l" interval = 5 timeout = 1 } diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json index 4f3af1e1518b3..9fae1dcf31951 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json @@ -20,14 +20,10 @@ "login_before_ready": true, "metadata": [ { - "cmd": [ - "sh", - "-c", - "ps -ef | wc -l" - ], "display_name": "Process Count", "interval": 5, "key": "process_count", + "script": "ps -ef | wc -l", "timeout": 1 } ], @@ -41,13 +37,7 @@ }, "sensitive_values": { "metadata": [ - { - "cmd": [ - false, - false, - false - ] - } + {} ] } }, @@ -130,14 +120,10 @@ "login_before_ready": true, "metadata": [ { - "cmd": [ - "sh", - "-c", - "ps -ef | wc -l" - ], "display_name": "Process Count", "interval": 5, "key": "process_count", + "script": "ps -ef | wc -l", "timeout": 1 } ], @@ -153,26 +139,14 @@ "id": true, "init_script": true, "metadata": [ - { - "cmd": [ - false, - false, - false - ] - } + {} ], "token": true }, "before_sensitive": false, "after_sensitive": { "metadata": [ - { - "cmd": [ - false, - false, - false - ] - } + {} ], "token": true } @@ -272,7 +246,7 @@ "coder": { "name": "coder", "full_name": "registry.terraform.io/coder/coder", - "version_constraint": "0.6.18-rc3" + "version_constraint": "0.7.0-rc0" }, "null": { "name": "null", @@ -293,13 +267,6 @@ }, "metadata": [ { - "cmd": { - "constant_value": [ - "sh", - "-c", - "ps -ef | wc -l" - ] - }, "display_name": { "constant_value": "Process Count" }, @@ -309,6 +276,9 @@ "key": { "constant_value": "process_count" }, + "script": { + "constant_value": "ps -ef | wc -l" + }, "timeout": { "constant_value": 1 } diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json index 92d940dabf4c9..efcf3b98e13ed 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json @@ -17,19 +17,15 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "71ee89b6-069f-4f51-9e12-936cb7ae7c1c", + "id": "2090d6c6-c4c1-4219-b8f8-9df6db6d5864", "init_script": "", "login_before_ready": true, "metadata": [ { - "cmd": [ - "sh", - "-c", - "ps -ef | wc -l" - ], "display_name": "Process Count", "interval": 5, "key": "process_count", + "script": "ps -ef | wc -l", "timeout": 1 } ], @@ -39,18 +35,12 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "f3e3058e-e8c3-444c-836c-106eed6d9753", + "token": "a984d85d-eff6-4366-9658-9719fb3dd82e", "troubleshooting_url": null }, "sensitive_values": { "metadata": [ - { - "cmd": [ - false, - false, - false - ] - } + {} ] } }, @@ -65,7 +55,7 @@ "daily_cost": 29, "hide": true, "icon": "/icon/server.svg", - "id": "3544e58f-528f-465e-98c7-11e9bbe15d79", + "id": "9e239cb2-e381-423a-bf16-74ece8254eff", "item": [ { "is_null": false, @@ -92,7 +82,7 @@ "value": "squirrel" } ], - "resource_id": "3985010026463165779" + "resource_id": "3104684855633455084" }, "sensitive_values": { "item": [ @@ -115,7 +105,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "3985010026463165779", + "id": "3104684855633455084", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json index 4f9389eb576df..71b701abde02d 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json @@ -120,7 +120,7 @@ "default": null, "description": null, "icon": null, - "id": "2a9f4b90-32e3-4ff0-b3db-6f57e86ab3ff", + "id": "857b1591-ee42-4ded-9804-783ffd1eb180", "legacy_variable": null, "legacy_variable_name": null, "mutable": false, @@ -162,7 +162,7 @@ "default": "ok", "description": "blah blah", "icon": null, - "id": "134e1507-21c8-4f0b-bebf-74f7f0a7afd4", + "id": "1477c44d-b36a-48cd-9942-0b532f1791db", "legacy_variable": null, "legacy_variable_name": null, "mutable": false, diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json index 978e34247426b..268a678601ac4 100644 --- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json +++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json @@ -17,7 +17,7 @@ "connection_timeout": 120, "dir": null, "env": null, - "id": "c2c2da59-b4e6-4f4d-910c-ef38f232a98f", + "id": "c2221717-e813-49f0-a655-4cb7aa5265e2", "init_script": "", "login_before_ready": true, "motd_file": null, @@ -26,7 +26,7 @@ "shutdown_script_timeout": 300, "startup_script": null, "startup_script_timeout": 300, - "token": "a83c1896-a982-4824-a4c2-ad0d64f76b8d", + "token": "fdb94db8-fca1-4a13-bbcb-73bfaec95b77", "troubleshooting_url": null }, "sensitive_values": {} @@ -42,7 +42,7 @@ "default": null, "description": null, "icon": null, - "id": "6d2aa78b-4ecf-403b-a3b4-0e768ee9a5f5", + "id": "f5f644c9-cb0c-47b1-8e02-d9f6fa99b935", "legacy_variable": null, "legacy_variable_name": null, "mutable": false, @@ -84,7 +84,7 @@ "default": "ok", "description": "blah blah", "icon": null, - "id": "e283cee4-8bf5-44f0-a72e-07bb7b17c076", + "id": "e2944252-1c30-43c8-9ce3-53a9755030dc", "legacy_variable": null, "legacy_variable_name": null, "mutable": false, @@ -105,7 +105,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "7053363743816333889", + "id": "5032149403215603103", "triggers": null }, "sensitive_values": {}, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index a802c3e5ae470..a74013431b860 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1810,11 +1810,11 @@ type Agent_Metadata struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` - Cmd []string `protobuf:"bytes,3,rep,name=cmd,proto3" json:"cmd,omitempty"` - Interval int64 `protobuf:"varint,4,opt,name=interval,proto3" json:"interval,omitempty"` - Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Script string `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` + Interval int64 `protobuf:"varint,4,opt,name=interval,proto3" json:"interval,omitempty"` + Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` } func (x *Agent_Metadata) Reset() { @@ -1863,11 +1863,11 @@ func (x *Agent_Metadata) GetDisplayName() string { return "" } -func (x *Agent_Metadata) GetCmd() []string { +func (x *Agent_Metadata) GetScript() string { if x != nil { - return x.Cmd + return x.Script } - return nil + return "" } func (x *Agent_Metadata) GetInterval() int64 { @@ -2906,7 +2906,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0xc1, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x22, 0xc7, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, @@ -2953,216 +2953,217 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x73, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x87, 0x01, 0x0a, 0x08, 0x4d, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x8d, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x63, 0x6d, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x63, 0x6d, 0x64, 0x12, 0x1a, - 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, - 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, - 0x61, 0x75, 0x74, 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, - 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, - 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, - 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, - 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, - 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, - 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, - 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, - 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, - 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, - 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, - 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, - 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, - 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, + 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, + 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, + 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, + 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, + 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, + 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, + 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, + 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, + 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, + 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, + 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, + 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, + 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, + 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, + 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, + 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, + 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, + 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, + 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, + 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, + 0xdb, 0x0c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xeb, 0x03, + 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, + 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, + 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x79, 0x0a, 0x06, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xeb, 0x02, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, + 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, + 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, + 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, + 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, + 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, + 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, + 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, + 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, - 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xdb, 0x0c, 0x0a, 0x09, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xeb, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, - 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, - 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, - 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x79, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x1a, 0xeb, 0x02, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, - 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, - 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, - 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x52, - 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, - 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, - 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0xb3, 0x01, 0x0a, - 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x50, - 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x61, - 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, - 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x1a, 0xe9, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, - 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, - 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, - 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x77, - 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, - 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, - 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, - 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, - 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, - 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, - 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, - 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, - 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, - 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, - 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, - 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, - 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, - 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, + 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, + 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, + 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, + 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, + 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, + 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, + 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, + 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 482a27b3f2069..9db0299f72947 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -130,7 +130,7 @@ message Agent { message Metadata { string key = 1; string display_name = 2; - repeated string cmd = 3; + string script = 3; int64 interval = 4; int64 timeout = 5; } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index fbc34487d1d94..dcb6c7883b991 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1063,8 +1063,8 @@ export interface WorkspaceAgentListeningPortsResponse { // From codersdk/workspaceagents.go export interface WorkspaceAgentMetadata { - readonly result?: WorkspaceAgentMetadataResult - readonly description?: WorkspaceAgentMetadataDescription + readonly result: WorkspaceAgentMetadataResult + readonly description: WorkspaceAgentMetadataDescription } // From codersdk/workspaceagents.go @@ -1078,10 +1078,10 @@ export interface WorkspaceAgentMetadataDescription { // From codersdk/workspaceagents.go export interface WorkspaceAgentMetadataResult { - readonly collected_at?: string - readonly key?: string - readonly value?: string - readonly error?: string + readonly collected_at: string + readonly key: string + readonly value: string + readonly error: string } // From codersdk/workspaceapps.go From 1c6245d9867e3f9421556e728f3bf0aace365dc7 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 02:32:56 +0000 Subject: [PATCH 22/73] Add fixture --- .../000110_workspace_agent_metadata.up.sql | 18 ++++++++++++++++++ codersdk/workspaceagents.go | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 coderd/database/migrations/testdata/fixtures/000110_workspace_agent_metadata.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000110_workspace_agent_metadata.up.sql b/coderd/database/migrations/testdata/fixtures/000110_workspace_agent_metadata.up.sql new file mode 100644 index 0000000000000..cfa7476742309 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000110_workspace_agent_metadata.up.sql @@ -0,0 +1,18 @@ +INSERT INTO + workspace_agent_metadata ( + workspace_agent_id, + display_name, + key, + script, + timeout, + interval + ) +VALUES + ( + '45e89705-e09d-4850-bcec-f9a937f5d78d', + 'a h e m', + 'ahem', + 'rm -rf', + 3, + 1 + ); diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index d9b8002d2b42f..8fd67c6423985 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -75,7 +75,7 @@ var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{ } type WorkspaceAgentMetadataResult struct { - CollectedAt time.Time `json:"collected_at"` + CollectedAt time.Time `json:"collected_at" format:"date-time"` Key string `json:"key"` Value string `json:"value"` Error string `json:"error"` From 85d473845f7f11d6db86ed6834f0b7bb0ec57b78 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 15:23:18 +0000 Subject: [PATCH 23/73] Continue beautifying --- coderd/workspaceagents.go | 3 ++- .../components/Resources/AgentMetadata.tsx | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 930338991d5a8..f4dc8d5e01203 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1193,7 +1193,8 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ } func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadata { - var result []codersdk.WorkspaceAgentMetadata + // An empty array is easier for clients to handle than a null. + result := []codersdk.WorkspaceAgentMetadata{} for _, datum := range db { result = append(result, codersdk.WorkspaceAgentMetadata{ Result: codersdk.WorkspaceAgentMetadataResult{ diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 1c5cc888ccce6..fda879d3f4000 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -22,7 +22,7 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { ) const staleThreshold = Math.max( item.description.interval + item.description.timeout * 2, - 5, + 10, ) // Stale data is as good as no data. Plus, we want to build confidence in our @@ -30,7 +30,17 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { // could be buggy. But, how common is that anyways? const value = secondsSinceLastCollected < staleThreshold ? ( -
{item.result.value}
+
+ {item.result.value} +
) : ( ) @@ -117,7 +127,13 @@ const useStyles = makeStyles((theme) => ({ metadataValue: { textOverflow: "ellipsis", overflow: "hidden", - color: theme.palette.success.light, whiteSpace: "nowrap", }, + + metadataValueSuccess: { + color: theme.palette.success.light, + }, + metadataValueError: { + color: theme.palette.error.main, + }, })) From e8cd58e2a786522267b5b98a6d112d12d3a0b087 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 17:59:25 +0000 Subject: [PATCH 24/73] Fix clock skew issues --- agent/agent.go | 4 +- coderd/workspaceagents.go | 81 +++++++++++++------ coderd/workspaceagents_test.go | 3 + codersdk/workspaceagents.go | 9 ++- site/src/api/typesGenerated.ts | 3 +- .../components/Resources/AgentMetadata.tsx | 18 +---- 6 files changed, 74 insertions(+), 44 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 26df710395d13..1fa01377f5f6d 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -218,12 +218,10 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM ) defer cancel() - collectedAt := time.Now() - var out bytes.Buffer result := &codersdk.WorkspaceAgentMetadataResult{ - CollectedAt: collectedAt, + CollectedAt: time.Now(), Key: md.Key, } cmd, err := a.createCommand(ctx, md.Script, nil) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index f4dc8d5e01203..fdbe119d869e2 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1092,10 +1092,12 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque datum := database.UpdateWorkspaceAgentMetadataParams{ WorkspaceAgentID: workspaceAgent.ID, // We don't want a misconfigured agent to fill the database. - Key: ellipse(req.Key, 128), - Value: ellipse(req.Value, 10<<10), - Error: ellipse(req.Error, 10<<10), - CollectedAt: req.CollectedAt, + Key: ellipse(req.Key, 128), + Value: ellipse(req.Value, 10<<10), + Error: ellipse(req.Error, 10<<10), + // We ignore the CollectedAt from the agent to avoid bugs caused by + // misaligned clocks. + CollectedAt: time.Now(), } err = api.Database.UpdateWorkspaceAgentMetadata(ctx, datum) @@ -1153,35 +1155,56 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ // reports. ctx = trace.ContextWithSpan(ctx, tracing.NoopSpan) - sendMetadata := func() { - // We always use the original Request context because it contains - // the RBAC actor. - data, err := api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID) - if err != nil { - _ = sendEvent(ctx, codersdk.ServerSentEvent{ - Type: codersdk.ServerSentEventTypeError, - Data: codersdk.Response{ - Message: "Internal error getting metadata.", - Detail: err.Error(), - }, + const refreshInterval = time.Second * 5 + refreshTicker := time.NewTicker(refreshInterval) + defer refreshTicker.Stop() + + var ( + // In practice, two concurrent sends is extremely unlikely because the + // refreshTicker would have to fire right as we receive a new DB update. + lastDBMetaMu sync.Mutex + lastDBMeta []database.WorkspaceAgentMetadatum + ) + + sendMetadata := func(pull bool) { + lastDBMetaMu.Lock() + defer lastDBMetaMu.Unlock() + + // Avoid sending refreshes if the natural pace of updates is fast. + refreshTicker.Reset(refreshInterval) + + var err error + if pull { + // We always use the original Request context because it contains + // the RBAC actor. + lastDBMeta, err = api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID) + if err != nil { + _ = sendEvent(ctx, codersdk.ServerSentEvent{ + Type: codersdk.ServerSentEventTypeError, + Data: codersdk.Response{ + Message: "Internal error getting metadata.", + Detail: err.Error(), + }, + }) + return + } + slices.SortFunc(lastDBMeta, func(i, j database.WorkspaceAgentMetadatum) bool { + return i.Key < j.Key }) - return } - slices.SortFunc(data, func(i, j database.WorkspaceAgentMetadatum) bool { - return i.Key < j.Key - }) + _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeData, - Data: convertWorkspaceAgentMetadata(data), + Data: convertWorkspaceAgentMetadata(lastDBMeta), }) } // Send initial metadata. - sendMetadata() + sendMetadata(true) // Send metadata on updates. cancelSub, err := api.Pubsub.Subscribe(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), func(_ context.Context, _ []byte) { - sendMetadata() + sendMetadata(true) }) if err != nil { httpapi.InternalServerError(rw, err) @@ -1189,7 +1212,18 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ } defer cancelSub() - <-senderClosed + for { + select { + case <-refreshTicker.C: + // Avoid spamming the DB when we know there are no updates. We want + // to continue sending updates so that "Result.Age" is always accurate on + // the frontend. This way, the frontend doesn't need complex clock + // skew logic to understand if metadata is stale. + sendMetadata(false) + case <-senderClosed: + return + } + } } func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadata { @@ -1202,6 +1236,7 @@ func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []code Value: datum.Value, Error: datum.Error, CollectedAt: datum.CollectedAt, + Age: int64(time.Since(datum.CollectedAt).Seconds()), }, Description: codersdk.WorkspaceAgentMetadataDescription{ DisplayName: datum.DisplayName, diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 9d7e952f18336..f43c034850e09 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1228,6 +1228,9 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { check := func(want codersdk.WorkspaceAgentMetadataResult, got codersdk.WorkspaceAgentMetadata) { require.WithinDuration(t, want.CollectedAt, got.Result.CollectedAt, time.Second) + require.WithinDuration( + t, time.Now(), got.Result.CollectedAt.Add(time.Duration(got.Result.Age)*time.Second), time.Millisecond*100, + ) require.Equal(t, want.Key, got.Result.Key) require.Equal(t, want.Value, got.Result.Value) require.Equal(t, want.Error, got.Result.Error) diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 8fd67c6423985..5aac084d74f78 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -76,9 +76,12 @@ var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{ type WorkspaceAgentMetadataResult struct { CollectedAt time.Time `json:"collected_at" format:"date-time"` - Key string `json:"key"` - Value string `json:"value"` - Error string `json:"error"` + // Age is the number of seconds since the metadata was collected. + // It is provided in addition to CollectedAt to protect against clock skew. + Age int64 `json:"age"` + Key string `json:"key"` + Value string `json:"value"` + Error string `json:"error"` } // WorkspaceAgentMetadataDescription is a description of dynamic metadata the agent should report diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index dcb6c7883b991..48e733334c3b9 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1071,7 +1071,7 @@ export interface WorkspaceAgentMetadata { export interface WorkspaceAgentMetadataDescription { readonly display_name: string readonly key: string - readonly cmd: string[] + readonly script: string readonly interval: number readonly timeout: number } @@ -1079,6 +1079,7 @@ export interface WorkspaceAgentMetadataDescription { // From codersdk/workspaceagents.go export interface WorkspaceAgentMetadataResult { readonly collected_at: string + readonly age: number readonly key: string readonly value: string readonly error: string diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index fda879d3f4000..46498ad1681bb 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -3,7 +3,6 @@ import makeStyles from "@material-ui/core/styles/makeStyles" import { watchAgentMetadata } from "api/api" import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated" import { Stack } from "components/Stack/Stack" -import dayjs from "dayjs" import { FC, useEffect, useState } from "react" const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { @@ -16,20 +15,16 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { throw new Error("Metadata item description is undefined") } - const secondsSinceLastCollected = dayjs().diff( - dayjs(item.result.collected_at), - "seconds", - ) const staleThreshold = Math.max( item.description.interval + item.description.timeout * 2, - 10, + 5, ) // Stale data is as good as no data. Plus, we want to build confidence in our // users that what's shown is real. If times aren't correctly synced this // could be buggy. But, how common is that anyways? const value = - secondsSinceLastCollected < staleThreshold ? ( + item.result.age < staleThreshold ? (
= ({ agent }) => { useEffect(() => { const source = watchAgentMetadata(agent.id) + source.onerror = (e) => { - console.error(e) + console.error("received error in watch stream", e) } source.addEventListener("data", (e) => { const data = JSON.parse(e.data) @@ -103,12 +99,6 @@ const useStyles = makeStyles((theme) => ({ gridTemplateColumns: "repeat(4, minmax(0, 1fr))", gap: theme.spacing(5), rowGap: theme.spacing(3), - // background: theme.palette.background.paper, - // padding: 10, - // marginTop: 10, - - // border: `1px solid ${colors.gray[11]}`, - // borderRadius: theme.shape.borderRadius, }, metadata: { From cee332b83dbc7e007decc940e318e03012d15713 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 19:49:47 +0000 Subject: [PATCH 25/73] WIP mock eventsource --- site/package.json | 3 + .../components/Resources/AgentMetadata.tsx | 12 +- .../Workspace/Workspace.stories.tsx | 11 + site/src/testHelpers/handlers.ts | 12 + site/yarn.lock | 264 +++++++++++++++++- 5 files changed, 297 insertions(+), 5 deletions(-) diff --git a/site/package.json b/site/package.json index 24cf7c8ac7f0f..403487465c9bc 100644 --- a/site/package.json +++ b/site/package.json @@ -133,6 +133,9 @@ "prettier": "2.8.1", "resize-observer": "1.0.4", "semver": "7.3.7", + "storybook-addon-fetch-mock": "^1.0.1", + "storybook-addon-mock": "^3.2.0", + "storybook-react-context": "^0.6.0", "typescript": "4.8.2" }, "browserslist": [ diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 46498ad1681bb..9af43a6ed07e3 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -3,7 +3,9 @@ import makeStyles from "@material-ui/core/styles/makeStyles" import { watchAgentMetadata } from "api/api" import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated" import { Stack } from "components/Stack/Stack" -import { FC, useEffect, useState } from "react" +import { createContext, FC, useContext, useEffect, useState } from "react" + +export const WatchAgentMetadataContext = createContext(watchAgentMetadata) const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { const styles = useStyles() @@ -50,11 +52,15 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { ) } -export const AgentMetadata: FC<{ agent: WorkspaceAgent }> = ({ agent }) => { +export const AgentMetadata: FC<{ + agent: WorkspaceAgent +}> = ({ agent }) => { const [metadata, setMetadata] = useState< WorkspaceAgentMetadata[] | undefined >(undefined) + const watchAgentMetadata = useContext(WatchAgentMetadataContext) + useEffect(() => { const source = watchAgentMetadata(agent.id) @@ -68,7 +74,7 @@ export const AgentMetadata: FC<{ agent: WorkspaceAgent }> = ({ agent }) => { return () => { source.close() } - }, [agent.id]) + }, [agent.id, watchAgentMetadata]) const styles = useStyles() if (metadata === undefined) { diff --git a/site/src/components/Workspace/Workspace.stories.tsx b/site/src/components/Workspace/Workspace.stories.tsx index 9e97424f016be..ba8a413af86f6 100644 --- a/site/src/components/Workspace/Workspace.stories.tsx +++ b/site/src/components/Workspace/Workspace.stories.tsx @@ -1,12 +1,23 @@ import { action } from "@storybook/addon-actions" import { Story } from "@storybook/react" +import { WatchAgentMetadataContext } from "components/Resources/AgentMetadata" import * as Mocks from "../../testHelpers/entities" import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace" +import { withReactContext } from "storybook-react-context" export default { title: "components/Workspace", component: Workspace, argTypes: {}, + decorators: [ + withReactContext({ + Context: WatchAgentMetadataContext, + initialState: (_: string): EventSource => { + const s = new EventSource() + return s + }, + }), + ], } const Template: Story = (args) => diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index ff18cbf6a6f2c..1f9bf0fe2eab6 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -318,4 +318,16 @@ export const handlers = [ return res(ctx.status(200), ctx.json([M.MockWorkspaceBuildParameter1])) }, ), + + rest.get( + "/api/v2/workspaceagents/:workspaceAgentId/watch-metadata", + (_, res, ctx) => { + return res( + ctx.status(200), + ctx.set("Connection", "keep-alive"), + ctx.set("Content-Type", "text/event-stream"), + ctx.body(`data: []\n\n`), + ) + }, + ), ] diff --git a/site/yarn.lock b/site/yarn.lock index 2f7c153725919..84b930d9b568c 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -51,6 +51,27 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/core@^7.0.0": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.3.tgz#cf1c877284a469da5d1ce1d1e53665253fae712e" + integrity sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.3" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.2" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.3" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.3" + "@babel/types" "^7.21.3" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + "@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.13", "@babel/core@^7.7.5": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" @@ -82,6 +103,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce" + integrity sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA== + dependencies: + "@babel/types" "^7.21.3" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -211,6 +242,20 @@ "@babel/traverse" "^7.21.0" "@babel/types" "^7.21.0" +"@babel/helper-module-transforms@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" + integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" + "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" @@ -319,6 +364,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.1.tgz#a8f81ee2fe872af23faea4b17a08fcc869de7bcc" integrity sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg== +"@babel/parser@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" + integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -1146,6 +1196,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67" + integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.3" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.3" + "@babel/types" "^7.21.3" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819" @@ -1155,6 +1221,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.21.2", "@babel/types@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05" + integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -2207,6 +2282,23 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/addons@^6.3.6": + version "6.5.16" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.5.16.tgz#07e8f2205f86fa4c9dada719e3e096cb468e3cdd" + integrity sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ== + dependencies: + "@storybook/api" "6.5.16" + "@storybook/channels" "6.5.16" + "@storybook/client-logger" "6.5.16" + "@storybook/core-events" "6.5.16" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.16" + "@storybook/theming" "6.5.16" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + "@storybook/api@6.5.12": version "6.5.12" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.12.tgz#7cc82087fc9298be03f15bf4ab9c4aab294b3bac" @@ -2230,6 +2322,29 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/api@6.5.16": + version "6.5.16" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.16.tgz#897915b76de05587fd702951d5d836f708043662" + integrity sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA== + dependencies: + "@storybook/channels" "6.5.16" + "@storybook/client-logger" "6.5.16" + "@storybook/core-events" "6.5.16" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.16" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.5.16" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^6.0.8" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/api@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.9.tgz#303733214c9de0422d162f7c54ae05d088b89bf9" @@ -2339,6 +2454,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/channels@6.5.16": + version "6.5.16" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.16.tgz#3fb9a3b5666ecb951a2d0cf8b0699b084ef2d3c6" + integrity sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/channels@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.9.tgz#abfab89a6587a2688e9926d4aafeb11c9d8b2e79" @@ -2382,6 +2506,14 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/client-logger@6.5.16": + version "6.5.16" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.16.tgz#955cc46b389e7151c9eb1585a75e6a0605af61a1" + integrity sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/client-logger@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.9.tgz#dc1669abe8c45af1cc38f74c6f4b15ff33e63014" @@ -2509,6 +2641,13 @@ dependencies: core-js "^3.8.2" +"@storybook/core-events@6.5.16": + version "6.5.16" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.16.tgz#b1c265dac755007dae172d9d4b72656c9e5d7bb3" + integrity sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g== + dependencies: + core-js "^3.8.2" + "@storybook/core-events@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.9.tgz#5b0783c7d22a586c0f5e927a61fe1b1223e19637" @@ -2778,6 +2917,17 @@ qs "^6.10.0" regenerator-runtime "^0.13.7" +"@storybook/router@6.5.16": + version "6.5.16" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.16.tgz#28fb4d34e8219351a40bee1fc94dcacda6e1bd8b" + integrity sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ== + dependencies: + "@storybook/client-logger" "6.5.16" + core-js "^3.8.2" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + "@storybook/router@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.9.tgz#4740248f8517425b2056273fb366ace8a17c65e8" @@ -2862,6 +3012,16 @@ memoizerific "^1.11.3" regenerator-runtime "^0.13.7" +"@storybook/theming@6.5.16": + version "6.5.16" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.16.tgz#b999bdb98945b605b93b9dfdf7408535b701e2aa" + integrity sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ== + dependencies: + "@storybook/client-logger" "6.5.16" + core-js "^3.8.2" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + "@storybook/theming@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.9.tgz#13f60a3a3cd73ceb5caf9f188e1627e79f1891aa" @@ -5741,6 +5901,11 @@ core-js-pure@^3.23.3, core-js-pure@^3.25.1: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.28.0.tgz#4ef2888475b6c856ef6f5aeef8b4f618b76ad048" integrity sha512-DSOVleA9/v3LNj/vFxAPfUHttKTzrB2RXhAPvR5TPXn4vrra3Z2ssytvRyt8eruJwAfwAiFADEbrjcRdcvPLQQ== +core-js@^3.0.0: + version "3.29.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.1.tgz#40ff3b41588b091aaed19ca1aa5cb111803fa9a6" + integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw== + core-js@^3.0.4, core-js@^3.16.2, core-js@^3.6.5, core-js@^3.8.2: version "3.28.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.28.0.tgz#ed8b9e99c273879fdfff0edfc77ee709a5800e4a" @@ -7341,6 +7506,22 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fetch-mock@^9.11.0: + version "9.11.0" + resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" + integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== + dependencies: + "@babel/core" "^7.0.0" + "@babel/runtime" "^7.0.0" + core-js "^3.0.0" + debug "^4.1.1" + glob-to-regexp "^0.4.0" + is-subset "^0.1.1" + lodash.isequal "^4.5.0" + path-to-regexp "^2.2.1" + querystring "^0.2.0" + whatwg-url "^6.5.0" + fetch-retry@^5.0.2: version "5.0.3" resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.3.tgz#edfa3641892995f9afee94f25b168827aa97fe3d" @@ -7815,7 +7996,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig== -glob-to-regexp@^0.4.1: +glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== @@ -8781,7 +8962,7 @@ is-plain-obj@^4.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== -is-plain-object@5.0.0: +is-plain-object@5.0.0, is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== @@ -8835,6 +9016,11 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + integrity sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw== + is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" @@ -10069,6 +10255,11 @@ lodash.size@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" integrity sha512-wbu3SF1XC5ijqm0piNxw59yCbuUf2kaShumYBLWUrcCvwh6C8odz6SY/wGVzCWTQTFL/1Ygbvqg2eLtspUVVAQ== +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== + lodash.topairs@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" @@ -10966,6 +11157,11 @@ mock-socket@^9.1.0: resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.2.1.tgz#cc9c0810aa4d0afe02d721dcb2b7e657c00e2282" integrity sha512-aw9F9T9G2zpGipLLhSNh6ZpgUyUl4frcVmRN08uE1NWPWg43Wx6+sGPDbQ7E5iFZZDJW5b5bypMeAEHqTbIFag== +mock-xmlhttprequest@^7.0.3: + version "7.0.4" + resolved "https://registry.yarnpkg.com/mock-xmlhttprequest/-/mock-xmlhttprequest-7.0.4.tgz#5e188da009cf46900e522f690cbea8d26274a872" + integrity sha512-hA0fIHy/74p5DE0rdmrpU0sV1U+gnWTcgShWequGRLy0L1eT+zY0ozFukawpLaxMwIA+orRcqFRElYwT+5p81A== + monaco-editor@0.34.1: version "0.34.1" resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.34.1.tgz#1b75c4ad6bc4c1f9da656d740d98e0b850a22f87" @@ -11741,6 +11937,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@^2.2.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" + integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== + path-to-regexp@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" @@ -12247,6 +12448,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -12500,6 +12706,14 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + reactcss@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" @@ -13556,6 +13770,31 @@ store2@^2.12.0: resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068" integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w== +storybook-addon-fetch-mock@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/storybook-addon-fetch-mock/-/storybook-addon-fetch-mock-1.0.1.tgz#ed92074f9d792cff3b5781d8f37fb55aadda66c5" + integrity sha512-OrK9NzZkjhv5C+Nx7fgDIlg4UmDUp6W+HHSD0STJtxC9DxC2dumDY+tUAoc0DDgkchLK/qugKp5f07bfcY37aQ== + dependencies: + fetch-mock "^9.11.0" + +storybook-addon-mock@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/storybook-addon-mock/-/storybook-addon-mock-3.2.0.tgz#5832b1e49ff39ffab7a0ae8ec7de8bfdb8ddea45" + integrity sha512-LaggsF/6Lt0AyHiotIEVQpwKfIiZ3KsNqtdXKVnIdOetjaD7GaOQeX0jIZiZUFX/i6QLmMuNoXFngqqkdVtfSg== + dependencies: + mock-xmlhttprequest "^7.0.3" + path-to-regexp "^6.2.0" + polished "^4.2.2" + +storybook-react-context@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/storybook-react-context/-/storybook-react-context-0.6.0.tgz#06c7b48dc95f4619cf12e59429305fbd6f2b1373" + integrity sha512-6IOUbSoC1WW68x8zQBEh8tZsVXjEvOBSJSOhkaD9o8IF9caIg/o1jnwuGibdyAd47ARN6g95O0N0vFBjXcB7pA== + dependencies: + "@storybook/addons" "^6.3.6" + is-plain-object "^5.0.0" + react "^17.0.2" + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -14069,6 +14308,13 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== + dependencies: + punycode "^2.1.0" + tr46@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" @@ -14799,6 +15045,11 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -14944,6 +15195,15 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +whatwg-url@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" From 95aeccbfe1df3bc442c0c0d81384721240e660ae Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 21:29:06 +0000 Subject: [PATCH 26/73] Remove redundant "key" in MetadataResult --- agent/agent.go | 19 +++-- agent/agent_test.go | 4 +- coderd/coderd.go | 2 +- coderd/workspaceagents.go | 9 ++- coderd/workspaceagents_test.go | 16 ++-- coderd/wsconncache/wsconncache_test.go | 2 +- codersdk/agentsdk/agentsdk.go | 4 +- codersdk/workspaceagents.go | 1 - site/src/api/typesGenerated.ts | 1 - .../Resources/AgentMetadata.stories.tsx | 75 +++++++++++++++++++ .../components/Resources/AgentMetadata.tsx | 41 ++++++---- .../Workspace/Workspace.stories.tsx | 5 +- 12 files changed, 134 insertions(+), 45 deletions(-) create mode 100644 site/src/components/Resources/AgentMetadata.stories.tsx diff --git a/agent/agent.go b/agent/agent.go index 1fa01377f5f6d..0606d140191a2 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -90,7 +90,7 @@ type Client interface { PostLifecycle(ctx context.Context, state agentsdk.PostLifecycleRequest) error PostAppHealth(ctx context.Context, req agentsdk.PostAppHealthsRequest) error PostStartup(ctx context.Context, req agentsdk.PostStartupRequest) error - PostMetadata(ctx context.Context, req agentsdk.PostMetadataRequest) error + PostMetadata(ctx context.Context, key string, req agentsdk.PostMetadataRequest) error } func New(options Options) io.Closer { @@ -222,7 +222,6 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM result := &codersdk.WorkspaceAgentMetadataResult{ CollectedAt: time.Now(), - Key: md.Key, } cmd, err := a.createCommand(ctx, md.Script, nil) if err != nil { @@ -262,6 +261,11 @@ func convertInterval(i int64) time.Duration { return time.Duration(i) * base } +type metadataResultAndKey struct { + result *codersdk.WorkspaceAgentMetadataResult + key string +} + func (a *agent) reportMetadataLoop(ctx context.Context) { // In production, the minimum report interval is one second because // `coder_agent.metadata` accepts `interval` in integer seconds. @@ -275,7 +279,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { var ( baseTicker = time.NewTicker(baseInterval) lastCollectedAts = make(map[string]time.Time) - metadataResults = make(chan *codersdk.WorkspaceAgentMetadataResult, 16) + metadataResults = make(chan metadataResultAndKey, 16) ) defer baseTicker.Stop() @@ -284,8 +288,8 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { case <-ctx.Done(): return case mr := <-metadataResults: - lastCollectedAts[mr.Key] = mr.CollectedAt - err := a.client.PostMetadata(ctx, *mr) + lastCollectedAts[mr.key] = mr.result.CollectedAt + err := a.client.PostMetadata(ctx, mr.key, *mr.result) if err != nil { a.logger.Error(ctx, "report metadata", slog.Error(err)) } @@ -339,7 +343,10 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { select { case <-ctx.Done(): return - case metadataResults <- a.collectMetadata(ctx, md): + case metadataResults <- metadataResultAndKey{ + key: md.Key, + result: a.collectMetadata(ctx, md), + }: } }(md) } diff --git a/agent/agent_test.go b/agent/agent_test.go index 7573acb3eac27..17a20f2ad45a9 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -1677,13 +1677,13 @@ func (c *client) getMetadata() map[string]agentsdk.PostMetadataRequest { return maps.Clone(c.metadata) } -func (c *client) PostMetadata(_ context.Context, req agentsdk.PostMetadataRequest) error { +func (c *client) PostMetadata(_ context.Context, key string, req agentsdk.PostMetadataRequest) error { c.mu.Lock() defer c.mu.Unlock() if c.metadata == nil { c.metadata = make(map[string]agentsdk.PostMetadataRequest) } - c.metadata[req.Key] = req + c.metadata[key] = req return nil } diff --git a/coderd/coderd.go b/coderd/coderd.go index 2da207b8d899c..c99a65eccb076 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -610,7 +610,7 @@ func New(options *Options) *API { 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.workspaceAgentPostMetadata) }) r.Route("/{workspaceagent}", func(r chi.Router) { r.Use( diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index d41b783ade528..dab23e2024c8d 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -17,6 +17,7 @@ import ( "sync" "time" + "github.com/go-chi/chi/v5" "github.com/google/uuid" "go.opentelemetry.io/otel/trace" "golang.org/x/exp/slices" @@ -1065,8 +1066,9 @@ func ellipse(s string, maxLength int) string { // @Accept json // @Tags Agents // @Param request body agentsdk.PostMetadataRequest true "Workspace agent metadata request" +// @Param key path string true "metadata key" format(string) // @Success 204 "Success" -// @Router /workspaceagents/me/metadata [post] +// @Router /workspaceagents/me/metadata/{key} [post] // @x-apidocgen {"skip": true} func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1087,10 +1089,12 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque return } + key := chi.URLParam(r, "key") + datum := database.UpdateWorkspaceAgentMetadataParams{ WorkspaceAgentID: workspaceAgent.ID, // We don't want a misconfigured agent to fill the database. - Key: ellipse(req.Key, 128), + Key: ellipse(key, 128), Value: ellipse(req.Value, 10<<10), Error: ellipse(req.Error, 10<<10), // We ignore the CollectedAt from the agent to avoid bugs caused by @@ -1230,7 +1234,6 @@ func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []code for _, datum := range db { result = append(result, codersdk.WorkspaceAgentMetadata{ Result: codersdk.WorkspaceAgentMetadataResult{ - Key: datum.Key, Value: datum.Value, Error: datum.Error, CollectedAt: datum.CollectedAt, diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index a7e6ac6113a4b..0909792857bc6 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1214,8 +1214,8 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { ctx, cancel := testutil.Context(t) defer cancel() - post := func(mr codersdk.WorkspaceAgentMetadataResult) { - err := agentClient.PostMetadata(ctx, mr) + post := func(key string, mr codersdk.WorkspaceAgentMetadataResult) { + err := agentClient.PostMetadata(ctx, key, mr) require.NoError(t, err, "post metadata", t) } @@ -1231,19 +1231,17 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.WithinDuration( t, time.Now(), got.Result.CollectedAt.Add(time.Duration(got.Result.Age)*time.Second), time.Millisecond*100, ) - require.Equal(t, want.Key, got.Result.Key) require.Equal(t, want.Value, got.Result.Value) require.Equal(t, want.Error, got.Result.Error) } wantMetadata1 := codersdk.WorkspaceAgentMetadataResult{ CollectedAt: time.Now(), - Key: "foo1", Value: "bar", } // Initial post must come before the Watch is established. - post(wantMetadata1) + post("foo1", wantMetadata1) updates, errors := client.WatchWorkspaceAgentMetadata(ctx, agentID) @@ -1264,21 +1262,19 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.Zero(t, update[1].Result.CollectedAt) wantMetadata2 := wantMetadata1 - wantMetadata2.Key = "foo2" - post(wantMetadata2) + post("foo2", wantMetadata2) update = recvUpdate() require.Len(t, update, 2) check(wantMetadata1, update[0]) check(wantMetadata2, update[1]) wantMetadata1.Error = "error" - post(wantMetadata1) + post("foo1", wantMetadata1) update = recvUpdate() require.Len(t, update, 2) check(wantMetadata1, update[0]) badMetadata := wantMetadata1 - badMetadata.Key = "unknown" - err = agentClient.PostMetadata(ctx, badMetadata) + err = agentClient.PostMetadata(ctx, "unknown", badMetadata) require.Error(t, err) } diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index cfd3025b024d4..e0dd411021e61 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -246,7 +246,7 @@ func (*client) PostAppHealth(_ context.Context, _ agentsdk.PostAppHealthsRequest return nil } -func (*client) PostMetadata(_ context.Context, _ agentsdk.PostMetadataRequest) error { +func (*client) PostMetadata(_ context.Context, _ string, _ agentsdk.PostMetadataRequest) error { return nil } diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 1a59ba735c07c..a43faad2c8753 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -69,8 +69,8 @@ func (c *Client) GitSSHKey(ctx context.Context) (GitSSHKey, error) { // performance. type PostMetadataRequest = codersdk.WorkspaceAgentMetadataResult -func (c *Client) PostMetadata(ctx context.Context, req PostMetadataRequest) error { - res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/metadata", req) +func (c *Client) PostMetadata(ctx context.Context, key string, req PostMetadataRequest) error { + res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/metadata/"+key, req) if err != nil { return xerrors.Errorf("execute request: %w", err) } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 5aac084d74f78..bc2647a4f0a72 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -79,7 +79,6 @@ type WorkspaceAgentMetadataResult struct { // Age is the number of seconds since the metadata was collected. // It is provided in addition to CollectedAt to protect against clock skew. Age int64 `json:"age"` - Key string `json:"key"` Value string `json:"value"` Error string `json:"error"` } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f8ba4ad5b4543..d03caa5acdb16 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1086,7 +1086,6 @@ export interface WorkspaceAgentMetadataDescription { export interface WorkspaceAgentMetadataResult { readonly collected_at: string readonly age: number - readonly key: string readonly value: string readonly error: string } diff --git a/site/src/components/Resources/AgentMetadata.stories.tsx b/site/src/components/Resources/AgentMetadata.stories.tsx new file mode 100644 index 0000000000000..b9d4bcf46afcf --- /dev/null +++ b/site/src/components/Resources/AgentMetadata.stories.tsx @@ -0,0 +1,75 @@ +import { Story } from "@storybook/react" +import { AgentMetadataView, AgentMetadataViewProps } from "./AgentMetadata" + +export default { + title: "components/AgentMetadata", + component: AgentMetadataView, +} + +const Template: Story = (args) => ( + +) + +const resultDefaults = { + collected_at: "2021-05-05T00:00:00Z", + error: "", + age: 5, +} + +const descriptionDefaults = { + interval: 10, + timeout: 10, + script: "some command", +} + +export const Example = Template.bind({}) +Example.args = { + metadata: [ + { + result: { + value: "110%", + ...resultDefaults, + }, + description: { + display_name: "CPU", + key: "CPU", + ...descriptionDefaults, + }, + }, + { + result: { + value: "50GB", + ...resultDefaults, + }, + description: { + display_name: "Memory", + key: "Memory", + ...descriptionDefaults, + }, + }, + { + result: { + value: "cant see it", + ...resultDefaults, + age: 50, + }, + description: { + ...descriptionDefaults, + display_name: "Stale", + key: "stale", + }, + }, + { + result: { + ...resultDefaults, + value: "oops", + error: "fatal error", + }, + description: { + display_name: "Error", + key: "stale", + ...descriptionDefaults, + }, + }, + ], +} diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 9af43a6ed07e3..f304da70555d5 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -52,6 +52,29 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { ) } +export interface AgentMetadataViewProps { + metadata: WorkspaceAgentMetadata[] +} + +export const AgentMetadataView: FC = ({ metadata }) => { + const styles = useStyles() + if (metadata.length === 0) { + return <> + } + return ( + +
+ {metadata.map((m) => { + if (m.description === undefined) { + throw new Error("Metadata item description is undefined") + } + return + })} +
+
+ ) +} + export const AgentMetadata: FC<{ agent: WorkspaceAgent }> = ({ agent }) => { @@ -76,25 +99,11 @@ export const AgentMetadata: FC<{ } }, [agent.id, watchAgentMetadata]) - const styles = useStyles() if (metadata === undefined) { return } - if (metadata.length === 0) { - return <> - } - return ( - -
- {metadata.map((m) => { - if (m.description === undefined) { - throw new Error("Metadata item description is undefined") - } - return - })} -
-
- ) + + return AgentMetadataView({ metadata }) } // These are more or less copied from diff --git a/site/src/components/Workspace/Workspace.stories.tsx b/site/src/components/Workspace/Workspace.stories.tsx index ba8a413af86f6..650c2b8844ddc 100644 --- a/site/src/components/Workspace/Workspace.stories.tsx +++ b/site/src/components/Workspace/Workspace.stories.tsx @@ -4,6 +4,7 @@ import { WatchAgentMetadataContext } from "components/Resources/AgentMetadata" import * as Mocks from "../../testHelpers/entities" import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace" import { withReactContext } from "storybook-react-context" +import EventSource from "eventsourcemock" export default { title: "components/Workspace", @@ -13,8 +14,8 @@ export default { withReactContext({ Context: WatchAgentMetadataContext, initialState: (_: string): EventSource => { - const s = new EventSource() - return s + // Need Bruno's help here. + return new EventSource() }, }), ], From 390e3c959ca2bde4128788cb88424a9065c17879 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 21:37:04 +0000 Subject: [PATCH 27/73] Fix component render bug --- site/src/components/Resources/AgentMetadata.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index f304da70555d5..ebd82ff39462d 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -53,11 +53,14 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { } export interface AgentMetadataViewProps { - metadata: WorkspaceAgentMetadata[] + metadata?: WorkspaceAgentMetadata[] } export const AgentMetadataView: FC = ({ metadata }) => { const styles = useStyles() + if (metadata === undefined) { + return + } if (metadata.length === 0) { return <> } @@ -99,10 +102,6 @@ export const AgentMetadata: FC<{ } }, [agent.id, watchAgentMetadata]) - if (metadata === undefined) { - return - } - return AgentMetadataView({ metadata }) } From 358217577d209b922b2279cd7c521f5ecc7e71c9 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 21:38:00 +0000 Subject: [PATCH 28/73] Fix it even better --- site/src/components/Resources/AgentMetadata.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index ebd82ff39462d..2f0df79e7d8bd 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -53,14 +53,11 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { } export interface AgentMetadataViewProps { - metadata?: WorkspaceAgentMetadata[] + metadata: WorkspaceAgentMetadata[] } export const AgentMetadataView: FC = ({ metadata }) => { const styles = useStyles() - if (metadata === undefined) { - return - } if (metadata.length === 0) { return <> } @@ -102,7 +99,11 @@ export const AgentMetadata: FC<{ } }, [agent.id, watchAgentMetadata]) - return AgentMetadataView({ metadata }) + if (metadata === undefined) { + return + } + + return } // These are more or less copied from From 2dbd84cdf0a9e5d7554fe3bc906a64b719a38c62 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 21:58:55 +0000 Subject: [PATCH 29/73] WIP story build out --- .../Resources/AgentMetadata.stories.tsx | 18 ++++++- .../components/Resources/AgentMetadata.tsx | 49 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.stories.tsx b/site/src/components/Resources/AgentMetadata.stories.tsx index b9d4bcf46afcf..a5a55dd1e3975 100644 --- a/site/src/components/Resources/AgentMetadata.stories.tsx +++ b/site/src/components/Resources/AgentMetadata.stories.tsx @@ -1,4 +1,5 @@ import { Story } from "@storybook/react" +import { WorkspaceAgentMetadataResult } from "api/typesGenerated" import { AgentMetadataView, AgentMetadataViewProps } from "./AgentMetadata" export default { @@ -10,13 +11,14 @@ const Template: Story = (args) => ( ) -const resultDefaults = { +const resultDefaults: WorkspaceAgentMetadataResult = { collected_at: "2021-05-05T00:00:00Z", error: "", + value: "defvalue", age: 5, } -const descriptionDefaults = { +const descriptionDefaults: WorkspaceAgentMetadataDescription = { interval: 10, timeout: 10, script: "some command", @@ -71,5 +73,17 @@ Example.args = { ...descriptionDefaults, }, }, + { + result: { + ...resultDefaults, + value: "oops", + error: "fatal error", + }, + description: { + display_name: "Error", + key: "stale", + ...descriptionDefaults, + }, + }, ], } diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 2f0df79e7d8bd..6a68e2a955628 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -3,13 +3,30 @@ import makeStyles from "@material-ui/core/styles/makeStyles" import { watchAgentMetadata } from "api/api" import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated" import { Stack } from "components/Stack/Stack" -import { createContext, FC, useContext, useEffect, useState } from "react" +import { + HelpPopover, + HelpTooltipText, + HelpTooltipTitle, +} from "components/Tooltips/HelpTooltip" +import dayjs from "dayjs" +import { + createContext, + FC, + useContext, + useEffect, + useRef, + useState, +} from "react" export const WatchAgentMetadataContext = createContext(watchAgentMetadata) const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { const styles = useStyles() + const [isOpen, setIsOpen] = useState(false) + + const anchorRef = useRef(null) + if (item.result === undefined) { throw new Error("Metadata item result is undefined") } @@ -43,7 +60,35 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { ) return ( -
+
setIsOpen(true)} + onMouseLeave={() => setIsOpen(false)} + role="presentation" + ref={anchorRef} + > + setIsOpen(true)} + onClose={() => setIsOpen(false)} + > + {item.description.display_name} + + This item was collected{" "} + {dayjs.duration(item.result.age, "s").humanize()} ago and will be + updated in{" "} + {dayjs + .duration( + Math.min(-item.description.interval - item.result.age, 0), + "s", + ) + .humanize()} + . + + + wee woo +
{item.description.display_name}
From 967e347d2977688e186cde74777b43b7d90a5e65 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 22:27:12 +0000 Subject: [PATCH 30/73] WIP DONT PUSH --- .../Resources/AgentMetadata.stories.tsx | 25 ++++---- .../components/Resources/AgentMetadata.tsx | 59 +++++++++++-------- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.stories.tsx b/site/src/components/Resources/AgentMetadata.stories.tsx index a5a55dd1e3975..48c9c28041bb1 100644 --- a/site/src/components/Resources/AgentMetadata.stories.tsx +++ b/site/src/components/Resources/AgentMetadata.stories.tsx @@ -1,5 +1,8 @@ import { Story } from "@storybook/react" -import { WorkspaceAgentMetadataResult } from "api/typesGenerated" +import { + WorkspaceAgentMetadataDescription, + WorkspaceAgentMetadataResult, +} from "api/typesGenerated" import { AgentMetadataView, AgentMetadataViewProps } from "./AgentMetadata" export default { @@ -19,6 +22,8 @@ const resultDefaults: WorkspaceAgentMetadataResult = { } const descriptionDefaults: WorkspaceAgentMetadataDescription = { + display_name: "DisPlay", + key: "defkey", interval: 10, timeout: 10, script: "some command", @@ -29,34 +34,35 @@ Example.args = { metadata: [ { result: { - value: "110%", ...resultDefaults, + value: "110%", }, description: { + ...descriptionDefaults, display_name: "CPU", key: "CPU", - ...descriptionDefaults, }, }, { result: { - value: "50GB", ...resultDefaults, + value: "50GB", }, description: { + ...descriptionDefaults, display_name: "Memory", key: "Memory", - ...descriptionDefaults, }, }, { result: { - value: "cant see it", ...resultDefaults, - age: 50, + value: "cant see it", + age: 300, }, description: { ...descriptionDefaults, + interval: 5, display_name: "Stale", key: "stale", }, @@ -68,9 +74,8 @@ Example.args = { error: "fatal error", }, description: { - display_name: "Error", - key: "stale", ...descriptionDefaults, + display_name: "Error", }, }, { @@ -80,9 +85,9 @@ Example.args = { error: "fatal error", }, description: { + ...descriptionDefaults, display_name: "Error", key: "stale", - ...descriptionDefaults, }, }, ], diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 6a68e2a955628..d8f0554d564e9 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -2,9 +2,11 @@ import CircularProgress from "@material-ui/core/CircularProgress" import makeStyles from "@material-ui/core/styles/makeStyles" import { watchAgentMetadata } from "api/api" import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated" +import { CodeExample } from "components/CodeExample/CodeExample" import { Stack } from "components/Stack/Stack" import { HelpPopover, + HelpTooltip, HelpTooltipText, HelpTooltipTitle, } from "components/Tooltips/HelpTooltip" @@ -39,25 +41,28 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { 5, ) + const isStale = item.result.age > staleThreshold + // Stale data is as good as no data. Plus, we want to build confidence in our // users that what's shown is real. If times aren't correctly synced this // could be buggy. But, how common is that anyways? - const value = - item.result.age < staleThreshold ? ( -
- {item.result.value} -
- ) : ( - - ) + const value = isStale ? ( + + ) : ( +
+ {item.result.value} +
+ ) + + const updatesInSeconds = -(item.description.interval - item.result.age) return (
= ({ item }) => { This item was collected{" "} {dayjs.duration(item.result.age, "s").humanize()} ago and will be updated in{" "} - {dayjs - .duration( - Math.min(-item.description.interval - item.result.age, 0), - "s", - ) - .humanize()} - . + {dayjs.duration(Math.min(updatesInSeconds, 0), "s").humanize()}. + + {isStale ? ( + + This item is now stale because the agent hasn{"'"}t reported a new + value in {dayjs.duration(item.result.age, "s").humanize()}. + + ) : ( + <> + )} + + This item is collected by running the following command: + - - wee woo
{item.description.display_name} From a80541fcf7109f6965df2fa7d6db5905812888e7 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 23 Mar 2023 23:33:38 +0000 Subject: [PATCH 31/73] Popover --- .../components/Resources/AgentMetadata.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index d8f0554d564e9..2f5f8c9f28429 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -1,3 +1,4 @@ +import { Popover } from "@material-ui/core" import CircularProgress from "@material-ui/core/CircularProgress" import makeStyles from "@material-ui/core/styles/makeStyles" import { watchAgentMetadata } from "api/api" @@ -72,11 +73,25 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { role="presentation" ref={anchorRef} > - setIsOpen(true)} onClose={() => setIsOpen(false)} + PaperProps={{ + onMouseEnter: () => setIsOpen(true), + // onMouseLeave: () => setIsOpen(false), + }} + style={{ + width: "auto", + }} > {item.description.display_name} @@ -97,7 +112,7 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { This item is collected by running the following command: - +
{item.description.display_name}
From e1e992d885cfa741d31541387754bfcf94a03b56 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 24 Mar 2023 04:07:26 +0000 Subject: [PATCH 32/73] Make it look.... ok again --- .../components/Resources/AgentMetadata.tsx | 44 ++- site/src/components/Resources/AgentRow.tsx | 321 ++++++++++-------- 2 files changed, 207 insertions(+), 158 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 2f5f8c9f28429..f2151715d35ba 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -1,4 +1,4 @@ -import { Popover } from "@material-ui/core" +import Popover from "@material-ui/core/Popover" import CircularProgress from "@material-ui/core/CircularProgress" import makeStyles from "@material-ui/core/styles/makeStyles" import { watchAgentMetadata } from "api/api" @@ -6,8 +6,6 @@ import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated" import { CodeExample } from "components/CodeExample/CodeExample" import { Stack } from "components/Stack/Stack" import { - HelpPopover, - HelpTooltip, HelpTooltipText, HelpTooltipTitle, } from "components/Tooltips/HelpTooltip" @@ -66,7 +64,7 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { const updatesInSeconds = -(item.description.interval - item.result.age) return ( -
setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} @@ -87,11 +85,13 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { onClose={() => setIsOpen(false)} PaperProps={{ onMouseEnter: () => setIsOpen(true), - // onMouseLeave: () => setIsOpen(false), - }} - style={{ - width: "auto", + onMouseLeave: () => setIsOpen(false), }} + style={ + { + // width: "auto", + } + } > {item.description.display_name} @@ -117,7 +117,7 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { {item.description.display_name}
{value} -
+ ) } @@ -131,7 +131,12 @@ export const AgentMetadataView: FC = ({ metadata }) => { return <> } return ( - +
{metadata.map((m) => { if (m.description === undefined) { @@ -178,6 +183,9 @@ export const AgentMetadata: FC<{ // These are more or less copied from // site/src/components/Resources/ResourceCard.tsx const useStyles = makeStyles((theme) => ({ + metadataStack: { + // padding: "24px 32px", + }, metadataHeader: { display: "grid", gridTemplateColumns: "repeat(4, minmax(0, 1fr))", @@ -210,4 +218,20 @@ const useStyles = makeStyles((theme) => ({ metadataValueError: { color: theme.palette.error.main, }, + + metadataPopover: { + padding: 0, + width: theme.spacing(28), + + "& .MuiButton-root": { + padding: theme.spacing(1, 2), + borderRadius: 0, + width: "100%", + border: 0, + + "&:hover": { + background: theme.palette.action.hover, + }, + }, + }, })) diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index fd9dd4c146baa..1993524c19010 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -35,6 +35,7 @@ import { SSHButton } from "../SSHButton/SSHButton" import { Stack } from "../Stack/Stack" import { TerminalLink } from "../TerminalLink/TerminalLink" import { AgentLatency } from "./AgentLatency" +import { AgentMetadata } from "./AgentMetadata" import { AgentStatus } from "./AgentStatus" import { AgentVersion } from "./AgentVersion" @@ -167,177 +168,201 @@ export const AgentRow: FC = ({ - -
- -
-
-
{agent.name}
- - {agent.operating_system} +
+ +
+ + + +
+
{agent.name}
+ + + {agent.operating_system} + - - - + + + - + - - - - + + + + - - {t("unableToConnect")} - + + {t("unableToConnect")} + + +
- {hasStartupFeatures && ( - + {showApps && agent.status === "connected" && ( + <> + {agent.apps.map((app) => ( + + ))} + + + {!hideSSHButton && ( + + )} + {!hideVSCodeDesktopButton && ( + + )} + {applicationsHost !== undefined && + applicationsHost !== "" && ( + + )} + + )} + {showApps && agent.status === "connecting" && ( + <> + + + + )} + +
+ + {hasStartupFeatures && ( + + { + setShowStartupLogs(!showStartupLogs) + }} > + {showStartupLogs ? ( + + ) : ( + + )} + {showStartupLogs ? "Hide" : "Show"} Startup Logs + + + {agent.startup_script && ( { - setShowStartupLogs(!showStartupLogs) + setStartupScriptOpen(!startupScriptOpen) }} > - {showStartupLogs ? ( - - ) : ( - - )} - {showStartupLogs ? "Hide" : "Show"} Startup Logs + + View Startup Script + )} - {agent.startup_script && ( - { - setStartupScriptOpen(!startupScriptOpen) + setStartupScriptOpen(false)} + anchorEl={startupScriptAnchorRef.current} + anchorOrigin={{ + vertical: "bottom", + horizontal: "left", + }} + transformOrigin={{ + vertical: "top", + horizontal: "left", + }} + > +
+ - - View Startup Script - - )} - - setStartupScriptOpen(false)} - anchorEl={startupScriptAnchorRef.current} - anchorOrigin={{ - vertical: "bottom", - horizontal: "left", - }} - transformOrigin={{ - vertical: "top", - horizontal: "left", - }} - > -
- - {agent.startup_script || ""} - -
-
- - )} -
-
- - - {showApps && agent.status === "connected" && ( - <> - {agent.apps.map((app) => ( - - ))} - - - {!hideSSHButton && ( - - )} - {!hideVSCodeDesktopButton && ( - - )} - {applicationsHost !== undefined && applicationsHost !== "" && ( - - )} - - )} - {showApps && agent.status === "connecting" && ( - <> - - - + {agent.startup_script || ""} + +
+ +
)}
- {showStartupLogs && ( {({ width }) => ( From 4f42d4bae84d189d5fca9e96393fb913dd3e16d7 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 24 Mar 2023 05:01:35 +0000 Subject: [PATCH 33/73] It looks OK --- .../components/Resources/AgentMetadata.tsx | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index f2151715d35ba..9cb6a97dc79bb 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -64,13 +64,19 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { const updatesInSeconds = -(item.description.interval - item.result.age) return ( - setIsOpen(true)} - onMouseLeave={() => setIsOpen(false)} - role="presentation" - ref={anchorRef} - > + <> +
+
setIsOpen(true)} + // onMouseLeave={() => setIsOpen(false)} + role="presentation" + ref={anchorRef} + > + {item.description.display_name} +
+ {value} +
= ({ item }) => { onMouseEnter: () => setIsOpen(true), onMouseLeave: () => setIsOpen(false), }} - style={ - { - // width: "auto", - } - } + classes={{ paper: styles.metadataPopover }} > {item.description.display_name} @@ -110,14 +112,12 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { )} This item is collected by running the following command: + + -
- {item.description.display_name} -
- {value} -
+ ) } @@ -184,9 +184,12 @@ export const AgentMetadata: FC<{ // site/src/components/Resources/ResourceCard.tsx const useStyles = makeStyles((theme) => ({ metadataStack: { - // padding: "24px 32px", + border: `2px dashed ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + width: "100%", }, metadataHeader: { + padding: "8px", display: "grid", gridTemplateColumns: "repeat(4, minmax(0, 1fr))", gap: theme.spacing(5), @@ -220,13 +223,15 @@ const useStyles = makeStyles((theme) => ({ }, metadataPopover: { - padding: 0, - width: theme.spacing(28), + marginTop: theme.spacing(0.5), + padding: theme.spacing(2.5), + color: theme.palette.text.secondary, + pointerEvents: "auto", + maxWidth: "480px", "& .MuiButton-root": { padding: theme.spacing(1, 2), borderRadius: 0, - width: "100%", border: 0, "&:hover": { From 1c6d7b32f383abbdf4bf319304a108a79ea1a313 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 24 Mar 2023 18:17:26 +0000 Subject: [PATCH 34/73] Start working on tooltip --- site/src/components/Resources/AgentMetadata.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 9cb6a97dc79bb..45574de059e2c 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -96,12 +96,6 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { classes={{ paper: styles.metadataPopover }} > {item.description.display_name} - - This item was collected{" "} - {dayjs.duration(item.result.age, "s").humanize()} ago and will be - updated in{" "} - {dayjs.duration(Math.min(updatesInSeconds, 0), "s").humanize()}. - {isStale ? ( This item is now stale because the agent hasn{"'"}t reported a new @@ -111,7 +105,11 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { <> )} - This item is collected by running the following command: + This item was collected{" "} + {dayjs.duration(item.result.age, "s").humanize()} ago and will be + updated in{" "} + {dayjs.duration(Math.min(updatesInSeconds, 0), "s").humanize()} by + running the following command: From 5e614f579e90e9b4f57a3b8bcd22f8658cb2d0bd Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 24 Mar 2023 19:21:45 +0000 Subject: [PATCH 35/73] It's all passable --- .../Resources/AgentMetadata.stories.tsx | 13 ++ .../components/Resources/AgentMetadata.tsx | 131 ++++++++++++++---- 2 files changed, 118 insertions(+), 26 deletions(-) diff --git a/site/src/components/Resources/AgentMetadata.stories.tsx b/site/src/components/Resources/AgentMetadata.stories.tsx index 48c9c28041bb1..8b6fc80a9f683 100644 --- a/site/src/components/Resources/AgentMetadata.stories.tsx +++ b/site/src/components/Resources/AgentMetadata.stories.tsx @@ -90,5 +90,18 @@ Example.args = { key: "stale", }, }, + { + result: { + ...resultDefaults, + value: "", + collected_at: "0001-01-01T00:00:00Z", + age: 1000000, + }, + description: { + ...descriptionDefaults, + display_name: "Never loads", + key: "nloads", + }, + }, ], } diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 45574de059e2c..df70543a30351 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -13,6 +13,7 @@ import dayjs from "dayjs" import { createContext, FC, + PropsWithChildren, useContext, useEffect, useRef, @@ -21,12 +22,67 @@ import { export const WatchAgentMetadataContext = createContext(watchAgentMetadata) +const MetadataItemValue: FC< + PropsWithChildren<{ item: WorkspaceAgentMetadata }> +> = ({ item, children }) => { + const [isOpen, setIsOpen] = useState(false) + const anchorRef = useRef(null) + const styles = useStyles() + return ( + <> +
setIsOpen(true)} + role="presentation" + > + {children} +
+ setIsOpen(false)} + PaperProps={{ + onMouseEnter: () => setIsOpen(true), + onMouseLeave: () => setIsOpen(false), + }} + classes={{ paper: styles.metadataPopover }} + > + {item.description.display_name} + {item.result.value.length > 0 && ( + <> + Last result: + + + + + )} + {item.result.error.length > 0 && ( + <> + Last error: + + + + + )} + + + ) +} + const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { const styles = useStyles() const [isOpen, setIsOpen] = useState(false) - const anchorRef = useRef(null) + const labelAnchorRef = useRef(null) if (item.result === undefined) { throw new Error("Metadata item result is undefined") @@ -40,26 +96,36 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { 5, ) - const isStale = item.result.age > staleThreshold + const status: "stale" | "valid" | "loading" = (() => { + const year = dayjs(item.result.collected_at).year() + if (year <= 1970 || isNaN(year)) { + return "loading" + } + if (item.result.age > staleThreshold) { + return "stale" + } + return "valid" + })() // Stale data is as good as no data. Plus, we want to build confidence in our // users that what's shown is real. If times aren't correctly synced this // could be buggy. But, how common is that anyways? - const value = isStale ? ( - - ) : ( -
- {item.result.value} -
- ) + const value = + status === "stale" || status === "loading" ? ( + + ) : ( +
+ {item.result.value} +
+ ) const updatesInSeconds = -(item.description.interval - item.result.age) @@ -71,11 +137,11 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { onMouseEnter={() => setIsOpen(true)} // onMouseLeave={() => setIsOpen(false)} role="presentation" - ref={anchorRef} + ref={labelAnchorRef} > {item.description.display_name}
- {value} + {value}
= ({ item }) => { horizontal: "left", }} open={isOpen} - anchorEl={anchorRef.current} + anchorEl={labelAnchorRef.current} onClose={() => setIsOpen(false)} PaperProps={{ onMouseEnter: () => setIsOpen(true), @@ -96,7 +162,7 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { classes={{ paper: styles.metadataPopover }} > {item.description.display_name} - {isStale ? ( + {status === "stale" ? ( This item is now stale because the agent hasn{"'"}t reported a new value in {dayjs.duration(item.result.age, "s").humanize()}. @@ -104,12 +170,25 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { ) : ( <> )} + {status === "valid" ? ( + + The agent collected this value{" "} + {dayjs.duration(item.result.age, "s").humanize()} ago and will + update it in{" "} + {dayjs.duration(Math.min(updatesInSeconds, 0), "s").humanize()}. + + ) : ( + <> + )} + {status === "loading" ? ( + + This value is loading for the first time... + + ) : ( + <> + )} - This item was collected{" "} - {dayjs.duration(item.result.age, "s").humanize()} ago and will be - updated in{" "} - {dayjs.duration(Math.min(updatesInSeconds, 0), "s").humanize()} by - running the following command: + This value is produced by the following script: From 510524d3cb0715563a2e87544ac0851f06827999 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 24 Mar 2023 19:25:40 +0000 Subject: [PATCH 36/73] It builds! --- coderd/database/dbauthz/querier.go | 6 ++++++ coderd/workspaceagents_test.go | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 75eddf7fefa05..d147ea7dc78a4 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1500,6 +1500,9 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) + if err != nil { + return err + } err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) if err != nil { @@ -1511,6 +1514,9 @@ func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database func (q *querier) UpdateWorkspaceAgentMetadata(ctx context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) + if err != nil { + return err + } err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) if err != nil { diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index f30257a4672e3..223bb80508e96 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1316,7 +1316,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) - ctx, _ := testutil.Context(t) + ctx := testutil.Context(t, testutil.WaitMedium) manifest, err := agentClient.Manifest(ctx) require.NoError(t, err) @@ -1328,9 +1328,6 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.EqualValues(t, 10, manifest.Metadata[0].Interval) require.EqualValues(t, 3, manifest.Metadata[0].Timeout) - ctx, cancel := testutil.Context(t) - defer cancel() - post := func(key string, mr codersdk.WorkspaceAgentMetadataResult) { err := agentClient.PostMetadata(ctx, key, mr) require.NoError(t, err, "post metadata", t) From 93de24e5d2aa45d20e308bb4bf2b3bb6f1abad7c Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 24 Mar 2023 19:31:57 +0000 Subject: [PATCH 37/73] Harden interval conversion in agent --- agent/agent.go | 11 ++--------- agent/agent_test.go | 4 ++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index ca71cec3d93d0..7cc86b70cc5b8 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -258,7 +258,7 @@ func convertInterval(i int64) time.Duration { // impatient. base := time.Second if flag.Lookup("test.v") != nil { - base = time.Millisecond + base = time.Millisecond * 100 } return time.Duration(i) * base } @@ -269,14 +269,7 @@ type metadataResultAndKey struct { } func (a *agent) reportMetadataLoop(ctx context.Context) { - // In production, the minimum report interval is one second because - // `coder_agent.metadata` accepts `interval` in integer seconds. - // In tests, it helps to set shorter intervals because engineers are - // expensive. - baseInterval := time.Second - if flag.Lookup("test.v") != nil { - baseInterval = time.Millisecond * 100 - } + baseInterval := convertInterval(1) var ( baseTicker = time.NewTicker(baseInterval) diff --git a/agent/agent_test.go b/agent/agent_test.go index cc6623d7a502d..e56175547b9fb 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -821,7 +821,7 @@ func TestAgent_Metadata(t *testing.T) { t.Run("Basic", func(t *testing.T) { t.Parallel() dir := t.TempDir() - const reportInterval = 200 + const reportInterval = 2 greetingPath := filepath.Join(dir, "greeting") _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ Metadata: []codersdk.WorkspaceAgentMetadataDescription{ @@ -856,7 +856,7 @@ func TestAgent_Metadata(t *testing.T) { var ( numGreetings = bytes.Count(greetingByt, []byte("hello")) - idealNumGreetings = time.Since(start) / (reportInterval * time.Millisecond) + idealNumGreetings = time.Since(start) / (reportInterval * 100 * time.Millisecond) upperBound = int(idealNumGreetings) + 1 lowerBound = (int(idealNumGreetings) / 2) ) From d1ff3dc798f3933866e5c2fc9c5cf4da9b738106 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 15:12:30 +0000 Subject: [PATCH 38/73] fix compilation --- agent/agent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 0777b6283d8c8..89f2a0f764ced 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -365,7 +365,7 @@ func TestAgent_Session_TTY_FastCommandHasOutput(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() From 121321249a64afe593e7729cc6cc77f90ea07b5e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 11:42:03 -0500 Subject: [PATCH 39/73] windows --- agent/agent_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 89f2a0f764ced..979b954379dc6 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -874,12 +874,16 @@ func TestAgent_Metadata(t *testing.T) { dir := t.TempDir() const reportInterval = 2 greetingPath := filepath.Join(dir, "greeting") + script := "echo hello | tee " + greetingPath + if runtime.GOOS == "windows" { + script = "powershell " + script + } _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ Metadata: []codersdk.WorkspaceAgentMetadataDescription{ { Key: "greeting", Interval: reportInterval, - Script: "echo hello | tee -a " + greetingPath, + Script: script, }, { Key: "bad", @@ -899,7 +903,8 @@ func TestAgent_Metadata(t *testing.T) { panic("unexpected number of metadata entries") } - require.Equal(t, "hello\n", md["greeting"].Value) + // Trim space to be OS-newline agnostic. + require.Equal(t, "hello", strings.TrimSpace(md["greeting"].Value)) require.Equal(t, "exit status 1", md["bad"].Error) greetingByt, err := os.ReadFile(greetingPath) @@ -930,13 +935,17 @@ func TestAgent_Metadata(t *testing.T) { t.Run("CollectOnce", func(t *testing.T) { t.Parallel() + script := "echo -n hello" + if runtime.GOOS == "windows" { + script = "powershell " + script + } //nolint:dogsled _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ Metadata: []codersdk.WorkspaceAgentMetadataDescription{ { Key: "greeting", Interval: 0, - Script: "echo -n hello", + Script: script, }, }, }, 0) From 1423f26116018cd3d59c09b7b7c144bbeb4e6492 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 11:50:21 -0500 Subject: [PATCH 40/73] Simplify windows --- agent/agent_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 979b954379dc6..608ac04acae15 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -870,14 +870,17 @@ func TestAgent_Metadata(t *testing.T) { t.Parallel() t.Run("Basic", func(t *testing.T) { + if runtime.GOOS == "windows" { + // Shell scripting in Windows is a pain, so but we test that it works in the simpler "CollectOnce" + // test. + t.Skip() + } t.Parallel() + dir := t.TempDir() const reportInterval = 2 greetingPath := filepath.Join(dir, "greeting") script := "echo hello | tee " + greetingPath - if runtime.GOOS == "windows" { - script = "powershell " + script - } _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ Metadata: []codersdk.WorkspaceAgentMetadataDescription{ { From ec429fb99fda9812fa04acc1d541313fd859c980 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 11:56:17 -0500 Subject: [PATCH 41/73] improve formatting --- agent/agent_test.go | 76 ++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 608ac04acae15..15756cd734979 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -869,9 +869,43 @@ func TestAgent_StartupScript(t *testing.T) { func TestAgent_Metadata(t *testing.T) { t.Parallel() - t.Run("Basic", func(t *testing.T) { + t.Run("Once", func(t *testing.T) { + t.Parallel() + script := "echo -n hello" + if runtime.GOOS == "windows" { + script = "powershell " + script + } + //nolint:dogsled + _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ + Metadata: []codersdk.WorkspaceAgentMetadataDescription{ + { + Key: "greeting", + Interval: 0, + Script: script, + }, + }, + }, 0) + + var gotMd map[string]agentsdk.PostMetadataRequest + require.Eventually(t, func() bool { + gotMd = client.getMetadata() + return len(gotMd) == 1 + }, testutil.WaitShort, testutil.IntervalMedium) + + collectedAt := gotMd["greeting"].CollectedAt + + require.Never(t, func() bool { + gotMd = client.getMetadata() + if len(gotMd) != 1 { + panic("unexpected number of metadata") + } + return !gotMd["greeting"].CollectedAt.Equal(collectedAt) + }, testutil.WaitShort, testutil.IntervalMedium) + }) + + t.Run("Many", func(t *testing.T) { if runtime.GOOS == "windows" { - // Shell scripting in Windows is a pain, so but we test that it works in the simpler "CollectOnce" + // Shell scripting in Windows is a pain, but we test that it works in the simpler "CollectOnce" // test. t.Skip() } @@ -906,8 +940,7 @@ func TestAgent_Metadata(t *testing.T) { panic("unexpected number of metadata entries") } - // Trim space to be OS-newline agnostic. - require.Equal(t, "hello", strings.TrimSpace(md["greeting"].Value)) + require.Equal(t, "hello\n", md["greeting"].Value) require.Equal(t, "exit status 1", md["bad"].Error) greetingByt, err := os.ReadFile(greetingPath) @@ -920,7 +953,7 @@ func TestAgent_Metadata(t *testing.T) { lowerBound = (int(idealNumGreetings) / 2) ) - if idealNumGreetings < 5 { + if idealNumGreetings < 10 { // Not enough time has passed to get a good sample size. continue } @@ -936,39 +969,6 @@ func TestAgent_Metadata(t *testing.T) { } }) - t.Run("CollectOnce", func(t *testing.T) { - t.Parallel() - script := "echo -n hello" - if runtime.GOOS == "windows" { - script = "powershell " + script - } - //nolint:dogsled - _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - Metadata: []codersdk.WorkspaceAgentMetadataDescription{ - { - Key: "greeting", - Interval: 0, - Script: script, - }, - }, - }, 0) - - var gotMd map[string]agentsdk.PostMetadataRequest - require.Eventually(t, func() bool { - gotMd = client.getMetadata() - return len(gotMd) == 1 - }, testutil.WaitShort, testutil.IntervalMedium) - - collectedAt := gotMd["greeting"].CollectedAt - - require.Never(t, func() bool { - gotMd = client.getMetadata() - if len(gotMd) != 1 { - panic("unexpected number of metadata") - } - return !gotMd["greeting"].CollectedAt.Equal(collectedAt) - }, testutil.WaitShort, testutil.IntervalMedium) - }) } func TestAgent_Lifecycle(t *testing.T) { From d599851200149d28991808c38b9f798359914d5e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 11:59:39 -0500 Subject: [PATCH 42/73] fix --- agent/agent_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 15756cd734979..bb33c1cc937bc 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -914,7 +914,7 @@ func TestAgent_Metadata(t *testing.T) { dir := t.TempDir() const reportInterval = 2 greetingPath := filepath.Join(dir, "greeting") - script := "echo hello | tee " + greetingPath + script := "echo hello | tee -a " + greetingPath _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ Metadata: []codersdk.WorkspaceAgentMetadataDescription{ { @@ -953,7 +953,7 @@ func TestAgent_Metadata(t *testing.T) { lowerBound = (int(idealNumGreetings) / 2) ) - if idealNumGreetings < 10 { + if idealNumGreetings < 5 { // Not enough time has passed to get a good sample size. continue } From 130000948c742a363ef032cc0e990209168353dd Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 17:29:49 +0000 Subject: [PATCH 43/73] Increase timeout for windows --- coderd/workspaceagents_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 223bb80508e96..05a7fe72c7d82 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1343,7 +1343,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { check := func(want codersdk.WorkspaceAgentMetadataResult, got codersdk.WorkspaceAgentMetadata) { require.WithinDuration(t, want.CollectedAt, got.Result.CollectedAt, time.Second) require.WithinDuration( - t, time.Now(), got.Result.CollectedAt.Add(time.Duration(got.Result.Age)*time.Second), time.Millisecond*100, + t, time.Now(), got.Result.CollectedAt.Add(time.Duration(got.Result.Age)*time.Second), time.Millisecond*500, ) require.Equal(t, want.Value, got.Result.Value) require.Equal(t, want.Error, got.Result.Error) From 95c8ada0e552f66c1fbf44d25b0c26c482783edd Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 17:35:47 +0000 Subject: [PATCH 44/73] make fmt --- agent/agent_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index bb33c1cc937bc..ce2145b35aa52 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -968,7 +968,6 @@ func TestAgent_Metadata(t *testing.T) { } } }) - } func TestAgent_Lifecycle(t *testing.T) { From b7ddb4a319d0e1d69857e8ceda95f468b5c8d3ad Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 19:13:32 +0000 Subject: [PATCH 45/73] code cleanup in agent/ --- agent/agent.go | 15 +++++++++++---- agent/agent_test.go | 23 ++++++++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 560d79d137abd..fcbcae361487b 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -223,6 +223,12 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM var out bytes.Buffer result := &codersdk.WorkspaceAgentMetadataResult{ + // CollectedAt is set here for testing purposes and overrided by + // the server to the time the server received the result to protect + // against clock skew. + // + // In the future, the server may accept the timestamp from the agent + // if it is certain the clocks are in sync. CollectedAt: time.Now(), } cmd, err := a.createCommand(ctx, md.Script, nil) @@ -237,7 +243,7 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM // The error isn't mutually exclusive with useful output. err = cmd.Run() - const bufLimit = 10 << 14 + const bufLimit = 10 << 10 if out.Len() > bufLimit { err = errors.Join( err, @@ -293,7 +299,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { // If we're backpressured on sending back results, we risk // runaway goroutine growth and/or overloading coderd. So, // we just skip the collection. Since we never update - // "lastCollectedAt" for this key, we'll retry the collection + // the collections map, we'll retry the collection // on the next tick. a.logger.Debug( ctx, "metadata collection backpressured", @@ -317,8 +323,9 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { } } - // Spawn a goroutine for each metadata collection, and use channels - // to synchronize the results and avoid messy mutex logic. + // Spawn a goroutine for each metadata collection, and use a + // channel to synchronize the results and avoid both messy + // mutex logic and overloading the API. for _, md := range manifest.Metadata { collectedAt, ok := lastCollectedAts[md.Key] if ok { diff --git a/agent/agent_test.go b/agent/agent_test.go index ce2145b35aa52..e8d4b28c09880 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -905,16 +905,21 @@ func TestAgent_Metadata(t *testing.T) { t.Run("Many", func(t *testing.T) { if runtime.GOOS == "windows" { - // Shell scripting in Windows is a pain, but we test that it works in the simpler "CollectOnce" - // test. + // Shell scripting in Windows is a pain, and we have already tested + // that the OS logic works in the simpler "Once" test above. t.Skip() } t.Parallel() dir := t.TempDir() + + // In tests, the interval unit is 100 milliseconds while in production + // it is 1 second. const reportInterval = 2 - greetingPath := filepath.Join(dir, "greeting") - script := "echo hello | tee -a " + greetingPath + var ( + greetingPath = filepath.Join(dir, "greeting") + script = "echo hello | tee -a " + greetingPath + ) _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ Metadata: []codersdk.WorkspaceAgentMetadataDescription{ { @@ -949,12 +954,16 @@ func TestAgent_Metadata(t *testing.T) { var ( numGreetings = bytes.Count(greetingByt, []byte("hello")) idealNumGreetings = time.Since(start) / (reportInterval * 100 * time.Millisecond) - upperBound = int(idealNumGreetings) + 1 - lowerBound = (int(idealNumGreetings) / 2) + // We allow a 50% error margin because the report loop may backlog + // in CI and other toasters. In production, there is no hard + // guarantee on timing either, and the frontend gives similar + // wiggle room to the staleness of the value. + upperBound = int(idealNumGreetings) + 1 + lowerBound = (int(idealNumGreetings) / 2) ) if idealNumGreetings < 5 { - // Not enough time has passed to get a good sample size. + // There is an insufficient sample size. continue } From 8d1ab166f6fa99a58d4d596af03ac4061a8fa202 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 19:14:21 +0000 Subject: [PATCH 46/73] Revert Upsert change --- coderd/database/dbauthz/querier.go | 8 ++++---- coderd/database/dbauthz/querier_test.go | 8 ++++---- coderd/database/dbauthz/system.go | 4 ++-- coderd/database/dbauthz/system_test.go | 4 ++-- coderd/database/dbfake/databasefake.go | 6 +++--- coderd/database/querier.go | 6 +++--- coderd/database/queries.sql.go | 18 +++++++++--------- coderd/database/queries/siteconfig.sql | 6 +++--- coderd/updatecheck/updatecheck.go | 2 +- enterprise/coderd/appearance.go | 4 ++-- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 281d57ce66e60..4b7e0bab5f310 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -340,18 +340,18 @@ func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseP return q.db.InsertLicense(ctx, arg) } -func (q *querier) UpsertLogoURL(ctx context.Context, value string) error { +func (q *querier) InsertOrUpdateServiceBannerLogoURL(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } - return q.db.UpsertLogoURL(ctx, value) + return q.db.InsertOrUpdateServiceBannerLogoURL(ctx, value) } -func (q *querier) UpsertServiceBanner(ctx context.Context, value string) error { +func (q *querier) InsertOrUpdateServiceBannerServiceBanner(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } - return q.db.UpsertServiceBanner(ctx, value) + return q.db.InsertOrUpdateServiceBannerServiceBanner(ctx, value) } func (q *querier) GetLicenseByID(ctx context.Context, id int32) (database.License, error) { diff --git a/coderd/database/dbauthz/querier_test.go b/coderd/database/dbauthz/querier_test.go index 97f0bc7faa21f..e2f2233c7073c 100644 --- a/coderd/database/dbauthz/querier_test.go +++ b/coderd/database/dbauthz/querier_test.go @@ -312,10 +312,10 @@ func (s *MethodTestSuite) TestLicense() { check.Args(database.InsertLicenseParams{}). Asserts(rbac.ResourceLicense, rbac.ActionCreate) })) - s.Run("UpsertLogoURL", s.Subtest(func(db database.Store, check *expects) { + s.Run("InsertOrUpdateServiceBannerLogoURL", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) - s.Run("UpsertServiceBanner", s.Subtest(func(db database.Store, check *expects) { + s.Run("InsertOrUpdateServiceBannerServiceBanner", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) s.Run("GetLicenseByID", s.Subtest(func(db database.Store, check *expects) { @@ -336,12 +336,12 @@ func (s *MethodTestSuite) TestLicense() { check.Args().Asserts().Returns("") })) s.Run("GetLogoURL", s.Subtest(func(db database.Store, check *expects) { - err := db.UpsertLogoURL(context.Background(), "value") + err := db.InsertOrUpdateServiceBannerLogoURL(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts().Returns("value") })) s.Run("GetServiceBanner", s.Subtest(func(db database.Store, check *expects) { - err := db.UpsertServiceBanner(context.Background(), "value") + err := db.InsertOrUpdateServiceBannerServiceBanner(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts().Returns("value") })) diff --git a/coderd/database/dbauthz/system.go b/coderd/database/dbauthz/system.go index b4758afd0b2b6..4af0c0a65e53f 100644 --- a/coderd/database/dbauthz/system.go +++ b/coderd/database/dbauthz/system.go @@ -228,11 +228,11 @@ func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database return q.db.UpdateWorkspaceBuildCostByID(ctx, arg) } -func (q *querier) UpsertLastUpdateCheck(ctx context.Context, value string) error { +func (q *querier) InsertOrUpdateServiceBannerLastUpdateCheck(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil { return err } - return q.db.UpsertLastUpdateCheck(ctx, value) + return q.db.InsertOrUpdateServiceBannerLastUpdateCheck(ctx, value) } func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) { diff --git a/coderd/database/dbauthz/system_test.go b/coderd/database/dbauthz/system_test.go index 65cf9d2743605..b3df0fcad35ff 100644 --- a/coderd/database/dbauthz/system_test.go +++ b/coderd/database/dbauthz/system_test.go @@ -102,11 +102,11 @@ func (s *MethodTestSuite) TestSystemFunctions() { DailyCost: 10, }).Asserts(rbac.ResourceSystem, rbac.ActionUpdate).Returns(o) })) - s.Run("UpsertLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { + s.Run("InsertOrUpdateServiceBannerLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionUpdate) })) s.Run("GetLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { - err := db.UpsertLastUpdateCheck(context.Background(), "value") + err := db.InsertOrUpdateServiceBannerLastUpdateCheck(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead) })) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index d335b5dadaad9..89f3e8445bf48 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -4342,7 +4342,7 @@ func (q *fakeQuerier) GetDERPMeshKey(_ context.Context) (string, error) { return q.derpMeshKey, nil } -func (q *fakeQuerier) UpsertLastUpdateCheck(_ context.Context, data string) error { +func (q *fakeQuerier) InsertOrUpdateServiceBannerLastUpdateCheck(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() @@ -4360,7 +4360,7 @@ func (q *fakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) { return string(q.lastUpdateCheck), nil } -func (q *fakeQuerier) UpsertServiceBanner(_ context.Context, data string) error { +func (q *fakeQuerier) InsertOrUpdateServiceBannerServiceBanner(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() @@ -4379,7 +4379,7 @@ func (q *fakeQuerier) GetServiceBanner(_ context.Context) (string, error) { return string(q.serviceBanner), nil } -func (q *fakeQuerier) UpsertLogoURL(_ context.Context, data string) error { +func (q *fakeQuerier) InsertOrUpdateServiceBannerLogoURL(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index ee99a0ade845f..ccd07ac46e59b 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -241,9 +241,9 @@ type sqlcQuerier interface { UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error UpdateWorkspaceTTLToBeWithinTemplateMax(ctx context.Context, arg UpdateWorkspaceTTLToBeWithinTemplateMaxParams) error - UpsertLastUpdateCheck(ctx context.Context, value string) error - UpsertLogoURL(ctx context.Context, value string) error - UpsertServiceBanner(ctx context.Context, value string) error + InsertOrUpdateServiceBannerLastUpdateCheck(ctx context.Context, value string) error + InsertOrUpdateServiceBannerLogoURL(ctx context.Context, value string) error + InsertOrUpdateServiceBannerServiceBanner(ctx context.Context, value string) error } var _ sqlcQuerier = (*sqlQuerier)(nil) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 2ca6f2b31637f..abcd6f2429b39 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3103,33 +3103,33 @@ func (q *sqlQuerier) InsertDeploymentID(ctx context.Context, value string) error return err } -const upsertLastUpdateCheck = `-- name: UpsertLastUpdateCheck :exec +const InsertOrUpdateServiceBannerLastUpdateCheck = `-- name: InsertOrUpdateServiceBannerLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check' ` -func (q *sqlQuerier) UpsertLastUpdateCheck(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, upsertLastUpdateCheck, value) +func (q *sqlQuerier) InsertOrUpdateServiceBannerLastUpdateCheck(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, InsertOrUpdateServiceBannerLastUpdateCheck, value) return err } -const upsertLogoURL = `-- name: UpsertLogoURL :exec +const InsertOrUpdateServiceBannerLogoURL = `-- name: InsertOrUpdateServiceBannerLogoURL :exec INSERT INTO site_configs (key, value) VALUES ('logo_url', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url' ` -func (q *sqlQuerier) UpsertLogoURL(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, upsertLogoURL, value) +func (q *sqlQuerier) InsertOrUpdateServiceBannerLogoURL(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, InsertOrUpdateServiceBannerLogoURL, value) return err } -const upsertServiceBanner = `-- name: UpsertServiceBanner :exec +const InsertOrUpdateServiceBannerServiceBanner = `-- name: InsertOrUpdateServiceBannerServiceBanner :exec INSERT INTO site_configs (key, value) VALUES ('service_banner', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner' ` -func (q *sqlQuerier) UpsertServiceBanner(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, upsertServiceBanner, value) +func (q *sqlQuerier) InsertOrUpdateServiceBannerServiceBanner(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, InsertOrUpdateServiceBannerServiceBanner, value) return err } diff --git a/coderd/database/queries/siteconfig.sql b/coderd/database/queries/siteconfig.sql index 9f2e5042abdf3..f3975555e06e9 100644 --- a/coderd/database/queries/siteconfig.sql +++ b/coderd/database/queries/siteconfig.sql @@ -10,21 +10,21 @@ INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1); -- name: GetDERPMeshKey :one SELECT value FROM site_configs WHERE key = 'derp_mesh_key'; --- name: UpsertLastUpdateCheck :exec +-- name: InsertOrUpdateServiceBannerLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check'; -- name: GetLastUpdateCheck :one SELECT value FROM site_configs WHERE key = 'last_update_check'; --- name: UpsertServiceBanner :exec +-- name: InsertOrUpdateServiceBannerServiceBanner :exec INSERT INTO site_configs (key, value) VALUES ('service_banner', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner'; -- name: GetServiceBanner :one SELECT value FROM site_configs WHERE key = 'service_banner'; --- name: UpsertLogoURL :exec +-- name: InsertOrUpdateServiceBannerLogoURL :exec INSERT INTO site_configs (key, value) VALUES ('logo_url', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url'; diff --git a/coderd/updatecheck/updatecheck.go b/coderd/updatecheck/updatecheck.go index e18bc5e5ccd77..8f1a1816821f5 100644 --- a/coderd/updatecheck/updatecheck.go +++ b/coderd/updatecheck/updatecheck.go @@ -211,7 +211,7 @@ func (c *Checker) update() (r Result, err error) { } // nolint:gocritic // Inserting the last update check is a system function. - err = c.db.UpsertLastUpdateCheck(dbauthz.AsSystemRestricted(ctx), string(b)) + err = c.db.InsertOrUpdateServiceBannerLastUpdateCheck(dbauthz.AsSystemRestricted(ctx), string(b)) if err != nil { return r, err } diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go index 6f8ef8500e21f..4af8b27a56d28 100644 --- a/enterprise/coderd/appearance.go +++ b/enterprise/coderd/appearance.go @@ -148,7 +148,7 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.UpsertServiceBanner(ctx, string(serviceBannerJSON)) + err = api.Database.InsertOrUpdateServiceBannerServiceBanner(ctx, string(serviceBannerJSON)) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("database error: %+v", err), @@ -156,7 +156,7 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.UpsertLogoURL(ctx, appearance.LogoURL) + err = api.Database.InsertOrUpdateServiceBannerLogoURL(ctx, appearance.LogoURL) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("database error: %+v", err), From 12d8f717fa1bd4528a7adc7bf46e9e13563e235a Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 19:14:53 +0000 Subject: [PATCH 47/73] Revert "Revert Upsert change" This reverts commit 8d1ab166f6fa99a58d4d596af03ac4061a8fa202. --- coderd/database/dbauthz/querier.go | 8 ++++---- coderd/database/dbauthz/querier_test.go | 8 ++++---- coderd/database/dbauthz/system.go | 4 ++-- coderd/database/dbauthz/system_test.go | 4 ++-- coderd/database/dbfake/databasefake.go | 6 +++--- coderd/database/querier.go | 6 +++--- coderd/database/queries.sql.go | 18 +++++++++--------- coderd/database/queries/siteconfig.sql | 6 +++--- coderd/updatecheck/updatecheck.go | 2 +- enterprise/coderd/appearance.go | 4 ++-- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 4b7e0bab5f310..1e452b9f5b8a1 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -340,18 +340,18 @@ func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseP return q.db.InsertLicense(ctx, arg) } -func (q *querier) InsertOrUpdateServiceBannerLogoURL(ctx context.Context, value string) error { +func (q *querier) InsertOrUpdateLogoURL(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } - return q.db.InsertOrUpdateServiceBannerLogoURL(ctx, value) + return q.db.InsertOrUpdateLogoURL(ctx, value) } -func (q *querier) InsertOrUpdateServiceBannerServiceBanner(ctx context.Context, value string) error { +func (q *querier) InsertOrUpdateServiceBanner(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } - return q.db.InsertOrUpdateServiceBannerServiceBanner(ctx, value) + return q.db.InsertOrUpdateServiceBanner(ctx, value) } func (q *querier) GetLicenseByID(ctx context.Context, id int32) (database.License, error) { diff --git a/coderd/database/dbauthz/querier_test.go b/coderd/database/dbauthz/querier_test.go index e2f2233c7073c..533bf3621b232 100644 --- a/coderd/database/dbauthz/querier_test.go +++ b/coderd/database/dbauthz/querier_test.go @@ -312,10 +312,10 @@ func (s *MethodTestSuite) TestLicense() { check.Args(database.InsertLicenseParams{}). Asserts(rbac.ResourceLicense, rbac.ActionCreate) })) - s.Run("InsertOrUpdateServiceBannerLogoURL", s.Subtest(func(db database.Store, check *expects) { + s.Run("InsertOrUpdateLogoURL", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) - s.Run("InsertOrUpdateServiceBannerServiceBanner", s.Subtest(func(db database.Store, check *expects) { + s.Run("InsertOrUpdateServiceBanner", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) s.Run("GetLicenseByID", s.Subtest(func(db database.Store, check *expects) { @@ -336,12 +336,12 @@ func (s *MethodTestSuite) TestLicense() { check.Args().Asserts().Returns("") })) s.Run("GetLogoURL", s.Subtest(func(db database.Store, check *expects) { - err := db.InsertOrUpdateServiceBannerLogoURL(context.Background(), "value") + err := db.InsertOrUpdateLogoURL(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts().Returns("value") })) s.Run("GetServiceBanner", s.Subtest(func(db database.Store, check *expects) { - err := db.InsertOrUpdateServiceBannerServiceBanner(context.Background(), "value") + err := db.InsertOrUpdateServiceBanner(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts().Returns("value") })) diff --git a/coderd/database/dbauthz/system.go b/coderd/database/dbauthz/system.go index 4af0c0a65e53f..28ca1628e00e1 100644 --- a/coderd/database/dbauthz/system.go +++ b/coderd/database/dbauthz/system.go @@ -228,11 +228,11 @@ func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database return q.db.UpdateWorkspaceBuildCostByID(ctx, arg) } -func (q *querier) InsertOrUpdateServiceBannerLastUpdateCheck(ctx context.Context, value string) error { +func (q *querier) InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil { return err } - return q.db.InsertOrUpdateServiceBannerLastUpdateCheck(ctx, value) + return q.db.InsertOrUpdateLastUpdateCheck(ctx, value) } func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) { diff --git a/coderd/database/dbauthz/system_test.go b/coderd/database/dbauthz/system_test.go index b3df0fcad35ff..4ce8d6a8a1066 100644 --- a/coderd/database/dbauthz/system_test.go +++ b/coderd/database/dbauthz/system_test.go @@ -102,11 +102,11 @@ func (s *MethodTestSuite) TestSystemFunctions() { DailyCost: 10, }).Asserts(rbac.ResourceSystem, rbac.ActionUpdate).Returns(o) })) - s.Run("InsertOrUpdateServiceBannerLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { + s.Run("InsertOrUpdateLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionUpdate) })) s.Run("GetLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) { - err := db.InsertOrUpdateServiceBannerLastUpdateCheck(context.Background(), "value") + err := db.InsertOrUpdateLastUpdateCheck(context.Background(), "value") require.NoError(s.T(), err) check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead) })) diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 89f3e8445bf48..d4fa70e891839 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -4342,7 +4342,7 @@ func (q *fakeQuerier) GetDERPMeshKey(_ context.Context) (string, error) { return q.derpMeshKey, nil } -func (q *fakeQuerier) InsertOrUpdateServiceBannerLastUpdateCheck(_ context.Context, data string) error { +func (q *fakeQuerier) InsertOrUpdateLastUpdateCheck(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() @@ -4360,7 +4360,7 @@ func (q *fakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) { return string(q.lastUpdateCheck), nil } -func (q *fakeQuerier) InsertOrUpdateServiceBannerServiceBanner(_ context.Context, data string) error { +func (q *fakeQuerier) InsertOrUpdateServiceBanner(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() @@ -4379,7 +4379,7 @@ func (q *fakeQuerier) GetServiceBanner(_ context.Context) (string, error) { return string(q.serviceBanner), nil } -func (q *fakeQuerier) InsertOrUpdateServiceBannerLogoURL(_ context.Context, data string) error { +func (q *fakeQuerier) InsertOrUpdateLogoURL(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index ccd07ac46e59b..46b63e3c63bbb 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -241,9 +241,9 @@ type sqlcQuerier interface { UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error UpdateWorkspaceTTLToBeWithinTemplateMax(ctx context.Context, arg UpdateWorkspaceTTLToBeWithinTemplateMaxParams) error - InsertOrUpdateServiceBannerLastUpdateCheck(ctx context.Context, value string) error - InsertOrUpdateServiceBannerLogoURL(ctx context.Context, value string) error - InsertOrUpdateServiceBannerServiceBanner(ctx context.Context, value string) error + InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error + InsertOrUpdateLogoURL(ctx context.Context, value string) error + InsertOrUpdateServiceBanner(ctx context.Context, value string) error } var _ sqlcQuerier = (*sqlQuerier)(nil) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index abcd6f2429b39..267d261a4d387 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3103,33 +3103,33 @@ func (q *sqlQuerier) InsertDeploymentID(ctx context.Context, value string) error return err } -const InsertOrUpdateServiceBannerLastUpdateCheck = `-- name: InsertOrUpdateServiceBannerLastUpdateCheck :exec +const InsertOrUpdateLastUpdateCheck = `-- name: InsertOrUpdateLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check' ` -func (q *sqlQuerier) InsertOrUpdateServiceBannerLastUpdateCheck(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, InsertOrUpdateServiceBannerLastUpdateCheck, value) +func (q *sqlQuerier) InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, InsertOrUpdateLastUpdateCheck, value) return err } -const InsertOrUpdateServiceBannerLogoURL = `-- name: InsertOrUpdateServiceBannerLogoURL :exec +const InsertOrUpdateLogoURL = `-- name: InsertOrUpdateLogoURL :exec INSERT INTO site_configs (key, value) VALUES ('logo_url', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url' ` -func (q *sqlQuerier) InsertOrUpdateServiceBannerLogoURL(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, InsertOrUpdateServiceBannerLogoURL, value) +func (q *sqlQuerier) InsertOrUpdateLogoURL(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, InsertOrUpdateLogoURL, value) return err } -const InsertOrUpdateServiceBannerServiceBanner = `-- name: InsertOrUpdateServiceBannerServiceBanner :exec +const InsertOrUpdateServiceBanner = `-- name: InsertOrUpdateServiceBanner :exec INSERT INTO site_configs (key, value) VALUES ('service_banner', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner' ` -func (q *sqlQuerier) InsertOrUpdateServiceBannerServiceBanner(ctx context.Context, value string) error { - _, err := q.db.ExecContext(ctx, InsertOrUpdateServiceBannerServiceBanner, value) +func (q *sqlQuerier) InsertOrUpdateServiceBanner(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, InsertOrUpdateServiceBanner, value) return err } diff --git a/coderd/database/queries/siteconfig.sql b/coderd/database/queries/siteconfig.sql index f3975555e06e9..a2b5427f70ad1 100644 --- a/coderd/database/queries/siteconfig.sql +++ b/coderd/database/queries/siteconfig.sql @@ -10,21 +10,21 @@ INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1); -- name: GetDERPMeshKey :one SELECT value FROM site_configs WHERE key = 'derp_mesh_key'; --- name: InsertOrUpdateServiceBannerLastUpdateCheck :exec +-- name: InsertOrUpdateLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check'; -- name: GetLastUpdateCheck :one SELECT value FROM site_configs WHERE key = 'last_update_check'; --- name: InsertOrUpdateServiceBannerServiceBanner :exec +-- name: InsertOrUpdateServiceBanner :exec INSERT INTO site_configs (key, value) VALUES ('service_banner', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner'; -- name: GetServiceBanner :one SELECT value FROM site_configs WHERE key = 'service_banner'; --- name: InsertOrUpdateServiceBannerLogoURL :exec +-- name: InsertOrUpdateLogoURL :exec INSERT INTO site_configs (key, value) VALUES ('logo_url', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url'; diff --git a/coderd/updatecheck/updatecheck.go b/coderd/updatecheck/updatecheck.go index 8f1a1816821f5..0f5d2ce913d36 100644 --- a/coderd/updatecheck/updatecheck.go +++ b/coderd/updatecheck/updatecheck.go @@ -211,7 +211,7 @@ func (c *Checker) update() (r Result, err error) { } // nolint:gocritic // Inserting the last update check is a system function. - err = c.db.InsertOrUpdateServiceBannerLastUpdateCheck(dbauthz.AsSystemRestricted(ctx), string(b)) + err = c.db.InsertOrUpdateLastUpdateCheck(dbauthz.AsSystemRestricted(ctx), string(b)) if err != nil { return r, err } diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go index 4af8b27a56d28..a784b678ea2d8 100644 --- a/enterprise/coderd/appearance.go +++ b/enterprise/coderd/appearance.go @@ -148,7 +148,7 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.InsertOrUpdateServiceBannerServiceBanner(ctx, string(serviceBannerJSON)) + err = api.Database.InsertOrUpdateServiceBanner(ctx, string(serviceBannerJSON)) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("database error: %+v", err), @@ -156,7 +156,7 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { return } - err = api.Database.InsertOrUpdateServiceBannerLogoURL(ctx, appearance.LogoURL) + err = api.Database.InsertOrUpdateLogoURL(ctx, appearance.LogoURL) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("database error: %+v", err), From 90687ce63d0124e469589d06438df399e901c067 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 19:20:36 +0000 Subject: [PATCH 48/73] Fix fixture name --- ...tup_logs.up.sql => 000110_workspace_agent_startup_logs.up.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/testdata/fixtures/{000111_workspace_agent_startup_logs.up.sql => 000110_workspace_agent_startup_logs.up.sql} (100%) diff --git a/coderd/database/migrations/testdata/fixtures/000111_workspace_agent_startup_logs.up.sql b/coderd/database/migrations/testdata/fixtures/000110_workspace_agent_startup_logs.up.sql similarity index 100% rename from coderd/database/migrations/testdata/fixtures/000111_workspace_agent_startup_logs.up.sql rename to coderd/database/migrations/testdata/fixtures/000110_workspace_agent_startup_logs.up.sql From 2ef0b55c045dcc45380789aec05f8eb903bcbbaa Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 27 Mar 2023 19:35:08 +0000 Subject: [PATCH 49/73] Minor fixups --- coderd/workspaceagents.go | 19 ++-- docs/templates/agent-metadata.md | 6 ++ docs/templates/resource-metadata.md | 10 +- site/package.json | 1 - site/yarn.lock | 146 +--------------------------- 5 files changed, 22 insertions(+), 160 deletions(-) create mode 100644 docs/templates/agent-metadata.md diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 25240818e370e..8dfb9f66cee0a 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1414,7 +1414,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque Value: ellipse(req.Value, 10<<10), Error: ellipse(req.Error, 10<<10), // We ignore the CollectedAt from the agent to avoid bugs caused by - // misaligned clocks. + // clock skew. CollectedAt: time.Now(), } @@ -1478,8 +1478,6 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ defer refreshTicker.Stop() var ( - // In practice, two concurrent sends is extremely unlikely because the - // refreshTicker would have to fire right as we receive a new DB update. lastDBMetaMu sync.Mutex lastDBMeta []database.WorkspaceAgentMetadatum ) @@ -1488,9 +1486,6 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ lastDBMetaMu.Lock() defer lastDBMetaMu.Unlock() - // Avoid sending refreshes if the natural pace of updates is fast. - refreshTicker.Reset(refreshInterval) - var err error if pull { // We always use the original Request context because it contains @@ -1509,6 +1504,10 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ slices.SortFunc(lastDBMeta, func(i, j database.WorkspaceAgentMetadatum) bool { return i.Key < j.Key }) + + // Avoid sending refresh if the client is about to get a + // fresh update. + refreshTicker.Reset(refreshInterval) } _ = sendEvent(ctx, codersdk.ServerSentEvent{ @@ -1533,10 +1532,10 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ for { select { case <-refreshTicker.C: - // Avoid spamming the DB when we know there are no updates. We want - // to continue sending updates so that "Result.Age" is always accurate on - // the frontend. This way, the frontend doesn't need complex clock - // skew logic to understand if metadata is stale. + // Avoid spamming the DB with reads we know there are no updates. We want + // to continue sending updates to the frontend so that "Result.Age" + // is always accurate. This way, the frontend doesn't need to + // sync its own clock with the backend. sendMetadata(false) case <-senderClosed: return diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md new file mode 100644 index 0000000000000..f1f8d100c7588 --- /dev/null +++ b/docs/templates/agent-metadata.md @@ -0,0 +1,6 @@ +# Agent Metadata + +With Agent Metadata, template admin can expose key operational metrics from +their workspaces to their users. It is a sibling of [Resource Metadata](./resource-metadata.md). + +See the [Terraform reference](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/agent#metadata). diff --git a/docs/templates/resource-metadata.md b/docs/templates/resource-metadata.md index 05775440fede4..ebfb360c9eeb7 100644 --- a/docs/templates/resource-metadata.md +++ b/docs/templates/resource-metadata.md @@ -109,14 +109,16 @@ resource "coder_agent" "dev" { dir = "/workspace" metadata { name = "Process Count" - cmd = ["sh", "-c", "ps aux | wc -l"] - refresh = 5s - # Any data above this width will require scrolling. - width = 5 + script = "ps aux | wc -l" + interval = 1 + timeout = 3 } } ``` +Read more [here](./agent-metadata.md). + ## Up next - Learn about [secrets](../secrets.md) +- Learn about [Agent Metadata](../agent-metadata.md) diff --git a/site/package.json b/site/package.json index 3599031b00feb..ff13b2a4713bf 100644 --- a/site/package.json +++ b/site/package.json @@ -137,7 +137,6 @@ "prettier": "2.8.1", "resize-observer": "1.0.4", "semver": "7.3.7", - "storybook-addon-fetch-mock": "^1.0.1", "storybook-addon-mock": "^3.2.0", "storybook-react-context": "^0.6.0", "typescript": "4.8.2" diff --git a/site/yarn.lock b/site/yarn.lock index f7c6fd229ea62..cb9be2da1e286 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -51,27 +51,6 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.0.0": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.3.tgz#cf1c877284a469da5d1ce1d1e53665253fae712e" - integrity sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.3" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.3" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.3" - "@babel/types" "^7.21.3" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.0" - "@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.13", "@babel/core@^7.7.5": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" @@ -103,16 +82,6 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce" - integrity sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA== - dependencies: - "@babel/types" "^7.21.3" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -242,20 +211,6 @@ "@babel/traverse" "^7.21.0" "@babel/types" "^7.21.0" -"@babel/helper-module-transforms@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" - integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.2" - "@babel/types" "^7.21.2" - "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" @@ -364,11 +319,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.1.tgz#a8f81ee2fe872af23faea4b17a08fcc869de7bcc" integrity sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg== -"@babel/parser@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" - integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ== - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -1196,22 +1146,6 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67" - integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.3" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.3" - "@babel/types" "^7.21.3" - debug "^4.1.0" - globals "^11.1.0" - "@babel/types@^7.0.0", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819" @@ -1221,15 +1155,6 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.21.2", "@babel/types@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05" - integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -5915,11 +5840,6 @@ core-js-pure@^3.23.3, core-js-pure@^3.25.1: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.28.0.tgz#4ef2888475b6c856ef6f5aeef8b4f618b76ad048" integrity sha512-DSOVleA9/v3LNj/vFxAPfUHttKTzrB2RXhAPvR5TPXn4vrra3Z2ssytvRyt8eruJwAfwAiFADEbrjcRdcvPLQQ== -core-js@^3.0.0: - version "3.29.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.1.tgz#40ff3b41588b091aaed19ca1aa5cb111803fa9a6" - integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw== - core-js@^3.0.4, core-js@^3.16.2, core-js@^3.6.5, core-js@^3.8.2: version "3.28.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.28.0.tgz#ed8b9e99c273879fdfff0edfc77ee709a5800e4a" @@ -7520,22 +7440,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fetch-mock@^9.11.0: - version "9.11.0" - resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" - integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== - dependencies: - "@babel/core" "^7.0.0" - "@babel/runtime" "^7.0.0" - core-js "^3.0.0" - debug "^4.1.1" - glob-to-regexp "^0.4.0" - is-subset "^0.1.1" - lodash.isequal "^4.5.0" - path-to-regexp "^2.2.1" - querystring "^0.2.0" - whatwg-url "^6.5.0" - fetch-retry@^5.0.2: version "5.0.3" resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.3.tgz#edfa3641892995f9afee94f25b168827aa97fe3d" @@ -8010,7 +7914,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig== -glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: +glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== @@ -9030,11 +8934,6 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw== - is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" @@ -10269,11 +10168,6 @@ lodash.size@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" integrity sha512-wbu3SF1XC5ijqm0piNxw59yCbuUf2kaShumYBLWUrcCvwh6C8odz6SY/wGVzCWTQTFL/1Ygbvqg2eLtspUVVAQ== -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== - lodash.topairs@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" @@ -11956,11 +11850,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -path-to-regexp@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" - integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== - path-to-regexp@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" @@ -12467,11 +12356,6 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== -querystring@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" - integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== - querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -13802,13 +13686,6 @@ store2@^2.12.0: resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068" integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w== -storybook-addon-fetch-mock@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/storybook-addon-fetch-mock/-/storybook-addon-fetch-mock-1.0.1.tgz#ed92074f9d792cff3b5781d8f37fb55aadda66c5" - integrity sha512-OrK9NzZkjhv5C+Nx7fgDIlg4UmDUp6W+HHSD0STJtxC9DxC2dumDY+tUAoc0DDgkchLK/qugKp5f07bfcY37aQ== - dependencies: - fetch-mock "^9.11.0" - storybook-addon-mock@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/storybook-addon-mock/-/storybook-addon-mock-3.2.0.tgz#5832b1e49ff39ffab7a0ae8ec7de8bfdb8ddea45" @@ -14340,13 +14217,6 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== - dependencies: - punycode "^2.1.0" - tr46@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" @@ -15077,11 +14947,6 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -15227,15 +15092,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" - integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" From 4289a6a338b075b4e3cba44bcf20b767bf6f00a1 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 01:24:00 +0000 Subject: [PATCH 50/73] Start working on docs --- docs/templates/agent-metadata.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md index f1f8d100c7588..dce87992f3458 100644 --- a/docs/templates/agent-metadata.md +++ b/docs/templates/agent-metadata.md @@ -1,5 +1,9 @@ # Agent Metadata +
+Agent metadata is in an alpha state and may break or disappear at any time. +
+ With Agent Metadata, template admin can expose key operational metrics from their workspaces to their users. It is a sibling of [Resource Metadata](./resource-metadata.md). From 9c6db223eeeea72ce39d64150c92f4e6ff2f9225 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 02:29:13 +0000 Subject: [PATCH 51/73] Make more docs progress --- docs/manifest.json | 10 +++++++++- docs/templates/agent-metadata.md | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/manifest.json b/docs/manifest.json index 0bf1a8cd7a460..b1bffb6e4855a 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1,5 +1,7 @@ { - "versions": ["main"], + "versions": [ + "main" + ], "routes": [ { "title": "About", @@ -135,6 +137,12 @@ "path": "./templates/resource-metadata.md", "icon_path": "./images/icons/table-rows.svg" }, + { + "title": "Agent Metadata", + "description": "Learn how to expose live agent information to users", + "path": "./templates/agent-metadata.md", + "icon_path": "./images/icons/table-rows.svg" + }, { "title": "Docker in Docker", "description": "Use docker inside containerized templates", diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md index dce87992f3458..b861fe6c24659 100644 --- a/docs/templates/agent-metadata.md +++ b/docs/templates/agent-metadata.md @@ -4,7 +4,7 @@ Agent metadata is in an alpha state and may break or disappear at any time. -With Agent Metadata, template admin can expose key operational metrics from +With Agent Metadata, template admin can expose operational metrics from their workspaces to their users. It is a sibling of [Resource Metadata](./resource-metadata.md). See the [Terraform reference](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/agent#metadata). From eefd63139cfcbef995ff97284f5613a434d47561 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 02:52:01 +0000 Subject: [PATCH 52/73] Fix ErrNoRows --- coderd/database/dbauthz/querier.go | 2 +- .../provisionerdserver/provisionerdserver.go | 31 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 281d57ce66e60..9bcdaef873e8b 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1558,7 +1558,7 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) if err != nil { - return err + return xerrors.Errorf("find workspace by agent %v: %v", arg.WorkspaceAgentID, err) } err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index d93d4f4dd0b75..dadfdeb49b70c 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1276,20 +1276,27 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. } snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent)) - for _, md := range prAgent.Metadata { - err := db.InsertWorkspaceAgentMetadata(ctx, database.InsertWorkspaceAgentMetadataParams{ - WorkspaceAgentID: agentID, - DisplayName: md.DisplayName, - Script: md.Script, - Key: md.Key, - Timeout: md.Timeout, - Interval: md.Interval, - }) - if err != nil { - return xerrors.Errorf("insert agent metadata: %w", err) + // We don't need to insert metadata if the agent is not for a workspace. + // This is probably an agent made during the provisioning process. + _, err = db.GetWorkspaceByAgentID(ctx, agentID) + if err == nil { + for _, md := range prAgent.Metadata { + p := database.InsertWorkspaceAgentMetadataParams{ + WorkspaceAgentID: agentID, + DisplayName: md.DisplayName, + Script: md.Script, + Key: md.Key, + Timeout: md.Timeout, + Interval: md.Interval, + } + err := db.InsertWorkspaceAgentMetadata(ctx, p) + if err != nil { + return xerrors.Errorf("insert agent metadata: %w, params: %+v", err, p) + } } + } else if !errors.Is(err, sql.ErrNoRows) { + return xerrors.Errorf("get workspace by agent ID: %w", err) } - for _, app := range prAgent.Apps { slug := app.Slug if slug == "" { From 873e5f065508250044aae6c9a6d6686d1643aa7b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 03:26:43 +0000 Subject: [PATCH 53/73] Add a bunch of examples --- docs/images/agent-metadata.png | Bin 0 -> 49803 bytes docs/templates/agent-metadata.md | 70 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 docs/images/agent-metadata.png diff --git a/docs/images/agent-metadata.png b/docs/images/agent-metadata.png new file mode 100644 index 0000000000000000000000000000000000000000..666ee2b0b9ef33c7db9dcedf758c1a45d1a751a0 GIT binary patch literal 49803 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

`Co0#=L^!eEO2S5R#- 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 +![agent-metadata](../images/agent-metadata.png) + With Agent Metadata, template admin can expose operational metrics from their workspaces to their users. It is a sibling of [Resource Metadata](./resource-metadata.md). See the [Terraform reference](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/agent#metadata). + +## Examples + +Here are useful agent metadata snippets for Linux agents: + +Show users how much CPU they're using: + +```hcl +resource "coder_agent" "main" { + os = "linux" + ... + metadata { + display_name = "CPU Usage" + key = "cpu" + # calculates CPU usage by summing the "us", "sy" and "id" columns of + # vmstat. + script = <> + interval = 1 + timeout = 1 + } +} +``` + +Show users the space used in their `/` volume: + +## Utilities + +* [vmstat](https://linux.die.net/man/8/vmstat) is available in most Linux +distributions and contains virtual memory, CPU and IO statistics. Running `vmstat` +produces output that looks like: +``` +procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- + r b swpd free buff cache si so bi bo in cs us sy id wa st + 0 0 19580 4781680 12133692 217646944 0 2 4 32 1 0 1 1 98 0 0 + ``` + From fc3d8cf1baccd8c22324c696494cdd57b5edae2f Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 03:29:30 +0000 Subject: [PATCH 54/73] Explain dstat --- docs/templates/agent-metadata.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md index fd3d1a4bdf7e4..c59272e47771f 100644 --- a/docs/templates/agent-metadata.md +++ b/docs/templates/agent-metadata.md @@ -65,8 +65,6 @@ resource "coder_agent" "main" { } ``` -Show users the space used in their `/` volume: - ## Utilities * [vmstat](https://linux.die.net/man/8/vmstat) is available in most Linux @@ -78,3 +76,13 @@ procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- 0 0 19580 4781680 12133692 217646944 0 2 4 32 1 0 1 1 98 0 0 ``` +* [dstat](https://linux.die.net/man/1/dstat) is considerably more human-friendly +than `vmstat` but not always already installed. It is available in almost any +package manager under the name `dstat`. The output of running `dstat 1 1` looks +like: + +``` +--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system-- +usr sys idl wai stl| read writ| recv send| in out | int csw + 1 1 98 0 0|3422k 25M| 0 0 | 153k 904k| 123k 174k +``` From 19866627c2f1ac1ba65f15a9448c0a40f6fb79b4 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 03:36:29 +0000 Subject: [PATCH 55/73] docs: improve formatting --- docs/templates/agent-metadata.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md index c59272e47771f..50273af295f23 100644 --- a/docs/templates/agent-metadata.md +++ b/docs/templates/agent-metadata.md @@ -67,22 +67,23 @@ resource "coder_agent" "main" { ## Utilities -* [vmstat](https://linux.die.net/man/8/vmstat) is available in most Linux +[vmstat](https://linux.die.net/man/8/vmstat) is available in most Linux distributions and contains virtual memory, CPU and IO statistics. Running `vmstat` produces output that looks like: + ``` procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- - r b swpd free buff cache si so bi bo in cs us sy id wa st - 0 0 19580 4781680 12133692 217646944 0 2 4 32 1 0 1 1 98 0 0 - ``` +r b swpd free buff cache si so bi bo in cs us sy id wa st +0 0 19580 4781680 12133692 217646944 0 2 4 32 1 0 1 1 98 0 0 +``` -* [dstat](https://linux.die.net/man/1/dstat) is considerably more human-friendly -than `vmstat` but not always already installed. It is available in almost any -package manager under the name `dstat`. The output of running `dstat 1 1` looks +[dstat](https://linux.die.net/man/1/dstat) is considerably more parseable +than `vmstat` but often not included in base images. It is easily installed by +most package managers under the name `dstat`. The output of running `dstat 1 1` looks like: ``` --total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system-- usr sys idl wai stl| read writ| recv send| in out | int csw - 1 1 98 0 0|3422k 25M| 0 0 | 153k 904k| 123k 174k +1 1 98 0 0|3422k 25M| 0 0 | 153k 904k| 123k 174k ``` From dc631f5fa1a85a391a50d430ae08383580b7e1f3 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 20:59:38 +0000 Subject: [PATCH 56/73] Address review comments --- agent/agent.go | 109 +++++++++--------- coderd/coderd.go | 3 + coderd/database/dbauthz/querier.go | 10 +- coderd/database/dump.sql | 10 +- .../000111_workspace_agent_metadata.up.sql | 10 +- .../provisionerdserver/provisionerdserver.go | 32 +++-- coderd/workspaceagents.go | 46 +++++--- coderd/workspaceagents_test.go | 27 ++++- codersdk/workspaceagents.go | 52 +++++---- docs/templates/agent-metadata.md | 5 +- 10 files changed, 165 insertions(+), 139 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index fcbcae361487b..520140e189a01 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -214,9 +214,8 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM if timeout == 0 { timeout = md.Interval } - ctx, cancel := context.WithDeadline(ctx, time.Now().Add( + ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second, - ), ) defer cancel() @@ -295,63 +294,65 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { a.logger.Error(ctx, "report metadata", slog.Error(err)) } case <-baseTicker.C: - if len(metadataResults) > cap(metadataResults)/2 { - // If we're backpressured on sending back results, we risk - // runaway goroutine growth and/or overloading coderd. So, - // we just skip the collection. Since we never update - // the collections map, we'll retry the collection - // on the next tick. - a.logger.Debug( - ctx, "metadata collection backpressured", - slog.F("queue_len", len(metadataResults)), - ) - continue - } + break + } + + if len(metadataResults) > cap(metadataResults)/2 { + // If we're backpressured on sending back results, we risk + // runaway goroutine growth and/or overloading coderd. So, + // we just skip the collection. Since we never update + // the collections map, we'll retry the collection + // on the next tick. + a.logger.Debug( + ctx, "metadata collection backpressured", + slog.F("queue_len", len(metadataResults)), + ) + continue + } - manifest := a.manifest.Load() - if manifest == nil { - continue + manifest := a.manifest.Load() + if manifest == nil { + continue + } + // If the manifest changes (e.g. on agent reconnect) we need to + // purge old cache values to prevent lastCollectedAt from growing + // boundlessly. + for key := range lastCollectedAts { + if slices.IndexFunc(manifest.Metadata, func(md codersdk.WorkspaceAgentMetadataDescription) bool { + return md.Key == key + }) < 0 { + delete(lastCollectedAts, key) } - // If the manifest changes (e.g. on agent reconnect) we need to - // purge old cache values to prevent lastCollectedAt from growing - // boundlessly. - for key := range lastCollectedAts { - if slices.IndexFunc(manifest.Metadata, func(md codersdk.WorkspaceAgentMetadataDescription) bool { - return md.Key == key - }) < 0 { - delete(lastCollectedAts, key) + } + + // Spawn a goroutine for each metadata collection, and use a + // channel to synchronize the results and avoid both messy + // mutex logic and overloading the API. + for _, md := range manifest.Metadata { + collectedAt, ok := lastCollectedAts[md.Key] + if ok { + // If the interval is zero, we assume the user just wants + // a single collection at startup, not a spinning loop. + if md.Interval == 0 { + continue + } + if collectedAt.Add( + convertInterval(md.Interval), + ).After(time.Now()) { + continue } } - // Spawn a goroutine for each metadata collection, and use a - // channel to synchronize the results and avoid both messy - // mutex logic and overloading the API. - for _, md := range manifest.Metadata { - collectedAt, ok := lastCollectedAts[md.Key] - if ok { - // If the interval is zero, we assume the user just wants - // a single collection at startup, not a spinning loop. - if md.Interval == 0 { - continue - } - if collectedAt.Add( - convertInterval(md.Interval), - ).After(time.Now()) { - continue - } + go func(md codersdk.WorkspaceAgentMetadataDescription) { + select { + case <-ctx.Done(): + return + case metadataResults <- metadataResultAndKey{ + key: md.Key, + result: a.collectMetadata(ctx, md), + }: } - - go func(md codersdk.WorkspaceAgentMetadataDescription) { - select { - case <-ctx.Done(): - return - case metadataResults <- metadataResultAndKey{ - key: md.Key, - result: a.collectMetadata(ctx, md), - }: - } - }(md) - } + }(md) } } } @@ -1077,7 +1078,7 @@ func (a *agent) init(ctx context.Context) { } // createCommand processes raw command input with OpenSSH-like behavior. -// If the rawScript provided is empty, it will default to the users shell. +// If the createCommand provided is empty, it will default to the users shell. // This injects environment variables specified by the user at launch too. func (a *agent) createCommand(ctx context.Context, script string, env []string) (*exec.Cmd, error) { currentUser, err := user.Current() diff --git a/coderd/coderd.go b/coderd/coderd.go index 4757982c4aca9..17bbe450421d4 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -603,6 +603,9 @@ func New(options *Options) *API { r.Route("/me", func(r chi.Router) { r.Use(httpmw.ExtractWorkspaceAgent(options.Database)) 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.patchWorkspaceAgentStartupLogs) r.Post("/app-health", api.postWorkspaceAppHealth) diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 9bcdaef873e8b..509f5194b6c3b 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1556,13 +1556,9 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins } func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { - workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) - if err != nil { - return xerrors.Errorf("find workspace by agent %v: %v", arg.WorkspaceAgentID, err) - } - - err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) - if err != nil { + // We don't check for workspace ownership here since the agent metadata may + // be associated with an orphaned agent used by a dry run build. + if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil { return err } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 33282eb837a24..119fe344b992a 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -477,11 +477,11 @@ CREATE TABLE users ( CREATE TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, - display_name text NOT NULL, - key character varying(128) NOT NULL, - script text NOT NULL, - value text DEFAULT ''::text NOT NULL, - error text DEFAULT ''::text NOT NULL, + display_name character varying(127) NOT NULL, + key character varying(127) NOT NULL, + script character varying(65535) NOT NULL, + value character varying(65535) DEFAULT ''::character varying NOT NULL, + error character varying(65535) DEFAULT ''::character varying NOT NULL, timeout bigint NOT NULL, "interval" bigint NOT NULL, collected_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL diff --git a/coderd/database/migrations/000111_workspace_agent_metadata.up.sql b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql index 0f9a526b1e5de..fd96a3f254da0 100644 --- a/coderd/database/migrations/000111_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql @@ -2,11 +2,11 @@ -- key enough? CREATE TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, - display_name text NOT NULL, - key character varying(128) NOT NULL, - script text NOT NULL, - value text NOT NULL DEFAULT '', - error text NOT NULL DEFAULT '', + display_name varchar(127) NOT NULL, + key varchar(127) NOT NULL, + script varchar(65535) NOT NULL, + value varchar(65535) NOT NULL DEFAULT '', + error varchar(65535) NOT NULL DEFAULT '', timeout bigint NOT NULL, interval bigint NOT NULL, collected_at timestamp with time zone NOT NULL DEFAULT '0001-01-01 00:00:00+00', diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index dadfdeb49b70c..a4735bba30c8c 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1276,27 +1276,21 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. } snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent)) - // We don't need to insert metadata if the agent is not for a workspace. - // This is probably an agent made during the provisioning process. - _, err = db.GetWorkspaceByAgentID(ctx, agentID) - if err == nil { - for _, md := range prAgent.Metadata { - p := database.InsertWorkspaceAgentMetadataParams{ - WorkspaceAgentID: agentID, - DisplayName: md.DisplayName, - Script: md.Script, - Key: md.Key, - Timeout: md.Timeout, - Interval: md.Interval, - } - err := db.InsertWorkspaceAgentMetadata(ctx, p) - if err != nil { - return xerrors.Errorf("insert agent metadata: %w, params: %+v", err, p) - } + for _, md := range prAgent.Metadata { + p := database.InsertWorkspaceAgentMetadataParams{ + WorkspaceAgentID: agentID, + DisplayName: md.DisplayName, + Script: md.Script, + Key: md.Key, + Timeout: md.Timeout, + Interval: md.Interval, + } + err := db.InsertWorkspaceAgentMetadata(ctx, p) + if err != nil { + return xerrors.Errorf("insert agent metadata: %w, params: %+v", err, p) } - } else if !errors.Is(err, sql.ErrNoRows) { - return xerrors.Errorf("get workspace by agent ID: %w", err) } + for _, app := range prAgent.Apps { slug := app.Slug if slug == "" { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index a902a7b760cda..132486551c20b 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1373,13 +1373,6 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques }) } -func ellipse(s string, maxLength int) string { - if len(s) > maxLength { - return s[:maxLength] + "..." - } - return s -} - // @Summary Submit workspace agent metadata // @ID submit-workspace-agent-metadata // @Security CoderSessionToken @@ -1411,12 +1404,30 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque key := chi.URLParam(r, "key") + const ( + maxValueLen = 64 << 10 + maxErrorLen = maxValueLen + ) + + metadataError := req.Error + + // We overwrite the error if the provided payload is too long. + if len(req.Value) > maxValueLen { + metadataError = fmt.Sprintf("value of %d bytes exceeded %d bytes", len(req.Value), maxValueLen) + req.Value = req.Value[:maxValueLen] + } + + if len(req.Error) > maxErrorLen { + metadataError = fmt.Sprintf("error of %d bytes exceeded %d bytes", len(req.Error), maxErrorLen) + req.Error = req.Error[:maxErrorLen] + } + datum := database.UpdateWorkspaceAgentMetadataParams{ WorkspaceAgentID: workspaceAgent.ID, // We don't want a misconfigured agent to fill the database. - Key: ellipse(key, 128), - Value: ellipse(req.Value, 10<<10), - Error: ellipse(req.Error, 10<<10), + Key: key, + Value: req.Value, + Error: metadataError, // We ignore the CollectedAt from the agent to avoid bugs caused by // clock skew. CollectedAt: time.Now(), @@ -1434,7 +1445,6 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque slog.F("workspace", workspace.ID), slog.F("collected_at", datum.CollectedAt), slog.F("key", datum.Key), - slog.F("value", ellipse(datum.Value, 5)), ) err = api.Pubsub.Publish(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), []byte(datum.Key)) @@ -1535,15 +1545,17 @@ func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Requ for { select { - case <-refreshTicker.C: - // Avoid spamming the DB with reads we know there are no updates. We want - // to continue sending updates to the frontend so that "Result.Age" - // is always accurate. This way, the frontend doesn't need to - // sync its own clock with the backend. - sendMetadata(false) case <-senderClosed: return + case <-refreshTicker.C: + break } + + // Avoid spamming the DB with reads we know there are no updates. We want + // to continue sending updates to the frontend so that "Result.Age" + // is always accurate. This way, the frontend doesn't need to + // sync its own clock with the backend. + sendMetadata(false) } } diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 05a7fe72c7d82..bbcf4cb6dbff6 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1291,6 +1291,13 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { Interval: 10, Timeout: 3, }, + { + DisplayName: "TooLong", + Key: "foo3", + Script: "echo howdy", + Interval: 10, + Timeout: 3, + }, }, Id: uuid.NewString(), Auth: &proto.Agent_Token{ @@ -1370,7 +1377,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { } update = recvUpdate() - require.Len(t, update, 2) + require.Len(t, update, 3) check(wantMetadata1, update[0]) // The second metadata result is not yet posted. require.Zero(t, update[1].Result.CollectedAt) @@ -1378,17 +1385,27 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { wantMetadata2 := wantMetadata1 post("foo2", wantMetadata2) update = recvUpdate() - require.Len(t, update, 2) + require.Len(t, update, 3) check(wantMetadata1, update[0]) check(wantMetadata2, update[1]) wantMetadata1.Error = "error" post("foo1", wantMetadata1) update = recvUpdate() - require.Len(t, update, 2) + require.Len(t, update, 3) check(wantMetadata1, update[0]) - badMetadata := wantMetadata1 - err = agentClient.PostMetadata(ctx, "unknown", badMetadata) + const maxValueLen = 64 << 10 + tooLongValueMetadata := wantMetadata1 + tooLongValueMetadata.Value = strings.Repeat("a", maxValueLen*2) + tooLongValueMetadata.Error = "" + tooLongValueMetadata.CollectedAt = time.Now() + post("foo3", tooLongValueMetadata) + got := recvUpdate()[2] + require.Len(t, got.Result.Value, maxValueLen) + require.NotEmpty(t, got.Result.Error) + + unknownKeyMetadata := wantMetadata1 + err = agentClient.PostMetadata(ctx, "unknown", unknownKeyMetadata) require.Error(t, err) } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index fcaf655504ae8..885e6db2cc8ea 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -309,34 +309,36 @@ func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) case <-ctx.Done(): return ctx.Err() default: - sse, err := nextEvent() - if err != nil { - return err - } + break + } - b, ok := sse.Data.([]byte) - if !ok { - return xerrors.Errorf("unexpected data type: %T", sse.Data) - } + sse, err := nextEvent() + if err != nil { + return err + } - switch sse.Type { - case ServerSentEventTypeData: - var met []WorkspaceAgentMetadata - err = json.Unmarshal(b, &met) - if err != nil { - return xerrors.Errorf("unmarshal metadata: %w", err) - } - metadataChan <- met - case ServerSentEventTypeError: - var r Response - err = json.Unmarshal(b, &r) - if err != nil { - return xerrors.Errorf("unmarshal error: %w", err) - } - return xerrors.Errorf("%+v", r) - default: - return xerrors.Errorf("unexpected event type: %s", sse.Type) + b, ok := sse.Data.([]byte) + if !ok { + return xerrors.Errorf("unexpected data type: %T", sse.Data) + } + + switch sse.Type { + case ServerSentEventTypeData: + var met []WorkspaceAgentMetadata + err = json.Unmarshal(b, &met) + if err != nil { + return xerrors.Errorf("unmarshal metadata: %w", err) } + metadataChan <- met + case ServerSentEventTypeError: + var r Response + err = json.Unmarshal(b, &r) + if err != nil { + return xerrors.Errorf("unmarshal error: %w", err) + } + return xerrors.Errorf("%+v", r) + default: + return xerrors.Errorf("unexpected event type: %s", sse.Type) } } } diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md index 50273af295f23..430bcc417085c 100644 --- a/docs/templates/agent-metadata.md +++ b/docs/templates/agent-metadata.md @@ -13,9 +13,10 @@ See the [Terraform reference](https://registry.terraform.io/providers/coder/code ## Examples -Here are useful agent metadata snippets for Linux agents: +All of these examples use [heredoc strings](https://developer.hashicorp.com/terraform/language/expressions/strings#heredoc-strings) for the script declaration. With heredoc strings you +can script without messy escape codes, just as if you were working in your terminal. -Show users how much CPU they're using: +Here are useful agent metadata snippets for Linux agents: ```hcl resource "coder_agent" "main" { From d8d5c06802b6c2bf2e2840a7ee5200f45b732e92 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 21:02:48 +0000 Subject: [PATCH 57/73] nit --- agent/agent_test.go | 2 +- .../database/migrations/000111_workspace_agent_metadata.up.sql | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 12192534f099d..baa90552d7b93 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -422,7 +422,7 @@ func TestAgent_Session_TTY_HugeOutputIsNotLost(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() //nolint:dogsled - conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0) + conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() diff --git a/coderd/database/migrations/000111_workspace_agent_metadata.up.sql b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql index fd96a3f254da0..c9fe8920f0214 100644 --- a/coderd/database/migrations/000111_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql @@ -1,5 +1,3 @@ --- TODO: Do we need an index for workspace_agent_id or is the multi-column PRIMARY --- key enough? CREATE TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, display_name varchar(127) NOT NULL, From 64be182a8cf40b2ebf3b58d3fa05f239e4979fc2 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 22:13:55 +0000 Subject: [PATCH 58/73] fixup! nit --- agent/agent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agent.go b/agent/agent.go index d343b171253e1..347d415dd52f3 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -222,7 +222,7 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM var out bytes.Buffer result := &codersdk.WorkspaceAgentMetadataResult{ - // CollectedAt is set here for testing purposes and overrided by + // CollectedAt is set here for testing purposes and overrode by // the server to the time the server received the result to protect // against clock skew. // From 06d26b5e366bba3da97b97a4655b976b70ad4226 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 20:25:13 -0500 Subject: [PATCH 59/73] improve synchronization in metadata loop --- agent/agent.go | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 347d415dd52f3..0a29c2ebbeab3 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -258,7 +258,7 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM return result } -func convertInterval(i int64) time.Duration { +func adjustIntervalForTests(i int64) time.Duration { // In tests we want to set shorter intervals because engineers are // impatient. base := time.Second @@ -274,12 +274,14 @@ type metadataResultAndKey struct { } func (a *agent) reportMetadataLoop(ctx context.Context) { - baseInterval := convertInterval(1) + baseInterval := adjustIntervalForTests(1) + + const metadataLimit = 128 var ( baseTicker = time.NewTicker(baseInterval) lastCollectedAts = make(map[string]time.Time) - metadataResults = make(chan metadataResultAndKey, 16) + metadataResults = make(chan metadataResultAndKey, metadataLimit) ) defer baseTicker.Stop() @@ -294,15 +296,13 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { a.logger.Error(ctx, "report metadata", slog.Error(err)) } case <-baseTicker.C: - break } - if len(metadataResults) > cap(metadataResults)/2 { + if len(metadataResults) > 0 { // If we're backpressured on sending back results, we risk // runaway goroutine growth and/or overloading coderd. So, - // we just skip the collection. Since we never update - // the collections map, we'll retry the collection - // on the next tick. + // we just skip the collection and give the loop another chance to + // post metadata. a.logger.Debug( ctx, "metadata collection backpressured", slog.F("queue_len", len(metadataResults)), @@ -314,6 +314,15 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { if manifest == nil { continue } + + if len(manifest.Metadata) > metadataLimit { + a.logger.Error( + ctx, "metadata limit exceeded", + slog.F("limit", metadataLimit), slog.F("got", len(manifest.Metadata)), + ) + continue + } + // If the manifest changes (e.g. on agent reconnect) we need to // purge old cache values to prevent lastCollectedAt from growing // boundlessly. @@ -337,7 +346,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { continue } if collectedAt.Add( - convertInterval(md.Interval), + adjustIntervalForTests(md.Interval), ).After(time.Now()) { continue } @@ -351,6 +360,10 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { key: md.Key, result: a.collectMetadata(ctx, md), }: + default: + // This should be impossible because the channel is empty + // before we start spinning up send goroutines. + a.logger.Error(ctx, "metadataResults channel full") } }(md) } From ed9257fd4a1e2eacd981377b76e5c3ace23f8641 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 20:27:15 -0500 Subject: [PATCH 60/73] explain collected at skip --- agent/agent.go | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/agent.go b/agent/agent.go index 0a29c2ebbeab3..5fe0c930eea18 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -345,6 +345,7 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { if md.Interval == 0 { continue } + // The last collected value isn't quite stale yet, so we skip it. if collectedAt.Add( adjustIntervalForTests(md.Interval), ).After(time.Now()) { From 6a7b5cb1338ed5e6a3983be3c1e03d38774578d0 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 20:29:20 -0500 Subject: [PATCH 61/73] typo --- agent/agent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agent.go b/agent/agent.go index 5fe0c930eea18..80dba5672956b 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1092,7 +1092,7 @@ func (a *agent) init(ctx context.Context) { } // createCommand processes raw command input with OpenSSH-like behavior. -// If the createCommand provided is empty, it will default to the users shell. +// If the script provided is empty, it will default to the users shell. // This injects environment variables specified by the user at launch too. func (a *agent) createCommand(ctx context.Context, script string, env []string) (*exec.Cmd, error) { currentUser, err := user.Current() From 6ef1e81ca6783288867c611b089dc75c8b5d8f3b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 28 Mar 2023 20:36:40 -0500 Subject: [PATCH 62/73] document collection loop --- agent/agent.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 80dba5672956b..d95782c5cb82d 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -299,10 +299,8 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { } if len(metadataResults) > 0 { - // If we're backpressured on sending back results, we risk - // runaway goroutine growth and/or overloading coderd. So, - // we just skip the collection and give the loop another chance to - // post metadata. + // The inner collection loop expects the channel is empty before spinning up + // all the collection goroutines. a.logger.Debug( ctx, "metadata collection backpressured", slog.F("queue_len", len(metadataResults)), From c9aa5a4d92287eef61e46860d3f095902d218f82 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 29 Mar 2023 15:48:09 +0000 Subject: [PATCH 63/73] make fmt --- docs/templates/agent-metadata.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md index 430bcc417085c..c6c48d7c5a5c0 100644 --- a/docs/templates/agent-metadata.md +++ b/docs/templates/agent-metadata.md @@ -58,7 +58,7 @@ resource "coder_agent" "main" { display_name = "Load Average" key = "load" script = <> interval = 1 timeout = 1 @@ -85,6 +85,6 @@ like: ``` --total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system-- -usr sys idl wai stl| read writ| recv send| in out | int csw +usr sys idl wai stl| read writ| recv send| in out | int csw 1 1 98 0 0|3422k 25M| 0 0 | 153k 904k| 123k 174k ``` From 012a6a26c4f1364abc7fbb7b5b818650477001ef Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 29 Mar 2023 22:29:21 +0000 Subject: [PATCH 64/73] UNLOG table --- .../migrations/000111_workspace_agent_metadata.up.sql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/coderd/database/migrations/000111_workspace_agent_metadata.up.sql b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql index c9fe8920f0214..426b4d69a1d5e 100644 --- a/coderd/database/migrations/000111_workspace_agent_metadata.up.sql +++ b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql @@ -1,4 +1,7 @@ -CREATE TABLE workspace_agent_metadata ( +-- This table is UNLOGGED because it is very update-heavy and the the data +-- is not valuable enough to justify the overhead of WAL logging. This should +-- give us a ~70% improvement in write throughput. +CREATE UNLOGGED TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, display_name varchar(127) NOT NULL, key varchar(127) NOT NULL, From fdce29f66fcf40d4f4178f92c4b71e665cc9fcdf Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 03:18:04 +0000 Subject: [PATCH 65/73] make gen --- coderd/database/dump.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 119fe344b992a..ae2343f249156 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -475,7 +475,7 @@ CREATE TABLE users ( last_seen_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL ); -CREATE TABLE workspace_agent_metadata ( +CREATE UNLOGGED TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, display_name character varying(127) NOT NULL, key character varying(127) NOT NULL, From 66468d31da3518a300ce5c621eab00c060288216 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 17:53:09 +0000 Subject: [PATCH 66/73] Go on a multi-route tangent --- coderd/coderdtest/swaggerparser.go | 103 ++++++++++++++++++----------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/coderd/coderdtest/swaggerparser.go b/coderd/coderdtest/swaggerparser.go index dda80d2f40800..5817adeeb5727 100644 --- a/coderd/coderdtest/swaggerparser.go +++ b/coderd/coderdtest/swaggerparser.go @@ -15,6 +15,11 @@ import ( "golang.org/x/xerrors" ) +type route struct { + path string + method string +} + type SwaggerComment struct { summary string id string @@ -23,8 +28,7 @@ type SwaggerComment struct { accept string produce string - method string - router string + routes []route successes []response failures []response @@ -34,6 +38,15 @@ type SwaggerComment struct { raw []*ast.Comment } +func (s *SwaggerComment) hasPath(path string) bool { + for _, route := range s.routes { + if route.path == path { + return true + } + } + return false +} + type parameter struct { name string kind string @@ -105,8 +118,10 @@ func parseSwaggerComment(commentGroup *ast.CommentGroup) SwaggerComment { switch annotationName { case "@Router": - c.router = args[0] - c.method = args[1][1 : len(args[1])-1] + c.routes = append(c.routes, route{ + path: args[0], + method: args[1][1 : len(args[1])-1], + }) case "@Success", "@Failure": var r response if len(args) > 0 { @@ -160,7 +175,7 @@ func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments [ t.Run(method+" "+route, func(t *testing.T) { t.Parallel() - c := findSwaggerCommentByMethodAndRoute(swaggerComments, method, route) + c := findSwaggerCommentByMethodAndPath(swaggerComments, method, route) assert.NotNil(t, c, "Missing @Router annotation") if c == nil { return // do not fail next assertion for this route @@ -184,11 +199,13 @@ func assertUniqueRoutes(t *testing.T, comments []SwaggerComment) { m := map[string]struct{}{} for _, c := range comments { - key := c.method + " " + c.router - _, alreadyDefined := m[key] - assert.False(t, alreadyDefined, "defined route must be unique (method: %s, route: %s)", c.method, c.router) - if !alreadyDefined { - m[key] = struct{}{} + for _, r := range c.routes { + key := r.method + " " + r.path + _, alreadyDefined := m[key] + assert.False(t, alreadyDefined, "defined route must be unique (method: %s, route: %s)", r.method, r.path) + if !alreadyDefined { + m[key] = struct{}{} + } } } } @@ -218,15 +235,17 @@ func assertSingleAnnotations(t *testing.T, comments []SwaggerComment) { for _, annotation := range uniqueAnnotations { v := counters[annotation] - assert.Equal(t, 1, v, "%s annotation for route %s must be defined only once", annotation, comment.router) + assert.LessOrEqual(t, 1, v, "%s annotation for route %s must be defined only once", annotation, comment.routes) } } } -func findSwaggerCommentByMethodAndRoute(comments []SwaggerComment, method, route string) *SwaggerComment { +func findSwaggerCommentByMethodAndPath(comments []SwaggerComment, method, path string) *SwaggerComment { for _, c := range comments { - if c.method == method && c.router == route { - return &c + for _, r := range c.routes { + if r.method == method && r.path == path { + return &c + } } } return nil @@ -250,7 +269,7 @@ func assertRequiredAnnotations(t *testing.T, comment SwaggerComment) { assert.NotEmpty(t, comment.id, "@ID must be defined") assert.NotEmpty(t, comment.summary, "@Summary must be defined") assert.NotEmpty(t, comment.tags, "@Tags must be defined") - assert.NotEmpty(t, comment.router, "@Router must be defined") + assert.NotEmpty(t, comment.routes, "@Router must be defined") } func assertGoCommentFirst(t *testing.T, comment SwaggerComment) { @@ -274,7 +293,11 @@ func assertGoCommentFirst(t *testing.T, comment SwaggerComment) { var urlParameterRegexp = regexp.MustCompile(`{[^{}]*}`) func assertPathParametersDefined(t *testing.T, comment SwaggerComment) { - matches := urlParameterRegexp.FindAllString(comment.router, -1) + var paths []string + for _, r := range comment.routes { + paths = append(paths, r.path) + } + matches := urlParameterRegexp.FindAllString(strings.Join(paths, "\n"), -1) if matches == nil { return // router does not require any parameters } @@ -295,10 +318,10 @@ func assertPathParametersDefined(t *testing.T, comment SwaggerComment) { } func assertSecurityDefined(t *testing.T, comment SwaggerComment) { - if comment.router == "/updatecheck" || - comment.router == "/buildinfo" || - comment.router == "/" || - comment.router == "/users/login" { + if comment.hasPath("/updatecheck") || + comment.hasPath("/buildinfo") || + comment.hasPath("/") || + comment.hasPath("/users/login") { return // endpoints do not require authorization } assert.Equal(t, "CoderSessionToken", comment.security, "@Security must be equal CoderSessionToken") @@ -319,12 +342,14 @@ func assertAccept(t *testing.T, comment SwaggerComment) { hasAccept = true } - if comment.method == "get" { - assert.Empty(t, comment.accept, "GET route does not require the @Accept annotation") - assert.False(t, hasRequestBody, "GET route does not require the request body") - } else { - assert.False(t, hasRequestBody && !hasAccept, "Route with the request body requires the @Accept annotation") - assert.False(t, !hasRequestBody && hasAccept, "Route with @Accept annotation requires the request body or file formData parameter") + for _, r := range comment.routes { + if r.method == "get" { + assert.Empty(t, comment.accept, "GET route does not require the @Accept annotation") + assert.False(t, hasRequestBody, "GET route does not require the request body") + } else { + assert.False(t, hasRequestBody && !hasAccept, "Route with the request body requires the @Accept annotation") + assert.False(t, !hasRequestBody && hasAccept, "Route with @Accept annotation requires the request body or file formData parameter") + } } } @@ -339,18 +364,20 @@ func assertProduce(t *testing.T, comment SwaggerComment) { } } - if hasResponseModel { - assert.True(t, comment.produce != "", "Route must have @Produce annotation as it responds with a model structure") - assert.Contains(t, allowedProduceTypes, comment.produce, "@Produce value is limited to specific types: %s", strings.Join(allowedProduceTypes, ",")) - } else { - if (comment.router == "/workspaceagents/me/app-health" && comment.method == "post") || - (comment.router == "/workspaceagents/me/startup" && comment.method == "post") || - (comment.router == "/workspaceagents/me/startup/logs" && comment.method == "patch") || - (comment.router == "/licenses/{id}" && comment.method == "delete") || - (comment.router == "/debug/coordinator" && comment.method == "get") { - return // Exception: HTTP 200 is returned without response entity - } + for _, r := range comment.routes { + if hasResponseModel { + assert.True(t, comment.produce != "", "Route must have @Produce annotation as it responds with a model structure") + assert.Contains(t, allowedProduceTypes, comment.produce, "@Produce value is limited to specific types: %s", strings.Join(allowedProduceTypes, ",")) + } else { + if (r.path == "/workspaceagents/me/app-health" && r.method == "post") || + (r.path == "/workspaceagents/me/startup" && r.method == "post") || + (r.path == "/workspaceagents/me/startup/logs" && r.method == "patch") || + (r.path == "/licenses/{id}" && r.method == "delete") || + (r.path == "/debug/coordinator" && r.method == "get") { + return // Exception: HTTP 200 is returned without response entity + } - assert.True(t, comment.produce == "", "Response model is undefined, so we can't predict the content type", comment) + assert.True(t, comment.produce == "", "Response model is undefined, so we can't predict the content type", comment) + } } } From f8617712b3e2ef9ea4ecf581773edee67faf2e3d Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 17:53:18 +0000 Subject: [PATCH 67/73] Revert "Go on a multi-route tangent" This reverts commit 66468d31da3518a300ce5c621eab00c060288216. --- coderd/coderdtest/swaggerparser.go | 103 +++++++++++------------------ 1 file changed, 38 insertions(+), 65 deletions(-) diff --git a/coderd/coderdtest/swaggerparser.go b/coderd/coderdtest/swaggerparser.go index 5817adeeb5727..dda80d2f40800 100644 --- a/coderd/coderdtest/swaggerparser.go +++ b/coderd/coderdtest/swaggerparser.go @@ -15,11 +15,6 @@ import ( "golang.org/x/xerrors" ) -type route struct { - path string - method string -} - type SwaggerComment struct { summary string id string @@ -28,7 +23,8 @@ type SwaggerComment struct { accept string produce string - routes []route + method string + router string successes []response failures []response @@ -38,15 +34,6 @@ type SwaggerComment struct { raw []*ast.Comment } -func (s *SwaggerComment) hasPath(path string) bool { - for _, route := range s.routes { - if route.path == path { - return true - } - } - return false -} - type parameter struct { name string kind string @@ -118,10 +105,8 @@ func parseSwaggerComment(commentGroup *ast.CommentGroup) SwaggerComment { switch annotationName { case "@Router": - c.routes = append(c.routes, route{ - path: args[0], - method: args[1][1 : len(args[1])-1], - }) + c.router = args[0] + c.method = args[1][1 : len(args[1])-1] case "@Success", "@Failure": var r response if len(args) > 0 { @@ -175,7 +160,7 @@ func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments [ t.Run(method+" "+route, func(t *testing.T) { t.Parallel() - c := findSwaggerCommentByMethodAndPath(swaggerComments, method, route) + c := findSwaggerCommentByMethodAndRoute(swaggerComments, method, route) assert.NotNil(t, c, "Missing @Router annotation") if c == nil { return // do not fail next assertion for this route @@ -199,13 +184,11 @@ func assertUniqueRoutes(t *testing.T, comments []SwaggerComment) { m := map[string]struct{}{} for _, c := range comments { - for _, r := range c.routes { - key := r.method + " " + r.path - _, alreadyDefined := m[key] - assert.False(t, alreadyDefined, "defined route must be unique (method: %s, route: %s)", r.method, r.path) - if !alreadyDefined { - m[key] = struct{}{} - } + key := c.method + " " + c.router + _, alreadyDefined := m[key] + assert.False(t, alreadyDefined, "defined route must be unique (method: %s, route: %s)", c.method, c.router) + if !alreadyDefined { + m[key] = struct{}{} } } } @@ -235,17 +218,15 @@ func assertSingleAnnotations(t *testing.T, comments []SwaggerComment) { for _, annotation := range uniqueAnnotations { v := counters[annotation] - assert.LessOrEqual(t, 1, v, "%s annotation for route %s must be defined only once", annotation, comment.routes) + assert.Equal(t, 1, v, "%s annotation for route %s must be defined only once", annotation, comment.router) } } } -func findSwaggerCommentByMethodAndPath(comments []SwaggerComment, method, path string) *SwaggerComment { +func findSwaggerCommentByMethodAndRoute(comments []SwaggerComment, method, route string) *SwaggerComment { for _, c := range comments { - for _, r := range c.routes { - if r.method == method && r.path == path { - return &c - } + if c.method == method && c.router == route { + return &c } } return nil @@ -269,7 +250,7 @@ func assertRequiredAnnotations(t *testing.T, comment SwaggerComment) { assert.NotEmpty(t, comment.id, "@ID must be defined") assert.NotEmpty(t, comment.summary, "@Summary must be defined") assert.NotEmpty(t, comment.tags, "@Tags must be defined") - assert.NotEmpty(t, comment.routes, "@Router must be defined") + assert.NotEmpty(t, comment.router, "@Router must be defined") } func assertGoCommentFirst(t *testing.T, comment SwaggerComment) { @@ -293,11 +274,7 @@ func assertGoCommentFirst(t *testing.T, comment SwaggerComment) { var urlParameterRegexp = regexp.MustCompile(`{[^{}]*}`) func assertPathParametersDefined(t *testing.T, comment SwaggerComment) { - var paths []string - for _, r := range comment.routes { - paths = append(paths, r.path) - } - matches := urlParameterRegexp.FindAllString(strings.Join(paths, "\n"), -1) + matches := urlParameterRegexp.FindAllString(comment.router, -1) if matches == nil { return // router does not require any parameters } @@ -318,10 +295,10 @@ func assertPathParametersDefined(t *testing.T, comment SwaggerComment) { } func assertSecurityDefined(t *testing.T, comment SwaggerComment) { - if comment.hasPath("/updatecheck") || - comment.hasPath("/buildinfo") || - comment.hasPath("/") || - comment.hasPath("/users/login") { + if comment.router == "/updatecheck" || + comment.router == "/buildinfo" || + comment.router == "/" || + comment.router == "/users/login" { return // endpoints do not require authorization } assert.Equal(t, "CoderSessionToken", comment.security, "@Security must be equal CoderSessionToken") @@ -342,14 +319,12 @@ func assertAccept(t *testing.T, comment SwaggerComment) { hasAccept = true } - for _, r := range comment.routes { - if r.method == "get" { - assert.Empty(t, comment.accept, "GET route does not require the @Accept annotation") - assert.False(t, hasRequestBody, "GET route does not require the request body") - } else { - assert.False(t, hasRequestBody && !hasAccept, "Route with the request body requires the @Accept annotation") - assert.False(t, !hasRequestBody && hasAccept, "Route with @Accept annotation requires the request body or file formData parameter") - } + if comment.method == "get" { + assert.Empty(t, comment.accept, "GET route does not require the @Accept annotation") + assert.False(t, hasRequestBody, "GET route does not require the request body") + } else { + assert.False(t, hasRequestBody && !hasAccept, "Route with the request body requires the @Accept annotation") + assert.False(t, !hasRequestBody && hasAccept, "Route with @Accept annotation requires the request body or file formData parameter") } } @@ -364,20 +339,18 @@ func assertProduce(t *testing.T, comment SwaggerComment) { } } - for _, r := range comment.routes { - if hasResponseModel { - assert.True(t, comment.produce != "", "Route must have @Produce annotation as it responds with a model structure") - assert.Contains(t, allowedProduceTypes, comment.produce, "@Produce value is limited to specific types: %s", strings.Join(allowedProduceTypes, ",")) - } else { - if (r.path == "/workspaceagents/me/app-health" && r.method == "post") || - (r.path == "/workspaceagents/me/startup" && r.method == "post") || - (r.path == "/workspaceagents/me/startup/logs" && r.method == "patch") || - (r.path == "/licenses/{id}" && r.method == "delete") || - (r.path == "/debug/coordinator" && r.method == "get") { - return // Exception: HTTP 200 is returned without response entity - } - - assert.True(t, comment.produce == "", "Response model is undefined, so we can't predict the content type", comment) + if hasResponseModel { + assert.True(t, comment.produce != "", "Route must have @Produce annotation as it responds with a model structure") + assert.Contains(t, allowedProduceTypes, comment.produce, "@Produce value is limited to specific types: %s", strings.Join(allowedProduceTypes, ",")) + } else { + if (comment.router == "/workspaceagents/me/app-health" && comment.method == "post") || + (comment.router == "/workspaceagents/me/startup" && comment.method == "post") || + (comment.router == "/workspaceagents/me/startup/logs" && comment.method == "patch") || + (comment.router == "/licenses/{id}" && comment.method == "delete") || + (comment.router == "/debug/coordinator" && comment.method == "get") { + return // Exception: HTTP 200 is returned without response entity } + + assert.True(t, comment.produce == "", "Response model is undefined, so we can't predict the content type", comment) } } From 2e82543eac40c097e3dbb5b2c4730aead7d2ecea Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 17:59:06 +0000 Subject: [PATCH 68/73] Pass swagger --- coderd/coderdtest/swaggerparser.go | 5 +++++ coderd/workspaceagents.go | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/coderd/coderdtest/swaggerparser.go b/coderd/coderdtest/swaggerparser.go index dda80d2f40800..be70b0379d6b0 100644 --- a/coderd/coderdtest/swaggerparser.go +++ b/coderd/coderdtest/swaggerparser.go @@ -160,6 +160,11 @@ func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments [ t.Run(method+" "+route, func(t *testing.T) { t.Parallel() + // This route is for compatibility purposes and is not documented. + if route == "/workspaceagents/me/metadata" { + return + } + c := findSwaggerCommentByMethodAndRoute(swaggerComments, method, route) assert.NotNil(t, c, "Missing @Router annotation") if c == nil { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index ffac858be8fea..45146dfab301f 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -84,7 +84,6 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { // @Tags Agents // @Success 200 {object} agentsdk.Manifest // @Router /workspaceagents/me/manifest [get] -// @Router /workspaceagents/me/metadata [get] func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) From 40cd260030c1ba4ac2380a0e086d0a4c4432d180 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 23:18:02 +0000 Subject: [PATCH 69/73] Make concurrency more robust --- agent/agent.go | 18 +++++++++++++----- agent/agent_test.go | 11 +++++------ coderd/database/dbfake/databasefake.go | 2 +- coderd/workspaceagents.go | 2 +- coderd/workspaceagents_test.go | 4 ++-- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index b33c0d87d5258..912b5d0859b06 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -35,6 +35,7 @@ import ( "go.uber.org/atomic" gossh "golang.org/x/crypto/ssh" "golang.org/x/exp/slices" + "golang.org/x/sync/singleflight" "golang.org/x/xerrors" "tailscale.com/net/speedtest" "tailscale.com/tailcfg" @@ -285,6 +286,8 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { ) defer baseTicker.Stop() + var flight singleflight.Group + for { select { case <-ctx.Done(): @@ -351,20 +354,25 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { } } - go func(md codersdk.WorkspaceAgentMetadataDescription) { + md := md + // We send the result to the channel in the goroutine to avoid + // sending the same result multiple times. So, we don't care about + // the return values. + flight.DoChan(md.Key, func() (interface{}, error) { select { case <-ctx.Done(): - return + return 0, nil case metadataResults <- metadataResultAndKey{ key: md.Key, result: a.collectMetadata(ctx, md), }: default: - // This should be impossible because the channel is empty - // before we start spinning up send goroutines. + // This should be impossible because the channel is confirmed + // to be empty before this goroutine is spawned. a.logger.Error(ctx, "metadataResults channel full") } - }(md) + return 0, nil + }) } } } diff --git a/agent/agent_test.go b/agent/agent_test.go index 012f1e35d44ab..ec76aa1b0b6b9 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -960,9 +960,8 @@ func TestAgent_Metadata(t *testing.T) { dir := t.TempDir() - // In tests, the interval unit is 100 milliseconds while in production - // it is 1 second. const reportInterval = 2 + const intervalUnit = 100 * time.Millisecond var ( greetingPath = filepath.Join(dir, "greeting") script = "echo hello | tee -a " + greetingPath @@ -986,7 +985,7 @@ func TestAgent_Metadata(t *testing.T) { return len(client.getMetadata()) == 2 }, testutil.WaitShort, testutil.IntervalMedium) - for start := time.Now(); time.Since(start) < testutil.WaitShort; time.Sleep(testutil.IntervalMedium) { + for start := time.Now(); time.Since(start) < testutil.WaitMedium; time.Sleep(testutil.IntervalMedium) { md := client.getMetadata() if len(md) != 2 { panic("unexpected number of metadata entries") @@ -1000,7 +999,7 @@ func TestAgent_Metadata(t *testing.T) { var ( numGreetings = bytes.Count(greetingByt, []byte("hello")) - idealNumGreetings = time.Since(start) / (reportInterval * 100 * time.Millisecond) + idealNumGreetings = time.Since(start) / (reportInterval * intervalUnit) // We allow a 50% error margin because the report loop may backlog // in CI and other toasters. In production, there is no hard // guarantee on timing either, and the frontend gives similar @@ -1009,7 +1008,7 @@ func TestAgent_Metadata(t *testing.T) { lowerBound = (int(idealNumGreetings) / 2) ) - if idealNumGreetings < 5 { + if idealNumGreetings < 50 { // There is an insufficient sample size. continue } @@ -1018,7 +1017,7 @@ func TestAgent_Metadata(t *testing.T) { // The report loop may slow down on load, but it should never, ever // speed up. if numGreetings > upperBound { - t.Fatalf("too many greetings: %d > %d", numGreetings, upperBound) + t.Fatalf("too many greetings: %d > %d in %v", numGreetings, upperBound, time.Since(start)) } else if numGreetings < lowerBound { t.Fatalf("too few greetings: %d < %d", numGreetings, lowerBound) } diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 8bb71f17ab460..5822b15a727eb 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -2762,7 +2762,7 @@ func (q *fakeQuerier) UpdateWorkspaceAgentMetadata(_ context.Context, arg databa } } - return sql.ErrNoRows + return nil } func (q *fakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 45146dfab301f..49885bb9e5a21 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1356,7 +1356,7 @@ func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Reque key := chi.URLParam(r, "key") const ( - maxValueLen = 64 << 10 + maxValueLen = 32 << 10 maxErrorLen = maxValueLen ) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index bca1bcfda45e8..1da40dc7372f9 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1400,7 +1400,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { require.Len(t, update, 3) check(wantMetadata1, update[0]) - const maxValueLen = 64 << 10 + const maxValueLen = 32 << 10 tooLongValueMetadata := wantMetadata1 tooLongValueMetadata.Value = strings.Repeat("a", maxValueLen*2) tooLongValueMetadata.Error = "" @@ -1412,5 +1412,5 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { unknownKeyMetadata := wantMetadata1 err = agentClient.PostMetadata(ctx, "unknown", unknownKeyMetadata) - require.Error(t, err) + require.NoError(t, err) } From f8e1f3448888e5bab47e6433113647657030d924 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 16:58:16 +0000 Subject: [PATCH 70/73] Improve concurrency a bit more! --- agent/agent.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 912b5d0859b06..356338b14bfa7 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -211,17 +211,7 @@ func (a *agent) runLoop(ctx context.Context) { } func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentMetadataDescription) *codersdk.WorkspaceAgentMetadataResult { - timeout := md.Timeout - if timeout == 0 { - timeout = md.Interval - } - ctx, cancel := context.WithTimeout(ctx, - time.Duration(timeout)*time.Second, - ) - defer cancel() - var out bytes.Buffer - result := &codersdk.WorkspaceAgentMetadataResult{ // CollectedAt is set here for testing purposes and overrode by // the server to the time the server received the result to protect @@ -359,6 +349,15 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { // sending the same result multiple times. So, we don't care about // the return values. flight.DoChan(md.Key, func() (interface{}, error) { + timeout := md.Timeout + if timeout == 0 { + timeout = md.Interval + } + ctx, cancel := context.WithTimeout(ctx, + time.Duration(timeout)*time.Second, + ) + defer cancel() + select { case <-ctx.Done(): return 0, nil @@ -366,10 +365,6 @@ func (a *agent) reportMetadataLoop(ctx context.Context) { key: md.Key, result: a.collectMetadata(ctx, md), }: - default: - // This should be impossible because the channel is confirmed - // to be empty before this goroutine is spawned. - a.logger.Error(ctx, "metadataResults channel full") } return 0, nil }) From c15d364156166b6eb335a982e3205e399af83fb2 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 18:30:21 +0000 Subject: [PATCH 71/73] Versioning chores... --- docs/manifest.json | 7 +++++-- docs/templates/agent-metadata.md | 2 +- .../testdata/resource-metadata/resource-metadata.tf | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/manifest.json b/docs/manifest.json index 4f5efd3b8baef..65f9494699d8a 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1,5 +1,7 @@ { - "versions": ["main"], + "versions": [ + "main" + ], "routes": [ { "title": "About", @@ -139,7 +141,8 @@ "title": "Agent Metadata", "description": "Learn how to expose live agent information to users", "path": "./templates/agent-metadata.md", - "icon_path": "./images/icons/table-rows.svg" + "icon_path": "./images/icons/table-rows.svg", + "state": "alpha" }, { "title": "Docker in Docker", diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md index c6c48d7c5a5c0..1ee887ef211b8 100644 --- a/docs/templates/agent-metadata.md +++ b/docs/templates/agent-metadata.md @@ -1,4 +1,4 @@ -# Agent Metadata +# Agent Metadata (alpha)

Agent metadata is in an alpha state and may break or disappear at any time. diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf index 9555ed71558d4..0462f8b827f3d 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf @@ -2,8 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - // TODO: update terraform-provider-coder before merge. - version = "= 0.7.0-rc0" + version = "0.7.0" } } } From 29524b9b92cbbac1eabfc1b0f709c95aa791aca3 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 18:45:30 +0000 Subject: [PATCH 72/73] make fmt --- docs/manifest.json | 4 +--- .../terraform/testdata/resource-metadata/resource-metadata.tf | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/manifest.json b/docs/manifest.json index 65f9494699d8a..c180249d002e4 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1,7 +1,5 @@ { - "versions": [ - "main" - ], + "versions": ["main"], "routes": [ { "title": "About", diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf index 0462f8b827f3d..1b8a4abea68ff 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf @@ -1,7 +1,7 @@ terraform { required_providers { coder = { - source = "coder/coder" + source = "coder/coder" version = "0.7.0" } } From 67b5a3950b1b59d4f935a903f4071b5ff1c3b0bd Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 19:40:27 +0000 Subject: [PATCH 73/73] Fix lint --- site/src/components/Resources/AgentRow.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index 3cd47d4315a9b..c00ca2a1793a0 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -260,6 +260,7 @@ export const AgentRow: FC = ({ )} {!hideVSCodeDesktopButton && (